@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 +17 -4
- package/package.json +1 -1
- package/payload/platform/plugins/cloudflare/references/manual-setup.md +12 -1
- package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +66 -39
- package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +1 -1
- package/payload/server/public/assets/{admin-DirN63aF.js → admin-BntwbBs-.js} +60 -60
- package/payload/server/public/index.html +1 -1
- package/payload/server/server.js +100 -25
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
|
-
|
|
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
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
@@ -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 —
|
|
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.
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
340
|
-
|
|
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 "
|
|
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
|
|
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
|
|