@rubytech/create-maxy-code 0.1.109 → 0.1.111

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.
Files changed (32) hide show
  1. package/dist/index.js +50 -150
  2. package/dist/snap-chromium.js +1 -2
  3. package/dist/uninstall.js +10 -9
  4. package/package.json +1 -1
  5. package/payload/platform/neo4j/schema.cypher +6 -3
  6. package/payload/platform/plugins/admin/PLUGIN.md +1 -0
  7. package/payload/platform/plugins/admin/mcp/dist/index.js +1 -1
  8. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  9. package/payload/platform/plugins/admin/skills/upgrade/SKILL.md +34 -0
  10. package/payload/platform/plugins/cloudflare/.claude-plugin/plugin.json +1 -1
  11. package/payload/platform/plugins/cloudflare/PLUGIN.md +9 -16
  12. package/payload/platform/plugins/cloudflare/mcp/dist/index.js +7 -12
  13. package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
  14. package/payload/platform/plugins/cloudflare/references/dashboard-guide.md +3 -3
  15. package/payload/platform/plugins/cloudflare/references/manual-setup.md +19 -54
  16. package/payload/platform/plugins/cloudflare/references/reset-guide.md +29 -28
  17. package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +29 -149
  18. package/payload/platform/plugins/docs/references/admin-session.md +1 -1
  19. package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
  20. package/payload/platform/plugins/docs/references/cloudflare.md +20 -29
  21. package/payload/platform/plugins/docs/references/platform.md +1 -1
  22. package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
  23. package/payload/platform/plugins/docs/references/troubleshooting.md +1 -1
  24. package/payload/platform/scripts/check-no-task-id-leaks.mjs +1 -1
  25. package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
  26. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  27. package/payload/platform/plugins/cloudflare/scripts/__tests__/tunnel-ingress.test.ts +0 -241
  28. package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.sh +0 -98
  29. package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.ts +0 -715
  30. package/payload/platform/plugins/cloudflare/scripts/reset-tunnel.sh +0 -107
  31. package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +0 -852
  32. package/payload/platform/plugins/cloudflare/scripts/tunnel-ingress.ts +0 -291
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { execFileSync, spawn, spawnSync } from "node:child_process";
3
- import { existsSync, mkdirSync, writeFileSync, cpSync, readFileSync, rmSync, readdirSync, appendFileSync, openSync, closeSync, chmodSync, symlinkSync, unlinkSync, lstatSync, statSync, readlinkSync, realpathSync, accessSync, constants as fsConstants } from "node:fs";
3
+ import { existsSync, mkdirSync, writeFileSync, cpSync, readFileSync, rmSync, readdirSync, appendFileSync, openSync, closeSync, chmodSync, statSync, realpathSync } from "node:fs";
4
4
  import { registerSpecialistAgentsAt, SpecialistSymlinkCollision } from "./specialist-registration.js";
5
5
  import { resolve, join, dirname } from "node:path";
6
6
  import { randomBytes } from "node:crypto";
@@ -588,8 +588,8 @@ function runChromiumPostInstallGate(chromiumBin) {
588
588
  }
