@rubytech/create-maxy-code 0.1.443 → 0.1.445
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/package.json +1 -1
- package/payload/platform/lib/graph-style/dist/index.d.ts +4 -0
- package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-style/dist/index.js +37 -1
- package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-style/src/__tests__/parity.test.ts +39 -0
- package/payload/platform/lib/graph-style/src/index.ts +41 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/audit.test.d.ts +2 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/audit.test.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/audit.test.js +46 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/audit.test.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/authorize.test.d.ts +2 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/authorize.test.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/authorize.test.js +27 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/authorize.test.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.d.ts +2 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js +51 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/house-credential.test.d.ts +2 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/house-credential.test.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/house-credential.test.js +39 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/house-credential.test.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/registry.test.d.ts +2 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/registry.test.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/registry.test.js +59 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/registry.test.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/remediate-run.test.d.ts +2 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/remediate-run.test.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/remediate-run.test.js +77 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/remediate-run.test.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/remediate.test.d.ts +2 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/remediate.test.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/remediate.test.js +23 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/remediate.test.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/audit.d.ts +19 -0
- package/payload/platform/lib/storage-broker/dist/audit.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/audit.js +41 -0
- package/payload/platform/lib/storage-broker/dist/audit.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/authorize.d.ts +11 -0
- package/payload/platform/lib/storage-broker/dist/authorize.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/authorize.js +17 -0
- package/payload/platform/lib/storage-broker/dist/authorize.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/bin/remediate-run.d.ts +25 -0
- package/payload/platform/lib/storage-broker/dist/bin/remediate-run.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/bin/remediate-run.js +131 -0
- package/payload/platform/lib/storage-broker/dist/bin/remediate-run.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts +20 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.js +53 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/house-credential.d.ts +6 -0
- package/payload/platform/lib/storage-broker/dist/house-credential.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/house-credential.js +33 -0
- package/payload/platform/lib/storage-broker/dist/house-credential.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/index.d.ts +7 -0
- package/payload/platform/lib/storage-broker/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/index.js +30 -0
- package/payload/platform/lib/storage-broker/dist/index.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/registry.d.ts +18 -0
- package/payload/platform/lib/storage-broker/dist/registry.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/registry.js +45 -0
- package/payload/platform/lib/storage-broker/dist/registry.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/remediate.d.ts +7 -0
- package/payload/platform/lib/storage-broker/dist/remediate.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/remediate.js +25 -0
- package/payload/platform/lib/storage-broker/dist/remediate.js.map +1 -0
- package/payload/platform/lib/storage-broker/src/__tests__/audit.test.ts +49 -0
- package/payload/platform/lib/storage-broker/src/__tests__/authorize.test.ts +28 -0
- package/payload/platform/lib/storage-broker/src/__tests__/cf-exec.test.ts +52 -0
- package/payload/platform/lib/storage-broker/src/__tests__/house-credential.test.ts +35 -0
- package/payload/platform/lib/storage-broker/src/__tests__/registry.test.ts +64 -0
- package/payload/platform/lib/storage-broker/src/__tests__/remediate-run.test.ts +79 -0
- package/payload/platform/lib/storage-broker/src/__tests__/remediate.test.ts +21 -0
- package/payload/platform/lib/storage-broker/src/audit.ts +55 -0
- package/payload/platform/lib/storage-broker/src/authorize.ts +22 -0
- package/payload/platform/lib/storage-broker/src/bin/remediate-run.ts +156 -0
- package/payload/platform/lib/storage-broker/src/cf-exec.ts +73 -0
- package/payload/platform/lib/storage-broker/src/house-credential.ts +32 -0
- package/payload/platform/lib/storage-broker/src/index.ts +14 -0
- package/payload/platform/lib/storage-broker/src/registry.ts +85 -0
- package/payload/platform/lib/storage-broker/src/remediate.ts +31 -0
- package/payload/platform/lib/storage-broker/tsconfig.json +8 -0
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +1 -1
- package/payload/platform/plugins/admin/hooks/lib/maxy-mcp-plugins.txt +1 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js +40 -24
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.d.ts +7 -7
- package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.d.ts.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.js +7 -7
- package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.js.map +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +8 -4
- package/payload/platform/plugins/cloudflare/PLUGIN.md +4 -1
- package/payload/platform/plugins/cloudflare/bin/__tests__/cf-schema-check.test.sh +128 -0
- package/payload/platform/plugins/cloudflare/bin/__tests__/cf-store-name.test.sh +77 -0
- package/payload/platform/plugins/cloudflare/bin/cf-schema-check.sh +60 -0
- package/payload/platform/plugins/cloudflare/bin/cf-store-name.sh +37 -0
- package/payload/platform/plugins/cloudflare/bin/remediate-storage-isolation.sh +36 -0
- package/payload/platform/plugins/cloudflare/references/api.md +4 -0
- package/payload/platform/plugins/cloudflare/references/d1-data-capture.md +58 -0
- package/payload/platform/plugins/cloudflare/references/r2-object-storage.md +39 -0
- package/payload/platform/plugins/cloudflare/skills/cloudflare/SKILL.md +2 -0
- package/payload/platform/plugins/cloudflare/skills/site-deploy/SKILL.md +2 -0
- package/payload/platform/plugins/docs/references/admin-session.md +2 -0
- package/payload/platform/plugins/docs/references/admin-ui.md +4 -0
- package/payload/platform/plugins/docs/references/cross-account-authority.md +3 -3
- package/payload/platform/plugins/outlook/references/auth.md +8 -0
- package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +3 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.js +11 -4
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +15 -10
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
- package/payload/platform/plugins/storage-broker/.claude-plugin/plugin.json +21 -0
- package/payload/platform/plugins/storage-broker/PLUGIN.md +48 -0
- package/payload/platform/plugins/storage-broker/lib/mcp-spawn-tee/index.js +193 -0
- package/payload/platform/plugins/storage-broker/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/__tests__/caller-scope-headers.test.d.ts +2 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/__tests__/caller-scope-headers.test.d.ts.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/__tests__/caller-scope-headers.test.js +16 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/__tests__/caller-scope-headers.test.js.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/call-api.d.ts +8 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/call-api.d.ts.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/call-api.js +39 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/call-api.js.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/caller-scope-headers.d.ts +7 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/caller-scope-headers.d.ts.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/caller-scope-headers.js +16 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/caller-scope-headers.js.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/index.d.ts +8 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/index.js +98 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/package.json +10 -0
- package/payload/platform/plugins/storage-broker/mcp/vitest.config.ts +4 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/format-attachment-suffix.test.d.ts +2 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/format-attachment-suffix.test.d.ts.map +1 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/format-attachment-suffix.test.js +44 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/format-attachment-suffix.test.js.map +1 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/format.d.ts +7 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/format.d.ts.map +1 -1
- package/payload/platform/plugins/whatsapp/mcp/dist/format.js +27 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/format.js.map +1 -1
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js +6 -19
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +3 -1
- package/payload/platform/plugins/work/mcp/dist/tools/work-create.d.ts.map +1 -1
- package/payload/platform/plugins/work/mcp/dist/tools/work-create.js +10 -7
- package/payload/platform/plugins/work/mcp/dist/tools/work-create.js.map +1 -1
- 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 +6 -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/http-server.d.ts +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 +99 -8
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +0 -13
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-path.d.ts +8 -0
- package/payload/platform/services/claude-session-manager/dist/jsonl-path.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-path.js +11 -0
- package/payload/platform/services/claude-session-manager/dist/jsonl-path.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/passive-admin-assignment.d.ts +20 -0
- package/payload/platform/services/claude-session-manager/dist/passive-admin-assignment.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/passive-admin-assignment.js +66 -0
- package/payload/platform/services/claude-session-manager/dist/passive-admin-assignment.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/passive-spawn-tools-tail.d.ts +19 -0
- package/payload/platform/services/claude-session-manager/dist/passive-spawn-tools-tail.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/passive-spawn-tools-tail.js +50 -0
- package/payload/platform/services/claude-session-manager/dist/passive-spawn-tools-tail.js.map +1 -0
- 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 +14 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/server/chunk-4W6IEUFE.js +175 -0
- package/payload/server/public/assets/{AdminLoginScreens-BJVwtpLH.js → AdminLoginScreens-D9L-CuWi.js} +1 -1
- package/payload/server/public/assets/{AdminShell-CMu8_Xwn.js → AdminShell-DSQrvZXq.js} +1 -1
- package/payload/server/public/assets/{Checkbox-CzD0ORqA.js → Checkbox-CFdt8ljo.js} +1 -1
- package/payload/server/public/assets/{admin-D0yk2LMq.js → admin-Ch4n-G0j.js} +1 -1
- package/payload/server/public/assets/{browser-CvfbgznO.js → browser-B2AIxiuk.js} +1 -1
- package/payload/server/public/assets/{calendar-fw4WethJ.js → calendar-BvItH3B0.js} +1 -1
- package/payload/server/public/assets/chat-eQFMaX9y.js +1 -0
- package/payload/server/public/assets/chevron-left-D2AnCEyE.js +1 -0
- package/payload/server/public/assets/data-BzOmbHoe.js +1 -0
- package/payload/server/public/assets/{file-download-Px4s0Bhp.js → file-download-CK9j_Jx2.js} +1 -1
- package/payload/server/public/assets/graph-DrE7KupQ.js +51 -0
- package/payload/server/public/assets/graph-labels-CwEtvIRV.js +1 -0
- package/payload/server/public/assets/{maximize-2-BwphzI9S.js → maximize-2-CuzEom3K.js} +1 -1
- package/payload/server/public/assets/{operator-zcIfeB14.js → operator-DlboYPPu.js} +1 -1
- package/payload/server/public/assets/page-BuWH5gNK.js +1 -0
- package/payload/server/public/assets/page-Co9E8U5x.js +32 -0
- package/payload/server/public/assets/{public-C0ehZCdE.js → public-DtFpPMTG.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-C7BqkMec.js → rotate-ccw-Cqp4oqHV.js} +1 -1
- package/payload/server/public/assets/{tasks-DlgiCafL.js → tasks-bQKOhMga.js} +1 -1
- package/payload/server/public/assets/{time-entry-format-CyNYVDkq.js → time-entry-format-BIL4ZaDK.js} +1 -1
- package/payload/server/public/assets/useCopyFeedback-rB8QuqyH.js +1 -0
- package/payload/server/public/assets/{useSubAccountSwitcher-1izQvPiD.css → useSubAccountSwitcher-CVHKTSst.css} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-XgpWvXpk.js → useVoiceRecorder-Ctny8qWx.js} +1 -1
- package/payload/server/public/browser.html +5 -5
- package/payload/server/public/calendar.html +6 -6
- package/payload/server/public/chat.html +12 -12
- package/payload/server/public/data.html +10 -10
- package/payload/server/public/graph.html +11 -11
- package/payload/server/public/index.html +13 -13
- package/payload/server/public/operator.html +14 -14
- package/payload/server/public/public.html +12 -12
- package/payload/server/public/tasks.html +5 -5
- package/payload/server/server.js +1082 -841
- package/payload/server/src-BQ2NYMOT.js +24 -0
- package/payload/server/public/assets/chat-DhD-NES_.js +0 -1
- package/payload/server/public/assets/chevron-left-B3xSeEDR.js +0 -1
- package/payload/server/public/assets/data-CUnDzDox.js +0 -1
- package/payload/server/public/assets/graph-DbT8nA6R.js +0 -51
- package/payload/server/public/assets/graph-labels-C1YIyLiA.js +0 -1
- package/payload/server/public/assets/page-6fCnIb45.js +0 -32
- package/payload/server/public/assets/page-CfQFAJia.js +0 -1
- package/payload/server/public/assets/useCopyFeedback-ByGUMN94.js +0 -1
- /package/payload/server/public/assets/{useSubAccountSwitcher-s6WlRn52.js → useSubAccountSwitcher-CHEt_m6q.js} +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// The only place the account-wide Cloudflare credential is used to touch
|
|
2
|
+
// Cloudflare. Every method injects the house token into the child env and shells
|
|
3
|
+
// wrangler. `run` is injectable so the wiring is unit-testable without wrangler.
|
|
4
|
+
|
|
5
|
+
import { execFile } from "node:child_process";
|
|
6
|
+
import type { HouseCredential } from "./house-credential.js";
|
|
7
|
+
|
|
8
|
+
export type RunFn = (
|
|
9
|
+
cmd: string,
|
|
10
|
+
args: string[],
|
|
11
|
+
env: Record<string, string>,
|
|
12
|
+
) => Promise<{ stdout: string }>;
|
|
13
|
+
|
|
14
|
+
export interface CfExec {
|
|
15
|
+
d1List(): Promise<{ name: string; uuid: string }[]>;
|
|
16
|
+
d1Create(name: string): Promise<{ uuid: string }>;
|
|
17
|
+
d1Query(name: string, sql: string): Promise<unknown>;
|
|
18
|
+
r2List(): Promise<{ name: string }[]>;
|
|
19
|
+
r2Create(name: string): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const defaultRun: RunFn = (cmd, args, env) =>
|
|
23
|
+
new Promise((resolve, reject) => {
|
|
24
|
+
execFile(cmd, args, { env }, (err, stdout, stderr) => {
|
|
25
|
+
if (err) {
|
|
26
|
+
reject(new Error(`${cmd} ${args.join(" ")} failed: ${err.message}\n${stderr}`));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
resolve({ stdout: stdout.toString() });
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
function parseJson(raw: string, context: string): unknown {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(raw);
|
|
36
|
+
} catch {
|
|
37
|
+
throw new Error(`storage-broker: ${context} returned non-JSON output: ${raw}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function makeCfExec(cred: HouseCredential, run: RunFn = defaultRun): CfExec {
|
|
42
|
+
const env = {
|
|
43
|
+
...process.env,
|
|
44
|
+
CLOUDFLARE_API_TOKEN: cred.apiToken,
|
|
45
|
+
CLOUDFLARE_ACCOUNT_ID: cred.accountId,
|
|
46
|
+
} as Record<string, string>;
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
async d1List() {
|
|
50
|
+
const { stdout } = await run("wrangler", ["d1", "list", "--json"], env);
|
|
51
|
+
return parseJson(stdout, "d1 list") as { name: string; uuid: string }[];
|
|
52
|
+
},
|
|
53
|
+
async d1Create(name) {
|
|
54
|
+
const { stdout } = await run("wrangler", ["d1", "create", name, "--json"], env);
|
|
55
|
+
return parseJson(stdout, "d1 create") as { uuid: string };
|
|
56
|
+
},
|
|
57
|
+
async d1Query(name, sql) {
|
|
58
|
+
const { stdout } = await run(
|
|
59
|
+
"wrangler",
|
|
60
|
+
["d1", "execute", name, "--remote", "--json", "--command", sql],
|
|
61
|
+
env,
|
|
62
|
+
);
|
|
63
|
+
return parseJson(stdout, "d1 execute");
|
|
64
|
+
},
|
|
65
|
+
async r2List() {
|
|
66
|
+
const { stdout } = await run("wrangler", ["r2", "bucket", "list", "--json"], env);
|
|
67
|
+
return parseJson(stdout, "r2 bucket list") as { name: string }[];
|
|
68
|
+
},
|
|
69
|
+
async r2Create(name) {
|
|
70
|
+
await run("wrangler", ["r2", "bucket", "create", name], env);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Reads the single account-wide Cloudflare credential from the house-only
|
|
2
|
+
// config directory. This file is the one place the account-wide capability
|
|
3
|
+
// lives; sub-account spawns never source it. Parses KEY=value lines without
|
|
4
|
+
// polluting process.env (same discipline as reconcile-bookings' readEnvFile).
|
|
5
|
+
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
|
|
9
|
+
export interface HouseCredential {
|
|
10
|
+
apiToken: string;
|
|
11
|
+
accountId: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function readHouseCredential(platformRoot: string): HouseCredential {
|
|
15
|
+
const path = join(platformRoot, "config", "cloudflare-house.env");
|
|
16
|
+
let raw: string;
|
|
17
|
+
try {
|
|
18
|
+
raw = readFileSync(path, "utf8");
|
|
19
|
+
} catch {
|
|
20
|
+
throw new Error(`storage-broker: house credential not found at ${path}`);
|
|
21
|
+
}
|
|
22
|
+
const env: Record<string, string> = {};
|
|
23
|
+
for (const line of raw.split("\n")) {
|
|
24
|
+
const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
25
|
+
if (m) env[m[1]] = m[2].trim();
|
|
26
|
+
}
|
|
27
|
+
const apiToken = env.CLOUDFLARE_API_TOKEN;
|
|
28
|
+
const accountId = env.CLOUDFLARE_ACCOUNT_ID;
|
|
29
|
+
if (!apiToken) throw new Error(`storage-broker: CLOUDFLARE_API_TOKEN missing in ${path}`);
|
|
30
|
+
if (!accountId) throw new Error(`storage-broker: CLOUDFLARE_ACCOUNT_ID missing in ${path}`);
|
|
31
|
+
return { apiToken, accountId };
|
|
32
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// storage-broker: the per-account Cloudflare D1/R2 isolation core.
|
|
2
|
+
//
|
|
3
|
+
// The lib is pure and dependency-free at runtime: it opens no Neo4j connection
|
|
4
|
+
// and shells no process on its own. Consumers inject a SessionLike (the ui
|
|
5
|
+
// route uses neo4j-store's getSession; the remediation entry opens its own
|
|
6
|
+
// driver) and, for cf-exec, a RunFn. That keeps every unit testable in
|
|
7
|
+
// isolation and the isolation logic auditable in one place.
|
|
8
|
+
|
|
9
|
+
export * from "./authorize.js";
|
|
10
|
+
export * from "./registry.js";
|
|
11
|
+
export * from "./house-credential.js";
|
|
12
|
+
export * from "./cf-exec.js";
|
|
13
|
+
export * from "./audit.js";
|
|
14
|
+
export * from "./remediate.js";
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// The ownership registry: which account owns which Cloudflare storage resource.
|
|
2
|
+
//
|
|
3
|
+
// Written with raw cypher MERGE stamped with system provenance, mirroring the
|
|
4
|
+
// house-level seedAccountGraphRoot path in the admin plugin. It deliberately
|
|
5
|
+
// does NOT go through graph-write's writeNodeWithEdges: that primitive's
|
|
6
|
+
// accountId floor rejects a write whose props.accountId differs from the
|
|
7
|
+
// writer process's ACCOUNT_ID, and the broker runs house-level, writing nodes
|
|
8
|
+
// owned by many different sub-accounts. The owning accountId here is always the
|
|
9
|
+
// trusted header value the service resolved, never an agent argument.
|
|
10
|
+
|
|
11
|
+
// A minimal shape of a neo4j-driver Session, so this lib carries no runtime or
|
|
12
|
+
// type dependency on neo4j-driver (mirrors the SeedSessionLike pattern used in
|
|
13
|
+
// platform/plugins/admin/mcp/src/tools/account-lifecycle.ts).
|
|
14
|
+
// Uses PromiseLike (not Promise) and get(): any so that a real neo4j-driver
|
|
15
|
+
// Session — whose run() returns a Result (a PromiseLike, and whose records are
|
|
16
|
+
// driver Records) — is structurally assignable under both v5 and v6 without a
|
|
17
|
+
// cast at the call site.
|
|
18
|
+
export interface SessionLike {
|
|
19
|
+
run(
|
|
20
|
+
cypher: string,
|
|
21
|
+
params?: Record<string, unknown>,
|
|
22
|
+
): PromiseLike<{ records: Array<{ get(key: string): any }> }>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type ResourceKind = "d1" | "r2";
|
|
26
|
+
|
|
27
|
+
export interface StorageResource {
|
|
28
|
+
accountId: string;
|
|
29
|
+
kind: ResourceKind;
|
|
30
|
+
name: string;
|
|
31
|
+
cfResourceId: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function registerResource(
|
|
35
|
+
session: SessionLike,
|
|
36
|
+
r: StorageResource,
|
|
37
|
+
): Promise<void> {
|
|
38
|
+
// MERGE on (kind, name) — the ownership key. On a shared Cloudflare account a
|
|
39
|
+
// storage name maps to exactly one owner, so the write key must match the
|
|
40
|
+
// resolveOwner lookup key; keying on accountId too would let two accounts hold
|
|
41
|
+
// a node for the same name and make ownership ambiguous. accountId is set only
|
|
42
|
+
// ON CREATE and never overwritten ON MATCH, so a register can never silently
|
|
43
|
+
// reassign an existing resource to a different account.
|
|
44
|
+
await session.run(
|
|
45
|
+
`MERGE (r:StorageResource {kind: $kind, name: $name})
|
|
46
|
+
ON CREATE SET r.accountId = $accountId,
|
|
47
|
+
r.createdByAgent = 'system',
|
|
48
|
+
r.createdBySource = 'storage-broker',
|
|
49
|
+
r.cfResourceId = $cfResourceId
|
|
50
|
+
ON MATCH SET r.cfResourceId = $cfResourceId`,
|
|
51
|
+
{ accountId: r.accountId, kind: r.kind, name: r.name, cfResourceId: r.cfResourceId },
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function resolveOwner(
|
|
56
|
+
session: SessionLike,
|
|
57
|
+
kind: ResourceKind,
|
|
58
|
+
name: string,
|
|
59
|
+
): Promise<string | null> {
|
|
60
|
+
const res = await session.run(
|
|
61
|
+
`MATCH (r:StorageResource {kind: $kind, name: $name})
|
|
62
|
+
RETURN r.accountId AS accountId LIMIT 1`,
|
|
63
|
+
{ kind, name },
|
|
64
|
+
);
|
|
65
|
+
return res.records.length ? (res.records[0].get("accountId") as string) : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function listResources(
|
|
69
|
+
session: SessionLike,
|
|
70
|
+
accountId: string,
|
|
71
|
+
kind: ResourceKind,
|
|
72
|
+
): Promise<StorageResource[]> {
|
|
73
|
+
const res = await session.run(
|
|
74
|
+
`MATCH (r:StorageResource {accountId: $accountId, kind: $kind})
|
|
75
|
+
RETURN r.name AS name, r.cfResourceId AS cfResourceId
|
|
76
|
+
ORDER BY r.name`,
|
|
77
|
+
{ accountId, kind },
|
|
78
|
+
);
|
|
79
|
+
return res.records.map((rec) => ({
|
|
80
|
+
accountId,
|
|
81
|
+
kind,
|
|
82
|
+
name: rec.get("name") as string,
|
|
83
|
+
cfResourceId: rec.get("cfResourceId") as string,
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// The testable core of the operator-run remediation. The mapping from existing
|
|
2
|
+
// resource name to owning account is operator-authored, and validation fails
|
|
3
|
+
// closed on any resource absent from it: ownership is a recorded fact, never a
|
|
4
|
+
// name-prefix guess. Token stripping removes every account-wide credential from
|
|
5
|
+
// a sub-account secrets file while preserving everything else verbatim.
|
|
6
|
+
|
|
7
|
+
export interface MappingResult {
|
|
8
|
+
ok: boolean;
|
|
9
|
+
missing: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Account-wide credentials stripped from a sub-account's cloudflare.env.
|
|
13
|
+
// CLOUDFLARE_ACCOUNT_ID is intentionally kept: it is an identifier, not a
|
|
14
|
+
// credential, and does not grant access on its own.
|
|
15
|
+
const STRIP_KEYS = ["CLOUDFLARE_API_TOKEN", "CF_PAGES_D1_TOKEN", "CF_PAGES_TOKEN"];
|
|
16
|
+
|
|
17
|
+
export function validateMapping(
|
|
18
|
+
enumerated: string[],
|
|
19
|
+
mapping: Record<string, string>,
|
|
20
|
+
): MappingResult {
|
|
21
|
+
const missing = enumerated.filter((name) => !(name in mapping));
|
|
22
|
+
return { ok: missing.length === 0, missing };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function stripAccountWideTokens(contents: string): string {
|
|
26
|
+
const kept = contents.split("\n").filter((line) => {
|
|
27
|
+
const key = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)=/)?.[1];
|
|
28
|
+
return !(key && STRIP_KEYS.includes(key));
|
|
29
|
+
});
|
|
30
|
+
return kept.join("\n");
|
|
31
|
+
}
|
|
@@ -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/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/active-rules/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 && tsc -p services/telegram-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/active-rules/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/active-rules/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 lib/storage-broker/tsconfig.json && tsc -p services/claude-session-manager/tsconfig.json && tsc -p services/whatsapp-channel/tsconfig.json && tsc -p services/webchat-channel/tsconfig.json && tsc -p services/telegram-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/active-rules/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 lib/storage-broker/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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "admin",
|
|
3
|
-
"description": "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), admin-hostname (the admin-host twin of public-hostname — resolves the host that serves /calendar and the rest of the admin shell behind sign-in from the same ingress + alias + operator files, so agents point the operator at the admin /calendar URL instead of the public host that 404s it), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, capabilities-here (deterministic install-introspection — returns enabledPlugins, tier, models, installed platform + premium specialists, registered Claude Code agent types, and the brand block as a single JSON object; the canonical tool for grounding any claim about what is enabled on the current account), account-update, the multi-account managed-service lifecycle tools (account_list, account_create, account_delete, account_purge, account_channel_routing, account_channel_routing_set — one Rubytech operator hosting N client accounts on one house install; create/delete/purge are operator-only and purge is two-step with a typed confirmation; account_create requires
|
|
3
|
+
"description": "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), admin-hostname (the admin-host twin of public-hostname — resolves the host that serves /calendar and the rest of the admin shell behind sign-in from the same ingress + alias + operator files, so agents point the operator at the admin /calendar URL instead of the public host that 404s it), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, capabilities-here (deterministic install-introspection — returns enabledPlugins, tier, models, installed platform + premium specialists, registered Claude Code agent types, and the brand block as a single JSON object; the canonical tool for grounding any claim about what is enabled on the current account), account-update, the multi-account managed-service lifecycle tools (account_list, account_create, account_delete, account_purge, account_channel_routing, account_channel_routing_set — one Rubytech operator hosting N client accounts on one house install; create/delete/purge are operator-only and purge is two-step with a typed confirmation; account_create requires the client business owner's real name (ownerName), refusing a blank or the placeholder \"Owner\" and seeding it onto the sub-account's owner AdminUser and owner Person, while a managing house admin (its userId) is optional — when supplied it is validated as a live house admin and recorded as the sub-account's managingAdminUserId, and when omitted the sub-account is unmanaged and its passive-intake tasks route to the install's primary admin; account_reassign_admin repoints a client sub-account's managing admin to another live house admin (the repair path for a dangling managingAdminUserId, or to record one on an unmanaged account); account_roster is the canonical per-account identity view — one row per sub-account giving business name, role, the managing house admin resolved to a name with status (assigned/absent/dangling), the business owner from the graph with status (named/nameless), and the WhatsApp account-manager binding if any, each field carrying what it means and does not (managing admin is the responsible house team member not the owner; account manager is WhatsApp routing only with no admin rights), so the agent never stitches admin-list + account.json + whatsapp-config; account_set_owner_name names (or corrects) a client sub-account's business owner by writing the owned Person — the canonical name source — refusing a blank or the placeholder \"Owner\", the guarded repair for a nameless owner (the standing identity-audit op=nameless-owned-person detector); account_channel_routing_set repoints the install's single WhatsApp/Telegram inbound stream to a different sub-account), admin-add (refuses on a client sub-account, where admin access is already install-wide so a per-operator seat grants nothing — the sub-account's one identity, its business owner, is set at account creation; the house account, where operators hold PIN logins and admins[] rows, is unchanged), admin-remove (refuses to remove a house admin who still manages one or more client sub-accounts, naming them so the operator reassigns each with account_reassign_admin first — never silently orphans them), admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), skill-search (free-text intent → skill resolver: ranks every plugin skill's SKILL.md name+description against a query and returns the top owners with their canonical skills/<slug>/ path, so intent→skill mapping survives the available-skills menu leaving context after a compaction), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, wifi, and action-approval tools (action-pending, action-approve, action-reject, action-edit) for managing the Maxy platform.",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Rubytech LLC"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: admin
|
|
3
3
|
surface: platform
|
|
4
|
-
description: "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), admin-hostname (the admin-host twin of public-hostname — resolves the host that serves /calendar and the rest of the admin shell behind sign-in from the same ingress + alias + operator files, so agents point the operator at the admin /calendar URL instead of the public host that 404s it), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, capabilities-here (deterministic install-introspection — returns enabledPlugins, tier, models, installed platform + premium specialists, registered Claude Code agent types, and the brand block as a single JSON object; the canonical tool for grounding any claim about what is enabled on the current account), account-update, the multi-account managed-service lifecycle tools (account_list, account_create, account_delete, account_purge, account_channel_routing, account_channel_routing_set — one Rubytech operator hosting N client accounts on one house install; create/delete/purge are operator-only and purge is two-step with a typed confirmation; account_create requires
|
|
4
|
+
description: "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), admin-hostname (the admin-host twin of public-hostname — resolves the host that serves /calendar and the rest of the admin shell behind sign-in from the same ingress + alias + operator files, so agents point the operator at the admin /calendar URL instead of the public host that 404s it), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, capabilities-here (deterministic install-introspection — returns enabledPlugins, tier, models, installed platform + premium specialists, registered Claude Code agent types, and the brand block as a single JSON object; the canonical tool for grounding any claim about what is enabled on the current account), account-update, the multi-account managed-service lifecycle tools (account_list, account_create, account_delete, account_purge, account_channel_routing, account_channel_routing_set — one Rubytech operator hosting N client accounts on one house install; create/delete/purge are operator-only and purge is two-step with a typed confirmation; account_create requires the client business owner's real name (ownerName), refusing a blank or the placeholder "Owner" and seeding it onto the sub-account's owner AdminUser and owner Person, while a managing house admin (its userId) is optional — when supplied it is validated as a live house admin and recorded as the sub-account's managingAdminUserId, and when omitted the sub-account is unmanaged and its passive-intake tasks route to the install's primary admin; account_reassign_admin repoints a client sub-account's managing admin to another live house admin (the repair path for a dangling managingAdminUserId, or to record one on an unmanaged account); account_roster is the canonical per-account identity view — one row per sub-account giving business name, role, the managing house admin resolved to a name with status (assigned/absent/dangling), the business owner from the graph with status (named/nameless), and the WhatsApp account-manager binding if any, each field carrying what it means and does not (managing admin is the responsible house team member not the owner; account manager is WhatsApp routing only with no admin rights), so the agent never stitches admin-list + account.json + whatsapp-config; account_set_owner_name names (or corrects) a client sub-account's business owner by writing the owned Person — the canonical name source — refusing a blank or the placeholder "Owner", the guarded repair for a nameless owner (the standing identity-audit op=nameless-owned-person detector); account_channel_routing_set repoints the install's single WhatsApp/Telegram inbound stream to a different sub-account), admin-add (refuses on a client sub-account, where admin access is already install-wide so a per-operator seat grants nothing — the sub-account's one identity, its business owner, is set at account creation; the house account, where operators hold PIN logins and admins[] rows, is unchanged), admin-remove (refuses to remove a house admin who still manages one or more client sub-accounts, naming them so the operator reassigns each with account_reassign_admin first — never silently orphans them), admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), skill-search (free-text intent → skill resolver: ranks every plugin skill's SKILL.md name+description against a query and returns the top owners with their canonical skills/<slug>/ path, so intent→skill mapping survives the available-skills menu leaving context after a compaction), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, wifi, and action-approval tools (action-pending, action-approve, action-reject, action-edit) for managing the Maxy platform."
|
|
5
5
|
tools:
|
|
6
6
|
- name: system-status
|
|
7
7
|
publicAllowlist: false
|
|
@@ -3365,9 +3365,20 @@ eagerTool(server, "account_roster", "Canonical per-account identity roster for t
|
|
|
3365
3365
|
const amByAccount = new Map();
|
|
3366
3366
|
try {
|
|
3367
3367
|
const internalPort = parseInt(PLATFORM_PORT, 10);
|
|
3368
|
+
// Task 1636 — the config route is house-scoped; forward this operator
|
|
3369
|
+
// session's platform-stamped caller identity so the gate authorises the
|
|
3370
|
+
// read. account_roster is operator-only, so its house session carries
|
|
3371
|
+
// ACCOUNT_ID (+ HOUSE_ADMIN_SCOPE for a non-specialist house admin); the
|
|
3372
|
+
// agent cannot forge either. Absent (e.g. a non-house context) the gate
|
|
3373
|
+
// fails closed and this best-effort column stays null.
|
|
3374
|
+
const scopeHeaders = { "content-type": "application/json" };
|
|
3375
|
+
if (process.env.ACCOUNT_ID)
|
|
3376
|
+
scopeHeaders["x-maxy-caller-account"] = process.env.ACCOUNT_ID;
|
|
3377
|
+
if (process.env.HOUSE_ADMIN_SCOPE)
|
|
3378
|
+
scopeHeaders["x-maxy-house-admin-scope"] = process.env.HOUSE_ADMIN_SCOPE;
|
|
3368
3379
|
const res = await fetch(`http://127.0.0.1:${internalPort}/api/whatsapp/config`, {
|
|
3369
3380
|
method: "POST",
|
|
3370
|
-
headers:
|
|
3381
|
+
headers: scopeHeaders,
|
|
3371
3382
|
body: JSON.stringify({ action: "list-account-managers" }),
|
|
3372
3383
|
signal: AbortSignal.timeout(5000),
|
|
3373
3384
|
});
|
|
@@ -3405,19 +3416,18 @@ eagerTool(server, "account_roster", "Canonical per-account identity roster for t
|
|
|
3405
3416
|
process.stderr.write(`${TAG} op=roster accounts=${accounts.length} nameless-owners=${namelessOwners} absent-mgr=${absentMgr} dangling-mgr=${danglingMgr}\n`);
|
|
3406
3417
|
return { content: [{ type: "text", text: accounts.length ? `Account roster:\n\n${lines.join("\n")}` : "No accounts found." }] };
|
|
3407
3418
|
});
|
|
3408
|
-
eagerTool(server, "account_create", "Provision a new CLIENT sub-account on this install (managed service). REQUIRES
|
|
3409
|
-
adminUserId: z.string().describe("The userId of the house admin who will manage this client sub-account.
|
|
3419
|
+
eagerTool(server, "account_create", "Provision a new CLIENT sub-account on this install (managed service). REQUIRES `ownerName`, the real name of the client business's owner (ask the operator for it — never invent it or reuse the house operator's name). OPTIONALLY takes `adminUserId`, the userId of the house team member who will manage this client (get the choices from admin-list; ask the operator, but omit it if the client is run by its owner rather than a house admin). Mints a UUID, scaffolds the account directory (agents, specialists, settings, account.json with role:\"client\", and managingAdminUserId when an admin is named), seeds default plugins, and seeds the account's graph root (a :LocalBusiness plus its owner :AdminUser and owner :Person carrying ownerName) so you can populate it immediately. Refuses if ownerName is missing/blank/the placeholder \"Owner\", or if a supplied adminUserId is not a current house admin. When no managing admin is set, that account's passive-intake tasks route to the install's primary admin. Returns the new accountId. Operator-only; never creates the house sub-account (that is the install itself).", {
|
|
3420
|
+
adminUserId: z.string().optional().describe("The userId of the house admin who will manage this client sub-account. Optional — omit it for a client run by its owner rather than a house admin (its passive-intake tasks then route to the install's primary admin). When supplied, it must be a current house admin (see admin-list) or creation is refused."),
|
|
3410
3421
|
ownerName: z.string().describe("The real name of the client business's owner. Applied to both the owner AdminUser and owner Person for the new sub-account. Required — creation is refused when missing, blank, or the placeholder \"Owner\". Ask the operator; never reuse the house operator's name."),
|
|
3411
3422
|
}, async ({ adminUserId, ownerName }) => {
|
|
3412
3423
|
if (!ACCOUNT_ID)
|
|
3413
3424
|
return refuseNoAccount("account_create");
|
|
3414
|
-
// Task
|
|
3415
|
-
//
|
|
3416
|
-
// the
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
}
|
|
3425
|
+
// Task 1630 — a managing admin is optional. `adminUserId` omitted/blank ⇒ the
|
|
3426
|
+
// sub-account carries no managingAdminUserId and its passive-intake tasks fall
|
|
3427
|
+
// back to the install's primary admin. A supplied adminUserId is normalized
|
|
3428
|
+
// and still validated below (a named-but-wrong admin is an error, not a
|
|
3429
|
+
// fallback — it catches operator typos, matching account_reassign_admin).
|
|
3430
|
+
const managingAdminUserId = adminUserId && adminUserId.trim() ? adminUserId.trim() : undefined;
|
|
3421
3431
|
// Task 1567 — the business owner's name is a hard precondition of the
|
|
3422
3432
|
// sub-account. Refuse before any scaffold so a nameless create leaves no
|
|
3423
3433
|
// partial account directory or rootless graph behind. The same predicate
|
|
@@ -3426,10 +3436,9 @@ eagerTool(server, "account_create", "Provision a new CLIENT sub-account on this
|
|
|
3426
3436
|
process.stderr.write(`[account-create] op=refused reason=no-owner-name\n`);
|
|
3427
3437
|
return { content: [{ type: "text", text: `account_create refused: give the client business owner's real name (ownerName). It cannot be blank or the placeholder "Owner". Ask the operator whose business this is.` }], isError: true };
|
|
3428
3438
|
}
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
return { content: [{ type: "text", text: `account_create refused: ${adminUserId} is not a current house admin. Call admin-list for the valid managing-admin choices.` }], isError: true };
|
|
3439
|
+
if (managingAdminUserId && !readHouseAdminUserIds(ACCOUNTS_ROOT).includes(managingAdminUserId)) {
|
|
3440
|
+
process.stderr.write(`[account-lifecycle] op=create-refused reason=unknown-admin adminUserId=${managingAdminUserId.slice(0, 8)}\n`);
|
|
3441
|
+
return { content: [{ type: "text", text: `account_create refused: ${managingAdminUserId} is not a current house admin. Call admin-list for the valid managing-admin choices, or omit adminUserId to leave the client unmanaged.` }], isError: true };
|
|
3433
3442
|
}
|
|
3434
3443
|
const newId = randomUUID();
|
|
3435
3444
|
try {
|
|
@@ -3472,26 +3481,33 @@ eagerTool(server, "account_create", "Provision a new CLIENT sub-account on this
|
|
|
3472
3481
|
finally {
|
|
3473
3482
|
await seedSession.close();
|
|
3474
3483
|
}
|
|
3475
|
-
process.stderr.write(`[account-lifecycle] op=create accountId=${newId} dir-exists=${dirExists} root-seeded=${rootSeeded} managingAdmin=${
|
|
3484
|
+
process.stderr.write(`[account-lifecycle] op=create accountId=${newId} dir-exists=${dirExists} root-seeded=${rootSeeded} managingAdmin=${managingAdminUserId ? managingAdminUserId.slice(0, 8) : "-"}\n`);
|
|
3476
3485
|
if (!rootSeeded) {
|
|
3477
3486
|
return { content: [{ type: "text", text: `account_create FAILED: graph root not seeded for ${newId} (business+admin read-back failed). The account directory was scaffolded but is rootless — run account_delete ${newId} and retry.` }], isError: true };
|
|
3478
3487
|
}
|
|
3479
|
-
// Task 1500 — persist the
|
|
3488
|
+
// Task 1500/1630 — persist the managing admin on the new sub-account when
|
|
3489
|
+
// one was named. Skipped when adminUserId was omitted: the account is left
|
|
3490
|
+
// unmanaged and its passive-intake tasks fall back to the primary admin.
|
|
3480
3491
|
// Done after the root seed so a rootless failure never leaves a half-created
|
|
3481
3492
|
// account claiming a manager. Its own try: at this point the account is
|
|
3482
3493
|
// fully created and seeded, so a write failure must not be reported as a
|
|
3483
3494
|
// generic create failure (which would push the operator to retry and
|
|
3484
3495
|
// orphan a second account). Report the real state — created but unmanaged —
|
|
3485
3496
|
// with the recovery action.
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3497
|
+
if (managingAdminUserId) {
|
|
3498
|
+
try {
|
|
3499
|
+
setManagingAdmin(ACCOUNTS_ROOT, newId, managingAdminUserId);
|
|
3500
|
+
}
|
|
3501
|
+
catch (err) {
|
|
3502
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3503
|
+
process.stderr.write(`[account-lifecycle] op=create-admin-write-failed accountId=${newId} error=${msg}\n`);
|
|
3504
|
+
return { content: [{ type: "text", text: `account_create: sub-account ${newId} was created and its graph root seeded, but recording the managing admin failed: ${msg}. The account exists with no managing admin — run account_reassign_admin ${newId} <adminUserId> to record one.` }], isError: true };
|
|
3505
|
+
}
|
|
3493
3506
|
}
|
|
3494
|
-
|
|
3507
|
+
const managedNote = managingAdminUserId
|
|
3508
|
+
? `managed by admin ${managingAdminUserId}`
|
|
3509
|
+
: `with no managing admin (its passive-intake tasks route to the primary admin; use account_reassign_admin to assign one)`;
|
|
3510
|
+
return { content: [{ type: "text", text: `Created client sub-account ${newId}, ${managedNote}. Switch to it from the sub-account picker to work in its data.` }] };
|
|
3495
3511
|
}
|
|
3496
3512
|
catch (err) {
|
|
3497
3513
|
const msg = err instanceof Error ? err.message : String(err);
|