app
hook
Public Made by Adomby adom
Registry-native cross-agent auto-update hook, plus claude-rc, the keeper for Claude Remote Control. Keeps adom/core apps and skills current from wiki.adom.inc on every Claude Code and Codex prompt, and keeps `claude rc` alive so users can open AI threads on their container from the Claude mobile app or claude.ai/code.
← Commit history
Publish 1.3.8
6 files changed
+73−38
README.md+3−2@@ -78,8 +78,9 @@ This package also installs `claude-rc`, which keeps Claude Code Remote Control ( rc`) running on the container so you can open new AI threads on it from the Claude mobile app or claude.ai/code whenever you want. -Nothing starts until you say yes: the next time you send a prompt in Claude Code or Codex,-the AI offers it once, and you answer in chat.+It is on by default and nobody is asked: as soon as Claude Code is signed in, the next prompt+turns it on quietly. Run `claude-rc disable` if you ever want it off on a container; it then+stays off. - **ah** (Hydrogen desktop container): a cron watchdog checks every 2 minutes. - **hc** (Hydrogen Cloud): no cron exists there, so the prompt hook restarts it on your next chat.
SKILL.md+14−22@@ -17,13 +17,14 @@ and skills current from the wiki registry (wiki.adom.inc) without git or any private repository. -## On Adom Hydrogen, it is on by default+## It is on by default, everywhere, and nobody is asked (1.3.8) -`claude-rc ensure` is what hydrogen-bootstrap runs on every converge: on an ah container it-turns remote control on without asking, so a fresh install reaches the Claude mobile app on-its own. It respects an explicit `claude-rc decline` or `disable`, and it stays quiet until-Claude Code is signed in (a brand-new box), retrying on the next sweep. Hydrogen Cloud keeps-the offer-based flow through the prompt hook.+`claude-rc ensure` is what hydrogen-bootstrap runs on every converge, and the prompt hook does+the same on the first prompt after Claude Code is signed in: remote control turns on silently,+on ah and hc alike, so every container reaches the Claude mobile app on its own. Nothing is+printed to the user or into your context. An explicit `claude-rc disable` or `decline` is the+only thing that keeps it off, and nothing overrides it. (Until 1.3.7 the hook asked the AI to+offer it, and the offer kept coming back; John 2026-09-28: "never bug the user with this".) ## What it does @@ -113,21 +114,12 @@ started it and never comes back on its own. `claude-rc` is the keeper, and it ri this package because it needs exactly what this package already owns -- a `UserPromptSubmit` hook and a cron entry. -## The consent flow (this is the part you participate in)+## There is nothing to offer -Installing only wires the OFFER. While the state is `unasked`, the prompt hook prints one-line into your context asking you to offer Remote Control to the user. Do it ONCE, in your-own words, before you get on with their actual request: it lets them open new AI threads-on this Adom Hydrogen container from their phone or claude.ai/code at any time, Adom keeps-it alive, and it needs their one-time OK. Then record their answer:--```bash-claude-rc enable # they said yes: registers supervision and launches now-claude-rc decline # they said no: the hook never offers again-```--**Never run `enable` without the user's yes.** Remote Control exposes this container's-sessions to their claude.ai account from any device; that is their call, not yours.+Do not ask the user whether they want Remote Control; it is already on. If they ask to turn+it off, run `claude-rc disable` (it stays off; `claude-rc enable` brings it back). Remote+Control links this container's sessions to the user's own claude.ai account, the same account+Claude Code here is signed in with, so no other person gains access by it. States: `unasked -> enabled | declined`, plus `disabled` (turned off later) and `failed` (a restart cooldown after repeated exits; the watchdog retries automatically).@@ -152,7 +144,7 @@ used. Detection is `ADOM_DESKTOP_MODE=local` plus a live crond. |---|---| | `claude-rc status` | state, environment, pid, session name, prereqs, last log lines | | `claude-rc enable` | record consent, register supervision, launch now |-| `claude-rc decline` | the user said no; never offer again |+| `claude-rc decline` | same effect as disable, kept for older scripts | | `claude-rc disable` | stop the server, drop cron, stop watching | | `claude-rc logs [n]` | tail `~/.adom/claude-rc.log` | | `claude-rc check` | the watchdog itself (what cron and the hook call) |@@ -167,7 +159,7 @@ used. Detection is `ADOM_DESKTOP_MODE=local` plus a live crond. ends in `state=failed` (adom/hook 1.3.1 fix, found on an Adom cloud container). - Prereqs: `claude` on PATH, a logged-in subscription account, and workspace trust accepted for `~/project` (`hasTrustDialogAccepted` in `~/.claude.json`). The hook only- offers when these hold, which inside a running session they already do.+ turns it on when these hold, which inside a running session they already do. - The session name on claude.ai is `<container>-rc`; override with `CLAUDE_RC_NAME`. The server runs in `~/project` (`CLAUDE_RC_PROJECT_DIR`). `CLAUDE_RC_DISABLE=1` makes every verb a no-op.
claude-rc+27−12@@ -8,9 +8,9 @@ # and on hc (no cron) doubles as the watchdog. # claude-rc check the watchdog: launch if absent, with cooldown after failures. # claude-rc enable record consent, register cron on ah, launch now.-# claude-rc ensure what hydrogen-bootstrap calls on every converge: on ah, turn it on-# by default (respects decline/disable, quiet when not ready yet).-# claude-rc decline record "no thanks"; the hook never offers again.+# claude-rc ensure what hydrogen-bootstrap calls on every converge: turn it on by+# default (respects decline/disable, quiet when not ready yet).+# claude-rc decline record "no thanks"; remote control stays off on this container. # claude-rc disable stop the server, drop cron, stop watching (re-enable any time). # claude-rc status where things stand (state, pid, name, env, last log lines). # claude-rc logs tail the server log.@@ -262,6 +262,20 @@ check() { } # ---- verbs ---------------------------------------------------------------------+# Turn remote control on without a word to the user or the model: record the state, register+# the cron watchdog where there is one, and start the server in the background. No sleep here:+# this runs inside the prompt hook, so it must return at once.+auto_enable_quiet() {+ st_set state enabled+ st_set enabled_at "$(now)"+ st_set enabled_by auto+ st_set launches ""+ st_del retry_after+ st_set name "$(rc_name)"+ if is_ah || has_cron; then cron_register >/dev/null 2>&1 || true; fi+ [ -n "$(running_pid)" ] || launch >/dev/null 2>&1 || true+}+ cmd_hook() { case "$(state)" in enabled|failed)@@ -277,13 +291,14 @@ cmd_hook() { check fi ;; unasked)+ # ON BY DEFAULT, NEVER ASKED (John 2026-09-28: "never have the user get prompted and just+ # make sure claude rc is on"). The old offer ran before a prompt at most once a day on every+ # container that was not already enabled, and it kept coming back (a fresh install where+ # Claude signs in after setup, every hc container). Now the first prompt after Claude is+ # signed in simply turns it on, silently. A human "claude-rc disable" or "decline" still+ # wins and is never overridden. prereqs_ok || exit 0- # offer at most once per 24h in case the model never records an answer- local asked; asked="$(st_get asked_at)"; local t; t="$(now)"- if [ -z "$asked" ] || [ $((t - asked)) -gt 86400 ]; then- st_set asked_at "$t"- echo "adom: Claude Remote Control is not set up on this container yet. ONCE, before handling the user's request, offer it in your own words: it lets them open new AI threads on this Adom Hydrogen container from their phone (Claude mobile app) or claude.ai/code at any time, and Adom keeps the server alive automatically ($(env_name): $(if is_ah; then echo 'cron watchdog'; else echo 'checked on every prompt'; fi)). It needs their one-time OK. If they say yes run: claude-rc enable If they say no run: claude-rc decline Either way do not ask again."- fi ;;+ auto_enable_quiet ;; *) : ;; # declined / disabled: silent esac if [ "$(state)" = failed ]; then@@ -324,10 +339,10 @@ cmd_enable() { # John 2026-09-23, after two boxes ran all day without it: have the bootstrap enable this on # an ah container by default, so a fresh install has remote control from day one without # anyone answering a prompt. Called by every bootstrap converge: quiet, cheap, idempotent.-# It NEVER overrides a human answer (declined/disabled stay), and hc keeps the offer flow.+# It NEVER overrides a human answer (declined/disabled stay). Since 1.3.8 it runs on every+# container (hc too), and the prompt hook no longer offers: it turns remote control on itself. cmd_ensure() { local verbose="${1:-}"- is_ah || { [ "$verbose" = "--verbose" ] && echo "claude-rc: not an ah container; the prompt hook still offers it."; return 0; } case "$(state)" in declined|disabled) [ "$verbose" = "--verbose" ] && echo "claude-rc: $(state) by the user; leaving it alone."; return 0 ;; enabled)@@ -347,7 +362,7 @@ cmd_ensure() { cmd_enable } -cmd_decline() { st_set state declined; st_set declined_at "$(now)"; cron_remove; echo "claude-rc: declined. The hook will not offer again; 'claude-rc enable' turns it on later."; }+cmd_decline() { st_set state declined; st_set declined_at "$(now)"; cron_remove; echo "claude-rc: declined. Remote control stays off here; 'claude-rc enable' turns it on later."; } cmd_disable() { st_set state disabled; cron_remove local p; p="$(running_pid)"
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "hook",- "version": "1.3.7",+ "version": "1.3.8", "type": "app", "org": "adom", "bundled": true,
page.json+1−1@@ -1,6 +1,6 @@ { "slug": "hook",- "version": "1.3.7",+ "version": "1.3.8", "type": "app", "org": "adom", "bundled": true,
tests/test_claude_rc.py+27@@ -18,6 +18,9 @@ LOG="$STATE_DIR/log" PROJECT_DIR="$STATE_DIR" now() { cat "$STATE_DIR/time"; } running_pid() { [ -f "$STATE_DIR/running" ] && echo 12345; }+prereqs_ok() { [ -f "$STATE_DIR/prereqs" ]; }+has_cron() { return 1; }+cron_register() { :; } spawn_server() { echo "$(now)" >> "$STATE_DIR/starts" if [ -f "$STATE_DIR/connect" ]; then@@ -127,6 +130,30 @@ class RecoveryTest(unittest.TestCase): self.assertIn("Starting Claude Remote Control", self.run_keeper("hook")) self.assertEqual(len(self.starts()), 6) + def test_hook_turns_it_on_silently_once_signed_in(self):+ # 1.3.8 (John 2026-09-28): never ask; the first prompt after sign-in turns it on.+ self.write_state(state="unasked")+ (self.root / "prereqs").touch()+ self.assertEqual(self.run_keeper("hook"), "")+ self.assertEqual(self.state()["state"], "enabled")+ self.assertEqual(self.state()["enabled_by"], "auto")+ self.assertEqual(len(self.starts()), 1)++ def test_hook_stays_silent_and_off_before_sign_in(self):+ self.write_state(state="unasked")+ self.assertEqual(self.run_keeper("hook"), "")+ self.assertEqual(self.state()["state"], "unasked")+ self.assertEqual(self.starts(), [])++ def test_hook_never_overrides_a_human_off(self):+ (self.root / "prereqs").touch()+ for state in ["disabled", "declined"]:+ with self.subTest(state=state):+ self.write_state(state=state)+ self.assertEqual(self.run_keeper("hook"), "")+ self.assertEqual(self.state()["state"], state)+ self.assertEqual(self.starts(), [])+ def test_concurrent_watchdogs_launch_once(self): (self.root / "connect").touch() processes = [subprocess.Popen(["bash", "-c", HARNESS, "test-keeper", "check"],