@rubytech/create-maxy-code 0.1.84 → 0.1.85

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.84",
3
+ "version": "0.1.85",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env bash
2
+ # setup-tunnel.sh OAuth display precedence — three-case test.
3
+ #
4
+ # Verifies that the chromium spawn in Step 1 (OAuth login) ALWAYS uses the
5
+ # brand-declared VNC display from brand.json, never the PTY-inherited
6
+ # DISPLAY env var. Covers the regression filed as task 221: PTY admin
7
+ # agent inherits DISPLAY=:0 from systemd, which would route the OAuth
8
+ # Authorize page onto the system display rather than the operator-visible
9
+ # brand VNC.
10
+ #
11
+ # Cases:
12
+ # 1. Positive — brand.json vncDisplay=100, DISPLAY=:0 in env
13
+ # → stream log shows `step=browser-spawn result=ok display=:100`.
14
+ # 2. Negative — brand.json missing vncDisplay field, DISPLAY=:0 in env
15
+ # → exit non-zero with `result=error reason=brand-vnc-display-unresolved`.
16
+ # 3. Negative — brand.json file absent (MAXY_PLATFORM_ROOT set, file missing)
17
+ # → exit non-zero with same reason.
18
+ #
19
+ # Mocks cloudflared (echo URL + sleep), systemd-run (no-op success), and
20
+ # the brand chromium binary via PATH override. SETUP_TUNNEL_LOGIN_TIMEOUT=1
21
+ # short-circuits the cert-poll so the positive case exits in ~1 s.
22
+ #
23
+ # Run: bash setup-tunnel-display-precedence.test.sh
24
+
25
+ set -uo pipefail
26
+
27
+ TEST_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
28
+ SCRIPT_PATH="$(dirname "${TEST_DIR}")/setup-tunnel.sh"
29
+
30
+ if [ ! -x "${SCRIPT_PATH}" ] && [ ! -r "${SCRIPT_PATH}" ]; then
31
+ echo "FAIL: setup-tunnel.sh not found at ${SCRIPT_PATH}"
32
+ exit 1
33
+ fi
34
+
35
+ if ! command -v jq >/dev/null 2>&1; then
36
+ echo "SKIP: jq is required for brand.json vncDisplay parsing in setup-tunnel.sh"
37
+ exit 0
38
+ fi
39
+
40
+ PASS=0
41
+ FAIL=0
42
+
43
+ run_case() {
44
+ local case_name="$1"
45
+ local brand_json_content="$2" # empty means "do not write the file"
46
+ local expected_exit="$3" # "nonzero" or a specific code; we only check non-zero vs result via grep
47
+ local expected_grep="$4" # pattern to grep in stream log
48
+
49
+ WORK="$(mktemp -d -t maxy-display-test.XXXXXX)"
50
+ trap 'rm -rf "$WORK"' RETURN
51
+
52
+ # Fake $HOME for ${CFG_DIR}=${HOME}/.${BRAND}/cloudflared paths
53
+ export HOME="${WORK}/home"
54
+ mkdir -p "${HOME}/.testbrand/cloudflared"
55
+ # Ensure default OAuth cert path also empty so pre-flight cert-promotion is skipped
56
+ mkdir -p "${HOME}/.cloudflared"
57
+
58
+ # MAXY_PLATFORM_ROOT layout
59
+ export MAXY_PLATFORM_ROOT="${WORK}/platform"
60
+ mkdir -p "${MAXY_PLATFORM_ROOT}/config"
61
+ # Path INSIDE the file is the chromium binary; the file itself does not
62
+ # need to be executable. The script's check is `[ -x "${path}" ]` which
63
+ # requires an absolute path — `command -v true` returns the shell
64
+ # builtin name, not a path. Pick the first existing absolute file.
65
+ if [ -x /usr/bin/true ]; then TRUE_BIN=/usr/bin/true
66
+ elif [ -x /bin/true ]; then TRUE_BIN=/bin/true
67
+ else
68
+ echo "SKIP: no /usr/bin/true or /bin/true available for chromium-binary mock"
69
+ return 0
70
+ fi
71
+ printf '%s\n' "${TRUE_BIN}" > "${MAXY_PLATFORM_ROOT}/config/chromium-binary.path"
72
+
73
+ if [ -n "${brand_json_content}" ]; then
74
+ printf '%s' "${brand_json_content}" > "${MAXY_PLATFORM_ROOT}/config/brand.json"
75
+ fi
76
+
77
+ export STREAM_LOG_PATH="${WORK}/stream.log"
78
+ : > "${STREAM_LOG_PATH}"
79
+
80
+ # PATH-override mocks
81
+ PATH_DIR="${WORK}/bin"
82
+ mkdir -p "${PATH_DIR}"
83
+
84
+ cat > "${PATH_DIR}/cloudflared" <<'MOCK'
85
+ #!/usr/bin/env bash
86
+ # Mock cloudflared: emit a URL once, then sleep long enough that the
87
+ # parent's cert-poll timeout fires.
88
+ echo "Please open the following URL in your browser:"
89
+ echo "https://dash.cloudflare.com/argotunnel?aud=test&callback=https%3A%2F%2Flogin.cloudflareaccess.org%2F"
90
+ sleep 30
91
+ MOCK
92
+ chmod +x "${PATH_DIR}/cloudflared"
93
+
94
+ cat > "${PATH_DIR}/systemd-run" <<'MOCK'
95
+ #!/usr/bin/env bash
96
+ # Mock systemd-run: succeed without launching anything.
97
+ exit 0
98
+ MOCK
99
+ chmod +x "${PATH_DIR}/systemd-run"
100
+
101
+ cat > "${PATH_DIR}/systemctl" <<'MOCK'
102
+ #!/usr/bin/env bash
103
+ # Mock systemctl: stop returns 5 (unit auto-collected) on the happy
104
+ # `--user stop` path; list-unit-files prints nothing.
105
+ case "${*}" in
106
+ *"stop"*) exit 5 ;;
107
+ *) exit 0 ;;
108
+ esac
109
+ MOCK
110
+ chmod +x "${PATH_DIR}/systemctl"
111
+
112
+ # stdbuf shim — macOS lacks GNU coreutils stdbuf. The script invokes
113
+ # `stdbuf -oL -eL cloudflared ...` purely for line-buffering of the
114
+ # cloudflared output; in this test the output is one short URL line so
115
+ # buffering doesn't matter. The shim strips the -o/-e flags and execs
116
+ # the rest of argv directly.
117
+ cat > "${PATH_DIR}/stdbuf" <<'MOCK'
118
+ #!/usr/bin/env bash
119
+ while [ $# -gt 0 ]; do
120
+ case "$1" in
121
+ -i*|-o*|-e*) shift ;;
122
+ --) shift; break ;;
123
+ *) break ;;
124
+ esac
125
+ done
126
+ exec "$@"
127
+ MOCK
128
+ chmod +x "${PATH_DIR}/stdbuf"
129
+
130
+ # Force DISPLAY=:0 to simulate PTY admin agent inheriting from systemd.
131
+ export DISPLAY=":0"
132
+ # Short-circuit cert-poll so positive case finishes in ~1 s.
133
+ export SETUP_TUNNEL_LOGIN_TIMEOUT=1
134
+ # Required by Step 2+3, but Step 1 cert-poll timeout exits before we
135
+ # get there; still set them so any drift surfaces a different failure.
136
+ export TUNNEL_NAME="setup-tunnel-display-test"
137
+
138
+ PATH="${PATH_DIR}:${PATH}" "${SCRIPT_PATH}" \
139
+ testbrand 19200 admin.test.bot \
140
+ > "${WORK}/stdout.log" 2> "${WORK}/stderr.log" &
141
+ SCRIPT_PID=$!
142
+
143
+ # Bound the wait — kill after 30 s as a safety net.
144
+ for _ in $(seq 1 60); do
145
+ if ! kill -0 "${SCRIPT_PID}" 2>/dev/null; then break; fi
146
+ sleep 0.5
147
+ done
148
+ if kill -0 "${SCRIPT_PID}" 2>/dev/null; then
149
+ kill -TERM "${SCRIPT_PID}" 2>/dev/null || true
150
+ sleep 1
151
+ kill -KILL "${SCRIPT_PID}" 2>/dev/null || true
152
+ fi
153
+ wait "${SCRIPT_PID}" 2>/dev/null
154
+ ACTUAL_EXIT=$?
155
+
156
+ if grep -qE "${expected_grep}" "${STREAM_LOG_PATH}"; then
157
+ echo "PASS: ${case_name}"
158
+ PASS=$((PASS + 1))
159
+ else
160
+ echo "FAIL: ${case_name}"
161
+ echo " expected stream log to match: ${expected_grep}"
162
+ echo " exit=${ACTUAL_EXIT}"
163
+ echo " --- stream log ---"
164
+ sed 's/^/ /' "${STREAM_LOG_PATH}" || true
165
+ echo " --- stderr ---"
166
+ sed 's/^/ /' "${WORK}/stderr.log" || true
167
+ FAIL=$((FAIL + 1))
168
+ fi
169
+ }
170
+
171
+ # Case 1 — Positive. vncDisplay=100, DISPLAY=:0 inherited.
172
+ # Expect: chromium-spawn phase line carries display=:100, NOT :0.
173
+ run_case "positive — brand.json vncDisplay=100 overrides inherited DISPLAY=:0" \
174
+ '{"vncDisplay": 100}' \
175
+ "nonzero" \
176
+ 'step=browser-spawn result=ok.*display=:100'
177
+
178
+ # Case 2 — Negative. brand.json present but no vncDisplay field.
179
+ # Expect: hard-fail with reason=brand-vnc-display-unresolved.
180
+ run_case "negative — brand.json missing vncDisplay → hard-fail" \
181
+ '{"port": 19200}' \
182
+ "nonzero" \
183
+ 'step=browser-spawn result=error reason=brand-vnc-display-unresolved'
184
+
185
+ # Case 3 — Negative. brand.json file absent altogether.
186
+ # Expect: same hard-fail.
187
+ run_case "negative — brand.json file absent → hard-fail" \
188
+ '' \
189
+ "nonzero" \
190
+ 'step=browser-spawn result=error reason=brand-vnc-display-unresolved'
191
+
192
+ echo ""
193
+ echo "Summary: ${PASS} passed, ${FAIL} failed"
194
+ [ "${FAIL}" -eq 0 ]
@@ -16,8 +16,8 @@
16
16
  #
