@rubytech/create-realagent 1.0.626 → 1.0.627

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
@@ -1,7 +1,7 @@
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 } from "node:fs";
4
- import { resolve, join } from "node:path";
3
+ import { existsSync, mkdirSync, writeFileSync, cpSync, readFileSync, rmSync, readdirSync, appendFileSync, openSync, closeSync, chmodSync, symlinkSync, unlinkSync, lstatSync, readlinkSync, accessSync, constants as fsConstants } from "node:fs";
4
+ import { resolve, join, dirname } from "node:path";
5
5
  import { randomBytes } from "node:crypto";
6
6
  const PAYLOAD_DIR = resolve(import.meta.dirname, "../payload");
7
7
  // Brand manifest — read from payload to derive all brand-specific installation values.
@@ -1260,6 +1260,107 @@ function setupAccount() {
1260
1260
  }
1261
1261
  }
1262
1262
  // ---------------------------------------------------------------------------
1263
+ // Tunnel script shortcuts
1264
+ //
1265
+ // The cloudflare plugin's SKILL.md, PLUGIN.md, and reference docs encode the
1266
+ // invocation `~/setup-tunnel.sh` (and `~/reset-tunnel.sh`). The filesystem
1267
+ // reality is <INSTALL_DIR>/platform/plugins/cloudflare/scripts/*.sh. Without
1268
+ // the symlink the agent's first SKILL-compliant invocation fails with exit
1269
+ // 127 — the discipline-violation loop Task 555 exists to close.
1270
+ //
1271
+ // Collision discipline:
1272
+ // - absent path → create
1273
+ // - symlink → same target → no-op
1274
+ // - symlink → different path inside INSTALL_DIR → replace (idempotent upgrade)
1275
+ // - symlink → dangling → repair + log
1276
+ // - symlink → live target outside INSTALL_DIR → exit 1 (cross-brand collision)
1277
+ // - regular file → exit 1
1278
+ // - unreadable symlink → exit 1
1279
+ // ---------------------------------------------------------------------------
1280
+ function createTunnelSymlink(linkPath, target) {
1281
+ const targetAbs = resolve(target);
1282
+ let lstat = null;
1283
+ try {
1284
+ lstat = lstatSync(linkPath);
1285
+ }
1286
+ catch (err) {
1287
+ const code = err.code;
1288
+ if (code !== "ENOENT") {
1289
+ console.error(`[create-maxy:error] ${linkPath} lstat failed: ${err.message}`);
1290
+ process.exit(1);
1291
+ }
1292
+ }
1293
+ if (lstat === null) {
1294
+ symlinkSync(targetAbs, linkPath);
1295
+ console.log(` [create-maxy] symlink ${linkPath} → ${targetAbs}`);
1296
+ logFile(` symlink created: ${linkPath} → ${targetAbs}`);
1297
+ return;
1298
+ }
1299
+ if (!lstat.isSymbolicLink()) {
1300
+ console.error(`[create-maxy:collision] ${linkPath} already exists target=<regular-file>`);
1301
+ console.error(` Remove the file and re-run: npx -y @rubytech/create-maxy`);
1302
+ process.exit(1);
1303
+ }
1304
+ let resolvedTarget;
1305
+ try {
1306
+ const raw = readlinkSync(linkPath);
1307
+ resolvedTarget = resolve(dirname(linkPath), raw);
1308
+ }
1309
+ catch (err) {
1310
+ console.error(`[create-maxy:collision] ${linkPath} symlink unreadable: ${err.message}`);
1311
+ console.error(` Remove the file and re-run: npx -y @rubytech/create-maxy`);
1312
+ process.exit(1);
1313
+ }
1314
+ if (resolvedTarget === targetAbs) {
1315
+ console.log(` [create-maxy] symlink ${linkPath} already points to ${targetAbs}`);
1316
+ logFile(` symlink unchanged: ${linkPath} → ${targetAbs}`);
1317
+ return;
1318
+ }
1319
+ const insideInstallDir = resolvedTarget === INSTALL_DIR || resolvedTarget.startsWith(INSTALL_DIR + "/");
1320
+ let targetExists = false;
1321
+ try {
1322
+ lstatSync(resolvedTarget);
1323
+ targetExists = true;
1324
+ }
1325
+ catch { /* resolvedTarget absent → dangling */ }
1326
+ if (insideInstallDir) {
1327
+ unlinkSync(linkPath);
1328
+ symlinkSync(targetAbs, linkPath);
1329
+ console.log(` [create-maxy] symlink replaced: ${linkPath} → ${targetAbs}`);
1330
+ logFile(` symlink replaced (stale same-brand): ${linkPath} was ${resolvedTarget}, now ${targetAbs}`);
1331
+ return;
1332
+ }
1333
+ if (!targetExists) {
1334
+ unlinkSync(linkPath);
1335
+ symlinkSync(targetAbs, linkPath);
1336
+ console.log(` [create-maxy] symlink repair: dangling ${linkPath} replaced (→ ${targetAbs})`);
1337
+ logFile(` symlink repaired (dangling): ${linkPath} was ${resolvedTarget}, now ${targetAbs}`);
1338
+ return;
1339
+ }
1340
+ console.error(`[create-maxy:collision] ${linkPath} already exists target=${resolvedTarget}`);
1341
+ console.error(` This symlink was created by a different install (not within ${INSTALL_DIR}).`);
1342
+ console.error(` Remove or relocate it, then re-run: npx -y @rubytech/create-maxy`);
1343
+ process.exit(1);
1344
+ }
1345
+ function installTunnelScripts() {
1346
+ const setupSrc = join(INSTALL_DIR, "platform/plugins/cloudflare/scripts/setup-tunnel.sh");
1347
+ const resetSrc = join(INSTALL_DIR, "platform/plugins/cloudflare/scripts/reset-tunnel.sh");
1348
+ const setupLink = resolve(process.env.HOME ?? "/root", "setup-tunnel.sh");
1349
+ const resetLink = resolve(process.env.HOME ?? "/root", "reset-tunnel.sh");
1350
+ for (const src of [setupSrc, resetSrc]) {
1351
+ try {
1352
+ chmodSync(src, 0o755);
1353
+ }
1354
+ catch (err) {
1355
+ console.error(`[create-maxy:error] tunnel script missing or chmod failed: ${src}`);
1356
+ console.error(` ${err.message}`);
1357
+ process.exit(1);
1358
+ }
1359
+ }
1360
+ createTunnelSymlink(setupLink, setupSrc);
1361
+ createTunnelSymlink(resetLink, resetSrc);
1362
+ }
1363
+ // ---------------------------------------------------------------------------
1263
1364
  // Cron Registration