589
589
  /**
590
590
  * write the resolved Chromium absolute path to
591
- * `<INSTALL_DIR>/platform/config/chromium-binary.path` so vnc.sh,
592
- * writeChromiumWrapper, and setup-tunnel.sh all read the same value. Called
591
+ * `<INSTALL_DIR>/platform/config/chromium-binary.path` so vnc.sh and
592
+ * writeChromiumWrapper read the same value. Called
593
593
  * after deployPayload so the platform/config/ directory exists. Idempotent:
594
594
  * re-running the installer with the same RESOLVED_CHROMIUM_BIN is a no-op
595
595
  * write (writeFileSync overwrites in place).
@@ -1284,31 +1284,45 @@ function setupDedicatedNeo4j() {
1284
1284
  shell("cp", ["-r", "/etc/neo4j", confDir], { sudo: true });
1285
1285
  }
1286
1286
  // Idempotent per-brand state — runs on every install.
1287
- // sed `s|^#?key=.*|key=value|` no-ops when the file already has the target
1288
- // value; mkdir -p, chown -R, and unit-write are idempotent by definition.
1289
- // NEO4J_HOME=${dataDir} makes server.directories.run resolve per-brand to
1290
- // ${dataDir}/run, so the launcher's pre-flight does not collide with the
1291
- // system unit's /var/lib/neo4j/run/neo4j.pid (root cause). Plugins
1292
- // and import are sed-overridden because the apt base conf pins them to
1293
- // absolute /var/lib/neo4j/plugins and /var/lib/neo4j/import (shared).
1294
- console.log(" [privileged] sed -i");
1295
- shell("sed", ["-i", `s/^#\\?server\\.bolt\\.listen_address=.*/server.bolt.listen_address=:${NEO4J_PORT}/`, `${confDir}/neo4j.conf`], { sudo: true });
1296
- console.log(" [privileged] sed -i");
1297
- shell("sed", ["-i", `s/^#\\?server\\.http\\.listen_address=.*/server.http.listen_address=:${httpPort}/`, `${confDir}/neo4j.conf`], { sudo: true });
1298
- console.log(" [privileged] sed -i");
1299
- shell("sed", ["-i", `s|^#\\?server\\.directories\\.data=.*|server.directories.data=${dataDir}/data|`, `${confDir}/neo4j.conf`], { sudo: true });
1300
- console.log(" [privileged] sed -i");
1301
- shell("sed", ["-i", `s|^#\\?server\\.directories\\.logs=.*|server.directories.logs=${logDir}|`, `${confDir}/neo4j.conf`], { sudo: true });
1302
- console.log(" [privileged] sed -i");
1303
- shell("sed", ["-i", `s|^#\\?server\\.directories\\.plugins=.*|server.directories.plugins=${dataDir}/plugins|`, `${confDir}/neo4j.conf`], { sudo: true });
1304
- console.log(" [privileged] sed -i");
1305
- shell("sed", ["-i", `s|^#\\?server\\.directories\\.import=.*|server.directories.import=${dataDir}/import|`, `${confDir}/neo4j.conf`], { sudo: true });
1306
- // Verify config was updated — sed silently no-ops if the key format changed
1307
- const confContent = spawnSync("grep", [`server.bolt.listen_address=:${NEO4J_PORT}`, `${confDir}/neo4j.conf`], { stdio: "pipe" });
1308
- if (confContent.status !== 0) {
1309
- console.error(` WARNING: neo4j.conf may not have been updated correctly bolt port ${NEO4J_PORT} not found in config`);
1310
- logFile(` WARNING: sed verification failed bolt port ${NEO4J_PORT} not found in ${confDir}/neo4j.conf`);
1311
- }
1287
+ // Older Neo4j 5.x templates ship a SECOND commented occurrence of these
1288
+ // keys in the "Other Neo4j system properties" section. An in-place
1289
+ // `sed s|^#?key=.*|key=value|` matches both and writes duplicate active
1290
+ // lines; the 5.26.26 parser turns that into a fatal startup error.
1291
+ // Delete-all-then-append guarantees exactly one active line per key
1292
+ // regardless of how many commented occurrences the template ships.
1293
+ const confPath = `${confDir}/neo4j.conf`;
1294
+ const confKeys = [
1295
+ ["server.bolt.listen_address", `:${NEO4J_PORT}`],
1296
+ ["server.http.listen_address", `:${httpPort}`],
1297
+ ["server.directories.data", `${dataDir}/data`],
1298
+ ["server.directories.logs", logDir],
1299
+ ["server.directories.plugins", `${dataDir}/plugins`],
1300
+ ["server.directories.import", `${dataDir}/import`],
1301
+ ];
1302
+ for (const [key, value] of confKeys) {
1303
+ const keyRe = key.replace(/\./g, "\\.");
1304
+ console.log(" [privileged] sed -i (delete)");
1305
+ shell("sed", ["-i", `/^#\\?${keyRe}=/d`, confPath], { sudo: true });
1306
+ console.log(" [privileged] echo >>");
1307
+ shell("sh", ["-c", `echo '${key}=${value}' >> ${confPath}`], { sudo: true });
1308
+ }
1309
+ // Verify exactly one active line per key. Any n>1 aborts the install
1310
+ // BEFORE the systemd unit is written. n==0 means the append failed.
1311
+ const activeCounts = [];
1312
+ for (const [key] of confKeys) {
1313
+ const r = spawnSync("sudo", ["grep", "-c", `^${key}=`, confPath], { stdio: "pipe" });
1314
+ const n = parseInt((r.stdout?.toString() ?? "0").trim(), 10) || 0;
1315
+ activeCounts.push(`${key}:${n}`);
1316
+ if (n !== 1) {
1317
+ const msg = `[neo4j-conf] FATAL ${key} active_count=${n}`;
1318
+ console.error(` ${msg}`);
1319
+ logFile(` ${msg}`);
1320
+ throw new Error(msg);
1321
+ }
1322
+ }
1323
+ const summary = `[neo4j-conf] keys_active=${activeCounts.join(",")}`;
1324
+ console.log(` ${summary}`);
1325
+ logFile(` ${summary}`);
1312
1326
  console.log(" [privileged] mkdir -p");
