@rubytech/create-maxy 1.0.667 → 1.0.669

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
@@ -206,6 +206,33 @@ function canSudo() {
206
206
  const result = spawnSync("sudo", ["-n", "true"], { stdio: "pipe", timeout: 5_000 });
207
207
  return result.status === 0;
208
208
  }
209
+ // Task 634 — verified-not-asserted apt-dep reconciliation.
210
+ //
211
+ // Returns the subset of `pkgs` that are not currently installed. Uses
212
+ // `dpkg -s <pkg>` per package (exit 0 = installed, any non-zero = missing)
213
+ // so we never parse dpkg's prose output for control flow
214
+ // (feedback_no_stdout_parsing_for_control_flow.md).
215
+ function pkgsMissing(pkgs) {
216
+ return pkgs.filter((p) => {
217
+ const r = spawnSync("dpkg", ["-s", p], { stdio: "pipe", timeout: 5_000 });
218
+ return r.status !== 0;
219
+ });
220
+ }
221
+ /**
222
+ * Install a logical group of apt packages, then verify each one landed in
223
+ * dpkg's installed state. The post-install `dpkg -s` probe catches the
224
+ * partial-failure class where `apt-get install` returns 0 but a package did
225
+ * not actually install — silent regressions that would otherwise only
226
+ * surface at runtime when the binary is missing.
227
+ */
228
+ function installAptGroup(label, pkgs) {
229
+ logFile(` apt install (${label}): ${pkgs.join(" ")}`);
230
+ shell("apt-get", ["install", "-y", ...pkgs], { sudo: true });
231
+ const stillMissing = pkgsMissing(pkgs);
232
+ if (stillMissing.length > 0) {
233
+ throw new Error(`apt-get install (${label}) returned 0 but packages are still not installed per dpkg -s: ${stillMissing.join(", ")}`);
234
+ }
235
+ }
209
236
  // ---------------------------------------------------------------------------
210
237
  // Installation steps
211
238
  // ---------------------------------------------------------------------------
@@ -216,28 +243,51 @@ function installSystemDeps() {
216
243
  console.log(" Skipping Linux-specific setup (not on Linux).");
217
244
  return;
218
245
  }