1264
1365
  //
1265
1366
  // Registers platform cron jobs (heartbeat, email-fetch, email-auto-respond).
@@ -1534,6 +1635,25 @@ WantedBy=multi-user.target
1534
1635
  console.error(vncLog);
1535
1636
  process.exit(1);
1536
1637
  }
1638
+ // Tunnel script smoke check — the cloudflare SKILL contract invokes
1639
+ // ~/setup-tunnel.sh directly. If the install-time symlink creation in
1640
+ // installTunnelScripts() didn't land, fail the install loudly here rather
1641
+ // than shipping the exact Task 456 gap Task 555 exists to close.
1642
+ for (const linkPath of [
1643
+ resolve(process.env.HOME ?? "/root", "setup-tunnel.sh"),
1644
+ resolve(process.env.HOME ?? "/root", "reset-tunnel.sh"),
1645
+ ]) {
1646
+ try {
1647
+ accessSync(linkPath, fsConstants.X_OK);
1648
+ console.log(` [create-maxy] verify: ${linkPath} executable ✓`);
1649
+ }
1650
+ catch (err) {
1651
+ console.error("");
1652
+ console.error(` [create-maxy:error] tunnel script symlink missing or not executable: ${linkPath}`);
1653
+ console.error(` ${err.message}`);
1654
+ process.exit(1);
1655
+ }
1656
+ }
1537
1657
  }
1538
1658
  // ---------------------------------------------------------------------------
1539
1659
  // Main