17
17
  # Step 1 owns the browser-spawn deterministically. The wrapper extracts
18
18
  # cloudflared's argotunnel URL from its stdout (regex below) and the moment
19
- # the URL surfaces, mechanically opens it on the Pi's VNC chromium via
20
- # `DISPLAY=${DISPLAY:-${BRAND_VNC_DISPLAY}} /usr/bin/chromium <url> &` — no reliance on
19
+ # the URL surfaces, mechanically opens it on the brand's VNC chromium via
20
+ # `DISPLAY=${BRAND_VNC_DISPLAY} /usr/bin/chromium <url> &` — no reliance on
21
21
  # cloudflared's optimistic xdg-open, no CDP auto-click, no DOM matcher.
22
22
  # The operator clicks the zone row + Authorize on the VNC themselves.
23
23
  # This is the wrappers-faithfully-relay-third-party-cli doctrine;
@@ -99,17 +99,22 @@ phase_line setup-tunnel step=start brand="${BRAND}" port="${PORT}" hostnames="${
99
99
  CFG_DIR="${HOME}/.${BRAND}/cloudflared"
100
100
  mkdir -p "${CFG_DIR}"
101
101
 
102
- # Per-brand X display. Each brand owns its own Xtigervnc display
103
- # so the Chromium tab opened to drive the OAuth Authorize click lands in
104
- # THIS brand's isolated VNC, not whichever brand booted first on a shared
105
- # device. Read from the brand manifest if MAXY_PLATFORM_ROOT is exported
106
- # (set by the systemd unit and by claude-agent::buildSpawnEnv); fall back
107
- # to the inherited DISPLAY env var, then to :99 for the default brand.
102
+ # Per-brand X display. Each brand owns its own Xtigervnc display so the
103
+ # Chromium tab opened to drive the OAuth Authorize click lands in THIS
104
+ # brand's isolated VNC, not whichever brand booted first on a shared
105
+ # device. brand.json `.vncDisplay` is the sole source of truth; the
106
+ # PTY-agent's inherited DISPLAY (typically `:0` from the systemd unit) is
107
+ # the WRONG surface and is never consulted. If brand.json is absent or
108
+ # does not declare a numeric `vncDisplay`, the chromium-spawn site in
109
+ # Step 1 hard-fails with `step=browser-spawn result=error
110
+ # reason=brand-vnc-display-unresolved` rather than silently falling back
111
+ # to a system display. Re-runs that skip Step 1 (cert.pem already in the
112
+ # brand path) tolerate an unresolved display because no chromium spawns.
108
113
  SETUP_TUNNEL_BRAND_JSON=""
