@rubytech/create-maxy-code 0.1.411 → 0.1.414
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/__tests__/installer-cwd-pin-and-auth-verify.test.js +82 -0
- package/dist/index.js +44 -5
- package/package.json +1 -1
- package/payload/platform/lib/account-scope/dist/index.d.ts +27 -0
- package/payload/platform/lib/account-scope/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/account-scope/dist/index.js +33 -0
- package/payload/platform/lib/account-scope/dist/index.js.map +1 -0
- package/payload/platform/lib/account-scope/src/index.ts +43 -0
- package/payload/platform/lib/account-scope/tsconfig.json +5 -0
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/platform/plugins/admin/hooks/lib/maxy-mcp-plugins.txt +1 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +37 -1
- package/payload/platform/plugins/business-assistant/skills/e-sign/SKILL.md +2 -0
- package/payload/platform/plugins/docs/references/cross-account-authority.md +25 -0
- package/payload/platform/plugins/docs/references/memory-guide.md +6 -0
- package/payload/platform/plugins/filesystem/.claude-plugin/plugin.json +21 -0
- package/payload/platform/plugins/filesystem/PLUGIN.md +63 -0
- package/payload/platform/plugins/filesystem/lib/mcp-spawn-tee/index.js +193 -0
- package/payload/platform/plugins/filesystem/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/filesystem/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/filesystem/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/index.js +91 -0
- package/payload/platform/plugins/filesystem/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/lib/__tests__/scope.test.d.ts +2 -0
- package/payload/platform/plugins/filesystem/mcp/dist/lib/__tests__/scope.test.d.ts.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/lib/__tests__/scope.test.js +89 -0
- package/payload/platform/plugins/filesystem/mcp/dist/lib/__tests__/scope.test.js.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/lib/result.d.ts +12 -0
- package/payload/platform/plugins/filesystem/mcp/dist/lib/result.d.ts.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/lib/result.js +6 -0
- package/payload/platform/plugins/filesystem/mcp/dist/lib/result.js.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.d.ts +45 -0
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.d.ts.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.js +86 -0
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.js.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/lib/walk.d.ts +11 -0
- package/payload/platform/plugins/filesystem/mcp/dist/lib/walk.d.ts.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/lib/walk.js +64 -0
- package/payload/platform/plugins/filesystem/mcp/dist/lib/walk.js.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/__tests__/handlers.test.d.ts +2 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/__tests__/handlers.test.d.ts.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/__tests__/handlers.test.js +94 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/__tests__/handlers.test.js.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-edit.d.ts +8 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-edit.d.ts.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-edit.js +40 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-edit.js.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-glob.d.ts +6 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-glob.d.ts.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-glob.js +13 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-glob.js.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-grep.d.ts +7 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-grep.d.ts.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-grep.js +71 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-grep.js.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-list.d.ts +6 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-list.d.ts.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-list.js +26 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-list.js.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-read.d.ts +6 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-read.d.ts.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-read.js +28 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-read.js.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-write.d.ts +7 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-write.d.ts.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-write.js +25 -0
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-write.js.map +1 -0
- package/payload/platform/plugins/filesystem/mcp/package.json +21 -0
- package/payload/platform/plugins/filesystem/mcp/vitest.config.ts +8 -0
- package/payload/platform/plugins/memory/PLUGIN.md +8 -0
- package/payload/platform/plugins/memory/mcp/dist/index-testable.d.ts +34 -0
- package/payload/platform/plugins/memory/mcp/dist/index-testable.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/index-testable.js +60 -0
- package/payload/platform/plugins/memory/mcp/dist/index-testable.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js +327 -146
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/resolve-owner-userid.d.ts +15 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/resolve-owner-userid.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/resolve-owner-userid.js +29 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/resolve-owner-userid.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/cross-account.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/cross-account.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/cross-account.test.js +74 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/cross-account.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/resolve-owner-userid.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/resolve-owner-userid.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/resolve-owner-userid.test.js +40 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/resolve-owner-userid.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/resolve-tool-account.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/resolve-tool-account.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/resolve-tool-account.test.js +54 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/resolve-tool-account.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts +11 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +13 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -1
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +2 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +7 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/claude-host-creds.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/claude-host-creds.js +6 -1
- package/payload/platform/services/claude-session-manager/dist/claude-host-creds.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/claude-keychain-write.d.ts +7 -1
- package/payload/platform/services/claude-session-manager/dist/claude-keychain-write.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/claude-keychain-write.js +7 -2
- package/payload/platform/services/claude-session-manager/dist/claude-keychain-write.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/config.d.ts +37 -0
- package/payload/platform/services/claude-session-manager/dist/config.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/config.js +34 -1
- package/payload/platform/services/claude-session-manager/dist/config.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/cross-account-census.d.ts +34 -0
- package/payload/platform/services/claude-session-manager/dist/cross-account-census.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/cross-account-census.js +98 -0
- package/payload/platform/services/claude-session-manager/dist/cross-account-census.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +10 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +17 -0
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +25 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-sidecar.d.ts +7 -0
- package/payload/platform/services/claude-session-manager/dist/session-sidecar.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-sidecar.js +8 -0
- package/payload/platform/services/claude-session-manager/dist/session-sidecar.js.map +1 -1
- package/payload/server/{chunk-ZYS5V5QG.js → chunk-3Z5WE53X.js} +4 -0
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/public/assets/{AdminLoginScreens-BRQgI2lW.js → AdminLoginScreens-CYPTMmTk.js} +1 -1
- package/payload/server/public/assets/{AdminShell-By9n5icG.js → AdminShell-CZH_EZIy.js} +1 -1
- package/payload/server/public/assets/{Checkbox-DjEWgYu1.js → Checkbox-B0pbYJDQ.js} +1 -1
- package/payload/server/public/assets/{Transcript-BejWvOaO.js → Transcript-CYaAPBFX.js} +1 -1
- package/payload/server/public/assets/{admin-BFEMryQm.js → admin-BKDMSRto.js} +1 -1
- package/payload/server/public/assets/{browser-CMJMEUz6.js → browser-CxeMDDZA.js} +1 -1
- package/payload/server/public/assets/{calendar-C6ZGlHEP.js → calendar-DiIiTY9-.js} +1 -1
- package/payload/server/public/assets/chat-C56Oqvt1.js +1 -0
- package/payload/server/public/assets/chevron-left-Da-iZGi_.js +1 -0
- package/payload/server/public/assets/data-ZUdT44Ba.js +1 -0
- package/payload/server/public/assets/{graph-DIrXtPLv.js → graph-DC3dwnMZ.js} +1 -1
- package/payload/server/public/assets/{graph-labels-BtPf1Jom.js → graph-labels-Csnovm_c.js} +1 -1
- package/payload/server/public/assets/{maximize-2-DeJ4K8kL.js → maximize-2-WRUJsg5n.js} +1 -1
- package/payload/server/public/assets/{operator-B8QhqzLI.js → operator-BpzeHTAw.js} +1 -1
- package/payload/server/public/assets/{page-iFOy0dFx.js → page-8jJOxx6y.js} +1 -1
- package/payload/server/public/assets/{page-Df6KYkf4.js → page-IQDLdebG.js} +1 -1
- package/payload/server/public/assets/{public-BrUIRQZd.js → public-Dqg7jwjq.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-CYfntpwu.js → rotate-ccw-BayMWwfc.js} +1 -1
- package/payload/server/public/assets/{useSubAccountSwitcher-Cvnc_fwQ.css → useSubAccountSwitcher-CRZfyJXR.css} +1 -1
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +5 -5
- package/payload/server/public/chat.html +9 -9
- package/payload/server/public/data.html +8 -8
- package/payload/server/public/graph.html +9 -9
- package/payload/server/public/index.html +11 -11
- package/payload/server/public/operator.html +11 -11
- package/payload/server/public/public.html +9 -9
- package/payload/server/server.js +72 -15
- package/payload/server/public/assets/chat-B7s62Nnp.js +0 -1
- package/payload/server/public/assets/chevron-left-Dfjcaiyr.js +0 -1
- package/payload/server/public/assets/data-741ZbBwR.js +0 -1
- /package/payload/server/public/assets/{useSubAccountSwitcher-BK_KfOLC.js → useSubAccountSwitcher-C9aLg8yI.js} +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Static-grep contracts for Task 1462 — the installer must survive being
|
|
2
|
+
// invoked from a working directory that deployPayload() later wipes.
|
|
3
|
+
//
|
|
4
|
+
// Three defects were compounding on the customer Pi upgrade:
|
|
5
|
+
// A. main() never pins the process CWD, so once deployPayload() rmSync's the
|
|
6
|
+
// operator's CWD (they ran `npx` from inside ~/.<brand>/platform), every
|
|
7
|
+
// subprocess inherits a deleted inode → JVM `getcwd() failed` and Bun
|
|
8
|
+
// ENOENT.
|
|
9
|
+
// B. resetNeo4jAuth returned the freshly-generated password even when the
|
|
10
|
+
// 15x cypher-shell verify loop never passed, so the app persisted a
|
|
11
|
+
// password Neo4j never actually accepted.
|
|
12
|
+
// C. the version marker was stamped mid-deployPayload, before plugin
|
|
13
|
+
// registration could abort, so an aborted upgrade reported success.
|
|
14
|
+
//
|
|
15
|
+
// Why static-grep, not behaviour: the touched paths spawn (systemctl, brew,
|
|
16
|
+
// launchctl, cypher-shell) and run the whole install on import — the same
|
|
17
|
+
// reason macos-darwin-branch.test.ts contracts resetNeo4jAuth by shape. This
|
|
18
|
+
// suite pins the code shape so a refactor that drops a fix fails loudly here.
|
|
19
|
+
import test from "node:test";
|
|
20
|
+
import assert from "node:assert/strict";
|
|
21
|
+
import { readFileSync } from "node:fs";
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
23
|
+
import { dirname, resolve } from "node:path";
|
|
24
|
+
// dist/__tests__/installer-cwd-pin-and-auth-verify.test.js → ../../src/index.ts
|
|
25
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const INDEX_TS = resolve(here, "../../src/index.ts");
|
|
27
|
+
const SRC = readFileSync(INDEX_TS, "utf-8");
|
|
28
|
+
// --- Defect A: CWD pin ---
|
|
29
|
+
test("installer pins the process CWD to a never-wiped directory at startup", () => {
|
|
30
|
+
assert.match(SRC, /process\.chdir\(/, "startup must call process.chdir to escape a wipeable inherited CWD");
|
|
31
|
+
// The pin target is the operator HOME (never wiped — deployPayload only
|
|
32
|
+
// rmSync's subdirs of INSTALL_DIR), with a "/" fallback if the chdir throws.
|
|
33
|
+
assert.match(SRC, /process\.chdir\(process\.env\.HOME\s*\?\?\s*["']\/["']\)/, "CWD pin target must be process.env.HOME with a '/' fallback");
|
|
34
|
+
assert.match(SRC, /process\.chdir\(["']\/["']\)/, "a '/' fallback chdir must exist for when the HOME chdir throws");
|
|
35
|
+
});
|
|
36
|
+
test("installer logs the resolved pinned CWD", () => {
|
|
37
|
+
assert.match(SRC, /cwd-pinned to=/, "startup must emit a grep-able resolved-CWD line");
|
|
38
|
+
});
|
|
39
|
+
test("the CWD pin runs before deployPayload wipes the tree", () => {
|
|
40
|
+
const chdirAt = SRC.indexOf("process.chdir(");
|
|
41
|
+
// `deployPayload();` (with semicolon) is the call site — the definition is
|
|
42
|
+
// `function deployPayload(): void`, so the semicolon disambiguates.
|
|
43
|
+
const deployCallAt = SRC.indexOf("deployPayload();");
|
|
44
|
+
assert.ok(chdirAt !== -1 && deployCallAt !== -1, "both anchors must exist");
|
|
45
|
+
assert.ok(chdirAt < deployCallAt, "process.chdir must appear before the deployPayload() call so no rmSync runs under a live inherited CWD");
|
|
46
|
+
});
|
|
47
|
+
// --- Defect B: resetNeo4jAuth verifies before returning ---
|
|
48
|
+
test("resetNeo4jAuth records whether the post-reset auth check ever passed", () => {
|
|
49
|
+
assert.match(SRC, /\[neo4j-auth-reset\] verified=/, "resetNeo4jAuth must emit a verified=<bool> port=<n> post-condition line");
|
|
50
|
+
});
|
|
51
|
+
test("resetNeo4jAuth throws instead of returning an unverified password", () => {
|
|
52
|
+
// The old shape returned `password` unconditionally after the wait loop.
|
|
53
|
+
// The fix guards the return on a `verified` flag and throws otherwise.
|
|
54
|
+
const fn = SRC.slice(SRC.indexOf("function resetNeo4jAuth"), SRC.indexOf("function redactInstallLogs"));
|
|
55
|
+
assert.ok(fn.length > 0, "resetNeo4jAuth body must be locatable");
|
|
56
|
+
const gateIdx = fn.search(/if\s*\(\s*!verified\s*\)/);
|
|
57
|
+
assert.ok(gateIdx !== -1, "resetNeo4jAuth must branch on !verified");
|
|
58
|
+
assert.match(fn, /throw new Error\(/, "resetNeo4jAuth must throw when the reset never verified");
|
|
59
|
+
// The credential can only be returned on the verified path: the !verified
|
|
60
|
+
// throw-gate must sit BEFORE `return password`, so an unverified reset can
|
|
61
|
+
// never fall through to a return. A positional check, not a
|
|
62
|
+
// `}...return password` regex — the fixed code legitimately has the
|
|
63
|
+
// throw-block's own `}` immediately before the return.
|
|
64
|
+
const returnIdx = fn.lastIndexOf("return password");
|
|
65
|
+
assert.ok(returnIdx !== -1, "resetNeo4jAuth must still return the password on the verified path");
|
|
66
|
+
assert.ok(gateIdx < returnIdx, "the !verified throw-gate must precede `return password` so an unverified reset can never return a credential");
|
|
67
|
+
});
|
|
68
|
+
// --- Defect C: version marker gated on full completion ---
|
|
69
|
+
test("deployPayload no longer stamps the version marker mid-deploy", () => {
|
|
70
|
+
const dp = SRC.slice(SRC.indexOf("function deployPayload"), SRC.indexOf("function registerLocalAndExternalPlugins"));
|
|
71
|
+
assert.ok(dp.length > 0, "deployPayload body must be locatable");
|
|
72
|
+
assert.ok(!/hostname\}-version`\)[\s\S]*?writeFileSync\([^\n]*PKG_VERSION/.test(dp), "deployPayload must not write the version marker — it moved to the end of a fully-successful install");
|
|
73
|
+
});
|
|
74
|
+
test("version marker is written only after plugin registration completes", () => {
|
|
75
|
+
// Anchor on the call site (`...Plugins();` with semicolon), not the
|
|
76
|
+
// `function ...Plugins(): void` definition — the definition sits far above
|
|
77
|
+
// main() and would make this pass trivially.
|
|
78
|
+
const registerCallAt = SRC.indexOf("registerLocalAndExternalPlugins();");
|
|
79
|
+
const markerAt = SRC.indexOf("version-marker written");
|
|
80
|
+
assert.ok(registerCallAt !== -1 && markerAt !== -1, "both anchors must exist");
|
|
81
|
+
assert.ok(markerAt > registerCallAt, "the version-marker write must appear after the registerLocalAndExternalPlugins() call so an aborted run does not report success");
|
|
82
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -1338,16 +1338,30 @@ function resetNeo4jAuth(port = DEFAULT_NEO4J_PORT, dataDir = "/var/lib/neo4j", p
|
|
|
1338
1338
|
shell("systemctl", ["start", serviceName], { sudo: true });
|
|
1339
1339
|
}
|
|
1340
1340
|
console.log(" Waiting for Neo4j to start...");
|
|
1341
|
+
let verified = false;
|
|
1341
1342
|
for (let i = 0; i < 15; i++) {
|
|
1342
1343
|
const check = spawnSync("cypher-shell", [
|
|
1343
1344
|
"-u", "neo4j", "-p", password,
|
|
1344
1345
|
"-a", `bolt://localhost:${port}`,
|
|
1345
1346
|
"RETURN 1",
|
|
1346
1347
|
], { stdio: "pipe", timeout: 5000 });
|
|
1347
|
-
if (check.status === 0)
|
|
1348
|
+
if (check.status === 0) {
|
|
1349
|
+
verified = true;
|
|
1348
1350
|
break;
|
|
1351
|
+
}
|
|
1349
1352
|
spawnSync("sleep", ["2"]);
|
|
1350
1353
|
}
|
|
1354
|
+
// Post-condition: the new password only becomes authoritative once a
|
|
1355
|
+
// cypher-shell login actually succeeds. Returning an unverified password let
|
|
1356
|
+
// ensureNeo4jPassword persist a credential Neo4j never accepted — a hard
|
|
1357
|
+
// mismatch no restart can heal (Task 1462). The commonest cause of a
|
|
1358
|
+
// never-verifying reset is a corrupted set-initial-password from a deleted
|
|
1359
|
+
// CWD (JVM `getcwd() failed`); the CWD pin removes that, this throw stops the
|
|
1360
|
+
// installer writing a bad password if it still fails.
|
|
1361
|
+
logFile(`[neo4j-auth-reset] verified=${verified} port=${port}`);
|
|
1362
|
+
if (!verified) {
|
|
1363
|
+
throw new Error(`Neo4j auth reset did not verify on port ${port}: cypher-shell RETURN 1 failed on all 15 attempts. The new password was NOT confirmed and will not be persisted. Check the install log for a JVM \`getcwd() failed\` (deleted install CWD) or a Neo4j that never accepted auth.`);
|
|
1364
|
+
}
|
|
1351
1365
|
return password;
|
|
1352
1366
|
}
|
|
1353
1367
|
/**
|
|
@@ -2478,10 +2492,10 @@ function deployPayload() {
|
|
|
2478
2492
|
// Mirrors checkAdminAuthInvariant() in platform/lib/admins-write/src/index.ts;
|
|
2479
2493
|
// future divergence between the two should be caught by the test suite.
|
|
2480
2494
|
runInstallInvariantCheck(persistentUsersFile, join(INSTALL_DIR, "data", "accounts"));
|
|
2481
|
-
//
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2495
|
+
// The version marker is NOT written here. It is stamped at the very end of a
|
|
2496
|
+
// fully-successful install (after plugin registration and every throwing
|
|
2497
|
+
// step), so an aborted upgrade cannot leave a marker that reports success it
|
|
2498
|
+
// did not complete. Task 1462.
|
|
2485
2499
|
console.log(` Deployed to ${INSTALL_DIR}`);
|
|
2486
2500
|
}
|
|
2487
2501
|
// register the local + external Claude Code plugins after the
|
|
@@ -4983,6 +4997,22 @@ const PLATFORM_HEADER = `[create-maxy] platform=${PLATFORM} arch=${process.arch}
|
|
|
4983
4997
|
console.log(PLATFORM_HEADER);
|
|
4984
4998
|
console.log(`[create-maxy] log=${LOG_FILE} persist=${PERSIST_DIR}`);
|
|
4985
4999
|
initLogging();
|
|
5000
|
+
// Pin the process CWD before any step runs. npx inherits the operator's shell
|
|
5001
|
+
// CWD; if they invoked the upgrade from inside the install tree (e.g. `cd
|
|
5002
|
+
// ~/.<brand>/platform && npx ...`), deployPayload()'s rmSync deletes that live
|
|
5003
|
+
// CWD mid-run and every subprocess spawned afterward inherits a deleted inode
|
|
5004
|
+
// (JVM `getcwd() failed` during the Neo4j auth reset; Bun ENOENT during plugin
|
|
5005
|
+
// registration). HOME is never wiped — deployPayload only rmSync's subdirs of
|
|
5006
|
+
// INSTALL_DIR — so it is a safe anchor; fall back to "/" if the HOME chdir
|
|
5007
|
+
// throws. Task 1462.
|
|
5008
|
+
try {
|
|
5009
|
+
process.chdir(process.env.HOME ?? "/");
|
|
5010
|
+
}
|
|
5011
|
+
catch {
|
|
5012
|
+
process.chdir("/");
|
|
5013
|
+
}
|
|
5014
|
+
console.log(` [create-maxy] cwd-pinned to=${process.cwd()}`);
|
|
5015
|
+
logFile(`[create-maxy] cwd-pinned to=${process.cwd()}`);
|
|
4986
5016
|
console.log("================================================================");
|
|
4987
5017
|
console.log(` ${BRAND.productName} — ${BRAND.tagline}. (${BRAND.hostname} v${PKG_VERSION})`);
|
|
4988
5018
|
console.log("================================================================");
|
|
@@ -5066,6 +5096,15 @@ try {
|
|
|
5066
5096
|
// (the reboot-loop this defends against). A partial/failed install exits
|
|
5067
5097
|
// before this point and never writes the drop-in.
|
|
5068
5098
|
configureHardwareWatchdog();
|
|
5099
|
+
// Stamp the version marker as the last thing a fully-successful install does.
|
|
5100
|
+
// Every throwing step (plugin registration, service install, account setup)
|
|
5101
|
+
// runs before this, so an aborted upgrade exits via the catch below and never
|
|
5102
|
+
// writes it — the running platform never reports a version it did not finish
|
|
5103
|
+
// installing. platform/config is excluded from the Task 848 freeze above, so
|
|
5104
|
+
// it is still writable here. Task 1462.
|
|
5105
|
+
const versionMarkerPath = join(INSTALL_DIR, "platform/config", `.${BRAND.hostname}-version`);
|
|
5106
|
+
writeFileSync(versionMarkerPath, PKG_VERSION, "utf-8");
|
|
5107
|
+
console.log(` [install] version-marker written path=${versionMarkerPath} version=${PKG_VERSION}`);
|
|
5069
5108
|
console.log("");
|
|
5070
5109
|
console.log("================================================================");
|
|
5071
5110
|
console.log("");
|
package/package.json
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type EffectiveAccount = {
|
|
2
|
+
accountId: string;
|
|
3
|
+
} | {
|
|
4
|
+
reject: "cross-account-denied" | "cross-account-invalid-target";
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the account a call acts on. Fail-closed: only a house-scoped session
|
|
8
|
+
* may set `targetAccountId`, and only to a valid account. There is no house
|
|
9
|
+
* fallback — an unscoped session that names a target is denied, never silently
|
|
10
|
+
* downgraded to its own account.
|
|
11
|
+
*
|
|
12
|
+
* `isValidAccountId` is injected (required, no default): the UI passes its
|
|
13
|
+
* fs-backed `isValidAccountId`; the `filesystem` plugin passes a check over
|
|
14
|
+
* `enumerateValidAccountIds`. Keeping validity injected is what lets this
|
|
15
|
+
* module stay pure.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveEffectiveAccountCore(input: {
|
|
18
|
+
ownAccountId: string;
|
|
19
|
+
houseScope: string | null;
|
|
20
|
+
targetAccountId?: string | null;
|
|
21
|
+
}, isValidAccountId: (id: string) => boolean): EffectiveAccount;
|
|
22
|
+
/**
|
|
23
|
+
* Boot-reader that consumers import to learn their house scope from the spawn
|
|
24
|
+
* env. An empty value reads as null — a non-house session carries no scope.
|
|
25
|
+
*/
|
|
26
|
+
export declare function readHouseAdminScope(env?: NodeJS.ProcessEnv): string | null;
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,gBAAgB,GACxB;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,GACrB;IAAE,MAAM,EAAE,sBAAsB,GAAG,8BAA8B,CAAA;CAAE,CAAC;AAExE;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAC3F,gBAAgB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,GACxC,gBAAgB,CAMlB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,IAAI,CAEvF"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveEffectiveAccountCore = resolveEffectiveAccountCore;
|
|
4
|
+
exports.readHouseAdminScope = readHouseAdminScope;
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the account a call acts on. Fail-closed: only a house-scoped session
|
|
7
|
+
* may set `targetAccountId`, and only to a valid account. There is no house
|
|
8
|
+
* fallback — an unscoped session that names a target is denied, never silently
|
|
9
|
+
* downgraded to its own account.
|
|
10
|
+
*
|
|
11
|
+
* `isValidAccountId` is injected (required, no default): the UI passes its
|
|
12
|
+
* fs-backed `isValidAccountId`; the `filesystem` plugin passes a check over
|
|
13
|
+
* `enumerateValidAccountIds`. Keeping validity injected is what lets this
|
|
14
|
+
* module stay pure.
|
|
15
|
+
*/
|
|
16
|
+
function resolveEffectiveAccountCore(input, isValidAccountId) {
|
|
17
|
+
const { ownAccountId, houseScope, targetAccountId } = input;
|
|
18
|
+
if (!targetAccountId)
|
|
19
|
+
return { accountId: ownAccountId };
|
|
20
|
+
if (!houseScope)
|
|
21
|
+
return { reject: "cross-account-denied" };
|
|
22
|
+
if (!isValidAccountId(targetAccountId))
|
|
23
|
+
return { reject: "cross-account-invalid-target" };
|
|
24
|
+
return { accountId: targetAccountId };
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Boot-reader that consumers import to learn their house scope from the spawn
|
|
28
|
+
* env. An empty value reads as null — a non-house session carries no scope.
|
|
29
|
+
*/
|
|
30
|
+
function readHouseAdminScope(env = process.env) {
|
|
31
|
+
return env.HOUSE_ADMIN_SCOPE ? env.HOUSE_ADMIN_SCOPE : null;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAyBA,kEASC;AAMD,kDAEC;AA5BD;;;;;;;;;;GAUG;AACH,SAAgB,2BAA2B,CACzC,KAA2F,EAC3F,gBAAyC;IAEzC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,GAAG,KAAK,CAAC;IAC5D,IAAI,CAAC,eAAe;QAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;IACzD,IAAI,CAAC,UAAU;QAAE,OAAO,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC;IAC3D,IAAI,CAAC,gBAAgB,CAAC,eAAe,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,8BAA8B,EAAE,CAAC;IAC1F,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,CAAC;AACxC,CAAC;AAED;;;GAGG;AACH,SAAgB,mBAAmB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACtE,OAAO,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9D,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// account-scope — the single fail-closed cross-account resolver.
|
|
2
|
+
//
|
|
3
|
+
// Task 1440 landed this logic inline in
|
|
4
|
+
// platform/ui/app/lib/claude-agent/account.ts. Task 1458 relocates the two
|
|
5
|
+
// pure primitives here so BOTH the UI lib (account.ts, which re-exports them)
|
|
6
|
+
// and MCP plugins (the `filesystem` plugin) import ONE definition — "one
|
|
7
|
+
// resolver, no drift", the same privilege-boundary discipline as the 1383/1385
|
|
8
|
+
// account-manager escalation. This module is pure: no filesystem access, no
|
|
9
|
+
// throw at module load, so it is safe to import into an MCP server that boots
|
|
10
|
+
// with empty env during plugin-system `tools/list` enumeration.
|
|
11
|
+
export type EffectiveAccount =
|
|
12
|
+
| { accountId: string }
|
|
13
|
+
| { reject: "cross-account-denied" | "cross-account-invalid-target" };
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the account a call acts on. Fail-closed: only a house-scoped session
|
|
17
|
+
* may set `targetAccountId`, and only to a valid account. There is no house
|
|
18
|
+
* fallback — an unscoped session that names a target is denied, never silently
|
|
19
|
+
* downgraded to its own account.
|
|
20
|
+
*
|
|
21
|
+
* `isValidAccountId` is injected (required, no default): the UI passes its
|
|
22
|
+
* fs-backed `isValidAccountId`; the `filesystem` plugin passes a check over
|
|
23
|
+
* `enumerateValidAccountIds`. Keeping validity injected is what lets this
|
|
24
|
+
* module stay pure.
|
|
25
|
+
*/
|
|
26
|
+
export function resolveEffectiveAccountCore(
|
|
27
|
+
input: { ownAccountId: string; houseScope: string | null; targetAccountId?: string | null },
|
|
28
|
+
isValidAccountId: (id: string) => boolean,
|
|
29
|
+
): EffectiveAccount {
|
|
30
|
+
const { ownAccountId, houseScope, targetAccountId } = input;
|
|
31
|
+
if (!targetAccountId) return { accountId: ownAccountId };
|
|
32
|
+
if (!houseScope) return { reject: "cross-account-denied" };
|
|
33
|
+
if (!isValidAccountId(targetAccountId)) return { reject: "cross-account-invalid-target" };
|
|
34
|
+
return { accountId: targetAccountId };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Boot-reader that consumers import to learn their house scope from the spawn
|
|
39
|
+
* env. An empty value reads as null — a non-house session carries no scope.
|
|
40
|
+
*/
|
|
41
|
+
export function readHouseAdminScope(env: NodeJS.ProcessEnv = process.env): string | null {
|
|
42
|
+
return env.HOUSE_ADMIN_SCOPE ? env.HOUSE_ADMIN_SCOPE : null;
|
|
43
|
+
}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"services/*"
|
|
7
7
|
],
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "tsc -p lib/models/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-lifeline/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/embed-client/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/admin-access-password/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json && tsc -p lib/obsidian-parser/tsconfig.json && tsc -p services/claude-session-manager/tsconfig.json && tsc -p services/whatsapp-channel/tsconfig.json && tsc -p services/webchat-channel/tsconfig.json && NODE_OPTIONS='--max-old-space-size=8192' tsc -b plugins/*/mcp/tsconfig.json",
|
|
10
|
-
"build:lib": "tsc -p lib/models/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-lifeline/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/embed-client/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/admin-access-password/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json && tsc -p lib/obsidian-parser/tsconfig.json",
|
|
9
|
+
"build": "tsc -p lib/models/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-lifeline/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/account-scope/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/embed-client/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/admin-access-password/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json && tsc -p lib/obsidian-parser/tsconfig.json && tsc -p services/claude-session-manager/tsconfig.json && tsc -p services/whatsapp-channel/tsconfig.json && tsc -p services/webchat-channel/tsconfig.json && NODE_OPTIONS='--max-old-space-size=8192' tsc -b plugins/*/mcp/tsconfig.json",
|
|
10
|
+
"build:lib": "tsc -p lib/models/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-lifeline/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/account-scope/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/embed-client/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/admin-access-password/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json && tsc -p lib/obsidian-parser/tsconfig.json",
|
|
11
11
|
"gen:canonical-tools": "node scripts/generate-canonical-tool-names.mjs",
|
|
12
12
|
"build:memory": "tsc -p plugins/memory/mcp/tsconfig.json",
|
|
13
13
|
"build:contacts": "tsc -p plugins/contacts/mcp/tsconfig.json",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: platform-architecture
|
|
3
3
|
description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
|
|
4
|
-
content-hash: sha256:
|
|
4
|
+
content-hash: sha256:9b227cf8ab845f23cb1899d5b2ac2581cfac3aae772715bf02d288ee93d8fbc4
|
|
5
5
|
brand: maxy-code
|
|
6
6
|
product-name: Maxy
|
|
7
7
|
---
|
|
@@ -516,6 +516,36 @@ A third layer closes the same gap from the platform side: when `claude-agent.ts`
|
|
|
516
516
|
|
|
517
517
|
**Boot-smoke as publish-time gate.** The memory MCP carries `scripts/boot-smoke.sh` that spawns `dist/index.js` with stub env, sleeps 2s, asserts `kill -0 <pid>`, and reports `[boot-smoke] memory ok|FAILED tail=<n-lines>`. Wired to `prepublish` in `plugins/memory/mcp/package.json`. The pattern is propagatable to other plugin MCPs — it's deliberately not generalised yet because each plugin's stub-env requirements differ (memory needs ACCOUNT_ID + PLATFORM_ROOT + NEO4J_URI + SESSION_ID; others differ).
|
|
518
518
|
|
|
519
|
+
---
|
|
520
|
+
# House-Admin Cross-Account Authority
|
|
521
|
+
Source: https://docs.getmaxy.com/cross-account-authority.md
|
|
522
|
+
|
|
523
|
+
# House-admin cross-account authority
|
|
524
|
+
|
|
525
|
+
The house account is the operator-owned account that runs the admin assistant. Sub-accounts are isolated: each one's tools read and write only that account's data, which is what keeps tenants separate.
|
|
526
|
+
|
|
527
|
+
By default the admin assistant is scoped the same way — it acts on the house account. Cross-account authority lets a house-scoped admin session act on a named sub-account by passing that account as a target, while every other session (public visitors, a session already scoped to a sub-account, background specialists) is refused. The refusal is fail-closed: a session that is not house-scoped and names a target is denied, never quietly redirected to its own account.
|
|
528
|
+
|
|
529
|
+
The operator wants this for two surfaces: the account graph and the account filesystem. Each is a separate adoption; a coverage ledger tracks them and a background check confirms neither silently regresses.
|
|
530
|
+
|
|
531
|
+
## Memory (graph)
|
|
532
|
+
|
|
533
|
+
The `memory` plugin gives the admin session access to a sub-account's knowledge graph. Its tools whose read or write is scoped solely by the account — search, lookup, write, update, edit, delete, edges, attachments, reports, conversation history, ingest, and graph maintenance — take an optional target. Omit it and the tool acts on the session's own account, exactly as before; pass a sub-account and a house-scoped admin session reads or writes that account's subgraph instead; any other session that names a target is refused.
|
|
534
|
+
|
|
535
|
+
`profile-read`/`update`/`delete` also take a target. They are keyed to a user as well as the account, so cross-account they cannot use the caller's own user (a house operator has no record inside a sub-account); instead they resolve the sub-account's owner — the single admin identity seeded with every account — and act on that owner's profile. A house-scoped session can therefore read or set a sub-account's profile (its owner's name, role, timezone, locale, and preferences); a target with no owner is refused rather than writing a stray record. Setting a sub-account owner's email or telephone this way is not available yet.
|
|
536
|
+
|
|
537
|
+
A few memory tools do **not** take a target. `session-compact` acts on the caller's own live session, which belongs to the account the operator is in, so pointing it at a sub-account has no meaning. `graph-prune-denylist-*` and `memory-ingest-extract` are not account-scoped at all (a shared deny-list and a temporary staging step), so a target would mean nothing. These stay on the session's own account.
|
|
538
|
+
|
|
539
|
+
## Filesystem
|
|
540
|
+
|
|
541
|
+
The `filesystem` plugin gives the admin session direct access to the account working directory (`data/accounts/<id>/`) — `SOUL.md`, generated documents, screenshots, arbitrary files. It exists because the native file tools (Read, Write, Edit, and the shell) are locked to the session's own account and cannot take a target. Its six tools are `file-read`, `file-write`, `file-edit`, `file-list`, `file-glob`, and `file-grep`.
|
|
542
|
+
|
|
543
|
+
Omit the target and each tool acts on the session's own account, exactly like the native tools. Pass a sub-account as the target and a house-scoped admin session reads or writes that account's files instead; any other session that names a target is refused. Every path is confined to the resolved account directory — a path that climbs out with `../`, an absolute path outside the account root, or a symlink pointing away is rejected before anything is read or written, so the tools can never reach another account or the platform's own files.
|
|
544
|
+
|
|
545
|
+
## Signals
|
|
546
|
+
|
|
547
|
+
A standing background check reconciles live sessions against the account roles and raises an alarm if any session still holds house authority for an account that is no longer the house account, so a stale grant cannot sit unnoticed. Each cross-account tool call also records its decision — who called, which account it targeted, whether it was allowed, and why — before it touches the graph or disk, and a rejected filesystem path escape is recorded on its own line. In normal operation neither an allowed cross-account call from a non-house session nor a path escape ever appears.
|
|
548
|
+
|
|
519
549
|
---
|
|
520
550
|
# Install Overview
|
|
521
551
|
Source: https://docs.getmaxy.com/install.md
|
|
@@ -1582,6 +1612,12 @@ If you want to write the public summary yourself — for instance, because the a
|
|
|
1582
1612
|
|
|
1583
1613
|
People entries (`:Person`) are deliberately excluded from this dual-summary system. Notes about contacts are private by definition and never get a public-facing form.
|
|
1584
1614
|
|
|
1615
|
+
## Working across accounts
|
|
1616
|
+
|
|
1617
|
+
If you run Maxy as the house account — the operator account that manages sub-accounts — you can point a memory action at one of your sub-accounts instead of your own. Name the account when you ask, for example "search Anneke's account for the roofing quote" or "add this note to the Jones account." Most memory actions honour this: search, read, write, edit, delete, reports, and conversation history. Profile actions honour it too — you can read and set a sub-account's profile (its owner's name, role, timezone, locale, and preferences) from the house account, for example "set the Jones account's name to Jane"; the profile is attributed to that sub-account's owner, so there is no orphan record and no PIN is needed. Setting a sub-account owner's email or telephone from the house account is not available yet. Compacting a conversation always acts on the session you are in, so it cannot be pointed at a sub-account.
|
|
1618
|
+
|
|
1619
|
+
Leave the account out and nothing changes — the action runs on your own account exactly as before. Only the house account can do this. A sub-account session that tries to reach another account is refused, and naming an account that does not exist is refused too. Sub-accounts stay fully isolated from each other.
|
|
1620
|
+
|
|
1585
1621
|
## Privacy
|
|
1586
1622
|
|
|
1587
1623
|
All memory is stored on your local Raspberry Pi. The Neo4j database never leaves your network. Maxy does not sync memory to any cloud service or third party.
|
|
@@ -439,6 +439,8 @@ shasum -a 256 "${ACCOUNT_DIR}/e-sign/<DOC_REF>/base.pdf" | awk '{print $1}' \
|
|
|
439
439
|
|
|
440
440
|
Before persisting, **read every rendered page** of `base.pdf` — full-bleed pages bleed correctly, no footer split across pages, no near-blank trailing page (§ 2 pagination; A4 dimensions are not proof of fit). If the deployed page is behind Cloudflare Access, do this base render in a session that can authenticate to Access — it is a one-time, operator-driven step, unlike dispatch (§ 6), which must never fetch the page at all. A new `DOC_REF` triggers a fresh render; an existing one keeps its persisted base.
|
|
441
441
|
|
|
442
|
+
**The deployed signing-page URL is an explicit hand-back.** This deploy produces one value every downstream caller needs: the signing page's absolute `https://` URL — the canonical host (§ 7: `https://<project>.pages.dev`, or a custom domain mapped to it) joined to the document's slug (§ 1, e.g. `https://<project>.pages.dev/quote-lakeside-7f3a9c.html`). Return it to whatever flow invoked e-sign. **Invariant:** any external document that embeds a "Sign Online" / acceptance link — a quote, a proposal, a cover letter — must carry this exact value in that link, and **must not be finalised, exported, or sent before this deploy has produced it**. A Sign-Online link cannot point at a page that does not yet exist: deploy first, hand back the URL, then the caller injects it and renders. The quotation client-quote path is one such caller and gates on this — `sitedesk-job/skills/quotation/references/quote-generation.md` refuses to export a client quote whose Sign-Online anchor is not the live `https://` URL handed back here.
|
|
443
|
+
|
|
442
444
|
---
|
|
443
445
|
|
|
444
446
|
## 6. Sweep + dispatch
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# House-admin cross-account authority
|
|
2
|
+
|
|
3
|
+
The house account is the operator-owned account that runs the admin assistant. Sub-accounts are isolated: each one's tools read and write only that account's data, which is what keeps tenants separate.
|
|
4
|
+
|
|
5
|
+
By default the admin assistant is scoped the same way — it acts on the house account. Cross-account authority lets a house-scoped admin session act on a named sub-account by passing that account as a target, while every other session (public visitors, a session already scoped to a sub-account, background specialists) is refused. The refusal is fail-closed: a session that is not house-scoped and names a target is denied, never quietly redirected to its own account.
|
|
6
|
+
|
|
7
|
+
The operator wants this for two surfaces: the account graph and the account filesystem. Each is a separate adoption; a coverage ledger tracks them and a background check confirms neither silently regresses.
|
|
8
|
+
|
|
9
|
+
## Memory (graph)
|
|
10
|
+
|
|
11
|
+
The `memory` plugin gives the admin session access to a sub-account's knowledge graph. Its tools whose read or write is scoped solely by the account — search, lookup, write, update, edit, delete, edges, attachments, reports, conversation history, ingest, and graph maintenance — take an optional target. Omit it and the tool acts on the session's own account, exactly as before; pass a sub-account and a house-scoped admin session reads or writes that account's subgraph instead; any other session that names a target is refused.
|
|
12
|
+
|
|
13
|
+
`profile-read`/`update`/`delete` also take a target. They are keyed to a user as well as the account, so cross-account they cannot use the caller's own user (a house operator has no record inside a sub-account); instead they resolve the sub-account's owner — the single admin identity seeded with every account — and act on that owner's profile. A house-scoped session can therefore read or set a sub-account's profile (its owner's name, role, timezone, locale, and preferences); a target with no owner is refused rather than writing a stray record. Setting a sub-account owner's email or telephone this way is not available yet.
|
|
14
|
+
|
|
15
|
+
A few memory tools do **not** take a target. `session-compact` acts on the caller's own live session, which belongs to the account the operator is in, so pointing it at a sub-account has no meaning. `graph-prune-denylist-*` and `memory-ingest-extract` are not account-scoped at all (a shared deny-list and a temporary staging step), so a target would mean nothing. These stay on the session's own account.
|
|
16
|
+
|
|
17
|
+
## Filesystem
|
|
18
|
+
|
|
19
|
+
The `filesystem` plugin gives the admin session direct access to the account working directory (`data/accounts/<id>/`) — `SOUL.md`, generated documents, screenshots, arbitrary files. It exists because the native file tools (Read, Write, Edit, and the shell) are locked to the session's own account and cannot take a target. Its six tools are `file-read`, `file-write`, `file-edit`, `file-list`, `file-glob`, and `file-grep`.
|
|
20
|
+
|
|
21
|
+
Omit the target and each tool acts on the session's own account, exactly like the native tools. Pass a sub-account as the target and a house-scoped admin session reads or writes that account's files instead; any other session that names a target is refused. Every path is confined to the resolved account directory — a path that climbs out with `../`, an absolute path outside the account root, or a symlink pointing away is rejected before anything is read or written, so the tools can never reach another account or the platform's own files.
|
|
22
|
+
|
|
23
|
+
## Signals
|
|
24
|
+
|
|
25
|
+
A standing background check reconciles live sessions against the account roles and raises an alarm if any session still holds house authority for an account that is no longer the house account, so a stale grant cannot sit unnoticed. Each cross-account tool call also records its decision — who called, which account it targeted, whether it was allowed, and why — before it touches the graph or disk, and a rejected filesystem path escape is recorded on its own line. In normal operation neither an allowed cross-account call from a non-house session nor a path escape ever appears.
|
|
@@ -156,6 +156,12 @@ If you want to write the public summary yourself — for instance, because the a
|
|
|
156
156
|
|
|
157
157
|
People entries (`:Person`) are deliberately excluded from this dual-summary system. Notes about contacts are private by definition and never get a public-facing form.
|
|
158
158
|
|
|
159
|
+
## Working across accounts
|
|
160
|
+
|
|
161
|
+
If you run {{productName}} as the house account — the operator account that manages sub-accounts — you can point a memory action at one of your sub-accounts instead of your own. Name the account when you ask, for example "search Anneke's account for the roofing quote" or "add this note to the Jones account." Most memory actions honour this: search, read, write, edit, delete, reports, and conversation history. Profile actions honour it too — you can read and set a sub-account's profile (its owner's name, role, timezone, locale, and preferences) from the house account, for example "set the Jones account's name to Jane"; the profile is attributed to that sub-account's owner, so there is no orphan record and no PIN is needed. Setting a sub-account owner's email or telephone from the house account is not available yet. Compacting a conversation always acts on the session you are in, so it cannot be pointed at a sub-account.
|
|
162
|
+
|
|
163
|
+
Leave the account out and nothing changes — the action runs on your own account exactly as before. Only the house account can do this. A sub-account session that tries to reach another account is refused, and naming an account that does not exist is refused too. Sub-accounts stay fully isolated from each other.
|
|
164
|
+
|
|
159
165
|
## Privacy
|
|
160
166
|
|
|
161
167
|
All memory is stored on your local Raspberry Pi. The Neo4j database never leaves your network. {{productName}} does not sync memory to any cloud service or third party.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "filesystem",
|
|
3
|
+
"description": "Account-scoped file access. Read, write, edit, list, glob, and grep files under the session's own account directory; the house admin may reach any sub-account's files by naming targetAccountId, gated fail-closed and confined to that account's directory.",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Rubytech LLC"
|
|
7
|
+
},
|
|
8
|
+
"mcpServers": {
|
|
9
|
+
"filesystem": {
|
|
10
|
+
"type": "stdio",
|
|
11
|
+
"command": "node",
|
|
12
|
+
"args": [
|
|
13
|
+
"${CLAUDE_PLUGIN_ROOT}/lib/mcp-spawn-tee/index.js",
|
|
14
|
+
"${CLAUDE_PLUGIN_ROOT}/mcp/dist/index.js"
|
|
15
|
+
],
|
|
16
|
+
"env": {
|
|
17
|
+
"MCP_SPAWN_TEE_NAME": "filesystem"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: filesystem
|
|
3
|
+
description: "Account-scoped file access. Read, write, edit, list, glob, and grep files under the session's own account directory; the house admin may reach any sub-account's files by naming targetAccountId, gated fail-closed and confined to that account's directory."
|
|
4
|
+
tools:
|
|
5
|
+
- name: file-read
|
|
6
|
+
publicAllowlist: false
|
|
7
|
+
adminAllowlist: true
|
|
8
|
+
- name: file-write
|
|
9
|
+
publicAllowlist: false
|
|
10
|
+
adminAllowlist: true
|
|
11
|
+
- name: file-edit
|
|
12
|
+
publicAllowlist: false
|
|
13
|
+
adminAllowlist: true
|
|
14
|
+
- name: file-list
|
|
15
|
+
publicAllowlist: false
|
|
16
|
+
adminAllowlist: true
|
|
17
|
+
- name: file-glob
|
|
18
|
+
publicAllowlist: false
|
|
19
|
+
adminAllowlist: true
|
|
20
|
+
- name: file-grep
|
|
21
|
+
publicAllowlist: false
|
|
22
|
+
adminAllowlist: true
|
|
23
|
+
metadata: {"platform":{"always":false,"embed":[],"pluginKey":"filesystem"}}
|
|
24
|
+
mcp:
|
|
25
|
+
command: node
|
|
26
|
+
args:
|
|
27
|
+
- ${PLATFORM_ROOT}/lib/mcp-spawn-tee/dist/index.js
|
|
28
|
+
- ${PLATFORM_ROOT}/plugins/filesystem/mcp/dist/index.js
|
|
29
|
+
env:
|
|
30
|
+
MCP_SPAWN_TEE_NAME: filesystem
|
|
31
|
+
LOG_DIR: ${LOG_DIR}
|
|
32
|
+
PLATFORM_ROOT: ${PLATFORM_ROOT}
|
|
33
|
+
ACCOUNT_ID: ${ACCOUNT_ID}
|
|
34
|
+
HOUSE_ADMIN_SCOPE: ${HOUSE_ADMIN_SCOPE}
|
|
35
|
+
SESSION_ID: ${SESSION_ID}
|
|
36
|
+
AGENT_SLUG: ${AGENT_SLUG}
|
|
37
|
+
mcp-manifest: auto
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
# Filesystem
|
|
41
|
+
|
|
42
|
+
Direct access to the account working directory (`data/accounts/<id>/`) — `SOUL.md`, generated documents, screenshots, and arbitrary files. These tools complement the native Read/Write/Edit tools, which are locked to your own account; only these tools can reach another account's files, and only for the house admin.
|
|
43
|
+
|
|
44
|
+
## Own account
|
|
45
|
+
|
|
46
|
+
Omit `targetAccountId`. Every tool takes a `path` relative to your account directory. `file-read`, `file-write`, `file-edit`, `file-list`, `file-glob`, and `file-grep` behave like their native counterparts but always resolve inside your account root.
|
|
47
|
+
|
|
48
|
+
## Cross-account (house admin only)
|
|
49
|
+
|
|
50
|
+
Pass `targetAccountId` to operate on a sub-account's directory. This is honoured only for a house-scoped admin session. A sub-account, specialist, or public session that passes `targetAccountId` is refused with `cross-account-denied`; an unknown account id is refused with `cross-account-invalid-target`. Use this to inspect or repair a client account's files from the house session without switching accounts.
|
|
51
|
+
|
|
52
|
+
## Boundaries
|
|
53
|
+
|
|
54
|
+
Every path is confined to the resolved account directory. A path that escapes it — `../`, an absolute path outside the account root, or a symlink pointing out — is rejected before any filesystem operation. Reads and greps skip files over 5 MB; grep skips binary files.
|
|
55
|
+
|
|
56
|
+
## Tools
|
|
57
|
+
|
|
58
|
+
- `file-read` — Read a UTF-8 file. Args: `path`, optional `targetAccountId`.
|
|
59
|
+
- `file-write` — Create or overwrite a UTF-8 file, making parent directories. Args: `path`, `content`, optional `targetAccountId`.
|
|
60
|
+
- `file-edit` — Replace one unique occurrence of `oldString` with `newString`. Fails if `oldString` is missing or not unique. Args: `path`, `oldString`, `newString`, optional `targetAccountId`.
|
|
61
|
+
- `file-list` — List a directory's entries with their type. Args: optional `path` (defaults to the account root), optional `targetAccountId`.
|
|
62
|
+
- `file-glob` — List files matching a glob (`**`, `*`, `?`). Args: `pattern`, optional `targetAccountId`.
|
|
63
|
+
- `file-grep` — Search file contents for a JavaScript regular expression, returning `file:line:preview`. Args: `pattern`, optional `path`, optional `targetAccountId`.
|