@@ -1824,6 +1944,7 @@ try {
1824
1944
  buildPlatform();
1825
1945
  setupVncViewer();
1826
1946
  setupAccount();
1947
+ installTunnelScripts(); // ~/setup-tunnel.sh, ~/reset-tunnel.sh — the SKILL contract
1827
1948
  installService();
1828
1949
  console.log("");
1829
1950
  console.log("================================================================");
package/dist/uninstall.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { spawnSync, execFileSync } from "node:child_process";
2
- import { existsSync, mkdirSync, readFileSync, rmSync, appendFileSync, cpSync } from "node:fs";
3
- import { resolve, join } from "node:path";
2
+ import { existsSync, mkdirSync, readFileSync, rmSync, appendFileSync, cpSync, lstatSync, readlinkSync, unlinkSync } from "node:fs";
3
+ import { resolve, join, dirname } from "node:path";
4
4
  import { homedir } from "node:os";
5
5
  import { createInterface } from "node:readline";
6
6
  const HOME = homedir();
@@ -262,6 +262,62 @@ export function exportData(exportPath) {
262
262
  console.log(` Export complete: ${exportPath}`);
263
263
  }
264
264
  // ---------------------------------------------------------------------------
265
+ // Step 3b: Remove tunnel script symlinks
266
+ //
267
+ // installTunnelScripts() creates ~/setup-tunnel.sh and ~/reset-tunnel.sh as
268
+ // symlinks into INSTALL_DIR. Must run BEFORE removeAppDirs removes INSTALL_DIR
269
+ // so readlink-then-compare still works.
270
+ //
271
+ // Only unlink if the symlink's resolved target is inside this brand's
272
+ // INSTALL_DIR. A symlink pointing elsewhere came from a different brand install
273
+ // on the same home (unsupported but not destructive) — leave it alone.
274
+ // ---------------------------------------------------------------------------
275
+ function removeTunnelSymlinks() {
276
+ log("3b", "Removing tunnel script symlinks...");
277
+ for (const linkPath of [
278
+ resolve(HOME, "setup-tunnel.sh"),
279
+ resolve(HOME, "reset-tunnel.sh"),
280
+ ]) {
281
+ let lstat;
282
+ try {
283
+ lstat = lstatSync(linkPath);
284
+ }
285
+ catch (err) {
286
+ if (err.code === "ENOENT") {
287
+ console.log(` ${linkPath} not found — skipping`);
288
+ continue;
289
+ }
290
+ console.log(` Failed to stat ${linkPath}: ${err.message}`);
291
+ continue;
292
+ }
293
+ if (!lstat.isSymbolicLink()) {
294
+ console.log(` ${linkPath} is not a symlink — leaving untouched`);
295
+ continue;
296
+ }
297
+ let resolvedTarget;
298
+ try {
299
+ const raw = readlinkSync(linkPath);
300
+ resolvedTarget = resolve(dirname(linkPath), raw);
301
+ }
302
+ catch (err) {
303
+ console.log(` ${linkPath} readlink failed: ${err.message} — leaving untouched`);
304
+ continue;
305
+ }
306
+ const insideInstallDir = resolvedTarget === INSTALL_DIR || resolvedTarget.startsWith(INSTALL_DIR + "/");
307
+ if (!insideInstallDir) {
308
+ console.log(` ${linkPath} → ${resolvedTarget} (outside ${INSTALL_DIR}) — leaving untouched`);
309
+ continue;
310
+ }
311
+ try {
312
+ unlinkSync(linkPath);
313
+ console.log(` Unlinked ${linkPath}`);
314
+ }
315
+ catch (err) {
316
+ console.log(` Failed to unlink ${linkPath}: ${err.message}`);
317
+ }
318
+ }
319
+ }
320
+ // ---------------------------------------------------------------------------
265
321
  // Step 4: Remove application directories
266
322
  // ---------------------------------------------------------------------------
267
323
  function removeAppDirs() {
@@ -550,6 +606,7 @@ export async function runUninstall(options) {
550
606
  ...(options.exportPath
551
607
  ? [{ name: "Export data", fn: () => exportData(options.exportPath) }]
552
608
  : []),
609
+ { name: "Remove tunnel script symlinks", fn: removeTunnelSymlinks },
553
610
  { name: "Remove application directories", fn: removeAppDirs },
554
611
  { name: "Remove Neo4j data", fn: removeNeo4jData },
555
612
  { name: "Purge system packages", fn: purgeSystemPackages },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-realagent",
3
- "version": "1.0.626",
3
+ "version": "1.0.627",
4
4
  "description": "Install Real Agent — Built for agents. By agents.",
5
5
  "bin": {
6
6
  "create-realagent": "./dist/index.js"