@rubytech/create-maxy-code 0.1.373 → 0.1.375
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__/cgroup-memory-controller-install-flow.test.js +41 -0
- package/dist/__tests__/cgroup-memory-controller.test.js +32 -0
- package/dist/cgroup-memory-controller.js +33 -0
- package/dist/index.js +54 -0
- package/package.json +1 -1
- package/payload/platform/neo4j/schema.cypher +9 -4
- package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +7 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js +43 -8
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/__tests__/account-lifecycle.test.js +50 -2
- package/payload/platform/plugins/admin/mcp/dist/tools/__tests__/account-lifecycle.test.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.d.ts +17 -0
- 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 +40 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/index.js +1 -4
- package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js +18 -36
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/ics-graph-ingest.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/ics-graph-ingest.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/ics-graph-ingest.test.js +153 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/ics-graph-ingest.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-write.test.js +11 -19
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-write.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts +0 -2
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js +10 -17
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/ics-graph-ingest.d.ts +13 -7
- package/payload/platform/plugins/scheduling/mcp/dist/lib/ics-graph-ingest.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/ics-graph-ingest.js +38 -33
- package/payload/platform/plugins/scheduling/mcp/dist/lib/ics-graph-ingest.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/meeting-write.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/meeting-write.js +13 -19
- package/payload/platform/plugins/scheduling/mcp/dist/lib/meeting-write.js.map +1 -1
- package/payload/platform/plugins/whatsapp/PLUGIN.md +6 -4
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js +31 -15
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
- package/payload/platform/scripts/migrate-whatsapp-to-disk.mjs +115 -0
- package/payload/platform/scripts/rss-sampler.sh +18 -2
- package/payload/platform/scripts/seed-neo4j.sh +17 -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 +2 -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.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +34 -14
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +2 -2
- 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 +19 -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 +42 -0
- package/payload/platform/services/claude-session-manager/dist/jsonl-path.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +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 +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +10 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +25 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-cap-audit.d.ts +75 -4
- package/payload/platform/services/claude-session-manager/dist/session-cap-audit.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-cap-audit.js +138 -15
- package/payload/platform/services/claude-session-manager/dist/session-cap-audit.js.map +1 -1
- package/payload/server/public/assets/AdminLoginScreens-3vFueaZy.js +1 -0
- package/payload/server/public/assets/AdminShell-DvczcTdp.js +1 -0
- package/payload/server/public/assets/{Checkbox-BagcrE4Q.js → Checkbox-KbEWQEVU.js} +1 -1
- package/payload/server/public/assets/{admin-CodAEpgQ.js → admin-Cm1tZXbf.js} +1 -1
- package/payload/server/public/assets/{browser-CRTlI7ck.js → browser-C38YYs28.js} +1 -1
- package/payload/server/public/assets/{calendar-BBEivUAw.js → calendar-CCN9px9o.js} +1 -1
- package/payload/server/public/assets/chat-fj5J5aKV.js +1 -0
- package/payload/server/public/assets/data-D1EXeX7P.js +1 -0
- package/payload/server/public/assets/{graph-HoHcI7qv.js → graph-DGXKuRbL.js} +3 -3
- package/payload/server/public/assets/{graph-labels-BJfK0tbH.js → graph-labels-DYRDEL2l.js} +1 -1
- package/payload/server/public/assets/{operator-5wtImBoF.js → operator-Bhtayf9U.js} +1 -1
- package/payload/server/public/assets/{page-CzTgPTp3.js → page-BSMtFFmx.js} +5 -5
- package/payload/server/public/assets/{public-CUopv7Ro.js → public-DO_OqjJa.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-DttFB1nY.js → rotate-ccw-DQfsmjLr.js} +1 -1
- package/payload/server/public/assets/useSubAccountSwitcher-CSCcvysa.css +1 -0
- package/payload/server/public/assets/useSubAccountSwitcher-z8xhukFw.js +9 -0
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +4 -4
- package/payload/server/public/chat.html +6 -6
- package/payload/server/public/data.html +4 -4
- package/payload/server/public/graph.html +7 -7
- package/payload/server/public/index.html +6 -6
- package/payload/server/public/operator.html +8 -8
- package/payload/server/public/public.html +6 -6
- package/payload/server/server.js +1169 -964
- package/payload/server/public/assets/AdminLoginScreens-CGTA9noi.js +0 -1
- package/payload/server/public/assets/AdminShell-CNwb23_A.js +0 -1
- package/payload/server/public/assets/OperatorConversations-BX9t3jgm.css +0 -1
- package/payload/server/public/assets/OperatorConversations-CivR0bnD.js +0 -9
- package/payload/server/public/assets/chat-r1qKuzHk.js +0 -1
- package/payload/server/public/assets/data-BkY0bFYS.js +0 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Static-grep contract for configureCgroupMemoryController()'s placement in
|
|
2
|
+
// the install flow — same precedent as watchdog-deferred-arming.test.ts for
|
|
3
|
+
// an installer side-effecting step that shells out with sudo and can't be
|
|
4
|
+
// safely executed in a unit test. The pure decision rule itself is covered
|
|
5
|
+
// by cgroup-memory-controller.test.ts.
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { dirname, resolve } from "node:path";
|
|
11
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const INDEX_TS = resolve(here, "../../src/index.ts");
|
|
13
|
+
const SRC = readFileSync(INDEX_TS, "utf-8");
|
|
14
|
+
function fnBody(name) {
|
|
15
|
+
const start = SRC.indexOf(`function ${name}`);
|
|
16
|
+
assert.ok(start >= 0, `could not locate ${name} in index.ts`);
|
|
17
|
+
const nextFn = SRC.indexOf("\nfunction ", start + 1);
|
|
18
|
+
assert.ok(nextFn > start, `could not bound ${name} body`);
|
|
19
|
+
return SRC.slice(start, nextFn);
|
|
20
|
+
}
|
|
21
|
+
test("configureCgroupMemoryController is called inside installService()'s Linux branch", () => {
|
|
22
|
+
const installServiceStart = SRC.indexOf("function installService(");
|
|
23
|
+
assert.ok(installServiceStart >= 0, "installService() not found");
|
|
24
|
+
const darwinReturn = SRC.indexOf("installServiceDarwin();\n return;", installServiceStart);
|
|
25
|
+
assert.ok(darwinReturn > installServiceStart, "expected the darwin early-return inside installService()");
|
|
26
|
+
const callSite = SRC.indexOf("configureCgroupMemoryController();", darwinReturn);
|
|
27
|
+
assert.ok(callSite > darwinReturn, "configureCgroupMemoryController() must be called after the darwin early-return (Linux-only)");
|
|
28
|
+
});
|
|
29
|
+
test("configureCgroupMemoryController never throws on a patch failure (non-fatal, matches the sysctl.d precedent)", () => {
|
|
30
|
+
const body = fnBody("configureCgroupMemoryController");
|
|
31
|
+
assert.ok(body.includes("try {"), "expected a try/catch around the sudo cp — a read-only /boot must not abort install");
|
|
32
|
+
assert.ok(body.includes("catch"), "expected a catch branch");
|
|
33
|
+
});
|
|
34
|
+
test("configureCgroupMemoryController always logs a resolved op=cgroup-memory-status line", () => {
|
|
35
|
+
const body = fnBody("configureCgroupMemoryController");
|
|
36
|
+
const occurrences = body.match(/op=cgroup-memory-status/g) ?? [];
|
|
37
|
+
assert.ok(occurrences.length >= 4, `expected at least 4 branches to log op=cgroup-memory-status, found ${occurrences.length}`);
|
|
38
|
+
});
|
|
39
|
+
test("configureCgroupMemoryController imports the pure decision rule from cgroup-memory-controller.js", () => {
|
|
40
|
+
assert.ok(SRC.includes('from "./cgroup-memory-controller.js"'), "index.ts must import memoryControllerMissing/cmdlineAlreadyPatched/patchCmdline rather than re-implementing the parsing");
|
|
41
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Pure decision rule for Task 1297: does this host's kernel have the memory
|
|
2
|
+
// cgroup controller enabled, and if not, does its boot cmdline already carry
|
|
3
|
+
// the fix. The wrapper in index.ts owns the fs reads and the sudo cp; this
|
|
4
|
+
// suite exercises only the pure functions — inputs in, decision out.
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import assert from "node:assert/strict";
|
|
7
|
+
import { CGROUP_MEMORY_CMDLINE_FLAGS, memoryControllerMissing, cmdlineAlreadyPatched, patchCmdline, } from "../cgroup-memory-controller.js";
|
|
8
|
+
test("memoryControllerMissing: false when memory is in the controller list", () => {
|
|
9
|
+
assert.equal(memoryControllerMissing("cpuset cpu io memory pids\n"), false);
|
|
10
|
+
});
|
|
11
|
+
test("memoryControllerMissing: true when memory is absent (the Pi default)", () => {
|
|
12
|
+
assert.equal(memoryControllerMissing("cpuset cpu io pids\n"), true);
|
|
13
|
+
});
|
|
14
|
+
test("memoryControllerMissing: true on an empty controller list", () => {
|
|
15
|
+
assert.equal(memoryControllerMissing(""), true);
|
|
16
|
+
});
|
|
17
|
+
test("cmdlineAlreadyPatched: true when the flags are present", () => {
|
|
18
|
+
const cmdline = "console=tty1 root=PARTUUID=abc rootwait cgroup_enable=memory cgroup_memory=1\n";
|
|
19
|
+
assert.equal(cmdlineAlreadyPatched(cmdline), true);
|
|
20
|
+
});
|
|
21
|
+
test("cmdlineAlreadyPatched: false on an unpatched line", () => {
|
|
22
|
+
const cmdline = "console=serial0,115200 console=tty1 root=PARTUUID=e9d78c4e-02 rootwait quiet splash\n";
|
|
23
|
+
assert.equal(cmdlineAlreadyPatched(cmdline), false);
|
|
24
|
+
});
|
|
25
|
+
test("patchCmdline: appends the flags with a single trailing newline", () => {
|
|
26
|
+
const cmdline = "console=tty1 root=PARTUUID=abc rootwait\n";
|
|
27
|
+
assert.equal(patchCmdline(cmdline), `console=tty1 root=PARTUUID=abc rootwait ${CGROUP_MEMORY_CMDLINE_FLAGS}\n`);
|
|
28
|
+
});
|
|
29
|
+
test("patchCmdline: works when the source has no trailing newline", () => {
|
|
30
|
+
const cmdline = "console=tty1 root=PARTUUID=abc rootwait";
|
|
31
|
+
assert.equal(patchCmdline(cmdline), `console=tty1 root=PARTUUID=abc rootwait ${CGROUP_MEMORY_CMDLINE_FLAGS}\n`);
|
|
32
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Pure logic for Task 1297: the Pi kernel ships with the memory cgroup
|
|
2
|
+
// controller disabled by default (/sys/fs/cgroup/cgroup.controllers has no
|
|
3
|
+
// `memory`), which makes every MemoryHigh=/MemoryMax= this platform
|
|
4
|
+
// configures (Task 1176, Task 1222) a silent no-op — confirmed fleet-wide
|
|
5
|
+
// across all 5 Pis via live inspection. The wrapper in index.ts owns the fs
|
|
6
|
+
// reads, locating the boot cmdline file, and the sudo cp. This module owns
|
|
7
|
+
// only the decision + the patched-content computation — inputs in, decision
|
|
8
|
+
// out. No reboot logic here or in the wrapper; a boot-param change only takes
|
|
9
|
+
// effect on the operator's next reboot.
|
|
10
|
+
export const CGROUP_MEMORY_CMDLINE_FLAGS = "cgroup_enable=memory cgroup_memory=1";
|
|
11
|
+
/** True when the cgroup-v2 root controller list (the raw content of
|
|
12
|
+
* /sys/fs/cgroup/cgroup.controllers) lacks `memory`. Inline rather than
|
|
13
|
+
* imported from platform/services/claude-session-manager because the
|
|
14
|
+
* installer is its own npm package and doesn't bundle platform services.
|
|
15
|
+
* Mirrors (negated) memoryControllerInList() in
|
|
16
|
+
* platform/services/claude-session-manager/src/rc-daemon.ts; future
|
|
17
|
+
* divergence between the two should be caught by the test suite. */
|
|
18
|
+
export function memoryControllerMissing(controllers) {
|
|
19
|
+
return !controllers.split(/\s+/).filter(Boolean).includes("memory");
|
|
20
|
+
}
|
|
21
|
+
/** True when the boot cmdline file already carries the enable flags —
|
|
22
|
+
* patching again would duplicate them. */
|
|
23
|
+
export function cmdlineAlreadyPatched(cmdline) {
|
|
24
|
+
return cmdline.includes("cgroup_enable=memory");
|
|
25
|
+
}
|
|
26
|
+
/** Append the enable flags to the single-line boot cmdline file. Callers
|
|
27
|
+
* must gate on cmdlineAlreadyPatched() first — this always appends, so
|
|
28
|
+
* calling it twice on the same content would duplicate the flags. Trims
|
|
29
|
+
* any trailing whitespace before appending, then restores exactly one
|
|
30
|
+
* trailing newline (cmdline.txt convention: one line, trailing newline). */
|
|
31
|
+
export function patchCmdline(cmdline) {
|
|
32
|
+
return `${cmdline.replace(/\s+$/, "")} ${CGROUP_MEMORY_CMDLINE_FLAGS}\n`;
|
|
33
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,7 @@ import { installAllBrewPackages } from "./brew-install.js";
|
|
|
19
19
|
import { parseSwVers, isSupportedMacosVersion } from "./macos-version.js";
|
|
20
20
|
import { decideChromiumAction, isSnapConfinedPath } from "./snap-chromium.js";
|
|
21
21
|
import { classifyPortHolder } from "./preflight-port-classifier.js";
|
|
22
|
+
import { memoryControllerMissing, cmdlineAlreadyPatched, patchCmdline } from "./cgroup-memory-controller.js";
|
|
22
23
|
import { parsePluginList, computeInstallActions, parseExternalPlugins, findUnregisteredResyncs, } from "./lib/plugin-install.js";
|
|
23
24
|
import { findPremiumMcpDirs } from "./lib/premium-mcp-discover.js";
|
|
24
25
|
import { pickBindDecision, mergeSmbConf, formatSambaMarker, SAMBA_ENABLE_UNITS, } from "./samba-provision.js";
|
|
@@ -3243,6 +3244,56 @@ function installServiceDarwin() {
|
|
|
3243
3244
|
console.log(` Server may still be starting. Check http://localhost:${PORT} in a moment.`);
|
|
3244
3245
|
}
|
|
3245
3246
|
}
|
|
3247
|
+
/** Task 1297 — the Pi kernel ships with the memory cgroup controller
|
|
3248
|
+
* disabled by default (/sys/fs/cgroup/cgroup.controllers has no `memory`),
|
|
3249
|
+
* which makes every MemoryHigh=/MemoryMax= this platform configures (Task
|
|
3250
|
+
* 1176, Task 1222) a silent no-op — confirmed fleet-wide, not theorized.
|
|
3251
|
+
* Patches the boot cmdline so the NEXT reboot activates it; never triggers
|
|
3252
|
+
* the reboot itself. Non-fatal: a read-only /boot partition, an absent
|
|
3253
|
+
* cmdline file (x86 hosts don't have one and don't need this), or a host
|
|
3254
|
+
* where the controller is already enabled (laptop, hart) all resolve to a
|
|
3255
|
+
* logged status line and a clean return — matching the sysctl.d QUIC
|
|
3256
|
+
* drop-in step's failure handling immediately below this function's call
|
|
3257
|
+
* site. */
|
|
3258
|
+
function configureCgroupMemoryController() {
|
|
3259
|
+
let controllers;
|
|
3260
|
+
try {
|
|
3261
|
+
controllers = readFileSync("/sys/fs/cgroup/cgroup.controllers", "utf-8");
|
|
3262
|
+
}
|
|
3263
|
+
catch {
|
|
3264
|
+
logFile("[install] op=cgroup-memory-status enabled=unknown patched=unknown reason=no-cgroup-v2-root");
|
|
3265
|
+
return;
|
|
3266
|
+
}
|
|
3267
|
+
if (!memoryControllerMissing(controllers)) {
|
|
3268
|
+
logFile("[install] op=cgroup-memory-status enabled=true patched=false");
|
|
3269
|
+
return;
|
|
3270
|
+
}
|
|
3271
|
+
const cmdlinePath = existsSync("/boot/firmware/cmdline.txt")
|
|
3272
|
+
? "/boot/firmware/cmdline.txt"
|
|
3273
|
+
: existsSync("/boot/cmdline.txt")
|
|
3274
|
+
? "/boot/cmdline.txt"
|
|
3275
|
+
: null;
|
|
3276
|
+
if (!cmdlinePath) {
|
|
3277
|
+
logFile("[install] op=cgroup-memory-status enabled=false patched=false reason=no-cmdline-file");
|
|
3278
|
+
return;
|
|
3279
|
+
}
|
|
3280
|
+
const cmdline = readFileSync(cmdlinePath, "utf-8");
|
|
3281
|
+
if (cmdlineAlreadyPatched(cmdline)) {
|
|
3282
|
+
logFile("[install] op=cgroup-memory-status enabled=false patched=true");
|
|
3283
|
+
return;
|
|
3284
|
+
}
|
|
3285
|
+
try {
|
|
3286
|
+
const tmpPath = "/tmp/cmdline.txt.maxy-cgroup-patch";
|
|
3287
|
+
writeFileSync(tmpPath, patchCmdline(cmdline));
|
|
3288
|
+
console.log(" [privileged] cp");
|
|
3289
|
+
shell("cp", [tmpPath, cmdlinePath], { sudo: true });
|
|
3290
|
+
spawnSync("rm", ["-f", tmpPath]);
|
|
3291
|
+
logFile("[install] op=cgroup-memory-status enabled=false patched=true");
|
|
3292
|
+
}
|
|
3293
|
+
catch {
|
|
3294
|
+
logFile("[install] op=cgroup-memory-status enabled=false patched=false reason=patch-failed");
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3246
3297
|
function installService() {
|
|
3247
3298
|
log("11", TOTAL, `Starting ${BRAND.productName}...`);
|
|
3248
3299
|
// Linux falls through to the systemd-user body below; darwin renders +
|
|
@@ -3265,6 +3316,9 @@ function installService() {
|
|
|
3265
3316
|
spawnSync("sudo", ["sysctl", "--system"], { stdio: "ignore", timeout: 10_000 });
|
|
3266
3317
|
}
|
|
3267
3318
|
catch { /* non-critical — values applied on next reboot */ }
|
|
3319
|
+
// Task 1297 — activate the fleet-wide memory ceiling (Task 1176/1222) by
|
|
3320
|
+
// enabling the kernel controller it has silently depended on since it shipped.
|
|
3321
|
+
configureCgroupMemoryController();
|
|
3268
3322
|
const serviceDir = resolve(process.env.HOME ?? "/root", ".config/systemd/user");
|
|
3269
3323
|
mkdirSync(serviceDir, { recursive: true });
|
|
3270
3324
|
// Create systemd user service
|
package/package.json
CHANGED
|
@@ -15,15 +15,20 @@
|
|
|
15
15
|
// Constraints
|
|
16
16
|
// ----------------------------------------------------------
|
|
17
17
|
|
|
18
|
-
// Person.email must be unique
|
|
18
|
+
// Person.email must be unique per account (Task 1304 — account scope is a
|
|
19
|
+
// property, not an edge, so email alone cannot be the natural key: two
|
|
20
|
+
// accounts can each hold a Person with the same email).
|
|
19
21
|
// Allows multiple nulls — phone-only contacts are not affected.
|
|
22
|
+
DROP CONSTRAINT person_email_unique IF EXISTS;
|
|
20
23
|
CREATE CONSTRAINT person_email_unique IF NOT EXISTS
|
|
21
|
-
FOR (p:Person) REQUIRE p.email IS UNIQUE;
|
|
24
|
+
FOR (p:Person) REQUIRE (p.accountId, p.email) IS UNIQUE;
|
|
22
25
|
|
|
23
|
-
// Person.telephone must be unique (phone-first channels:
|
|
26
|
+
// Person.telephone must be unique per account (phone-first channels:
|
|
27
|
+
// WhatsApp, SMS, Telegram). Composite for the same reason as email above.
|
|
24
28
|
// Allows multiple nulls — email-only contacts are not affected.
|
|
29
|
+
DROP CONSTRAINT person_telephone_unique IF EXISTS;
|
|
25
30
|
CREATE CONSTRAINT person_telephone_unique IF NOT EXISTS
|
|
26
|
-
FOR (p:Person) REQUIRE p.telephone IS UNIQUE;
|
|
31
|
+
FOR (p:Person) REQUIRE (p.accountId, p.telephone) IS UNIQUE;
|
|
27
32
|
|
|
28
33
|
// Service.serviceId must be unique within the Maxy account
|
|
29
34
|
CREATE CONSTRAINT service_id_unique IF NOT EXISTS
|
|
@@ -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 — 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), admin-add, admin-remove, 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.",
|
|
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_channel_routing_set repoints the install's single WhatsApp/Telegram inbound stream to a different sub-account), admin-add, admin-remove, 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 — 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), admin-add, admin-remove, 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
|
+
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_channel_routing_set repoints the install's single WhatsApp/Telegram inbound stream to a different sub-account), admin-add, admin-remove, 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
|
|
@@ -39,6 +39,12 @@ tools:
|
|
|
39
39
|
- name: account_purge
|
|
40
40
|
publicAllowlist: false
|
|
41
41
|
adminAllowlist: true
|
|
42
|
+
- name: account_channel_routing
|
|
43
|
+
publicAllowlist: false
|
|
44
|
+
adminAllowlist: true
|
|
45
|
+
- name: account_channel_routing_set
|
|
46
|
+
publicAllowlist: false
|
|
47
|
+
adminAllowlist: true
|
|
42
48
|
- name: admin-add
|
|
43
49
|
publicAllowlist: false
|
|
44
50
|
adminAllowlist: true
|
|
@@ -27,7 +27,7 @@ import { publishSite } from "./tools/publish-site.js";
|
|
|
27
27
|
import { resolveCapabilitiesHere } from "./tools/capabilities-here.js";
|
|
28
28
|
import { runCatalogueCensus } from "./lib/catalogue-census.js";
|
|
29
29
|
import { lifelineTool } from "../../../../lib/mcp-lifeline/dist/index.js";
|
|
30
|
-
import { listAccountsFromDir, isHouseAccount, archiveAccountDir, provisionClientAccount, ACCOUNT_NODE_COUNT_CYPHER, ACCOUNT_PURGE_CYPHER, } from "./tools/account-lifecycle.js";
|
|
30
|
+
import { listAccountsFromDir, isHouseAccount, archiveAccountDir, provisionClientAccount, findHouseAccount, getChannelRoutingAccountId, setChannelRoutingAccountId, ACCOUNT_NODE_COUNT_CYPHER, ACCOUNT_PURGE_CYPHER, } from "./tools/account-lifecycle.js";
|
|
31
31
|
function refusalGuidance(kind) {
|
|
32
32
|
switch (kind) {
|
|
33
33
|
case "unsafe-slug":
|
|
@@ -3249,14 +3249,14 @@ eagerTool(server, "action-edit", "Edit a pending action's input and then execute
|
|
|
3249
3249
|
// PLATFORM_ROOT rather than the per-session ACCOUNT_ID.
|
|
3250
3250
|
// ---------------------------------------------------------------------------
|
|
3251
3251
|
const ACCOUNTS_ROOT = resolve(PLATFORM_ROOT, "..", "data/accounts");
|
|
3252
|
-
eagerTool(server, "account_list", "List every account on this install (one house + N clients) with its role. Managed-service operator tool — the active account drives which account a switched session reads and writes.", {}, async () => {
|
|
3252
|
+
eagerTool(server, "account_list", "List every sub-account on this install (one house + N clients) with its role. Managed-service operator tool — the active sub-account drives which account a switched session reads and writes.", {}, async () => {
|
|
3253
3253
|
const accounts = listAccountsFromDir(ACCOUNTS_ROOT);
|
|
3254
3254
|
const lines = accounts.map((a) => `${a.accountId} role=${a.role}${a.isHouse ? " (house)" : ""}`);
|
|
3255
3255
|
return {
|
|
3256
3256
|
content: [{ type: "text", text: accounts.length ? lines.join("\n") : "No accounts found." }],
|
|
3257
3257
|
};
|
|
3258
3258
|
});
|
|
3259
|
-
eagerTool(server, "account_create", "Provision a new CLIENT account on this install (managed service). Mints a UUID, scaffolds the account directory (agents, specialists, settings, account.json with role:\"client\"), seeds default plugins, and adds you (the install owner) to the account's admins so you can switch to it. Returns the new accountId. Operator-only; never creates the house account (that is the install itself).", {}, async () => {
|
|
3259
|
+
eagerTool(server, "account_create", "Provision a new CLIENT sub-account on this install (managed service). Mints a UUID, scaffolds the account directory (agents, specialists, settings, account.json with role:\"client\"), seeds default plugins, and adds you (the install owner) to the sub-account's admins so you can switch to it. Returns the new accountId. Operator-only; never creates the house sub-account (that is the install itself).", {}, async () => {
|
|
3260
3260
|
if (!ACCOUNT_ID)
|
|
3261
3261
|
return refuseNoAccount("account_create");
|
|
3262
3262
|
const newId = randomUUID();
|
|
@@ -3278,7 +3278,7 @@ eagerTool(server, "account_create", "Provision a new CLIENT account on this inst
|
|
|
3278
3278
|
if (!dirExists) {
|
|
3279
3279
|
return { content: [{ type: "text", text: `account_create FAILED: scaffold did not produce ${accountJson}` }], isError: true };
|
|
3280
3280
|
}
|
|
3281
|
-
return { content: [{ type: "text", text: `Created client account ${newId}. Switch to it from the account picker to work in its data.` }] };
|
|
3281
|
+
return { content: [{ type: "text", text: `Created client sub-account ${newId}. Switch to it from the sub-account picker to work in its data.` }] };
|
|
3282
3282
|
}
|
|
3283
3283
|
catch (err) {
|
|
3284
3284
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -3286,7 +3286,7 @@ eagerTool(server, "account_create", "Provision a new CLIENT account on this inst
|
|
|
3286
3286
|
return { content: [{ type: "text", text: `account_create FAILED: ${msg}` }], isError: true };
|
|
3287
3287
|
}
|
|
3288
3288
|
});
|
|
3289
|
-
eagerTool(server, "account_delete", "Archive a CLIENT account: move its directory to data/accounts/.trash/ so it no longer appears in the account list. Graph data is RETAINED — run account_purge with the typed confirmation to irreversibly delete it. Refuses to archive the house account. Pass the accountId to archive.", {
|
|
3289
|
+
eagerTool(server, "account_delete", "Archive a CLIENT sub-account: move its directory to data/accounts/.trash/ so it no longer appears in the sub-account list. Graph data is RETAINED — run account_purge with the typed confirmation to irreversibly delete it. Refuses to archive the house sub-account. Pass the accountId to archive.", {
|
|
3290
3290
|
accountId: z.string().describe("The client accountId (UUID) to archive."),
|
|
3291
3291
|
}, async ({ accountId }) => {
|
|
3292
3292
|
if (!ACCOUNT_ID)
|
|
@@ -3295,7 +3295,7 @@ eagerTool(server, "account_delete", "Archive a CLIENT account: move its director
|
|
|
3295
3295
|
return { content: [{ type: "text", text: `account_delete: ${accountId} is not a registered account on this install.` }], isError: true };
|
|
3296
3296
|
}
|
|
3297
3297
|
if (isHouseAccount(ACCOUNTS_ROOT, accountId)) {
|
|
3298
|
-
return { content: [{ type: "text", text: `account_delete refused: ${accountId} is the house account and cannot be archived.` }], isError: true };
|
|
3298
|
+
return { content: [{ type: "text", text: `account_delete refused: ${accountId} is the house sub-account and cannot be archived.` }], isError: true };
|
|
3299
3299
|
}
|
|
3300
3300
|
const ts = new Date().toISOString().replace(/[:.]/g, "").replace(/-/g, "");
|
|
3301
3301
|
try {
|
|
@@ -3308,14 +3308,14 @@ eagerTool(server, "account_delete", "Archive a CLIENT account: move its director
|
|
|
3308
3308
|
return { content: [{ type: "text", text: `account_delete FAILED: ${msg}` }], isError: true };
|
|
3309
3309
|
}
|
|
3310
3310
|
});
|
|
3311
|
-
eagerTool(server, "account_purge", "IRREVERSIBLY delete every graph node carrying
|
|
3311
|
+
eagerTool(server, "account_purge", "IRREVERSIBLY delete every graph node carrying a sub-account's accountId (DETACH DELETE). Two-step safety: account_delete archives first; this purges. Requires a typed confirmation equal to the accountId itself — any mismatch refuses. Refuses the house sub-account. Returns the node count before and after (after must be zero).", {
|
|
3312
3312
|
accountId: z.string().describe("The accountId (UUID) whose graph data to purge."),
|
|
3313
3313
|
confirm: z.string().describe("Type the accountId exactly to confirm the irreversible purge."),
|
|
3314
3314
|
}, async ({ accountId, confirm }) => {
|
|
3315
3315
|
if (!ACCOUNT_ID)
|
|
3316
3316
|
return refuseNoAccount("account_purge");
|
|
3317
3317
|
if (isHouseAccount(ACCOUNTS_ROOT, accountId)) {
|
|
3318
|
-
return { content: [{ type: "text", text: `account_purge refused: ${accountId} is the house account.` }], isError: true };
|
|
3318
|
+
return { content: [{ type: "text", text: `account_purge refused: ${accountId} is the house sub-account.` }], isError: true };
|
|
3319
3319
|
}
|
|
3320
3320
|
if (confirm !== accountId) {
|
|
3321
3321
|
return { content: [{ type: "text", text: `account_purge refused: confirmation does not match the accountId. Type the accountId exactly to confirm.` }], isError: true };
|
|
@@ -3341,6 +3341,41 @@ eagerTool(server, "account_purge", "IRREVERSIBLY delete every graph node carryin
|
|
|
3341
3341
|
await session.close();
|
|
3342
3342
|
}
|
|
3343
3343
|
});
|
|
3344
|
+
eagerTool(server, "account_channel_routing", "Reports which sub-account WhatsApp/Telegram inbound currently attaches to on this install. There is exactly one WhatsApp number per install (not one per sub-account) — this is the single install-wide routing target, defaulting to the house sub-account.", {}, async () => {
|
|
3345
|
+
const house = findHouseAccount(ACCOUNTS_ROOT);
|
|
3346
|
+
if (!house) {
|
|
3347
|
+
return { content: [{ type: "text", text: "account_channel_routing: no house account found on this install." }], isError: true };
|
|
3348
|
+
}
|
|
3349
|
+
const target = getChannelRoutingAccountId(ACCOUNTS_ROOT) ?? house.accountId;
|
|
3350
|
+
const isDefault = target === house.accountId;
|
|
3351
|
+
return {
|
|
3352
|
+
content: [{ type: "text", text: `WhatsApp/Telegram inbound currently routes to: ${target}${isDefault ? " (house, default)" : ""}` }],
|
|
3353
|
+
};
|
|
3354
|
+
});
|
|
3355
|
+
eagerTool(server, "account_channel_routing_set", "Repoints the single install-wide WhatsApp/Telegram inbound stream to the given sub-account. Refuses an accountId that is not a registered sub-account on this install. Takes effect on the next inbound message — no restart needed.", {
|
|
3356
|
+
accountId: z.string().describe("The sub-account UUID to route WhatsApp/Telegram inbound to."),
|
|
3357
|
+
}, async ({ accountId }) => {
|
|
3358
|
+
if (!ACCOUNT_ID)
|
|
3359
|
+
return refuseNoAccount("account_channel_routing_set");
|
|
3360
|
+
const known = listAccountsFromDir(ACCOUNTS_ROOT);
|
|
3361
|
+
if (!known.some((a) => a.accountId === accountId)) {
|
|
3362
|
+
return { content: [{ type: "text", text: `account_channel_routing_set refused: ${accountId} is not a registered sub-account on this install.` }], isError: true };
|
|
3363
|
+
}
|
|
3364
|
+
const house = findHouseAccount(ACCOUNTS_ROOT);
|
|
3365
|
+
if (!house) {
|
|
3366
|
+
return { content: [{ type: "text", text: "account_channel_routing_set FAILED: no house account found on this install." }], isError: true };
|
|
3367
|
+
}
|
|
3368
|
+
const previous = getChannelRoutingAccountId(ACCOUNTS_ROOT) ?? house.accountId;
|
|
3369
|
+
try {
|
|
3370
|
+
setChannelRoutingAccountId(ACCOUNTS_ROOT, accountId);
|
|
3371
|
+
process.stderr.write(`[platform-account-id] op=repoint from=${previous.slice(0, 8)} to=${accountId.slice(0, 8)}\n`);
|
|
3372
|
+
return { content: [{ type: "text", text: `WhatsApp/Telegram inbound now routes to ${accountId}.` }] };
|
|
3373
|
+
}
|
|
3374
|
+
catch (err) {
|
|
3375
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3376
|
+
return { content: [{ type: "text", text: `account_channel_routing_set FAILED: ${msg}` }], isError: true };
|
|
3377
|
+
}
|
|
3378
|
+
});
|
|
3344
3379
|
// Cleanup on exit (SIGTERM for systemd service stops on Pi)
|
|
3345
3380
|
const cleanup = async () => {
|
|
3346
3381
|
await closeDriver();
|