@rubytech/create-maxy-code 0.1.108 → 0.1.109
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/package.json +1 -1
- package/payload/platform/plugins/cloudflare/PLUGIN.md +1 -1
- package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.sh +12 -12
- package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.ts +9 -43
- package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +27 -29
- package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +14 -9
- package/payload/platform/plugins/docs/references/platform.md +4 -22
- package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
- package/payload/platform/plugins/docs/references/troubleshooting.md +4 -316
- package/payload/platform/plugins/venture-studio/PLUGIN.md +35 -4
- package/payload/platform/plugins/venture-studio/bin/scaffold.sh +104 -0
- package/payload/platform/plugins/venture-studio/skills/investor-data-room/SKILL.md +3 -1
- package/payload/platform/plugins/work/mcp/dist/cli/project-create-cli.d.ts +2 -0
- package/payload/platform/plugins/work/mcp/dist/cli/project-create-cli.d.ts.map +1 -0
- package/payload/platform/plugins/work/mcp/dist/cli/project-create-cli.js +78 -0
- package/payload/platform/plugins/work/mcp/dist/cli/project-create-cli.js.map +1 -0
- package/payload/platform/scripts/vnc.sh +4 -3
- package/payload/premium-plugins/venture-studio/PLUGIN.md +35 -4
- package/payload/premium-plugins/venture-studio/bin/scaffold.sh +104 -0
- package/payload/premium-plugins/venture-studio/skills/investor-data-room/SKILL.md +3 -1
- package/payload/server/{chunk-AGFS3TVN.js → chunk-FSPPVVWM.js} +1303 -496
- package/payload/server/maxy-edge.js +21 -259
- package/payload/server/public/assets/{ChatInput-DJsqm_Gf.js → ChatInput-CsnIedhS.js} +1 -5
- package/payload/server/public/assets/{Checkbox-DGZG9BKc.js → Checkbox-CWugFyFT.js} +1 -1
- package/payload/server/public/assets/admin-D3gZuyUn.js +217 -0
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-Dw16BhiX.js → architectureDiagram-Q4EWVU46-CeTRKWDb.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-DIOpmf5Y.js → blockDiagram-DXYQGD6D-DeeIX5U3.js} +1 -1
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-Tdb_HZeX.js → c4Diagram-AHTNJAMY-CFsqZuil.js} +1 -1
- package/payload/server/public/assets/channel-CrSx5mnG.js +1 -0
- package/payload/server/public/assets/{chunk-336JU56O-CebpwDDe.js → chunk-336JU56O-DpIXuFM0.js} +2 -2
- package/payload/server/public/assets/{chunk-426QAEUC-BtRCmfDU.js → chunk-426QAEUC-Qk8qqrvA.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-BZ3GEWs3.js → chunk-4TB4RGXK-DtM8-CUn.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-iDI6Xu0U.js → chunk-5FUZZQ4R-CrSQ4ySU.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-SQD_EpYa.js → chunk-5PVQY5BW-Bn2nQwdj.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-CtCcA7_e.js → chunk-EDXVE4YY-CzCPnR0P.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-pXVGVCbb.js → chunk-ENJZ2VHE-CgZj9RoG.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-Dkzg9o2N.js → chunk-ICPOFSXX-wy-eNjwW.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-1ZZWzf9F.js → chunk-OYMX7WX6-CMmJtL8S.js} +1 -1
- package/payload/server/public/assets/{chunk-U2HBQHQK-CpQ3kzO0.js → chunk-U2HBQHQK-CFCW7OaT.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP-C2LkxroC.js → chunk-X2U36JSP-Bgh-CJSN.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-Bl5jBOt5.js → chunk-YZCP3GAM-BXKwZ4vN.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-CHtnptPS.js → chunk-ZZ45TVLE-BiOuK5NP.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-C3IDJsqN.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BOIrJ5Zb.js +1 -0
- package/payload/server/public/assets/clone-P8Fkz7JD.js +1 -0
- package/payload/server/public/assets/{dagre-BziN0Nkh.js → dagre-DyCxa9Q2.js} +1 -1
- package/payload/server/public/assets/{dagre-KV5264BT-B7OG1g1Y.js → dagre-KV5264BT-Cffo_hmf.js} +1 -1
- package/payload/server/public/assets/data-DRfwJPja.js +1 -0
- package/payload/server/public/assets/{diagram-5BDNPKRD-Bf31nIDs.js → diagram-5BDNPKRD-Bt93Du3V.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-DQu85hhH.js → diagram-G4DWMVQ6-v4r-tBsC.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-5tstbs4y.js → diagram-MMDJMWI5-DKrVPOP-.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635--MOV1U4o.js → diagram-TYMM5635-B1PzWQb9.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-BtAnOJmd.js → erDiagram-SMLLAGMA-ClQzsQAs.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-CybiCIih.js → flowDiagram-DWJPFMVM-h3IbpN_7.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-B44jN7Cy.js → ganttDiagram-T4ZO3ILL-ClaxOJi8.js} +1 -1
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-CT7iYcwg.js → gitGraphDiagram-UUTBAWPF-pM-U4ClD.js} +1 -1
- package/payload/server/public/assets/graph-B4FFYwus.js +1 -0
- package/payload/server/public/assets/graph-labels-Cpk9Ktt0.js +1 -0
- package/payload/server/public/assets/{graphlib-DP4o0pYL.js → graphlib-CwimOv_M.js} +1 -1
- package/payload/server/public/assets/{infoDiagram-42DDH7IO-CvxFpFHN.js → infoDiagram-42DDH7IO-DcO3Giwe.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-CUwWLPeR.js → ishikawaDiagram-UXIWVN3A-BcdSL5VS.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-y-OnGgLi.js → journeyDiagram-VCZTEJTY-CYahHYzf.js} +1 -1
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-BhrSe8R4.js → kanban-definition-6JOO6SKY-Cl5KhoS4.js} +1 -1
- package/payload/server/public/assets/lib--yuBd0Xi.js +33 -0
- package/payload/server/public/assets/{line-C9uS7z4J.js → line-d_2oTxLp.js} +1 -1
- package/payload/server/public/assets/{mermaid-parser.core-Cg4ZdKp-.js → mermaid-parser.core-CMMDGv3x.js} +1 -1
- package/payload/server/public/assets/{mermaid.core-BHdKOsex.js → mermaid.core-DK9ENGbr.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-NBYiXHo7.js → mindmap-definition-QFDTVHPH-mA3x3MFG.js} +1 -1
- package/payload/server/public/assets/page-CgDmg_fX.js +1 -0
- package/payload/server/public/assets/{page-D9YpwhIu.js → page-D9lluVl7.js} +2 -2
- package/payload/server/public/assets/{pieDiagram-DEJITSTG-CbojC64C.js → pieDiagram-DEJITSTG-CW1nGFlY.js} +1 -1
- package/payload/server/public/assets/public-DdLMkNdS.js +7 -0
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-CnnoZXcL.js → quadrantDiagram-34T5L4WZ-DI5igJhR.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-DFRFRtyJ.js → requirementDiagram-MS252O5E-DyVeI4e_.js} +1 -1
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-DU5gcpzO.js → sankeyDiagram-XADWPNL6-CCTTBYKY.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-BN7HZ6Hq.js → sequenceDiagram-FGHM5R23-hVVf35ly.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-DG6cCjvg.js → stateDiagram-FHFEXIEX-CSLuQDUX.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-C5JejhQU.js +1 -0
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-qmV-8SEV.js → timeline-definition-GMOUNBTQ-sxC3W8Q4.js} +1 -1
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-BMXQ1s3n.js → vennDiagram-DHZGUBPP-Ro-ton6Z.js} +1 -1
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-CoAdP1Gw.js → wardleyDiagram-NUSXRM2D-T-dKD0Zx.js} +1 -1
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-CZlfGTEn.js → xychartDiagram-5P7HB3ND-CfProPts.js} +1 -1
- package/payload/server/public/data.html +4 -4
- package/payload/server/public/graph.html +5 -5
- package/payload/server/public/index.html +7 -7
- package/payload/server/public/public.html +4 -4
- package/payload/server/server.js +299 -1392
- package/payload/platform/plugins/cloudflare/scripts/_stream-log.sh +0 -154
- package/payload/server/chunk-BDFOTLPW.js +0 -759
- package/payload/server/chunk-JRBCOVA4.js +0 -1305
- package/payload/server/cloudflare-task-tracker-M5ONAGUT.js +0 -22
- package/payload/server/public/assets/admin-BvzMvMGo.js +0 -217
- package/payload/server/public/assets/channel-DThrH4QF.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-BC6oGTNX.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-COwC5Umh.js +0 -1
- package/payload/server/public/assets/clone-Chp7hvnA.js +0 -1
- package/payload/server/public/assets/data-BAgaPs4j.js +0 -1
- package/payload/server/public/assets/graph-Cma7EArf.js +0 -1
- package/payload/server/public/assets/graph-labels-DyKk6Sxf.js +0 -1
- package/payload/server/public/assets/lib-CpkYtEDz.js +0 -29
- package/payload/server/public/assets/page-B4IWl3aZ.js +0 -1
- package/payload/server/public/assets/public-BtOXjy3A.js +0 -8
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CvVd1Q_q.js +0 -1
package/package.json
CHANGED
|
@@ -31,7 +31,7 @@ The plugin registers no agent-facing MCP tools. Every Cloudflare operation is dr
|
|
|
31
31
|
|
|
32
32
|
| Script | Purpose |
|
|
33
33
|
|---|---|
|
|
34
|
-
| [`scripts/setup-tunnel.sh`](scripts/setup-tunnel.sh) | Autonomous end-to-end setup: OAuth login, tunnel resolve (operator-supplied identity), DNS route, config + state, service restart, post-restart verification. Invocation: `~/setup-tunnel.sh <brand> <port> <admin-hostname> [<public-hostname>] [<apex-hostname>]`. Required env: `
|
|
34
|
+
| [`scripts/setup-tunnel.sh`](scripts/setup-tunnel.sh) | Autonomous end-to-end setup: OAuth login, tunnel resolve (operator-supplied identity), DNS route, config + state, service restart, post-restart verification. Invocation: `~/setup-tunnel.sh <brand> <port> <admin-hostname> [<public-hostname>] [<apex-hostname>]`. Required env: `ACCOUNT_DIR`, AND exactly one of `TUNNEL_ID` (operator selected an existing tunnel — the agent enumerates them via `cloudflared tunnel list --output json` and presents the list in chat) or `TUNNEL_NAME` (operator typed a name to create). Apex hostnames print an `ACTION REQUIRED` block for the dashboard record the CLI cannot create. Step 1 (wrappers faithfully relay third-party CLI) spawns `cloudflared tunnel login`, extracts the argotunnel URL from its stdout, and prints it as `OAUTH_URL: <url>` on its own stdout — the native Claude Code PTY linkifies bare URLs so the operator clicks it from chat and authorizes Cloudflare in their own browser. The script does not spawn a browser of its own; cloudflared's OAuth callback writes `~/.cloudflared/cert.pem` regardless of which browser completed the Authorize click. 180 s budget with a 2-second `step=oauth-login result=awaiting-cert` heartbeat. No CDP auto-click, no DOM matcher. |
|
|
35
35
|
| [`scripts/reset-tunnel.sh`](scripts/reset-tunnel.sh) | Deletes every tunnel on the brand's CF account and wipes `${CFG_DIR}`. Does not touch the platform service, stray CNAMEs, or token-mode connectors — those require dashboard cleanup or `pkill`. Invocation: `~/reset-tunnel.sh <brand>`. No polling blocks — every long-wait is bounded by `cloudflared`'s network round-trip, so no heartbeat contract applies. |
|
|
36
36
|
|
|
37
37
|
### Skills
|
|
@@ -24,17 +24,18 @@
|
|
|
24
24
|
|
|
25
25
|
set -euo pipefail
|
|
26
26
|
|
|
27
|
-
# --------------------------------------------------------------------------
|
|
28
|
-
# Shared stream-log helpers (require STREAM_LOG_PATH, phase_line, …).
|
|
29
|
-
# --------------------------------------------------------------------------
|
|
30
|
-
|
|
31
|
-
# shellcheck source=_stream-log.sh
|
|
32
27
|
# Resolve symlinks before dirname — ~/list-cf-domains.sh is installed as a
|
|
33
28
|
# symlink into $HOME, so the raw BASH_SOURCE[0] points at $HOME, not the
|
|
34
|
-
# scripts directory
|
|
29
|
+
# scripts directory.
|
|
35
30
|
SCRIPT_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
|
|
32
|
+
# Phase-line helper. Stream-log plumbing retired in Task 287 — every
|
|
33
|
+
# observation goes straight to stdout for the PTY to render.
|
|
34
|
+
phase_line() {
|
|
35
|
+
local scope=$1
|
|
36
|
+
shift
|
|
37
|
+
printf '[%s] %s\n' "${scope}" "$*"
|
|
38
|
+
}
|
|
38
39
|
|
|
39
40
|
if [ "$#" -lt 1 ] || [ -z "${1:-}" ]; then
|
|
40
41
|
phase_line list-cf-domains phase=error reason=brand-arg-missing
|
|
@@ -45,10 +46,9 @@ BRAND="${1}"
|
|
|
45
46
|
CONFIG_DIR=".${BRAND}"
|
|
46
47
|
mkdir -p "${HOME}/${CONFIG_DIR}/logs"
|
|
47
48
|
|
|
48
|
-
# MAXY_PLATFORM_ROOT
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
# plugins/ → platform → install root (three dirs up).
|
|
49
|
+
# MAXY_PLATFORM_ROOT may be set by the calling environment. Direct-from-Bash
|
|
50
|
+
# invocation has no such env so derive it from the resolved script location:
|
|
51
|
+
# scripts/ → cloudflare/ → plugins/ → platform → install root (three dirs up).
|
|
52
52
|
if [ -z "${MAXY_PLATFORM_ROOT:-}" ]; then
|
|
53
53
|
MAXY_PLATFORM_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
|
|
54
54
|
fi
|
|
@@ -34,15 +34,15 @@
|
|
|
34
34
|
// zones at t=1000 ms). The `scrapeDomains` poll waits for the observed count
|
|
35
35
|
// to stabilise across two consecutive iterations before returning.
|
|
36
36
|
//
|
|
37
|
-
// Contract with the caller (list-cf-domains.sh
|
|
37
|
+
// Contract with the caller (the agent invokes list-cf-domains.sh via Bash):
|
|
38
38
|
// stdout on exit 0: JSON `string[]` (empty array means signed-in-but-empty)
|
|
39
|
-
// stderr on any path:
|
|
39
|
+
// stderr on any path: `[list-cf-domains] …` phase lines
|
|
40
40
|
// exit 1: `reason=<enum>` on stderr; scrape-empty-but-no-error is exit 0
|
|
41
|
-
// with body dump (the enum exists so the
|
|
42
|
-
// "selector drift" from "empty account" — both
|
|
43
|
-
// stdout).
|
|
41
|
+
// with body dump (the enum exists so the operator/agent can
|
|
42
|
+
// distinguish "selector drift" from "empty account" — both
|
|
43
|
+
// shapes arrive via stdout).
|
|
44
44
|
|
|
45
|
-
import {
|
|
45
|
+
import { readFileSync } from "node:fs";
|
|
46
46
|
import { writeFile } from "node:fs/promises";
|
|
47
47
|
import { resolve } from "node:path";
|
|
48
48
|
import { homedir } from "node:os";
|
|
@@ -90,45 +90,11 @@ type Reason =
|
|
|
90
90
|
| "brand-config-missing"
|
|
91
91
|
| "cdp-port-unresolved";
|
|
92
92
|
|
|
93
|
-
// Sticky flag: once a stream-log write fails (EACCES, ENOENT, …), skip
|
|
94
|
-
// subsequent file writes for the rest of this process. Repeated appends to an
|
|
95
|
-
// unwritable path would spam stderr with identical `phase=stream-log-write-failed`
|
|
96
|
-
// lines and crowd result.stderr that the route handler regex reads. Losing a
|
|
97
|
-
// log line is strictly better than aborting the scrape — observability failure
|
|
98
|
-
// must not mask the scrape's real signal.
|
|
99
|
-
let streamLogWriteFailed = false;
|
|
100
|
-
|
|
101
93
|
function logPhase(line: string): void {
|
|
102
|
-
// Stderr:
|
|
103
|
-
//
|
|
104
|
-
//
|
|
94
|
+
// Stderr: the agent-via-Bash invocation streams the script's stderr into
|
|
95
|
+
// the PTY chat verbatim, so phase lines land in the operator's view
|
|
96
|
+
// alongside any other script output.
|
|
105
97
|
process.stderr.write(`[list-cf-domains] ${line}\n`);
|
|
106
|
-
|
|
107
|
-
// Stream log: direct write by the TS helper (not via wrapper tee) so phase
|
|
108
|
-
// lines reach the per-conversation file the tailer reads, even on
|
|
109
|
-
// exit 0 where runFormSpawn discards `result.stderr`. Format parity with
|
|
110
|
-
// `_stream-log.sh phase_line`: `[<ISO-ts>] [script:<scope>] <content>\n` —
|
|
111
|
-
// the `script:` prefix is the chat-surface namespace so the line
|
|
112
|
-
// passes the tightened tailer regex (`[mcp:*]` / `[init]` / `[api-wait-*]`
|
|
113
|
-
// lines in the same file are excluded by construction). Asserted by the
|
|
114
|
-
// vitest regression under platform/ui/__tests__. Sync append so the
|
|
115
|
-
// terminal `phase=script-exit code=0 count=N` lands before process exit.
|
|
116
|
-
const streamLogPath = process.env.STREAM_LOG_PATH;
|
|
117
|
-
if (!streamLogPath || streamLogWriteFailed) return;
|
|
118
|
-
try {
|
|
119
|
-
appendFileSync(
|
|
120
|
-
streamLogPath,
|
|
121
|
-
`[${new Date().toISOString()}] [script:list-cf-domains] ${line}\n`,
|
|
122
|
-
);
|
|
123
|
-
} catch (err) {
|
|
124
|
-
streamLogWriteFailed = true;
|
|
125
|
-
const detail = (err instanceof Error ? err.message : String(err))
|
|
126
|
-
.slice(0, 200)
|
|
127
|
-
.replace(/"/g, "'");
|
|
128
|
-
process.stderr.write(
|
|
129
|
-
`[list-cf-domains] phase=stream-log-write-failed detail="${detail}"\n`,
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
98
|
}
|
|
133
99
|
|
|
134
100
|
function die(reason: Reason, detail = ""): never {
|
|
@@ -14,32 +14,32 @@
|
|
|
14
14
|
# hostnames. The script writes the ingress rule for them but prints an
|
|
15
15
|
# explicit ACTION REQUIRED message naming the manual dashboard step.
|
|
16
16
|
#
|
|
17
|
-
# Step 1 surfaces the argotunnel URL on stdout as an `OAUTH_URL:` line.
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
# brand VNC iframe is derived/optional and never the OAuth-completion path.
|
|
23
|
-
# cloudflared's stdout+stderr is teed line-by-line into STREAM_LOG_PATH so
|
|
24
|
-
# the chat UI's server-side tailer renders live progress in-turn.
|
|
17
|
+
# Step 1 surfaces the argotunnel URL on stdout as an `OAUTH_URL:` line. The
|
|
18
|
+
# native Claude Code PTY linkifies bare URLs so the operator clicks it from
|
|
19
|
+
# their admin chat directly. The script spawns no browser of its own.
|
|
20
|
+
# cloudflared's stdout+stderr stream verbatim into the PTY chat alongside
|
|
21
|
+
# the script's own `[setup-tunnel] step=<phase> …` phase lines.
|
|
25
22
|
|
|
26
23
|
set -euo pipefail
|
|
27
24
|
|
|
28
25
|
# Resolve symlinks before dirname — ~/setup-tunnel.sh is installed as a symlink
|
|
29
26
|
# (see packages/create-maxy-code/src/index.ts:installTunnelScripts), so the raw
|
|
30
27
|
# BASH_SOURCE[0] points at $HOME, not the scripts directory where the sibling
|
|
31
|
-
#
|
|
32
|
-
# the stream-log-contract scanner can statically resolve the
|
|
33
|
-
# tunnel-ingress.ts target.
|
|
28
|
+
# helper (tunnel-ingress.ts) lives.
|
|
34
29
|
SCRIPT_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
|
|
35
30
|
|
|
36
31
|
# --------------------------------------------------------------------------
|
|
37
|
-
#
|
|
32
|
+
# Phase-line helper. Stream-log plumbing retired in Task 287 — every
|
|
33
|
+
# observation goes straight to stdout for the PTY to render. Format is
|
|
34
|
+
# `[<scope>] <key>=<value> …` to keep operator-grep parity with the prior
|
|
35
|
+
# `[<scope>] phase=…` shape that the now-deleted _stream-log.sh emitted.
|
|
38
36
|
# --------------------------------------------------------------------------
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
phase_line() {
|
|
39
|
+
local scope=$1
|
|
40
|
+
shift
|
|
41
|
+
printf '[%s] %s\n' "${scope}" "$*"
|
|
42
|
+
}
|
|
43
43
|
|
|
44
44
|
# --------------------------------------------------------------------------
|
|
45
45
|
# Args
|
|
@@ -123,13 +123,12 @@ fi
|
|
|
123
123
|
# cert.pem, mv.
|
|
124
124
|
#
|
|
125
125
|
# Control flow when both certs are missing:
|
|
126
|
-
# 1. Spawn cloudflared
|
|
127
|
-
#
|
|
128
|
-
# (the chat-surface namespace — see _stream-log.sh header).
|
|
126
|
+
# 1. Spawn cloudflared; its stdout+stderr stream into the PTY via stderr
|
|
127
|
+
# prefixed with `[script:setup-tunnel:cloudflared]`.
|
|
129
128
|
# 2. Extract the authorize URL with a tolerant regex as it streams.
|
|
130
|
-
# 3. Print the URL on stdout as `OAUTH_URL: <url>`
|
|
131
|
-
#
|
|
132
|
-
#
|
|
129
|
+
# 3. Print the URL on stdout as `OAUTH_URL: <url>` — the native Claude
|
|
130
|
+
# Code PTY linkifies it. The script does not spawn a browser; the
|
|
131
|
+
# operator's local browser is the canonical surface. cloudflared's
|
|
133
132
|
# OAuth callback writes ~/.cloudflared/cert.pem regardless of which
|
|
134
133
|
# browser completed the Authorize click (it's a server-to-server poll
|
|
135
134
|
# against login.cloudflareaccess.org).
|
|
@@ -192,16 +191,15 @@ if [ ! -f "${CFG_DIR}/cert.pem" ]; then
|
|
|
192
191
|
}
|
|
193
192
|
trap cleanup_oauth EXIT
|
|
194
193
|
|
|
195
|
-
# cloudflared is line-buffered (stdbuf)
|
|
196
|
-
#
|
|
197
|
-
# PIPESTATUS[0] (cloudflared's
|
|
194
|
+
# cloudflared is line-buffered (stdbuf); each line is echoed verbatim into
|
|
195
|
+
# the PTY (stderr) while the OAuth URL is also extracted as it streams.
|
|
196
|
+
# The subshell holds the whole pipeline so PIPESTATUS[0] (cloudflared's
|
|
197
|
+
# exit code) is reachable later.
|
|
198
198
|
(
|
|
199
199
|
stdbuf -oL -eL cloudflared \
|
|
200
200
|
--origincert "${CFG_DIR}/cert.pem" tunnel login 2>&1 |
|
|
201
201
|
while IFS= read -r line; do
|
|
202
|
-
|
|
203
|
-
printf '[%s] [script:setup-tunnel:cloudflared] %s\n' "${ts}" "${line}" >> "${STREAM_LOG_PATH}"
|
|
204
|
-
printf '%s\n' "${line}" >&2
|
|
202
|
+
printf '[script:setup-tunnel:cloudflared] %s\n' "${line}" >&2
|
|
205
203
|
printf '%s\n' "${line}" > "${LAST_LINE_FILE}"
|
|
206
204
|
if [ ! -s "${URL_FILE}" ]; then
|
|
207
205
|
url="$(printf '%s' "${line}" | grep -oE 'https://dash\.cloudflare\.com/argotunnel\?[^ ]+' | head -1 || true)"
|
|
@@ -240,8 +238,8 @@ if [ ! -f "${CFG_DIR}/cert.pem" ]; then
|
|
|
240
238
|
AUTH_URL="$(cat "${URL_FILE}")"
|
|
241
239
|
phase_line setup-tunnel step=oauth-url-extracted url_extracted=1
|
|
242
240
|
|
|
243
|
-
# Emit the URL on stdout in a shape
|
|
244
|
-
# The
|
|
241
|
+
# Emit the URL on stdout in a shape the PTY linkifies natively.
|
|
242
|
+
# The native Claude Code terminal renderer turns bare URLs into clicks so
|
|
245
243
|
# the operator authorizes Cloudflare in the same browser they are using
|
|
246
244
|
# for the admin chat. The script does not spawn a browser of its own.
|
|
247
245
|
printf 'OAUTH_URL: %s\n' "${AUTH_URL}"
|
|
@@ -22,9 +22,9 @@ Any Cloudflare action outside these four surfaces is a discipline violation —
|
|
|
22
22
|
|
|
23
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 — 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
|
-
Step 1's OAuth flow is a state machine over two observable variables: the brand-scoped cert path (`${CFG_DIR}/cert.pem`) and the OAuth-default cert path (`~/.cloudflared/cert.pem`). When the brand-scoped cert is missing but the default-path cert is present from any prior partial run, the wrapper promotes it (`mv`) and emits `step=oauth-login result=ok reason=cert-promoted-from-default-path` without re-spawning cloudflared. When both are missing, the wrapper spawns `cloudflared tunnel login`, extracts the argotunnel URL from its stdout, and prints it on its own stdout as `OAUTH_URL: <url>` plus a `step=oauth-url-extracted url_extracted=1` phase line. The
|
|
25
|
+
Step 1's OAuth flow is a state machine over two observable variables: the brand-scoped cert path (`${CFG_DIR}/cert.pem`) and the OAuth-default cert path (`~/.cloudflared/cert.pem`). When the brand-scoped cert is missing but the default-path cert is present from any prior partial run, the wrapper promotes it (`mv`) and emits `step=oauth-login result=ok reason=cert-promoted-from-default-path` without re-spawning cloudflared. When both are missing, the wrapper spawns `cloudflared tunnel login`, extracts the argotunnel URL from its stdout, and prints it on its own stdout as `OAUTH_URL: <url>` plus a `step=oauth-url-extracted url_extracted=1` phase line. The native Claude Code PTY linkifies bare URLs, so the `OAUTH_URL: <https…>` line surfaces in chat as a clickable link the operator opens in a new tab of their own browser — the canonical operator-visible OAuth surface. The wrapper does not spawn a browser of its own; cloudflared's OAuth callback polls Cloudflare server-to-server and writes `~/.cloudflared/cert.pem` regardless of which browser completed the Authorize click. The wrapper's cert-poll (180 s budget) picks the cert up and `mv`s it to the brand-scoped path. There is no CDP auto-click, no DOM matcher, no consent-page driver — the wrapper's job is to faithfully relay `cloudflared tunnel login` and surface the URL for the operator to click.
|
|
26
26
|
|
|
27
|
-
**Operator-visible-surface doctrine.** The operator's visible surface is
|
|
27
|
+
**Operator-visible-surface doctrine.** The operator's visible surface is the admin chat PTY itself plus their own local browser. Any agent claim that ties a UI outcome to a Pi-side surface (a brand VNC chromium tab, a `:N` display) is wrong by construction — the operator may not be watching that surface. Surfaces the agent can trust as operator-visible: the admin chat PTY (the script's stdout, including its `OAUTH_URL` line) and pages the operator opened from links rendered in that chat. Everything else is best-effort.
|
|
28
28
|
|
|
29
29
|
### How inputs reach the script
|
|
30
30
|
|
|
@@ -32,9 +32,10 @@ The agent collects inputs in plain chat, then invokes the script via Bash. Four
|
|
|
32
32
|
|
|
33
33
|
Sequence:
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
0. **Derive `BRAND` from disk.** `BRAND=$(jq -r .hostname ~/.<configDir>/brand.json)` — never hardcode `maxy` or any literal brand name. The same install can host different brands on different ports; the `brand.json.hostname` field is the only authoritative source. Substituting the wrong literal here cascades into a script writing to the wrong `~/.${BRAND}/` tree and restarting the wrong service.
|
|
36
|
+
1. **Set the admin password only if not configured.** Gate on `[ -s "${HOME}/.${BRAND}/remote-password" ]` first — when the file is non-empty, a password already exists from a prior onboarding run. Skip the set step entirely. Only when the file is missing or empty, prompt the operator for the password and POST it to `curl -X POST http://127.0.0.1:${PORT}/api/remote-auth/set-password -H 'Content-Type: application/json' -d '{"password":"…"}'` (`${PORT}` from `brand.json`). Rotation requires the operator to ask for it explicitly — never re-set on every retry.
|
|
36
37
|
2. **(Optional) enumerate existing tunnels** when the operator wants to pick from the logged-in account rather than create a new one. Invoke `cloudflared tunnel list --output json` via Bash, present the result as a numbered list, and let the operator type the number (use that tunnel's `id`) or a fresh name.
|
|
37
|
-
3. **Invoke the script** with `~/setup-tunnel.sh
|
|
38
|
+
3. **Invoke the script** with `~/setup-tunnel.sh "$BRAND" "$PORT" "$ADMIN_FQDN" [<public-fqdn>] [<apex-fqdn>]`. Pass either `TUNNEL_ID=<uuid>` (operator selected) or `TUNNEL_NAME=<name>` (operator named a new one) in env — exactly one. `ACCOUNT_DIR` is resolved from the `${HOME}/.${BRAND}/` layout.
|
|
38
39
|
4. **Alias-domain classification.** For each non-admin hostname that does not start with `public.`, append it to `~/{configDir}/alias-domains.json` so `isPublicHost()` treats it as public. The platform server watches that file (`watchFile`, ~2 s pickup) — no restart needed. Use this idempotent Bash command per hostname (HOST is the FQDN, FILE is `~/.${BRAND}/alias-domains.json`):
|
|
39
40
|
|
|
40
41
|
```bash
|
|
@@ -52,10 +53,14 @@ Repeated calls with the same hostname are a no-op (the `| unique` filter dedupes
|
|
|
52
53
|
~/setup-tunnel.sh <brand> <port> <admin-hostname> [<public-hostname>] [<apex-hostname>]
|
|
53
54
|
```
|
|
54
55
|
|
|
55
|
-
|
|
56
|
+
Examples — placeholders show the substitution shape; the literal `BRAND` always derives from `brand.json.hostname`:
|
|
56
57
|
|
|
57
58
|
```
|
|
58
|
-
|
|
59
|
+
BRAND=$(jq -r .hostname ~/.maxy/brand.json) # e.g. yields "maxy"
|
|
60
|
+
~/setup-tunnel.sh "$BRAND" 19200 admin.maxy.bot public.maxy.bot maxy.chat
|
|
61
|
+
|
|
62
|
+
BRAND=$(jq -r .hostname ~/.maxy-code/brand.json) # e.g. yields "maxy-code"
|
|
63
|
+
~/setup-tunnel.sh "$BRAND" 19200 admin.maxy.bot public.maxy.bot maxy.chat
|
|
59
64
|
```
|
|
60
65
|
|
|
61
66
|
### Optional SSH and SMB ingress
|
|
@@ -104,12 +109,12 @@ The agent invokes the script directly via the Bash tool — there is no form, no
|
|
|
104
109
|
|
|
105
110
|
### Narrating OAuth progress
|
|
106
111
|
|
|
107
|
-
The script
|
|
112
|
+
The script prints structured phase lines to stdout (`[setup-tunnel] step=<phase> <key=value …>`) — these stream straight into the PTY chat verbatim. The `OAUTH_URL: <https…>` line is on a line by itself; the native Claude Code terminal renderer linkifies it so the operator clicks the link directly from chat and authorises in their own browser. The agent's narration job is to let that link do the work and not claim where any tab is open.
|
|
108
113
|
|
|
109
|
-
- **When `step=oauth-url-extracted url_extracted=1` appears,** the
|
|
114
|
+
- **When `step=oauth-url-extracted url_extracted=1` appears,** the `OAUTH_URL: …` line above it is already clickable in the operator's chat. The agent says one short line — "click the Authorize link above and I'll pick up the cert once you've authorised" — and waits. No claim about which device or screen the link opens on; the operator's browser handles that.
|
|
110
115
|
- **When `step=oauth-login result=ok reason=cert-promoted-from-default-path` appears,** a prior run already completed OAuth — Step 1 short-circuits to the existing cert. No operator action is needed.
|
|
111
116
|
- **When `step=oauth-login result=error reason=<token>` appears,** the agent restates the literal `reason=…` and any `last_line=…` field and stops per the discipline rule below. The reasons that can fire on this path are `cloudflared-exited-before-url`, `url-not-extracted`, `timeout-waiting-cert`, `cloudflared-exited-no-cert`, and `cert-promote-failed`. `timeout-waiting-cert` specifically means the operator did not click Authorize within 180 s; the remediation is a fresh `~/setup-tunnel.sh` invocation, which will land on the cert-promotion pre-flight if the operator authorised after the timeout.
|
|
112
|
-
-
|
|
117
|
+
- **`result=ok` on a script-internal phase is not narration evidence.** The agent narrates from operator-action outcomes — link clicked, cert landed — not from internal phase ticks.
|
|
113
118
|
|
|
114
119
|
### When the script exits non-zero
|
|
115
120
|
|
|
@@ -62,7 +62,7 @@ The graph view (at `/graph`) lets you explore the memory directly. Pick a catego
|
|
|
62
62
|
|
|
63
63
|
## The Web Interface
|
|
64
64
|
|
|
65
|
-
The web app runs on your Pi on port 19200. A small always-on front door (`maxy-edge`) owns that port
|
|
65
|
+
The web app runs on your Pi on port 19200. A small always-on front door (`maxy-edge`) owns that port. The edge also hosts the `/api/admin/version` route so the HeaderMenu version display keeps reading even during a mid-restart of the brand service. Login cookies are HMAC-signed with a shared key on disk, so both processes recognise the same session without any coordination and you do not have to log in again after an update. Every request is also classified as LAN or external based on the network shape it arrived on — LAN browsers reach admin directly; the remote password screen only appears on the tunnel-exposed admin domain. It provides:
|
|
66
66
|
|
|
67
67
|
- **Admin chat** (at `/`) — your primary interface, PIN-protected
|
|
68
68
|
- **Public chat** (at `/{agent-name}`) — visitor-facing agents, each with their own URL. On public hostnames, the root path serves the default agent.
|
|
@@ -108,31 +108,13 @@ The Data search panel ranks results by combining vector similarity with keyword
|
|
|
108
108
|
|
|
109
109
|
## Software Update and Cloudflare Setup
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
Both flows run on the native Claude Code PTY surface in admin chat (Task 287). There is no in-app upgrade modal and no Cloudflare setup form — the agent invokes the relevant Bash command directly and its stdout streams into chat verbatim.
|
|
112
112
|
|
|
113
|
-
**Software
|
|
114
|
-
-
|
|
115
|
-
- a heartbeat every 5 seconds carrying `state=active` + `last_phase` so you can see the installer is alive even during silent phases (tarball download, dep install);
|
|
116
|
-
- a final banner on exit with the return code and elapsed time.
|
|
117
|
-
|
|
118
|
-
If the browser drops the SSE connection mid-upgrade (typical during the maxy restart window), the panel reconnects within two seconds and replays any lines you missed from the persisted log.
|
|
113
|
+
- **Software update.** Re-run the installer (`npx -y @rubytech/create-<brand>@latest`) from a shell; HeaderMenu's version row turns sage when `installed === latest`.
|
|
114
|
+
- **Cloudflare setup.** Operator asks in chat; the agent invokes `~/setup-tunnel.sh <brand> <port> <admin-hostname> [<public-hostname>] [<apex-hostname>]` via the Bash tool. The script's `[setup-tunnel] step=<phase> …` phase lines and cloudflared's own stdout stream into the PTY; the `OAUTH_URL: <url>` line is linkified by the terminal so the operator clicks it and authorises Cloudflare in their own browser.
|
|
119
115
|
|
|
120
116
|
**Mid-turn stream-drop banners.** If a chat turn ends abruptly the bubble shows one of two messages depending on what actually happened. You see "Server is restarting — reconnect will happen automatically." only when the app server itself emits the restart signal — typically during a Software Update or a Cloudflare setup that re-launches the brand service. You see "Lost connection — retrying." when your browser's connection to the Pi dropped mid-stream while the server was still up — typically a flaky Wi-Fi moment or the tunnel hiccupping. Either way the chat resumes once the connection is back; the previously-rendered messages stay on screen so you don't lose context.
|
|
121
117
|
|
|
122
|
-
**Cloudflare setup flow.** Same pattern — POST to `/api/admin/cloudflare/setup` launches a `cloudflare-setup` action that runs `~/setup-tunnel.sh <brand> <port> <hostname...>`. When the script emits the OAuth consent URL on stdout, the log panel surfaces an **"Authorise in Cloudflare"** button; clicking it opens the consent page in a new tab. After you approve, the script's callback receives `cert.pem` and the setup continues through `tunnel create`/`route`/`run`. On devices where a VNC Chromium is also running, the script can drive the click via CDP automatically (same button remains a harmless safety net). Setup failures return a `CloudflareSetupError` carrying `inputsAlreadyHeld:{admin,public,apex}` and `discoveryResults:{tunnels,domains}` (from the process-lifetime discovery cache `/tunnels` and `/domains` populate on success); the form appends both fields as a fenced JSON block to the chat-relay body so the agent's next reply quotes held values verbatim rather than re-soliciting hostnames.
|
|
123
|
-
|
|
124
|
-
**Active-chat stream-log click telemetry.** Clicking "Stream log" in the active chat fetches the URL inline (`/api/admin/logs?type=stream&conversationId=…&download=1`), emits a `[stream-log-click] status=<c> bytes=<n> conversationId=<tail>` line in `server.log` via the `/api/_client-error` event pipe, and downloads the same response. Operator-grep `[stream-log-click] status=404` for client/server identity mismatches.
|
|
125
|
-
|
|
126
|
-
**Bundle-mtime session prelude.** Each admin session boot stamps `[boot] bundleMtime=<iso> conversationId=<tail>` in `server.log` next to `[plugins] MCP servers for session:`; the value is consumed by the Cloudflare setup route's retry-decision branch (it compares against the most recent terminal `:Task.completedAt`), never by the LLM. The prior `<deployment>` block was removed in 2026-05-13 doctrine fix because the admin LLM read it as narration permission for internal state; the route now owns the retry decision, the agent restates the literal envelope.
|
|
127
|
-
|
|
128
|
-
**Post-action restatement block (2026-05-13 doctrine fix).** `assembleSystemPrompt` injects `<post-action>actionId=<id> outcome=<completed|failed> phase=<phase> at=<iso></post-action>` into the system prompt whenever the dispatcher resolves a terminal `:Task {kind:"cloudflare-tunnel-login"}` on the conversation. Agent-type-agnostic. The IDENTITY clause "If `<post-action>` is present, restate the literal outcome and stop" routes the operator-visible reply. Observability: `[post-action-block] injected|no-prior-action|lookup-failed` log line every turn.
|
|
129
|
-
|
|
130
|
-
**Admin reply scrub (2026-05-13 doctrine fix).** Closed-list token scrub at `platform/ui/app/lib/admin-reply-scrub.ts`; tokens `bundle`, `bundleMtime`, `overnight`, `the fix may`, `was patched`, `re-rendering`, `paste the output`, `run the script`, `re-attempt`. Hit records a violation under `llm-narrates-internal-state` (rule_family `extension`) and emits `[admin-reply-scrub] agent=<name> tokens_found=[…] reply_blocked=false`. Future doctrine paragraphs naming a new internal token MUST extend the list in the same PR.
|
|
131
|
-
|
|
132
|
-
**Sudo password** is prompted once per upgrade. The admin server pipes it to `sudo -S -v` to validate + cache, then forwards it to the action unit via `systemd-run --setenv=SUDO_PASSWORD` so the installer's in-unit `sudo -S` reads it directly — per-TTY sudoers configurations where the user-level cache does not cover a fresh systemd-run unit still work. The password is never written to any log, SSE frame, or persisted file.
|
|
133
|
-
|
|
134
|
-
**Log files.** Each action writes its full output to `~/.maxy/logs/actions/<actionId>.log` for seven days. `journalctl --user --identifier=maxy-action-<actionId>` gives the systemd-level view.
|
|
135
|
-
|
|
136
118
|
**Authorisation** is inherited from the same `canAccessAdmin()` gate that wraps every `/api/admin/*` route.
|
|
137
119
|
|
|
138
120
|
## AI Content Provenance
|
|
@@ -152,7 +152,7 @@ After this, every `console.error("[your-tool]...")` from any tool in the plugin
|
|
|
152
152
|
|
|
153
153
|
**How the tee decides which file to write to:** the platform sets `STREAM_LOG_PATH` as an environment variable on every MCP server spawn, pointing to the conversation-scoped stream log. The MCP server does not know about conversations — it just trusts `STREAM_LOG_PATH`. Multiple concurrent conversations produce multiple concurrent MCP server processes, each teeing to its own file; no cross-conversation leakage.
|
|
154
154
|
|
|
155
|
-
|
|
155
|
+
**Bash scripts stream straight into the PTY.** Maxy Code's admin and public chat run on the native Claude Code PTY (Task 287). The per-conversation server-side stream log that the retired web-UI dispatcher tailed is gone; agent-invoked shell scripts (`setup-tunnel.sh`, `reset-tunnel.sh`, `list-cf-domains.sh`) print `[<scope>] <key>=<value>` phase lines directly to stdout, which the PTY renders in chat verbatim.
|
|
156
156
|
|
|
157
157
|
**Retrieve MCP diagnostic lines for a conversation:**
|
|
158
158
|
|