109
114
  if [ -n "${MAXY_PLATFORM_ROOT:-}" ] && [ -f "${MAXY_PLATFORM_ROOT}/config/brand.json" ]; then
110
115
  SETUP_TUNNEL_BRAND_JSON="${MAXY_PLATFORM_ROOT}/config/brand.json"
111
116
  fi
112
- BRAND_VNC_DISPLAY=":99"
117
+ BRAND_VNC_DISPLAY=""
113
118
  if [ -n "${SETUP_TUNNEL_BRAND_JSON}" ] && command -v jq >/dev/null 2>&1; then
114
119
  _bvd=$(jq -r '.vncDisplay // empty' "${SETUP_TUNNEL_BRAND_JSON}" 2>/dev/null || true)
115
120
  if [ -n "${_bvd}" ] && [ "${_bvd}" -eq "${_bvd}" ] 2>/dev/null; then
@@ -185,8 +190,8 @@ fi
185
190
  # $STREAM_LOG_PATH with prefix [script:setup-tunnel:cloudflared]
186
191
  # (the chat-surface namespace — see _stream-log.sh header).
187
192
  # 2. Extract the authorize URL with a tolerant regex as it streams.
188
- # 3. The instant the URL is extracted, mechanically open it on the Pi
189
- # VNC chromium via `DISPLAY=${DISPLAY:-${BRAND_VNC_DISPLAY}} /usr/bin/chromium <url> &`.
193
+ # 3. The instant the URL is extracted, mechanically open it on the brand
194
+ # VNC chromium via `DISPLAY=${BRAND_VNC_DISPLAY} /usr/bin/chromium <url> &`.
190
195
  # Fire-and-forget — chromium is already running with CDP enabled at
