← Commit history

Publish 1.3.0

John Lauer ·b2064ba02e ·1mo ago ·parent 63bc7fd
7 files changed +455−22
README.md+17−2
@@ -4,8 +4,6 @@ skills current from [wiki.adom.inc](https://wiki.adom.inc) automatically, on every Claude Code and Codex prompt. No git, no private repository. -![adom-hook updating core in the background on a prompt](docs/shot.png)- ## Why  The old update path fetched a private GitHub repo on every prompt. That works for@@ -72,3 +70,20 @@ adom-wiki uninstall adom/hook ```  Unwires the hook from both `~/.claude/settings.json` and `~/.codex/hooks.json`.+++## claude-rc: Remote Control, kept alive++This package also installs `claude-rc`, which keeps Claude Code Remote Control (`claude+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.++- **ah** (Hydrogen desktop container): a cron watchdog restarts the server within 2 minutes.+- **hc** (Hydrogen Cloud): no cron exists there, so the prompt hook restarts it on your next chat.++```+claude-rc status      claude-rc enable      claude-rc disable      claude-rc logs+```
SKILL.md+80−1
@@ -1,6 +1,13 @@ --- name: adom-hook-description: Registry-native cross-agent auto-update hook; keeps adom/core apps and skills current from wiki.adom.inc on every Claude Code and Codex prompt.+description: >-+  The two things adom wires into every Claude Code and Codex prompt: the registry-native+  auto-update hook (keeps adom/core apps and skills current from wiki.adom.inc), and+  claude-rc, which keeps Claude Remote Control alive so the user can open AI threads on+  this container from their phone. Read when packages are behind or an update failed, and+  when the user says claude rc, remote control, use my container from my phone, open a+  thread from my phone, claude-rc status, or you see an "adom: Claude Remote Control" line+  in your context. ---  # adom-hook@@ -86,3 +93,75 @@ It is also a dependency of `adom/core`, so a normal bootstrap installs it for yo The Codex wiring (`~/.codex/hooks.json`) targets Codex's `UserPromptSubmit` event and its additionalContext channel. Verify against a live Codex install before relying on it there; the Claude Code path is the verified one.+++# claude-rc: Claude Remote Control, kept alive++`claude rc` (Claude Code Remote Control) turns this container into a server the user can+open new Claude threads on from the Claude mobile app or https://claude.ai/code. It is a+plain foreground process with no built-in supervisor: it dies with the terminal that+started it and never comes back on its own. `claude-rc` is the keeper, and it rides in+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)++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.++States: `unasked -> enabled | declined`, plus `disabled` (turned off later) and `failed`+(the server kept exiting; the hook reports that at most every 6 hours).++## ah vs hc: supervision differs, and you should say which the user has++- **ah** (Adom Hydrogen desktop container, `ADOM_DESKTOP_MODE=local`): cron exists, so+  `enable` registers `@reboot` and `*/2 * * * * claude-rc check`. The server comes back+  within two minutes even while the user is away -- this is what makes "open a thread from+  my phone whenever I want" actually true.+- **hc** (Hydrogen Cloud): no cron and no systemd, so THE PROMPT HOOK IS THE WATCHDOG. On+  every chat submit it relaunches a dead server and tells you in one line. If it dies+  while the user is away it stays down until their next prompt. Say so plainly rather than+  promising ah behaviour on hc.++systemd is not reachable from inside either container (no bus, no sudo), so it is never+used. Detection is `ADOM_DESKTOP_MODE=local` plus a live crond.++## Verbs++| verb | what |+|---|---|+| `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 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) |+| `claude-rc hook` | the UserPromptSubmit hook; never call it by hand |++## Facts worth knowing before you debug it++- `claude rc` skips its "Enable Remote Control? (y/n)" prompt when stdin is not a TTY, so+  the watchdog launches it headless with `setsid nohup ... </dev/null`.+- 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.+- 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.+- A restart is a NEW server. Sessions opened through the old one are still on disk and+  resumable with `claude --resume <id>`.+- Crash-loop guard: five launches within an hour flips the state to `failed` rather than+  spinning. The usual cause is a signed-out account. `claude-rc enable` retries.+- It dies with the container. On ah the `@reboot` line brings it back; on hc the next+  prompt does.
claude-rcadded+224
@@ -0,0 +1,224 @@+#!/usr/bin/env bash+# claude-rc -- keep Claude Code Remote Control (`claude rc`) alive on an Adom container.+#+# One script, several verbs. Everything is idempotent and safe to call often.+#+#   claude-rc hook      the UserPromptSubmit hook. Fast + silent in the common case.+#                       Offers setup once (prints one line into the model's context),+#                       and on hc (no cron) doubles as the watchdog.+#   claude-rc check     the watchdog: launch `claude rc` if it is not running.+#   claude-rc enable    record consent, register cron on ah, launch now.+#   claude-rc decline   record "no thanks"; the hook never offers again.+#   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.+#+# Environments (see the ah-vs-hc note in SKILL.md):+#   ah  Adom Hydrogen desktop container: cron is available -> real 2-minute watchdog.+#   hc  Hydrogen Cloud: no cron, no systemd -> the prompt hook is the watchdog.+#+# Tunables (env):+#   CLAUDE_RC_PROJECT_DIR   directory the server runs in   (default $HOME/project)+#   CLAUDE_RC_NAME          session name shown on claude.ai (default <container>-rc)+#   CLAUDE_RC_DISABLE       any non-empty value makes every verb a no-op+set -u+[ -n "${CLAUDE_RC_DISABLE:-}" ] && exit 0+export PATH="$HOME/.local/bin:$PATH"++STATE_DIR="$HOME/.adom"+STATE="$STATE_DIR/claude-rc.state"+LOG="$STATE_DIR/claude-rc.log"+SELF="$HOME/.local/bin/claude-rc"+PROJECT_DIR="${CLAUDE_RC_PROJECT_DIR:-$HOME/project}"+# Anchored to the START of the cmdline so a shell whose command text merely+# mentions "claude rc" (this script's own callers, a chat transcript) never matches.+PAT='^\S*/claude (rc|remote-control)( |$)'+mkdir -p "$STATE_DIR"++# ---- state file: KEY=VALUE lines -------------------------------------------+st_get() { [ -f "$STATE" ] && sed -n "s/^$1=//p" "$STATE" | tail -1; }+st_set() { # st_set KEY VALUE+  local tmp="$STATE.tmp.$$"+  { [ -f "$STATE" ] && grep -v "^$1=" "$STATE"; echo "$1=$2"; } > "$tmp" 2>/dev/null+  mv "$tmp" "$STATE"+}+now() { date +%s; }+state() { local s; s="$(st_get state)"; echo "${s:-unasked}"; }++# ---- environment -------------------------------------------------------------+is_ah() { [ "${ADOM_DESKTOP_MODE:-}" = "local" ]; }+has_cron() { command -v crontab >/dev/null 2>&1 && { pgrep -x cron >/dev/null 2>&1 || pgrep -x crond >/dev/null 2>&1; }; }+env_name() { if is_ah; then echo ah; else echo hc; fi; }+container_name() {+  local n="${ADOM_CONTAINER_NAME:-$(hostname 2>/dev/null || echo adom)}"+  echo "$n" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9-\n' '-'+}+rc_name() { echo "${CLAUDE_RC_NAME:-$(container_name)-rc}"; }++running_pid() { pgrep -f "$PAT" | head -1; }+# TERM, wait up to 8s for a graceful exit, then KILL. `claude rc` takes a moment to+# tear down its relay connection, so a bare pkill + immediate pgrep still sees it.+stop_server() {+  pkill -f "$PAT" 2>/dev/null || return 0+  local i; for i in 1 2 3 4 5 6 7 8; do [ -z "$(running_pid)" ] && return 0; sleep 1; done+  pkill -9 -f "$PAT" 2>/dev/null || true; sleep 1+}++# Prerequisites `claude rc` needs: the CLI, a logged-in account, and the workspace+# trust dialog accepted for PROJECT_DIR. A UserPromptSubmit hook fires from inside a+# session so these normally already hold; the check matters for Codex callers.+prereqs_ok() {+  command -v claude >/dev/null 2>&1 || return 1+  [ -d "$PROJECT_DIR" ] || return 1+  command -v python3 >/dev/null 2>&1 || return 0+  python3 - "$HOME/.claude.json" "$PROJECT_DIR" <<'PY' 2>/dev/null+import json, sys+try:+    d = json.load(open(sys.argv[1]))+except Exception:+    sys.exit(1)+p = d.get("projects", {}).get(sys.argv[2], {})+sys.exit(0 if p.get("hasTrustDialogAccepted") else 1)+PY+}++# ---- cron (ah only) ----------------------------------------------------------+CRON_CMD="$SELF check"+cron_register() {+  has_cron || return 1+  { crontab -l 2>/dev/null | grep -vF "$SELF" || true+    echo "@reboot $CRON_CMD >/dev/null 2>&1"+    echo "*/2 * * * * $CRON_CMD >/dev/null 2>&1"; } | crontab - 2>/dev/null+}+cron_remove() {+  command -v crontab >/dev/null 2>&1 || return 0+  if crontab -l 2>/dev/null | grep -qF "$SELF"; then+    { crontab -l 2>/dev/null | grep -vF "$SELF" || true; } | crontab - 2>/dev/null+  fi+}+cron_registered() { crontab -l 2>/dev/null | grep -qF "$CRON_CMD"; }++# ---- launch / watchdog ---------------------------------------------------------+rotate_log() {+  [ -f "$LOG" ] && [ "$(stat -c %s "$LOG" 2>/dev/null || echo 0)" -gt 5000000 ] && mv "$LOG" "$LOG.1"+  return 0+}+launch() {+  cd "$PROJECT_DIR" || return 1+  rotate_log+  echo "[$(date -Is)] claude-rc: launching (name=$(rc_name), env=$(env_name))" >> "$LOG"+  setsid nohup claude rc --name "$(rc_name)" </dev/null >> "$LOG" 2>&1 &+  # crash-loop guard: remember launch times, keep the last hour's worth+  local t; t="$(now)"+  local hist; hist="$(st_get launches)"+  local kept=""; local x+  for x in ${hist//,/ }; do [ $((t - x)) -lt 3600 ] && kept="$kept,$x"; done+  kept="${kept#,}"; kept="${kept:+$kept,}$t"+  st_set launches "$kept"+  st_set last_launch "$t"+  local n; n=$(echo "$kept" | tr ',' '\n' | grep -c .)+  if [ "$n" -ge 5 ]; then+    st_set state failed+    st_set failed_at "$t"+    st_set fail_reason "$(tr -d '\r' < "$LOG" | grep -v '^\s*$' | grep -v 'claude-rc:' | tail -3 | tr '\n' ' ' | cut -c1-300)"+    echo "[$(date -Is)] claude-rc: $n launches within an hour; marking failed. Run 'claude-rc enable' to retry." >> "$LOG"+    return 2+  fi+  return 0+}+check() {+  [ "$(state)" = "enabled" ] || return 0+  if [ -n "$(running_pid)" ]; then return 0; fi+  launch; return $?+}++# ---- verbs ---------------------------------------------------------------------+cmd_hook() {+  case "$(state)" in+    enabled)+      if [ -z "$(running_pid)" ]; then+        if launch; then+          echo "adom: Claude Remote Control was not running on this container; relaunched it as '$(rc_name)'. It should reappear on claude.ai/code and the mobile app within a minute."+        else+          echo "adom: Claude Remote Control keeps exiting on this container and has been paused after repeated relaunches. Last output: $(st_get fail_reason). Tell the user; 'claude-rc logs' shows more and 'claude-rc enable' retries."+        fi+      fi ;;+    failed)+      # remind at most every 6h+      local last; last="$(st_get fail_reported)"; local t; t="$(now)"+      if [ -z "$last" ] || [ $((t - last)) -gt 21600 ]; then+        st_set fail_reported "$t"+        echo "adom: Claude Remote Control is paused on this container after it kept exiting (last output: $(st_get fail_reason)). Mention it to the user once; 'claude-rc enable' retries, 'claude-rc disable' turns it off."+      fi ;;+    unasked)+      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 ;;+    *) : ;;  # declined / disabled: silent+  esac+  exit 0+}++cmd_enable() {+  st_set state enabled+  st_set enabled_at "$(now)"+  st_set launches ""+  st_set name "$(rc_name)"+  local how="prompt-hook watchdog (hc: no cron here)"+  if is_ah || has_cron; then+    if cron_register; then how="cron watchdog every 2 min + @reboot (ah)"; else how="prompt-hook watchdog (cron unavailable)"; fi+  fi+  if [ -n "$(running_pid)" ]; then+    echo "claude-rc: enabled; server already running (pid $(running_pid)); supervision: $how"+  else+    launch || true+    sleep 4+    if [ -n "$(running_pid)" ]; then+      echo "claude-rc: enabled and running as '$(rc_name)' (pid $(running_pid)); supervision: $how. It appears on claude.ai/code and the Claude mobile app within about a minute."+    else+      echo "claude-rc: enabled, but the server exited right away. Last output:"; tr -d '\r' < "$LOG" | grep -v '^\s*$' | tail -5+      echo "Usually: not logged in ('claude' first), or workspace trust not accepted in $PROJECT_DIR."+    fi+  fi+}+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_disable() {+  st_set state disabled; cron_remove+  local p; p="$(running_pid)"+  if [ -n "$p" ]; then+    stop_server+    echo "claude-rc: disabled; stopped server pid $p and removed cron entries."+  else+    echo "claude-rc: disabled; cron entries removed."+  fi+}+cmd_status() {+  local p; p="$(running_pid)"+  echo "state:       $(state)"+  echo "env:         $(env_name) (ADOM_DESKTOP_MODE=${ADOM_DESKTOP_MODE:-unset}, cron: $(if has_cron; then echo yes; else echo no; fi), cron registered: $(if cron_registered; then echo yes; else echo no; fi))"+  echo "server:      $(if [ -n "$p" ]; then echo "running pid $p"; else echo "not running"; fi)"+  echo "name:        $(rc_name)"+  echo "project dir: $PROJECT_DIR"+  echo "prereqs:     $(if prereqs_ok; then echo ok; else echo "missing (claude on PATH + workspace trust accepted in $PROJECT_DIR)"; fi)"+  [ "$(state)" = failed ] && echo "fail reason: $(st_get fail_reason)"+  echo "log:         $LOG"+  [ -f "$LOG" ] && { echo "--- last lines:"; tr -d '\r' < "$LOG" | grep -v '^\s*$' | tail -4; }+  return 0+}+cmd_logs() { [ -f "$LOG" ] && tr -d '\r' < "$LOG" | grep -v '^\s*$' | tail -${1:-40}; }++case "${1:-status}" in+  hook)    cmd_hook ;;+  check)   check ;;+  enable)  cmd_enable ;;+  decline) cmd_decline ;;+  disable) cmd_disable ;;+  status)  cmd_status ;;+  logs)    cmd_logs "${2:-40}" ;;+  -h|--help|help) sed -n 2,24p "$0" ;;+  *) echo "claude-rc: unknown verb '$1' (hook|check|enable|decline|disable|status|logs)" >&2; exit 2 ;;+esac
install.sh+44−7
@@ -12,6 +12,35 @@ mkdir -p "$HOOK_DIR" install -m 0755 "$PKG_DIR/check-updates.sh" "$HOOK" HOOK_CMD="bash $HOOK" +# --- claude-rc: the Remote Control keeper (adom/hook 1.3.0) --------------------+# Same machinery this package already owns (a UserPromptSubmit hook + cron), so it+# lives here rather than in a package of its own. It keeps `claude rc` alive so a+# user can open new AI threads on this container from the Claude mobile app or+# claude.ai/code. It is CONSENT-FIRST: installing only wires the offer; nothing+# runs until the user says yes in chat (see SKILL.md).+BIN_DIR="${HOME}/.local/bin"+RC="${BIN_DIR}/claude-rc"+RC_HOOK_CMD="$RC hook"+mkdir -p "$BIN_DIR"+install -m 0755 "$PKG_DIR/claude-rc" "$RC"++# Migrate the hand-rolled predecessor (a ~/.adom/hooks/claude-rc-watchdog.sh plus its+# own cron lines). Its presence IS consent -- the user asked for it explicitly -- so+# carry that over as enabled instead of re-asking.+RC_STATE="${HOME}/.adom/claude-rc.state"+LEGACY_RC="${HOOK_DIR}/claude-rc-watchdog.sh"+if [ -f "$LEGACY_RC" ] || crontab -l 2>/dev/null | grep -q "claude-rc-watchdog.sh"; then+  if command -v crontab >/dev/null 2>&1 && crontab -l 2>/dev/null | grep -q "claude-rc-watchdog.sh"; then+    { crontab -l 2>/dev/null | grep -v "claude-rc-watchdog.sh" || true; } | crontab - 2>/dev/null || true+  fi+  rm -f "$LEGACY_RC"+  if ! grep -q '^state=' "$RC_STATE" 2>/dev/null; then+    mkdir -p "$(dirname "$RC_STATE")"+    echo "state=enabled" >> "$RC_STATE"+    echo "  migrated the pre-package claude-rc watchdog (consent carried over as enabled)"+  fi+fi+ # Drop the stale launcher file from older adom/hook installs (now adom-core-update.sh). rm -f "${HOOK_DIR}/check-updates.sh" 2>/dev/null || true @@ -20,12 +49,13 @@ if ! command -v python3 >/dev/null 2>&1; then   exit 0 fi -# Wire HOOK_CMD into one agent config's hooks.UserPromptSubmit, removing any legacy-# auto-update hook first. $1 = config path, $2 = "1" to include a timeout field.+# Wire a command into one agent config's hooks.UserPromptSubmit, removing any legacy+# auto-update hook first. $1 = config path, $2 = "1" to include a timeout field,+# $3 = the command (defaults to the auto-update hook). wire() {-  local path="$1" with_timeout="$2"+  local path="$1" with_timeout="$2" cmd="${3:-$HOOK_CMD}"   mkdir -p "$(dirname "$path")"-  python3 - "$path" "$HOOK_CMD" "$with_timeout" <<'PY'+  python3 - "$path" "$cmd" "$with_timeout" <<'PY' import json, sys path, cmd, with_timeout = sys.argv[1], sys.argv[2], sys.argv[3] == "1" try:@@ -67,14 +97,21 @@ if changed:     with open(path, "w") as f:         json.dump(d, f, indent=2)     note = " (retired %d legacy hook(s))" % removed if removed else ""-    print("  wired adom-core-update into %s%s" % (path, note))+    print("  wired %s into %s%s" % (cmd.split("/")[-1].split()[0], path, note)) else:-    print("  adom-core-update already wired in %s" % path)+    print("  %s already wired in %s" % (cmd.split("/")[-1].split()[0], path)) PY }  wire "${HOME}/.claude/settings.json" 1 wire "${HOME}/.codex/hooks.json" 0+wire "${HOME}/.claude/settings.json" 1 "$RC_HOOK_CMD"+wire "${HOME}/.codex/hooks.json" 0 "$RC_HOOK_CMD"++# Re-assert supervision when consent already exists (re-install, image bake, migration).+if grep -q '^state=enabled' "$RC_STATE" 2>/dev/null; then+  "$RC" enable || true+fi  # --------------------------------------------------------------------------- # Headless trigger (adom/hook #1). UserPromptSubmit only fires inside Claude Code@@ -179,4 +216,4 @@ if [ -f "$PKG_DIR/SKILL.md" ]; then   cp "$PKG_DIR/SKILL.md" "$HOME/.claude/skills/adom-hook/SKILL.md" fi -echo "Installed adom-hook (auto-update wired for Claude + Codex; checks every ${ADOM_HOOK_INTERVAL:-1800}s)"+echo "Installed adom-hook (auto-update wired for Claude + Codex; checks every ${ADOM_HOOK_INTERVAL:-1800}s; claude-rc staged, offer on next prompt)"
package.json+36−4
@@ -1,10 +1,10 @@ {   "slug": "hook",-  "version": "1.2.4",+  "version": "1.3.0",   "type": "app",   "org": "adom",   "bundled": true,-  "description": "Registry-native cross-agent auto-update hook. Keeps adom/core apps and skills current from wiki.adom.inc on every Claude Code and Codex prompt, in the background.",+  "description": "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.",   "dependencies": {},   "scripts": {     "install": "./install.sh",@@ -16,7 +16,11 @@     "bootstrap",     "claude-code",     "codex",-    "registry"+    "registry",+    "remote-control",+    "claude-rc",+    "mobile",+    "watchdog"   ],   "discovery_triggers": [     "auto update skills and apps",@@ -25,7 +29,14 @@     "adom-wiki update hook",     "claude code update hook",     "codex update hook",-    "registry auto update"+    "registry auto update",+    "claude remote control",+    "claude rc",+    "open a thread from my phone",+    "use my container from my phone",+    "spawn claude threads from mobile",+    "remote control not running",+    "claude-rc status"   ],   "hero": {     "headline": "Always current. Zero clicks.",@@ -38,6 +49,27 @@       "src": "check-updates.sh",       "dest": "~/.adom/hooks/adom-core-update.sh",       "mode": "0755"+    },+    {+      "src": "claude-rc",+      "dest": "~/.local/bin/claude-rc",+      "mode": "0755"+    }+  ],+  "changelog": "1.3.0: add claude-rc, which keeps Claude Code Remote Control (`claude rc`) alive so a user can open new AI threads on their container from the Claude mobile app or claude.ai/code. It rides in adom/hook because it needs exactly what this package already owns: a UserPromptSubmit hook and a cron entry. Consent-first -- installing only wires the offer, and the AI asks the user once. Supervision differs by environment: a 2-minute cron watchdog on ah, and the prompt hook itself on hc, which has neither cron nor systemd. No dependency changes.",+  "discovery_pitch": "Use when adom packages are behind or an update failed, and when the user asks about Claude Remote Control -- opening AI threads on this container from their phone or claude.ai/code.",+  "sample_prompts": [+    {+      "label": "Is remote control running?",+      "prompt": "is claude remote control running on this container?"+    },+    {+      "label": "Set it up",+      "prompt": "set up claude rc so I can open threads here from my phone"+    },+    {+      "label": "Am I up to date?",+      "prompt": "are my adom packages current with the wiki?"     }   ] }\ No newline at end of file
page.json+44−5
@@ -1,10 +1,10 @@ {   "slug": "hook",-  "version": "1.2.4",+  "version": "1.3.0",   "type": "app",   "org": "adom",   "bundled": true,-  "description": "Registry-native cross-agent auto-update hook. Keeps adom/core apps and skills current from wiki.adom.inc on every Claude Code and Codex prompt, in the background.",+  "description": "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.",   "dependencies": {},   "scripts": {     "install": "./install.sh",@@ -16,7 +16,11 @@     "bootstrap",     "claude-code",     "codex",-    "registry"+    "registry",+    "remote-control",+    "claude-rc",+    "mobile",+    "watchdog"   ],   "discovery_triggers": [     "auto update skills and apps",@@ -25,7 +29,14 @@     "adom-wiki update hook",     "claude code update hook",     "codex update hook",-    "registry auto update"+    "registry auto update",+    "claude remote control",+    "claude rc",+    "open a thread from my phone",+    "use my container from my phone",+    "spawn claude threads from mobile",+    "remote control not running",+    "claude-rc status"   ],   "hero": {     "type": "billboard",@@ -38,5 +49,33 @@   "author": {     "name": "Kyle Bergstedt",     "email": "[email protected]"-  }+  },+  "managed_files": [+    {+      "src": "check-updates.sh",+      "dest": "~/.adom/hooks/adom-core-update.sh",+      "mode": "0755"+    },+    {+      "src": "claude-rc",+      "dest": "~/.local/bin/claude-rc",+      "mode": "0755"+    }+  ],+  "changelog": "1.3.0: add claude-rc, which keeps Claude Code Remote Control (`claude rc`) alive so a user can open new AI threads on their container from the Claude mobile app or claude.ai/code. It rides in adom/hook because it needs exactly what this package already owns: a UserPromptSubmit hook and a cron entry. Consent-first -- installing only wires the offer, and the AI asks the user once. Supervision differs by environment: a 2-minute cron watchdog on ah, and the prompt hook itself on hc, which has neither cron nor systemd. No dependency changes.",+  "discovery_pitch": "Use when adom packages are behind or an update failed, and when the user asks about Claude Remote Control -- opening AI threads on this container from their phone or claude.ai/code.",+  "sample_prompts": [+    {+      "label": "Is remote control running?",+      "prompt": "is claude remote control running on this container?"+    },+    {+      "label": "Set it up",+      "prompt": "set up claude rc so I can open threads here from my phone"+    },+    {+      "label": "Am I up to date?",+      "prompt": "are my adom packages current with the wiki?"+    }+  ] }\ No newline at end of file
uninstall.sh+10−3
@@ -5,10 +5,10 @@ HOOK="${HOME}/.adom/hooks/adom-core-update.sh" HOOK_CMD="bash $HOOK"  unwire() {-  local path="$1"+  local path="$1" cmd="${2:-$HOOK_CMD}"   [ -f "$path" ] || return 0   command -v python3 >/dev/null 2>&1 || return 0-  python3 - "$path" "$HOOK_CMD" <<'PY'+  python3 - "$path" "$cmd" <<'PY' import json, sys path, cmd = sys.argv[1], sys.argv[2] try:@@ -33,6 +33,13 @@ PY unwire "${HOME}/.claude/settings.json" unwire "${HOME}/.codex/hooks.json" +# claude-rc rides in this package: stop it, drop its cron, unwire and remove it.+RC="${HOME}/.local/bin/claude-rc"+[ -x "$RC" ] && "$RC" disable >/dev/null 2>&1 || true+unwire "${HOME}/.claude/settings.json" "$RC hook"+unwire "${HOME}/.codex/hooks.json" "$RC hook"+rm -f "$RC" "${HOME}/.adom/claude-rc.state"+ # Drop the headless cron trigger too, or uninstall would leave entries pointing at # a script it just deleted (cron would then log a failure every 30 minutes). if command -v crontab >/dev/null 2>&1; then@@ -45,4 +52,4 @@ fi rm -f "$HOOK" rm -rf "${HOME}/.claude/skills/adom-hook" rm -f "${HOME}/.adom/last-update.status" "${HOME}/.adom/last-update.log" "${HOME}/.adom/last-core-check"-echo "Removed adom-hook (unwired from Claude + Codex, cron entries dropped)"+echo "Removed adom-hook (auto-update and claude-rc unwired from Claude + Codex, cron entries dropped; ~/.adom/claude-rc.log kept)"