219
- if (canSudo()) {
220
- shell("apt-get", ["update"], { sudo: true });
221
- shell("apt-get", ["install", "-y", "curl", "git", "unzip", "jq", "avahi-daemon", "avahi-utils", "poppler-utils", "ffmpeg"], { sudo: true });
222
- // xterm is the *preferred* terminal-emulator binary for the VNC-rendered
223
- // Terminal surface (Task 632 gnome-terminal is a D-Bus launcher that
224
- // delegates window creation to the session's gnome-terminal-server,
225
- // opening windows on the wrong display; xterm has no IPC layer and
226
- // honours DISPLAY directly). Kept in the apt list unconditionally so
227
- // vnc.sh's resolve_terminal_bin has a display-safe binary on every
228
- // supported distro (Ubuntu 24.04 noble/universe, Debian 12 bookworm/main
229
- // verified). xdotool (Task 632) backs the post-spawn display-membership
230
- // assertion in vnc.sh check_window_on_display, closing the silent-fail
231
- // class where PID is alive but no window is mapped on the target display.
232
- shell("apt-get", ["install", "-y", "tigervnc-standalone-server", "python3-websockify", "novnc", "xdg-utils", "chromium", "xterm", "xdotool"], { sudo: true });
233
- shell("apt-get", ["install", "-y", "hostapd", "dnsmasq"], { sudo: true });
234
- // tmux backs the admin terminal's persistent named session (Task 591).
235
- // ttyd is NOT in Debian Bookworm's apt repo (Task 602) — it ships as a
236
- // pinned upstream binary installed by provisionTtydBinary() in step 11.
237
- shell("apt-get", ["install", "-y", "tmux"], { sudo: true });
246
+ const BASE_DEPS = ["curl", "git", "unzip", "jq", "avahi-daemon", "avahi-utils", "poppler-utils", "ffmpeg"];
247
+ // xterm is the *preferred* terminal-emulator binary for the VNC-rendered
248
+ // Terminal surface (Task 632 gnome-terminal is a D-Bus launcher that
249
+ // delegates window creation to the session's gnome-terminal-server,
250
+ // opening windows on the wrong display; xterm has no IPC layer and
251
+ // honours DISPLAY directly). Kept in the apt list unconditionally so
252
+ // vnc.sh's resolve_terminal_bin has a display-safe binary on every
253
+ // supported distro (Ubuntu 24.04 noble/universe, Debian 12 bookworm/main
254
+ // verified). xdotool (Task 632) backs the post-spawn display-membership
255
+ // assertion in vnc.sh check_window_on_display, closing the silent-fail
256
+ // class where PID is alive but no window is mapped on the target display.
257
+ const VNC_DEPS = ["tigervnc-standalone-server", "python3-websockify", "novnc", "xdg-utils", "chromium", "xterm", "xdotool"];
258
+ const WIFI_DEPS = ["hostapd", "dnsmasq"];
259
+ // tmux backs the admin terminal's persistent named session (Task 591).
260
+ // ttyd is NOT in Debian Bookworm's apt repo (Task 602) it ships as a
261
+ // pinned upstream binary installed by provisionTtydBinary() in step 11.
262
+ const TERMINAL_DEPS = ["tmux"];
263
+ const ALL_APT_DEPS = [...BASE_DEPS, ...VNC_DEPS, ...WIFI_DEPS, ...TERMINAL_DEPS];
264
+ // Task 634 — verify the "deps are present" assumption with `dpkg -s` instead
265
+ // of asserting it (feedback_loud_failures.md). The previous silent-skip
266
+ // branch was benign until Task 632 added xdotool (the first new apt dep
267
+ // since the skip path became load-bearing on user-password-sudo devices).
268
+ const missing = pkgsMissing(ALL_APT_DEPS);
269
+ if (missing.length === 0) {
270
+ logFile(` all system deps present per dpkg -s (${ALL_APT_DEPS.length} packages) — skipping apt install`);
271
+ console.log(` All ${ALL_APT_DEPS.length} system deps already installed — skipping apt install.`);
238
272
  }
239
273
  else {
240
- console.log(" Skipping apt-get (sudo unavailable non-interactively deps assumed present from prior install)");
274
+ const canEscalate = canSudo() || process.stdout.isTTY === true;
275
+ if (!canEscalate) {
276
+ const repair = `sudo apt-get install -y ${missing.join(" ")}`;
277
+ console.error(` MISSING ${missing.length} system deps per dpkg -s: ${missing.join(" ")}`);
278
+ console.error(` Non-interactive sudo is unavailable; cannot prompt for password from a non-TTY shell.`);
279
+ console.error(` Re-run this installer from an interactive shell, or repair manually:`);
280
+ console.error(` ${repair}`);
281
+ logFile(` FAIL: missing apt deps without interactive sudo: ${missing.join(",")}`);
282
+ throw new Error(`installSystemDeps: missing packages (${missing.join(", ")}) and sudo is unavailable non-interactively — repair with: ${repair}`);
283
+ }
284
+ console.log(` Missing apt deps (${missing.length}): ${missing.join(", ")}`);
285
+ console.log(` Installing via sudo apt-get — sudo may prompt for your password...`);
286
+ shell("apt-get", ["update"], { sudo: true });
287
+ installAptGroup("base utilities", BASE_DEPS);
288
+ installAptGroup("VNC stack", VNC_DEPS);
289
+ installAptGroup("WiFi AP", WIFI_DEPS);
290
+ installAptGroup("terminal", TERMINAL_DEPS);
241
291
  }
242
292
  // Hostname resolution — four sources, in priority order:
243
293
  // 1. --hostname flag (unconditional — the caller is the authority)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy",
3
- "version": "1.0.667",
3
+ "version": "1.0.669",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy": "./dist/index.js"
@@ -84,7 +84,7 @@ Ask naturally:
84
84
 
85
85
  Maxy answers relational questions — "list all my people", "how many tasks do I have", "find the person with email X", "show me the 20 most recently created nodes" — via direct read-only Cypher against your Neo4j. This is faster and more precise than semantic search when the question is "the exact set where", not "things similar to".
86
86
 
87
- You can also open a visual view of your graph at any time from the burger menu → **Graph**. Click the **Filter** button in the toolbar to open the filter menu — it lists only the node types that actually exist in your graph, one row per type, showing your per-type node count and sorted so the most-connected types sit at the top. Leaf types that only appear inside a parent (messages inside a conversation, sections inside a document) are never filter rows — you reach them by clicking the parent and exploring its neighbourhood. Infrastructural types (`ToolCall`, `WorkflowRun`, `WorkflowStep`, `ReviewAlert`) never appear as filter rows — the first three are execution plumbing, and `ReviewAlert` has its own dedicated surface in admin chat. Active rows render a force-directed map, coloured by label (Person, Service, KnowledgeDocument, Task, …). Click a node to see its properties and explore its neighbourhood; click the **Back** control or empty canvas to return to your filter view with the same rows still selected. Type in the search box to highlight matches; submitting a search also widens the filter to include any node types the hits belong to, so relevant matches render instead of disappearing into a "not in current view" banner. The **×** buttons on the search box and inside the filter menu clear the current search and the current selection respectively — clearing the filter selection does not touch your saved default.
87
+ You can also open a visual view of your graph at any time from the burger menu → **Graph**. Click the **Filter** button in the toolbar to open the filter menu — it lists only the node types that actually exist in your graph, one row per type, showing your per-type node count and sorted so the most-connected types sit at the top. Leaf types that only appear inside a parent (messages inside a conversation, sections inside a document) are never filter rows — you reach them by clicking the parent and exploring its neighbourhood. Infrastructural types (`ToolCall`, `WorkflowRun`, `WorkflowStep`, `ReviewAlert`) never appear as filter rows — the first three are execution plumbing, and `ReviewAlert` has its own dedicated surface in admin chat. Active rows render a force-directed map, coloured by label (Person, Service, KnowledgeDocument, Task, …). Click a node from the filter view to explore its neighbourhood. Once inside a neighbourhood, clicking another node (a Message inside a Conversation, for example) opens its properties in the side panel without collapsing the view — peer Messages stay on canvas and **Back** still returns to your filter rows in one step. Click the **Back** control or empty canvas to return to your filter view with the same rows still selected. Type in the search box to highlight matches; submitting a search also widens the filter to include any node types the hits belong to, so relevant matches render instead of disappearing into a "not in current view" banner. The **×** buttons on the search box and inside the filter menu clear the current search and the current selection respectively — clearing the filter selection does not touch your saved default.
88
88
 
89
89
  Conversations and Messages carry role/channel sublabels so you can read the chat topology by colour alone — admin vs public conversations and user vs assistant messages render in distinct shades, and the filter menu splits them into separate rows.
90
90
 
@@ -172,6 +172,41 @@ Most common failures and fixes:
172
172
  - `[terminal-launch] failed err="window absent from target display after spawn" pid=<N> display=:99 observed_windows=0` → the spawned emulator's window landed on the wrong display (Task 632 — almost always a stale gnome-terminal binary on an upgraded install; the fix ships `xterm` as the `:99` default but a stale `resolve_terminal_bin` on an old bundle can still trigger it). Check `which gnome-terminal xterm xdotool` — all three should exist; if not, re-run the installer. Also check `DISPLAY=:99 xdotool search --onlyvisible --class '.'` manually: empty output confirms no window on `:99`; a non-empty result after a fresh click means the window IS there and the check itself is wrong (file an issue).
173
173
  - `ensure-terminal action="escalate-vnc-restart"` followed by `degraded` → `Xtigervnc` itself is not coming up. Check `~/.maxy/logs/vnc-boot.log` for the tigervnc startup lines.
174
174
 
175
+ ## Terminal fails after upgrade — `Invalid response` or `window absent from target display`
176
+
177
+ **Symptom:** You upgraded a Maxy device and the header-menu **Terminal** click now fails — overlay shows `Invalid response`, or `terminal-launch.log` shows `[terminal-launch] failed err="window absent from target display after spawn" pid=<N> display=:99 observed_windows=0 transport=vnc cmd="/usr/bin/xterm "`. The terminal worked on the previous version and the upgrade reported success.
178
+
179
+ **What it means:** The installer declared a new apt dep between versions (e.g. `xdotool` in 1.0.667), but on your device the installer's apt step was silently skipped because `sudo` requires a password and the installer could not escalate non-interactively. The declared dep was never installed, and `vnc.sh check_window_on_display` then either failed to find the binary (exit 127 — Task 634 now surfaces this as a distinct `failed err="xdotool not installed — re-run installer to repair"` line) or reached the `window absent` path because its preflight tool was missing. See `.docs/deployment.md` ("sudo interactivity contract") for the full mechanism.
180
+
181
+ **Check:**
182
+
183
+ ```bash
184
+ dpkg -s xdotool 2>&1 | head -2 # should report "Status: install ok installed"
185
+ which xdotool # should return /usr/bin/xdotool
186
+ ```
187
+
188
+ **Fix — two options:**
189
+
190
+ 1. **Re-run the installer from an interactive shell** (preferred on upgrade — picks up any future missing deps too):
191
+
192
+ ```bash
193
+ npx -y @rubytech/create-maxy@latest
194
+ ```
195
+
196
+ The installer now probes `dpkg -s` for every declared apt dep and either prompts you for your sudo password to install missing ones, or loud-fails non-interactively with the exact `sudo apt-get install -y <list>` command to repair. It will no longer print `Skipping apt-get (deps assumed present from prior install)` and continue as if green.
197
+
198
+ 2. **Repair manually** (faster if you only want the one binary):
199
+
200
+ ```bash
201
+ sudo apt-get install -y xdotool
202
+ ```
203
+
204
+ Then click **Terminal** again — it should spawn within a second and the overlay should render the xterm.
205
+
206
+ **Regression boundary:** if you see `[terminal-launch] failed err="xdotool not installed — re-run installer to repair"` in `~/.maxy/logs/terminal-launch.log`, the vnc.sh preflight is telling you the binary is missing before spawn — same class as above, distinct from the Task 632 `window absent from target display after spawn` string. The two strings never appear for the same click.
207
+
208
+ ---
209
+
175
210
  ## Terminal iframe renders black and cursor vanishes over the canvas
176
211
 
177
212
  **Symptom:** Header-menu Terminal click appears to succeed — no error toast, overlay opens — but the iframe renders uniformly black, keystrokes do not reach any shell, and the mouse cursor disappears the moment it enters the iframe (visible elsewhere in the page).
@@ -214,6 +214,16 @@ start_chrome() {
214
214
  # loud-fail log if neither is installed — matches the operator invariant
215
215
  # "no ttyd fallback, no silent substitution" from Task 627. gnome-terminal
216
216
  # retains `--wait` (see Task 627 pgrep-visibility fix). xterm takes no flag.
217
+ # xterm flags for the :99 VNC framebuffer. Fontconfig alias `monospace`
218
+ # resolves via /etc/fonts to the distro's default monospace face (DejaVu
219
+ # Sans Mono on Ubuntu/Debian) — no quoting needed, so the flag string
220
+ # survives unquoted $flags word-splitting in start_terminal_on. Geometry
221
+ # 160x45 fills the Xtigervnc display (1280x800 — see Xtigervnc -geometry
222
+ # flag below) at 11pt; the prior empty flag string left xterm at its 80x24
223
+ # default with the 6x13 `fixed` bitmap font, which rendered ~480x312 px of
224
+ # the 1280x800 framebuffer with a black void around it.
225
+ XTERM_VNC_FLAGS='-fa monospace -fs 11 -geometry 160x45'
226
+
217
227
  resolve_terminal_bin() {
218
228
  local target_display="${1:-:99}"
219
229
  local xterm_bin='' gnome_bin=''
@@ -221,17 +231,35 @@ resolve_terminal_bin() {
221
231
  [ -x /usr/bin/gnome-terminal ] && gnome_bin='/usr/bin/gnome-terminal'
222
232
 
223
233
  if [ "$target_display" = ":99" ]; then
224
- [ -n "$xterm_bin" ] && { printf '%s\t%s\n' "$xterm_bin" ''; return 0; }
234
+ [ -n "$xterm_bin" ] && { printf '%s\t%s\n' "$xterm_bin" "$XTERM_VNC_FLAGS"; return 0; }
225
235
  [ -n "$gnome_bin" ] && { printf '%s\t%s\n' "$gnome_bin" '--wait'; return 0; }
226
236
  else
227
237
  [ -n "$gnome_bin" ] && { printf '%s\t%s\n' "$gnome_bin" '--wait'; return 0; }
228
- [ -n "$xterm_bin" ] && { printf '%s\t%s\n' "$xterm_bin" ''; return 0; }
238
+ [ -n "$xterm_bin" ] && { printf '%s\t%s\n' "$xterm_bin" "$XTERM_VNC_FLAGS"; return 0; }
229
239
  fi
230
240
  tlog "failed err=\"no terminal emulator installed (expected /usr/bin/gnome-terminal or /usr/bin/xterm)\""
231
241
  log "ERROR: no terminal emulator binary found — install xterm (apt-get install -y xterm)"
232
242
  return 1
233
243
  }
234
244
 
245
+ # Task 634 — runtime preflight for terminal-launch dependencies. Separates
246
+ # exit 127 (xdotool missing from PATH) from exit 1 (xdotool present but no
247
+ # window mapped) in check_window_on_display. The installer now verifies
248
+ # apt deps via `dpkg -s` but an operator can still `apt remove xdotool`
249
+ # post-install, so this is the matching runtime guard. Emits the failure
250
+ # string on stderr so ensureTerminal's extractFailureLine captures it
251
+ # verbatim into server.log's `ensure-terminal err=...` field.
252
+ preflight_terminal_deps() {
253
+ if command -v xdotool >/dev/null 2>&1; then
254
+ log "xdotool: $(command -v xdotool)"
255
+ return 0
256
+ fi
257
+ tlog "failed err=\"xdotool not installed — re-run installer to repair\""
258
+ log "ERROR: xdotool missing — start-terminal cannot assert display-membership (re-run: npx -y @rubytech/create-maxy@latest)"
259
+ echo "[terminal-launch] failed err=\"xdotool not installed — re-run installer to repair\"" >&2
260
+ return 1
261
+ }
262
+
235
263
  # Verify that at least one visible window is mapped on $target_display. Closes
236
264
  # the "PID visible in pgrep but window is not on target" class of silent fail
237
265
  # (Task 632). Uses xdotool's exit code directly — no stdout parsing for
@@ -243,8 +271,14 @@ resolve_terminal_bin() {
243
271
  # Returns 0 if any visible window appears on the display within ~1s, 1 otherwise.
244
272
  check_window_on_display() {
245
273
  local target_display="$1"
274
+ # Task 634 — route xdotool stderr to vnc-boot.log instead of /dev/null.
275
+ # preflight_terminal_deps removes the 127-vs-1 conflation from production,
276
+ # but if xdotool ever fails for a non-missing-binary reason (broken X
277
+ # server, malformed DISPLAY), the diagnostic now lands in a grep-addressable
278
+ # log instead of vanishing. Control flow still depends solely on the exit
279
+ # code (feedback_no_stdout_parsing_for_control_flow.md).
246
280
  for _ in 1 2 3; do
247
- if DISPLAY="$target_display" xdotool search --onlyvisible --class '.' >/dev/null 2>&1; then
281
+ if DISPLAY="$target_display" xdotool search --onlyvisible --class '.' >/dev/null 2>>"$LOG_FILE"; then
248
282
  return 0
249
283
  fi
250
284
  sleep 0.3
@@ -386,10 +420,12 @@ start_terminal_on() {
386
420
  }
387
421
 
388
422
  start_terminal() {
423
+ preflight_terminal_deps || return 1
389
424
  start_terminal_on ":99" "vnc"
390
425
  }
391
426
 
392
427
  start_terminal_native() {
428
+ preflight_terminal_deps || return 1
393
429
  discover_native_session
394
430
  start_terminal_on "${NATIVE_DISPLAY}" "native"
395
431
  }