@rubytech/create-realagent 1.0.625 → 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.
Files changed (45) hide show
  1. package/dist/index.js +125 -3
  2. package/dist/uninstall.js +59 -2
  3. package/package.json +1 -1
  4. package/payload/platform/plugins/admin/mcp/dist/index.js +5 -8
  5. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  6. package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +1 -1
  7. package/payload/platform/plugins/docs/references/cloudflare.md +1 -1
  8. package/payload/platform/plugins/memory/PLUGIN.md +24 -0
  9. package/payload/platform/plugins/memory/mcp/dist/index.js +141 -0
  10. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  11. package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.d.ts +66 -0
  12. package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.d.ts.map +1 -0
  13. package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.js +411 -0
  14. package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.js.map +1 -0
  15. package/payload/platform/plugins/memory/mcp/dist/scripts/graph-prune.d.ts +18 -0
  16. package/payload/platform/plugins/memory/mcp/dist/scripts/graph-prune.d.ts.map +1 -0
  17. package/payload/platform/plugins/memory/mcp/dist/scripts/graph-prune.js +80 -0
  18. package/payload/platform/plugins/memory/mcp/dist/scripts/graph-prune.js.map +1 -0
  19. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.d.ts +8 -0
  20. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.d.ts.map +1 -0
  21. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.js +7 -0
  22. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.js.map +1 -0
  23. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.d.ts +7 -0
  24. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.d.ts.map +1 -0
  25. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.js +28 -0
  26. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.js.map +1 -0
  27. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-list.d.ts +7 -0
  28. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-list.d.ts.map +1 -0
  29. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-list.js +7 -0
  30. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-list.js.map +1 -0
  31. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.d.ts +7 -0
  32. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.d.ts.map +1 -0
  33. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.js +27 -0
  34. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.js.map +1 -0
  35. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-run.d.ts +7 -0
  36. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-run.d.ts.map +1 -0
  37. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-run.js +10 -0
  38. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-run.js.map +1 -0
  39. package/payload/platform/templates/agents/admin/IDENTITY.md +20 -4
  40. package/payload/platform/templates/agents/public/IDENTITY.md +18 -0
  41. package/payload/platform/templates/specialists/agents/content-producer.md +18 -0
  42. package/payload/platform/templates/specialists/agents/personal-assistant.md +18 -0
  43. package/payload/platform/templates/specialists/agents/project-manager.md +18 -0
  44. package/payload/platform/templates/specialists/agents/research-assistant.md +18 -0
  45. package/payload/server/server.js +20 -0
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).
@@ -1300,6 +1401,7 @@ function installCrons() {
1300
1401
  `* * * * * mkdir -p ${accountLogDir} && PLATFORM_ROOT=${platformRoot} ${accountEnv}${nodeBin} ${platformRoot}/plugins/scheduling/mcp/dist/scripts/check-due-events.js >> ${accountLogDir}/check-due-events.log 2>&1 # heartbeat`,
1301
1402
  `* * * * * mkdir -p ${accountLogDir} && PLATFORM_ROOT=${platformRoot} ${accountEnv}${nodeBin} ${platformRoot}/plugins/email/mcp/dist/scripts/email-fetch.js >> ${accountLogDir}/email-fetch.log 2>&1 # email-fetch`,
1302
1403
  `* * * * * mkdir -p ${accountLogDir} && PLATFORM_ROOT=${platformRoot} ${accountEnv}${nodeBin} ${platformRoot}/plugins/email/mcp/dist/scripts/email-auto-respond.js >> ${accountLogDir}/email-auto-respond.log 2>&1 # email-auto-respond`,
1404
+ `0 3 * * * mkdir -p ${accountLogDir} && PLATFORM_ROOT=${platformRoot} ${accountEnv}${nodeBin} ${platformRoot}/plugins/memory/mcp/dist/scripts/graph-prune.js >> ${accountLogDir}/graph-prune.log 2>&1 # graph-prune`,
1303
1405
  ];
