@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.
- package/dist/index.js +125 -3
- package/dist/uninstall.js +59 -2
- package/package.json +1 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js +5 -8
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +1 -1
- package/payload/platform/plugins/docs/references/cloudflare.md +1 -1
- package/payload/platform/plugins/memory/PLUGIN.md +24 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js +141 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.d.ts +66 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.js +411 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/graph-prune.d.ts +18 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/graph-prune.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/graph-prune.js +80 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/graph-prune.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.d.ts +8 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.js +7 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.d.ts +7 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.js +28 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-list.d.ts +7 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-list.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-list.js +7 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-list.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.d.ts +7 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.js +27 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-run.d.ts +7 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-run.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-run.js +10 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-run.js.map +1 -0
- package/payload/platform/templates/agents/admin/IDENTITY.md +20 -4
- package/payload/platform/templates/agents/public/IDENTITY.md +18 -0
- package/payload/platform/templates/specialists/agents/content-producer.md +18 -0
- package/payload/platform/templates/specialists/agents/personal-assistant.md +18 -0
- package/payload/platform/templates/specialists/agents/project-manager.md +18 -0
- package/payload/platform/templates/specialists/agents/research-assistant.md +18 -0
- 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
|
@@ -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
|
-
`
|
|
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", "
|
|
1238
|
-
"
|
|
1239
|
-
"
|
|
1240
|
-
"
|
|
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 = [
|