191
196
  # :9222, so the invocation IPCs the URL into the running instance as
192
197
  # a new tab. The spawn is intentionally NOT tracked in the EXIT trap:
@@ -237,7 +242,22 @@ if [ ! -f "${CFG_DIR}/cert.pem" ] && [ -f "${HOME}/.cloudflared/cert.pem" ]; the
237
242
  fi
238
243
 
239
244
  if [ ! -f "${CFG_DIR}/cert.pem" ]; then
240
- phase_line setup-tunnel step=oauth-login cert_path="${CFG_DIR}/cert.pem" display="${DISPLAY:-${BRAND_VNC_DISPLAY}}"
245
+ # Brand owns the OAuth chromium display unconditionally. If brand.json
246
+ # didn't declare a numeric vncDisplay, the install is broken — refuse to
247
+ # spawn chromium on whatever DISPLAY the PTY agent happened to inherit
248
+ # (typically :0 from the systemd unit, which is NOT the operator-visible
249
+ # brand VNC). Doctrine block at top of file: brand wins, every time.
250
+ if [ -z "${BRAND_VNC_DISPLAY}" ]; then
251
+ phase_line setup-tunnel step=browser-spawn result=error \
252
+ reason=brand-vnc-display-unresolved \
253
+ brand_json="${SETUP_TUNNEL_BRAND_JSON:-unset}"
254
+ echo "ERROR: setup-tunnel.sh: brand VNC display could not be resolved." >&2
255
+ echo " brand.json: ${SETUP_TUNNEL_BRAND_JSON:-unset}" >&2
256
+ echo " Required: brand.json must declare numeric 'vncDisplay'." >&2
257
+ exit 1
258
+ fi
259
+
260
+ phase_line setup-tunnel step=oauth-login cert_path="${CFG_DIR}/cert.pem" display="${BRAND_VNC_DISPLAY}"
241
261
 
