← Commit history

Publish 1.3.8

John Lauer ·633d88582d ·21d ago ·parent 14e6bc9
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"],