1313
1327
  shell("mkdir", ["-p", `${dataDir}/data`, `${dataDir}/plugins`, `${dataDir}/import`, logDir], { sudo: true });
1314
1328
  console.log(" [privileged] chown -R");
@@ -2174,11 +2188,10 @@ function setupVncViewer() {
2174
2188
 
2175
2189
  // Layer-6 beacon: emit event=rfb-connected and
2176
2190
  // event=rfb-error to the operator-grep lifecycle endpoint so
2177
- // server.log shows the noVNC outcome alongside [setup-tunnel] /
2178
- // [cloudflare-setup] / [device-url:click] / [http] / [websockify].
2179
- // Same-origin POST works whether the iframe is parented by the
2180
- // React BrowserViewer or by vnc-popout.html.
2181
- function reportBrowserViewerEvent(event, fields) {
2191
+ // server.log shows the noVNC outcome alongside [device-url:click]
2192
+ // / [http] / [websockify]. Same-origin POST works whether the
2193
+ // iframe is parented by the React VNC viewer or by vnc-popout.html.
2194
+ function reportVncIframeEvent(event, fields) {
2182
2195
  try {
2183
2196
  var body = JSON.stringify(Object.assign(
2184
2197
  { event: event, surface: 'iframe' },
@@ -2212,7 +2225,7 @@ function setupVncViewer() {
2212
2225
  rfb.addEventListener('connect', () => {
2213
2226
  status.classList.add('hidden');
2214
2227
  retryCount = 0;
2215
- reportBrowserViewerEvent('rfb-connected', {
2228
+ reportVncIframeEvent('rfb-connected', {
2216
2229
  durationMs: Date.now() - connectStartedAt,
2217
2230
  });
2218
2231
  });
@@ -2222,7 +2235,7 @@ function setupVncViewer() {
2222
2235
  const detail = e.detail || {};
2223
2236
  const reason = detail.reason || (detail.clean === false ? 'Connection refused' : '');
2224
2237
  reportClientEvent('disconnect', reason || (detail.clean === false ? 'unclean-close' : 'normal'));
2225
- reportBrowserViewerEvent('rfb-error', {
2238
+ reportVncIframeEvent('rfb-error', {
2226
2239
  durationMs: Date.now() - connectStartedAt,
2227
2240
  errorCode: detail.clean === false ? 'unclean-close' : 'normal',
2228
2241
  message: reason || '',
@@ -2422,27 +2435,6 @@ function writeEntitlementFromFlag(payload) {
2422
2435
  console.log(line);
2423
2436
  logFile(line);
2424
2437
  }
2425
- // ---------------------------------------------------------------------------
2426
- // Tunnel script shortcuts
2427
- //
2428
- // The cloudflare plugin's SKILL.md, PLUGIN.md, and reference docs encode the
2429
- // invocation `~/setup-tunnel.sh` (and `~/reset-tunnel.sh`). The filesystem
2430
- // reality is <INSTALL_DIR>/platform/plugins/cloudflare/scripts/*.sh. Without
2431
- // the symlink the agent's first SKILL-compliant invocation fails with exit
2432
- // 127 — the discipline-violation loop the symlink exists to close.
2433
- //
2434
- // Collision discipline (last-writer-wins across brands):
2435
- // - absent path → create
2436
- // - regular file → exit 1 (operator-owned file, do not clobber)
2437
- // - symlink → same target → no-op
2438
- // - symlink → any other target → overwrite (unlink + symlink)
2439
- // covers stale-same-brand, dangling,
2440
- // unreadable, and peer-brand cases.
2441
- // setup-tunnel.sh takes <brand> as argv
2442
- // so the script still operates on the
2443
- // correct brand regardless of who owns
2444
- // the shortcut symlink.
2445
- // ---------------------------------------------------------------------------
2446
2438
  function registerSpecialistAgents() {
2447
2439
  try {
2448
2440
  registerSpecialistAgentsAt(join(INSTALL_DIR, "platform/templates/specialists/agents"), join(CLAUDE_CONFIG_DIR, "agents"), (line) => { console.log(line); logFile(line); });
@@ -2456,76 +2448,6 @@ function registerSpecialistAgents() {
2456
2448
  throw err;
2457
2449
  }
2458
2450
  }
2459
- function createTunnelSymlink(linkPath, target) {
2460
- const targetAbs = resolve(target);
2461
- let lstat = null;
2462
- try {
2463
- lstat = lstatSync(linkPath);
2464
- }
2465
- catch (err) {
2466
- const code = err.code;
2467
- if (code !== "ENOENT") {
2468
- console.error(`Setup failed: ${linkPath} lstat failed: ${err.message}`);
2469
- console.error(`[create-maxy:error] ${linkPath} lstat failed: ${err.message}`);
2470
- process.exit(1);
2471
- }
2472
- }
2473
- if (lstat === null) {
2474
- symlinkSync(targetAbs, linkPath);
2475
- console.log(` [create-maxy] symlink ${linkPath} → ${targetAbs}`);
2476
- logFile(` symlink created: ${linkPath} → ${targetAbs}`);
2477
- return;
2478
- }
2479
- if (!lstat.isSymbolicLink()) {
2480
- console.error(`Setup failed: ${linkPath} collision (regular file)`);
2481
- console.error(`[create-maxy:collision] ${linkPath} already exists target=<regular-file>`);
2482
- console.error(` Remove the file and re-run: npx -y @rubytech/create-maxy`);
2483
- process.exit(1);
2484
- }
2485
- // Brand isolation: a symlink at this path is either stale-same-brand,
2486
- // dangling, unreadable, or pointing at another brand. All four cases are resolved
2487
- // by last-writer-wins overwrite — setup-tunnel.sh takes <brand> as argv, so
2488
- // whichever brand's installer ran last owns the shortcut.
2489
- const resolvedTarget = (() => {
2490
- try {
2491
- return resolve(dirname(linkPath), readlinkSync(linkPath));
2492
- }
2493
- catch {
2494
- return "<unreadable>";
2495
- }
2496
- })();
2497
- if (resolvedTarget === targetAbs) {
2498
- console.log(` [create-maxy] symlink ${linkPath} already points to ${targetAbs}`);
2499
- logFile(` symlink unchanged: ${linkPath} → ${targetAbs}`);
2500
- return;
2501
- }
2502
- unlinkSync(linkPath);
2503
- symlinkSync(targetAbs, linkPath);
2504
- console.log(` [create-maxy] symlink replaced: ${linkPath} → ${targetAbs}`);
2505
- logFile(` symlink replaced: ${linkPath} was ${resolvedTarget}, now ${targetAbs}`);
2506
- }
2507
- function installTunnelScripts() {
2508
- const setupSrc = join(INSTALL_DIR, "platform/plugins/cloudflare/scripts/setup-tunnel.sh");
2509
- const resetSrc = join(INSTALL_DIR, "platform/plugins/cloudflare/scripts/reset-tunnel.sh");
2510
- const listSrc = join(INSTALL_DIR, "platform/plugins/cloudflare/scripts/list-cf-domains.sh");
2511
- const setupLink = resolve(process.env.HOME ?? "/root", "setup-tunnel.sh");
2512
- const resetLink = resolve(process.env.HOME ?? "/root", "reset-tunnel.sh");
2513
- const listLink = resolve(process.env.HOME ?? "/root", "list-cf-domains.sh");
2514
- for (const src of [setupSrc, resetSrc, listSrc]) {
2515
- try {
2516
- chmodSync(src, 0o755);
2517
- }
2518
- catch (err) {
2519
- console.error(`Setup failed: tunnel script missing or chmod failed: ${src}`);
2520
- console.error(`[create-maxy:error] tunnel script missing or chmod failed: ${src}`);
2521
- console.error(` ${err.message}`);
2522
- process.exit(1);
2523
- }
2524
- }
2525
- createTunnelSymlink(setupLink, setupSrc);
2526
- createTunnelSymlink(resetLink, resetSrc);
2527
- createTunnelSymlink(listLink, listSrc);
2528
- }
2529
2451
  // ---------------------------------------------------------------------------
2530
2452
  // Account discovery (shared by installService + the post-install summary)
2531
2453
  //
@@ -3238,27 +3160,6 @@ WantedBy=multi-user.target
3238
3160
  process.exit(1);
3239
3161
  }
3240
3162
  }
3241
- // Tunnel script smoke check — the cloudflare SKILL contract invokes
3242
- // ~/setup-tunnel.sh directly. If the install-time symlink creation in
3243
- // installTunnelScripts() didn't land, fail the install loudly here rather
3244
- // than shipping a missing-symlink gap that would loop the agent on exit 127.
3245
- for (const linkPath of [
3246
- resolve(process.env.HOME ?? "/root", "setup-tunnel.sh"),
3247
- resolve(process.env.HOME ?? "/root", "reset-tunnel.sh"),
3248
- resolve(process.env.HOME ?? "/root", "list-cf-domains.sh"),
3249
- ]) {
3250
- try {
3251
- accessSync(linkPath, fsConstants.X_OK);
3252
- console.log(` [create-maxy] verify: ${linkPath} executable ✓`);
3253
- }
3254
- catch (err) {
3255
- console.error("");
3256
- console.error(`Setup failed: tunnel script symlink missing or not executable: ${linkPath}`);
3257
- console.error(` [create-maxy:error] tunnel script symlink missing or not executable: ${linkPath}`);
3258
- console.error(` ${err.message}`);
3259
- process.exit(1);
3260
- }
3261
- }
3262
3163
  }
3263
3164
  // ---------------------------------------------------------------------------
3264
3165
  // Samba provisioning. Runs after installService() so SMB never
@@ -3729,8 +3630,8 @@ try {
3729
3630
  installWhisperCpp();
3730
3631
  deployPayload(); // Must happen before ensureNeo4jPassword — restores config backup
3731
3632
  // write the resolved Chromium absolute path into the deployed
3732
- // platform/config/ so vnc.sh, writeChromiumWrapper, and setup-tunnel.sh
3733
- // all read the same value. Must run after deployPayload (config dir is
3633
+ // platform/config/ so vnc.sh and writeChromiumWrapper read the same
3634
+ // value. Must run after deployPayload (config dir is
3734
3635
  // a payload subdirectory). Linux-only; no-op on darwin/non-linux.
3735
3636
  writeChromiumBinaryPathFile();
3736
3637
  // scrub plaintext neo4j passwords from any pre-fix install-*.log.
@@ -3746,7 +3647,6 @@ try {
3746
3647
  if (ENTITLEMENT_PAYLOAD) {
3747
3648
  writeEntitlementFromFlag(ENTITLEMENT_PAYLOAD);
3748
3649
  }
3749
- installTunnelScripts(); // ~/setup-tunnel.sh, ~/reset-tunnel.sh — the SKILL contract
3750
3650
  installService();
3751
3651
  // Samba on the LAN. Runs after the brand systemd unit is up so
3752
3652
  // SMB provisioning never blocks the admin server starting; loud-fail per
@@ -1,7 +1,6 @@
1
1
  // pure decision: should the laptop installer replace snap-confined
2
2
  // Chromium with Google Chrome stable, and which absolute binary path should
3
- // vnc.sh / writeChromiumWrapper / setup-tunnel.sh / the Playwright env var
4
- // resolve to?
3
+ // vnc.sh / writeChromiumWrapper / the Playwright env var resolve to?
5
4
  //
6
5
  // Mirrors apt-resolve.ts and port-resolution.ts: inputs
7
6
  // in, decision out, no I/O. The installer wraps this with the actual
package/dist/uninstall.js CHANGED
@@ -330,22 +330,23 @@ export function exportData(exportPath) {
330
330
  console.log(` Export complete: ${exportPath}`);
331
331
  }
332
332
  // ---------------------------------------------------------------------------
333
- // Step 3b: Remove tunnel script symlinks
333
+ // Step 3b: Remove legacy tunnel shortcut symlinks
334
334
  //
335
- // installTunnelScripts() creates ~/setup-tunnel.sh and ~/reset-tunnel.sh as
336
- // symlinks into INSTALL_DIR. Must run BEFORE removeAppDirs removes INSTALL_DIR
337
- // so readlink-then-compare still works.
335
+ // Older installer versions created ~/setup-tunnel and ~/reset-tunnel (with
336
+ // the ".sh" suffix) as symlinks into INSTALL_DIR's cloudflare scripts
337
+ // directory. The wrapper scripts and their installer hook were retired in
338
+ // Task 288 (PTY-native cloudflared via Bash). This step removes any leftover
339
+ // symlinks from previous installs. Must run BEFORE removeAppDirs removes
340
+ // INSTALL_DIR so readlink-then-compare still works.
338
341
  //
339
342
  // Only unlink if the symlink's resolved target is inside this brand's
340
343
  // INSTALL_DIR. A symlink pointing elsewhere came from a different brand install
341
344
  // on the same home (unsupported but not destructive) — leave it alone.
342
345
  // ---------------------------------------------------------------------------
346
+ const LEGACY_TUNNEL_SHORTCUTS = ["setup-tunnel", "reset-tunnel"].map((n) => `${n}.sh`);
343
347
  function removeTunnelSymlinks() {
344
- log("3b", "Removing tunnel script symlinks...");
345
- for (const linkPath of [
346
- resolve(HOME, "setup-tunnel.sh"),
347
- resolve(HOME, "reset-tunnel.sh"),
348
- ]) {
348
+ log("3b", "Removing legacy tunnel shortcut symlinks...");
349
+ for (const linkPath of LEGACY_TUNNEL_SHORTCUTS.map((name) => resolve(HOME, name))) {
349
350
  let lstat;
350
351
  try {
351
352
  lstat = lstatSync(linkPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.109",
3
+ "version": "0.1.111",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -1071,9 +1071,12 @@ FOR (n:Task) ON (n.createdBySession);
1071
1071
 
1072
1072
  // ----------------------------------------------------------
1073
1073
  // CloudflareTunnel / CloudflareHostname — graph audit of
1074
- // the tunnel-login deterministic flow. Written by the
1075
- // /api/admin/cloudflare/setup endpoint after setup-tunnel.sh exits 0,
1076
- // linked via (Task {kind:'cloudflare-tunnel-login'})-[:PRODUCED]->.
1074
+ // the tunnel-login flow. Historically written by the
1075
+ // /api/admin/cloudflare/setup endpoint after setup-tunnel.sh exits 0;
1076
+ // after Task 288 (PTY-native cloudflared) both the endpoint and the
1077
+ // wrapper script are gone. The constraints below remain so legacy
1078
+ // projections stay queryable, but no current writer emits these nodes.
1079
+ // Linked via (Task {kind:'cloudflare-tunnel-login'})-[:PRODUCED]->.
1077
1080
  // CloudflareHostname carries the routed FQDN; CloudflareTunnel
1078
1081
  // carries the connector identity. Source of truth remains the on-disk
1079
1082
  // cert.pem + tunnel.state + config.yml — these nodes are the graph
@@ -127,6 +127,7 @@ Tools are available via the `admin` MCP server.
127
127
  | Session reset / continue / resume | User asks to clear the session, start fresh, continue the last session, or pick up where they left off | `skills/session-management/SKILL.md` |
128
128
  | Commitment follow-through | A `<commitment-detected>` block appears in the prompt; the owner has just made a commitment that needs a backing mechanism | `skills/commitment-followthrough/SKILL.md` |
129
129
  | Show or download a file | User asks to view, attach, or download a file or document, or the current turn produces a document the owner needs to review | `skills/file-presentation/SKILL.md` |
130
+ | Upgrade Maxy | User asks to upgrade, update, install the latest version, or accepts an upgrade offer | `skills/upgrade/SKILL.md` |
130
131
 
131
132
  ## Hooks
132
133
 
@@ -1803,7 +1803,7 @@ eagerTool(server, "session-resume", "Resume a previous session. Loads the select
1803
1803
  server.tool("remote-auth-set-password", "Set the remote access password. Hashes with scrypt and writes to the platform's brand-specific config directory with mode 0600. " +
1804
1804
  "Validates strength (8+ chars, digit, special character, no spaces anywhere). " +
1805
1805
  "Protects the admin interface when exposed over the tunnel — has no effect on the public endpoint or the tunnel itself. " +
1806
- "Set before `setup-tunnel.sh` — the script's post-restart verification curls the admin hostname and fails if remote-auth is not configured.", { password: z.string() }, async ({ password }) => {
1806
+ "Set before configuring the Cloudflare tunnel — the post-restart `curl -I https://<admin-hostname>` verification fails if remote-auth is not configured.", { password: z.string() }, async ({ password }) => {
1807
1807
  if (!ACCOUNT_ID)
1808
1808
  return refuseNoAccount("remote-auth-set-password");
1809
1809
  // Validate strength — same rules as the web server's validatePasswordStrength