@rubytech/create-realagent 1.0.630 → 1.0.632

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -384,7 +384,10 @@ function installClaudeCode() {
384
384
  let needsInstall = true;
385
385
  if (commandExists("claude")) {
386
386
  try {
387
- const installed = execFileSync("claude", ["--version"], { encoding: "utf-8", timeout: 10_000 }).trim();
387
+ // `claude --version` prints "2.1.114 (Claude Code)" extract the semver so
388
+ // the equality check against `npm view` (which returns bare "2.1.114") works.
389
+ const rawVersion = execFileSync("claude", ["--version"], { encoding: "utf-8", timeout: 10_000 }).trim();
390
+ const installed = rawVersion.match(/^(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/)?.[1] ?? rawVersion;
388
391
  let latest = null;
389
392
  try {
390
393
  latest = execFileSync("npm", ["view", "@anthropic-ai/claude-code", "version"], { encoding: "utf-8", timeout: 30_000 }).trim();
@@ -408,9 +411,19 @@ function installClaudeCode() {
408
411
  log("3", TOTAL, "Installing Claude Code...");
409
412
  }
410
413
  if (needsInstall) {
411
- console.log(" This may take 15–30 minutes on Raspberry Pi...");
412
- shellRetry("npm", ["install", "-g", ...NPM_NET_FLAGS, "--loglevel", "verbose", "@anthropic-ai/claude-code@latest"], { sudo: true, timeout: 2_400_000 }, // 40 min — Pi downloads can take 25+ min
413
- 3, 30);
414
+ // `npm install -g` needs write access to the global prefix, which on Linux is
415
+ // root-owned by default so we run it under sudo. When sudo requires a password
416
+ // and the installer is running non-interactively (e.g. systemd-run --scope on
417
+ // upgrade), sudo fails instantly. Skip the upgrade in that case; the running
418
+ // installation is assumed adequate. Matches the apt-get skip in step 1.
419
+ if (isLinux() && !canSudo()) {
420
+ console.log(" Skipping Claude Code upgrade (sudo unavailable non-interactively — keeping installed version)");
421
+ }
422
+ else {
423
+ console.log(" This may take 15–30 minutes on Raspberry Pi...");
424
+ shellRetry("npm", ["install", "-g", ...NPM_NET_FLAGS, "--loglevel", "verbose", "@anthropic-ai/claude-code@latest"], { sudo: true, timeout: 2_400_000 }, // 40 min — Pi downloads can take 25+ min
425
+ 3, 30);
426
+ }
414
427
  }
415
428
  console.log(" Registering claude-plugins-official marketplace...");
416
429
  const marketplaceList = spawnSync("claude", ["plugin", "marketplace", "list"], { stdio: "pipe", encoding: "utf-8" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-realagent",
3
- "version": "1.0.630",
3
+ "version": "1.0.632",
4
4
  "description": "Install Real Agent — Built for agents. By agents.",
5
5
  "bin": {
6
6
  "create-realagent": "./dist/index.js"
@@ -298,13 +298,24 @@ Prints the UUID from Step 3. If it prints empty or null, the heredoc's env expan
298
298
 
299
299
  You do **not** run `cloudflared` manually. The brand's existing user-space systemd unit (`~/.config/systemd/user/${BRAND}.service`) declares `ExecStartPre=/home/<user>/${BRAND}/platform/scripts/resume-tunnel.sh`, and that pre-start script reads `${CFG_DIR}/tunnel.state` and `${CFG_DIR}/config.yml` (the files Steps 5 and 5b just wrote) and spawns the connector in the user's cgroup. Restarting the brand service is what picks up the new config.
300
300
 
301
- > **Note:** When walking through by hand you run this step yourself. The automation script `platform/plugins/cloudflare/scripts/setup-tunnel.sh` runs it for you — the script is autonomous and completes the deployment, including the service restart and post-restart verification (ps-grep for the connector + curl each subdomain). If you used the script, this step is already done.
301
+ > **Note:** When walking through by hand you run this step yourself. The automation script `platform/plugins/cloudflare/scripts/setup-tunnel.sh` runs it for you — with a critical twist documented below. If you used the script, this step is already done and the service will restart a few seconds after the script exits.
302
302
 
303
303
 
304
304
  ```
305
305
  systemctl --user restart "${BRAND}.service"
306
306
  ```
307
307
 
308
+ **Why the script dispatches the restart via `systemd-run` instead of a direct `systemctl restart` (Task 558):** when the admin agent invokes `setup-tunnel.sh` via the Bash tool, the script runs *inside* `${BRAND}.service`'s cgroup. A direct `systemctl --user restart ${BRAND}.service` from that cgroup tells systemd to SIGTERM the entire cgroup — the node server, the claude subprocess, the Bash child, and the script itself all die simultaneously. cgroup membership is inherited: `setsid`, `nohup`, `disown`, and `&` all stay in the caller's cgroup, and `systemd-run --scope` runs in the caller's scope. Only `systemd-run --user --unit=<name> --on-active=<N>s` creates a genuinely new transient unit with its own cgroup. The script uses that primitive to arm the restart a few seconds after its own exit:
309
+
310
+ ```
311
+ systemd-run --user --unit=maxy-tunnel-restart-<nonce>.service --on-active=3s --collect \
312
+ /bin/systemctl --user restart "${BRAND}.service"
313
+ ```
314
+
315
+ The script then emits `[setup-tunnel] step=service-restart-dispatched` and `step=service-restart-armed exit=0` in the per-conversation stream log so operators see exactly when the restart was scheduled, exits 0, and the transient timer fires from outside the service's cgroup — semantically identical to this manual runbook's `systemctl --user restart`.
316
+
317
+ When walking through manually you do **not** need `systemd-run` — your SSH shell already lives in a separate user-scope cgroup (`user@<uid>.service`), so the direct `systemctl restart` does not kill the caller. The script's extra indirection only matters when the caller *is* the service being restarted.
318
+
308
319
  **Why:** `resume-tunnel.sh` is the deterministic, brand-scoped spawner. Running `cloudflared` manually duplicates the connector (two processes for one tunnel) and races the brand service on every service restart. The service path is the only correct production path.
309
320
 
310
321
  **Success:**
@@ -306,7 +306,23 @@ echo "wrote ${CFG_DIR}/tunnel.state"
306
306
  # --------------------------------------------------------------------------
307
307
  # Restart the brand's user-space service so resume-tunnel.sh (its
308
308
  # ExecStartPre) picks up the new tunnel.state + config.yml and spawns the
309
- # connector. The script is autonomous — it completes the deployment.
309
+ # connector.
310
+ #
311
+ # CRITICAL: this script runs inside ${BRAND}.service's cgroup whenever the
312
+ # admin agent invokes it via the Bash tool. `systemctl --user restart
313
+ # ${BRAND}.service` from inside that cgroup SIGTERMs the whole cgroup —
314
+ # the node server, the claude subprocess, the Bash child, and this script
315
+ # itself (Task 558). Dispatching the restart to a transient systemd-run
316
+ # unit is the ONLY primitive that creates a new cgroup outside the service
317
+ # — setsid/nohup/disown/& all inherit cgroup membership, and
318
+ # `systemd-run --scope` runs in the caller's scope.
319
+ #
320
+ # The transient timer fires $RESTART_DELAY seconds after dispatch; the
321
+ # script exits 0 cleanly in microseconds, then the timer restarts the
322
+ # service from its own cgroup — semantically identical to an operator
323
+ # SSH-invoked `systemctl restart`. Post-restart verification (connector
324
+ # up + hostname probe) is out of scope here — the client reconnects and
325
+ # the next admin turn can verify via MCP tools.
310
326
  # --------------------------------------------------------------------------
311
327
 
312
328
  if ! systemctl --user list-unit-files "${BRAND}.service" --no-pager --no-legend | grep -q "${BRAND}.service"; then
@@ -315,48 +331,57 @@ if ! systemctl --user list-unit-files "${BRAND}.service" --no-pager --no-legend
315
331
  exit 1
316
332
  fi
317
333
 
318
- echo "Restarting ${BRAND}.service to spawn the connector with the new tunnel state..."
319
- systemctl --user restart "${BRAND}.service"
320
-
321
- # Give resume-tunnel.sh a moment to spawn the connector before probing.
322
- sleep 3
334
+ if ! command -v systemd-run >/dev/null 2>&1; then
335
+ phase_line setup-tunnel step=service-restart-dispatched result=error \
336
+ reason=systemd-run-missing
337
+ echo "ERROR: systemd-run is not in PATH." >&2
338
+ echo " The script dispatches the ${BRAND}.service restart to a transient" >&2
339
+ echo " systemd user unit so it does not kill its own cgroup (Task 558)." >&2
340
+ echo " Install systemd userspace (standard on supported Maxy Pi images)." >&2
341
+ exit 1
342
+ fi
323
343
 
324
- # --------------------------------------------------------------------------
325
- # Post-restart verification — connector running and subdomains live?
326
- # Poll each hostname up to VERIFY_TIMEOUT seconds so DNS propagation has
327
- # time to catch up (subdomains routed by cloudflared typically resolve
328
- # within 10-30s; apex-flattened or freshly-created records may need longer).
329
- # --------------------------------------------------------------------------
344
+ RESTART_DELAY=3
345
+ TRANSIENT_UNIT="maxy-tunnel-restart-$$-$(date +%s)"
346
+ phase_line setup-tunnel step=service-restart-dispatched \
347
+ unit="${TRANSIENT_UNIT}" delay="${RESTART_DELAY}s" \
348
+ cmd="systemctl --user restart ${BRAND}.service"
349
+
350
+ # Dispatch via systemd-run --user --on-active — creates a transient unit
351
+ # with its own cgroup that fires the restart after RESTART_DELAY seconds.
352
+ # --collect auto-GCs the unit after it terminates. The script exits before
353
+ # the timer fires; no race because the exit is microseconds and the timer
354
+ # is seconds. Capture stderr so the operator sees the actual systemd-run
355
+ # failure reason (e.g. "Failed to connect to bus" when linger is disabled).
356
+ SYSTEMD_RUN_ERR="$(mktemp -t maxy-systemd-run-err.XXXXXX)"
357
+ if systemd-run --user --unit="${TRANSIENT_UNIT}.service" \
358
+ --description="Detached restart of ${BRAND}.service (Task 558)" \
359
+ --on-active="${RESTART_DELAY}s" \
360
+ --collect \
361
+ /bin/systemctl --user restart "${BRAND}.service" 2>"${SYSTEMD_RUN_ERR}"; then
362
+ RESTART_RC=0
363
+ else
364
+ RESTART_RC=$?
365
+ fi
330
366
 
331
- if ! ps -ef | grep -q "[c]loudflared.*--config ${CFG_DIR}/config.yml"; then
332
- echo "" >&2
333
- echo "ERROR: ${BRAND}.service restarted but no cloudflared connector is running" >&2
334
- echo " with --config ${CFG_DIR}/config.yml." >&2
335
- echo " Check ${HOME}/.${BRAND}/logs/cloudflared.log for the failure reason." >&2
367
+ if [ "${RESTART_RC}" -ne 0 ]; then
368
+ STDERR_TEXT="$(cat "${SYSTEMD_RUN_ERR}" 2>/dev/null | tr '\n' ' ' | head -c 500 || echo 'unavailable')"
369
+ rm -f "${SYSTEMD_RUN_ERR}"
370
+ phase_line setup-tunnel step=service-restart-dispatched result=error \
371
+ reason=systemd-run-failed exit="${RESTART_RC}" unit="${TRANSIENT_UNIT}" \
372
+ stderr="${STDERR_TEXT}"
373
+ echo "ERROR: systemd-run failed to dispatch the transient restart (exit=${RESTART_RC})." >&2
374
+ echo " systemd-run stderr: ${STDERR_TEXT}" >&2
375
+ echo " If stderr mentions 'Failed to connect to bus', the user-scope systemd" >&2
376
+ echo " instance isn't running. Fix: 'loginctl enable-linger \$(whoami)' and retry." >&2
377
+ echo " The service was NOT restarted. Re-run the script or restart manually:" >&2
378
+ echo " systemctl --user restart ${BRAND}.service" >&2
336
379
  exit 1
337
380
  fi
381
+ rm -f "${SYSTEMD_RUN_ERR}"
338
382
 
339
- VERIFY_TIMEOUT=60
340
- POLL_INTERVAL=5
341
- echo "Connector running against ${CFG_DIR}/config.yml — verifying each subdomain hostname (up to ${VERIFY_TIMEOUT}s per host for DNS propagation):"
342
- for H in "${HOSTNAMES[@]}"; do
343
- if is_apex "$H"; then continue; fi
344
- ELAPSED=0
345
- STATUS="000"
346
- while [ "${ELAPSED}" -lt "${VERIFY_TIMEOUT}" ]; do
347
- STATUS=$(curl -o /dev/null -s -w '%{http_code}' --max-time 5 -I "https://${H}" || echo "000")
348
- if [ "${STATUS}" != "530" ] && [ "${STATUS}" != "000" ]; then
349
- break
350
- fi
351
- sleep "${POLL_INTERVAL}"
352
- ELAPSED=$((ELAPSED + POLL_INTERVAL))
353
- done
354
- if [ "${STATUS}" = "530" ] || [ "${STATUS}" = "000" ]; then
355
- echo " ${H}: HTTP ${STATUS} after ${VERIFY_TIMEOUT}s — DNS propagation slow or connector-to-edge issue"
356
- else
357
- echo " ${H}: HTTP ${STATUS} — live (after ${ELAPSED}s)"
358
- fi
359
- done
383
+ phase_line setup-tunnel step=service-restart-armed exit=0 unit="${TRANSIENT_UNIT}"
384
+ echo "${BRAND}.service restart armed via ${TRANSIENT_UNIT} (fires in ${RESTART_DELAY}s)."
360
385
 
361
386
  # --------------------------------------------------------------------------
362
387
  # Apex ACTION REQUIRED summary
@@ -380,6 +405,8 @@ if [ "${#APEX_HOSTNAMES[@]}" -gt 0 ]; then
380
405
  echo "============================================================"
381
406
  fi
382
407
 
408
+ phase_line setup-tunnel step=done tunnel="${TUNNEL_NAME}" id="${TUNNEL_ID}"
383
409
  echo ""
384
410
  echo "Done. tunnel=${TUNNEL_NAME} id=${TUNNEL_ID}"
385
- echo "Verify subdomain hostnames with: curl -I https://${HOSTNAMES[0]}"
411
+ echo "Service will restart in ~${RESTART_DELAY}s to load the new config."
412
+ echo "Verify hostnames with: curl -I https://${HOSTNAMES[0]}"
@@ -20,7 +20,7 @@ Any Cloudflare action outside these four surfaces is a discipline violation —
20
20
 
21
21
  ## 1. Autonomous path — `setup-tunnel.sh`
22
22
 
23
- Use this when the operator wants Cloudflare set up (or re-set up) end-to-end on the device. The script handles OAuth login, tunnel creation, DNS routing for each subdomain, config.yml + tunnel.state, service restart, and post-restart verification — all in one invocation. Apex hostnames cannot be routed by the CLI; when one is passed, the script prints an `ACTION REQUIRED` block naming the exact dashboard record to edit.
23
+ Use this when the operator wants Cloudflare set up (or re-set up) end-to-end on the device. The script handles OAuth login, tunnel creation, DNS routing for each subdomain, config.yml + tunnel.state, and dispatches the `${BRAND}.service` restart to a transient `systemd-run` unit (Task 558) — all in one invocation. The restart fires a few seconds after the script exits so the script does not kill its own cgroup when invoked via the Bash tool; the chat UI receives a `server_shutdown` SSE frame and reconnects automatically. Post-restart hostname verification is out of scope for the script (connector is not up when the script exits) — verify via the next admin turn or manually with `curl -I https://<hostname>`. Apex hostnames cannot be routed by the CLI; when one is passed, the script prints an `ACTION REQUIRED` block naming the exact dashboard record to edit.
24
24
 
25
25
  ### Inputs to collect before invoking
26
26