1304
1406
  // Read existing crontab (empty string if none)
1305
1407
  const existing = spawnSync("crontab", ["-l"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
@@ -1321,7 +1423,7 @@ function installCrons() {
1321
1423
  stdio: ["pipe", "pipe", "pipe"],
1322
1424
  });
1323
1425
  if (write.status === 0) {
1324
- console.log(" Cron jobs: registered (heartbeat, email-fetch, email-auto-respond)");
1426
+ console.log(" Cron jobs: registered (heartbeat, email-fetch, email-auto-respond, graph-prune)");
1325
1427
  }
1326
1428
  else {
1327
1429
  console.error(` Cron jobs: failed to register — ${(write.stderr || "").trim()}`);
@@ -1533,6 +1635,25 @@ WantedBy=multi-user.target
1533
1635
  console.error(vncLog);
1534
1636
  process.exit(1);
1535
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
+ }
1536
1657
  }
1537
1658
  // ---------------------------------------------------------------------------
1538
1659
  // Main
@@ -1823,6 +1944,7 @@ try {
1823
1944
  buildPlatform();
1824
1945
  setupVncViewer();
1825
1946
  setupAccount();
1947
+ installTunnelScripts(); // ~/setup-tunnel.sh, ~/reset-tunnel.sh — the SKILL contract
1826
1948
  installService();
1827
1949
  console.log("");
1828
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.625",
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"
@@ -353,7 +353,7 @@ server.tool("remote-auth-status", "Check whether the remote access password is c
353
353
  ? `Remote access password is configured.`
354
354
  : `Remote access password is not configured. The admin interface will refuse to expose over the tunnel until one is set.\n\n` +
355
355
  `${deviceUrlBlock({ url: setupUrl, intent: "Set remote access password", hostname: osHostname() })}\n\n` +
356
- `Click the button above to open the setup page on the device's browser. Do NOT collect the password in chat.`;
356
+ `Ask the operator to set a password — either by clicking the button above (opens the setup page on the device) or by telling you one you can pass to \`remote-auth-set-password\`.`;
357
357
  return {
358
358
  content: [{ type: "text", text }],
359
359
  };
@@ -1234,13 +1234,10 @@ server.tool("session-reset", "Reset the current session. Compacts conversation h
1234
1234
  server.tool("session-resume", "Resume a previous session. Loads the selected session's message history into the chat timeline " +
1235
1235
  "and routes new messages to that conversation. Call after the user selects a session from the " +
1236
1236
  "session-list results. Pass the conversationId from the selected session.", { conversationId: z.string().uuid().describe("The conversationId of the session to resume") }, async () => ({ content: [{ type: "text", text: "resumed" }] }));
1237
- server.tool("remote-auth-set-password", "NEVER collect a password in chat passwords must not appear in conversation history. " +
1238
- "Direct the user to set their remote access password in the browser at `/__remote-auth/setup` (always wrap this URL in backticks — double underscores trigger markdown escaping). " +
1239
- "This tool exists only for admin recovery when the browser setup is unavailable. " +
1240
- "Hashes with scrypt and writes to the platform's brand-specific config directory. " +
1241
- "The remote access password protects the admin interface when accessed over the internet — " +
1242
- "it has no effect on the public endpoint or the tunnel itself. " +
1243
- "Set this before invoking setup-tunnel.sh; the script's post-restart verification curls the admin hostname, which fails unless the remote-auth gate is configured (admin must not be exposed unauthenticated).", { password: z.string() }, async ({ password }) => {
1237
+ 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. " +
1238
+ "Validates strength (8+ chars, digit, special character, no leading/trailing spaces). " +
1239
+ "Protects the admin interface when exposed over the tunnel — has no effect on the public endpoint or the tunnel itself. " +
1240
+ "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 }) => {
1244
1241
  const trimmed = password.trim();
1245
1242
  // Validate strength — same rules as the web server's validatePasswordStrength
1246
1243
  const checks = [