242
262
  URL_FILE="$(mktemp -t maxy-setup-tunnel-url.XXXXXX)"
243
263
  LAST_LINE_FILE="$(mktemp -t maxy-setup-tunnel-last.XXXXXX)"
@@ -267,7 +287,7 @@ if [ ! -f "${CFG_DIR}/cert.pem" ]; then
267
287
  # extracted as it streams. The subshell holds the whole pipeline so
268
288
  # PIPESTATUS[0] (cloudflared's exit code) is reachable later.
269
289
  (
270
- DISPLAY="${DISPLAY:-${BRAND_VNC_DISPLAY}}" stdbuf -oL -eL cloudflared \
290
+ DISPLAY="${BRAND_VNC_DISPLAY}" stdbuf -oL -eL cloudflared \
271
291
  --origincert "${CFG_DIR}/cert.pem" tunnel login 2>&1 |
272
292
  while IFS= read -r line; do
273
293
  ts="$(stream_log_ts)"
@@ -342,7 +362,7 @@ if [ ! -f "${CFG_DIR}/cert.pem" ]; then
342
362
  # `/usr/bin/chromium` here would re-introduce the AppArmor SingletonLock
343
363
  # failure on the laptop.
344
364
  CHROMIUM_UNIT="maxy-oauth-chromium-${BRAND}-$$.service"
345
- CHROMIUM_LAUNCH_DISPLAY="${DISPLAY:-${BRAND_VNC_DISPLAY}}"
365
+ CHROMIUM_LAUNCH_DISPLAY="${BRAND_VNC_DISPLAY}"
346
366
  CHROMIUM_SPAWN_ERR="$(mktemp -t maxy-oauth-chromium-err.XXXXXX)"
347
367
  if systemd-run --user \
348
368
  --unit="${CHROMIUM_UNIT}" \
@@ -100,6 +100,15 @@ The YAML and JSON rendering live in a pure Node helper at
100
100
 
101
101
  The agent invokes the script directly via the Bash tool — there is no form, no endpoint relay. Stream the script's stdout into chat verbatim as it arrives; if an `ACTION REQUIRED` block appears, quote it exactly — the operator needs the specific dashboard instructions it contains.
102
102
 
103
+ ### Reading the stream log before narrating progress
104
+
105
+ Chat narration is contingent on phase-line evidence, not on the script's overall `result=ok`. The script tees structured phase lines into the stream log as it runs (`[setup-tunnel] step=<phase> <key=value …>`). The agent reads them and narrates from them:
106
+
107
+ - **Before claiming "the authorise page is open on the device's screen"** (or any equivalent), the agent must have seen `step=browser-spawn result=ok display=:<N>` in the stream log AND must have confirmed `:<N>` matches the brand VNC display recorded in `brand.json` `.vncDisplay` (the chat surface the operator watches). If the operator is on a Pi where the admin chat shares brand VNC `:100`, then `display=:100` is the only value consistent with "page is open on the device". Any other value means the page is on a screen the operator cannot see.
108
+ - **If `display=:<N>` does not match `vncDisplay`,** the agent surfaces the literal `OAUTH_URL: <url>` line from the script's stdout and asks the operator to open it in their shared browser themselves. No claim that the spawn succeeded. The script's chromium spawn is treated as best-effort that may have landed on the wrong screen.
109
+ - **If `step=browser-spawn result=error`,** the agent restates the literal `reason=…` and any `stderr=…` fields and stops, per the discipline rule below. The most common error in this site is `reason=brand-vnc-display-unresolved`, which means `brand.json` does not declare a numeric `vncDisplay` and the installer has to be re-run to fix the brand manifest before another setup attempt makes sense.
110
+ - **The script's `result=ok` flag alone is not evidence that the page is operator-visible.** The display field is the gate. Read it, compare it to brand state, then narrate.
111
+
103
112
  ### When the script exits non-zero
104
113
 
105
114
  Relay the script's stdout to the operator verbatim, name the literal exit code, and cite `references/reset-guide.md` for the next action. Do not attempt a second invocation under a different flag combination, a Playwright-driven dashboard inspection, or an alternative `cloudflared` command sequence. The discipline rule below applies.