@rubytech/create-maxy-code 0.1.404 → 0.1.406

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/__tests__/launchd-plist.test.js +13 -1
  2. package/dist/__tests__/neo4j-darwin-dedicated.test.js +52 -0
  3. package/dist/__tests__/neo4j-datadir.test.js +8 -7
  4. package/dist/__tests__/neo4j-teardown.test.js +17 -1
  5. package/dist/index.js +183 -9
  6. package/dist/launchd-plist.js +21 -0
  7. package/dist/neo4j-darwin-dedicated.js +39 -0
  8. package/dist/neo4j-datadir.js +9 -5
  9. package/dist/neo4j-teardown.js +15 -0
  10. package/dist/uninstall.js +34 -8
  11. package/package.json +1 -1
  12. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +2 -2
  13. package/payload/platform/plugins/scheduling/PLUGIN.md +2 -0
  14. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/agent-dispatch-manager-grounding.test.d.ts +2 -0
  15. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/agent-dispatch-manager-grounding.test.d.ts.map +1 -0
  16. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/agent-dispatch-manager-grounding.test.js +19 -0
  17. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/agent-dispatch-manager-grounding.test.js.map +1 -0
  18. package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/deliverability-grounding.test.d.ts +2 -0
  19. package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/deliverability-grounding.test.d.ts.map +1 -0
  20. package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/deliverability-grounding.test.js +33 -0
  21. package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/deliverability-grounding.test.js.map +1 -0
  22. package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +2 -0
  23. package/payload/platform/plugins/whatsapp/skills/manage-whatsapp-config/SKILL.md +5 -1
  24. package/payload/platform/services/claude-session-manager/dist/claude-host-creds.d.ts +8 -7
  25. package/payload/platform/services/claude-session-manager/dist/claude-host-creds.d.ts.map +1 -1
  26. package/payload/platform/services/claude-session-manager/dist/claude-host-creds.js +63 -40
  27. package/payload/platform/services/claude-session-manager/dist/claude-host-creds.js.map +1 -1
  28. package/payload/platform/services/claude-session-manager/dist/claude-keychain-write.d.ts +40 -0
  29. package/payload/platform/services/claude-session-manager/dist/claude-keychain-write.d.ts.map +1 -0
  30. package/payload/platform/services/claude-session-manager/dist/claude-keychain-write.js +159 -0
  31. package/payload/platform/services/claude-session-manager/dist/claude-keychain-write.js.map +1 -0
  32. package/payload/platform/services/claude-session-manager/dist/connector-availability.d.ts +36 -0
  33. package/payload/platform/services/claude-session-manager/dist/connector-availability.d.ts.map +1 -0
  34. package/payload/platform/services/claude-session-manager/dist/connector-availability.js +76 -0
  35. package/payload/platform/services/claude-session-manager/dist/connector-availability.js.map +1 -0
  36. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +2 -1
  37. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
  38. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +2 -2
  39. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
  40. package/payload/premium-plugins/venture-studio/PLUGIN.md +1 -1
  41. package/payload/premium-plugins/venture-studio/bin/scan-private-data.sh +61 -0
  42. package/payload/premium-plugins/venture-studio/skills/investor-data-room/SKILL.md +31 -32
  43. package/payload/premium-plugins/venture-studio/skills/investor-data-room/references/business-plan-template.md +5 -5
  44. package/payload/premium-plugins/venture-studio/skills/investor-data-room/references/competitor-analysis.md +1 -1
  45. package/payload/premium-plugins/venture-studio/skills/investor-data-room/references/compliance-research-checklist.md +2 -2
  46. package/payload/premium-plugins/venture-studio/skills/investor-data-room/references/data-room-structure.md +1 -1
  47. package/payload/premium-plugins/venture-studio/skills/investor-data-room/references/deck-blueprint-template.md +38 -26
  48. package/payload/premium-plugins/venture-studio/skills/investor-data-room/references/design-tokens-application.md +2 -2
  49. package/payload/premium-plugins/venture-studio/skills/investor-data-room/references/internal-workings-scrub.md +6 -6
  50. package/payload/premium-plugins/venture-studio/skills/investor-data-room/references/strategy-frameworks.md +1 -1
  51. package/payload/premium-plugins/venture-studio/skills/investor-data-room/references/termsheet-template.md +17 -18
  52. package/payload/premium-plugins/venture-studio/skills/investor-data-room/templates/prospectus/index.html +156 -234
  53. package/payload/premium-plugins/venture-studio/skills/investor-data-room/templates/prospectus/term_sheet.html +13 -13
  54. package/payload/premium-plugins/venture-studio/skills/zero-to-prototype/references/strategy-red-team.md +1 -1
  55. package/payload/server/public/assets/{AdminLoginScreens-BzErD9Zo.js → AdminLoginScreens-D36zz_SY.js} +1 -1
  56. package/payload/server/public/assets/{AdminShell-BihejKg_.js → AdminShell-DH6Lcj-d.js} +1 -1
  57. package/payload/server/public/assets/{Checkbox-Ds004CBE.js → Checkbox-54KtArF3.js} +1 -1
  58. package/payload/server/public/assets/{Transcript-CgaMQKgY.js → Transcript-BCpiXt4J.js} +1 -1
  59. package/payload/server/public/assets/{admin-BSuRtMws.js → admin-C0FCHcJj.js} +1 -1
  60. package/payload/server/public/assets/{browser-BO2eR_YY.js → browser-bKeZ5iLx.js} +1 -1
  61. package/payload/server/public/assets/{calendar-12QuR_oh.js → calendar-BDPBArFA.js} +1 -1
  62. package/payload/server/public/assets/chat-Cp_1ebzU.js +1 -0
  63. package/payload/server/public/assets/chevron-left-S7Nk-se6.js +1 -0
  64. package/payload/server/public/assets/data-BshlTgIW.js +1 -0
  65. package/payload/server/public/assets/{graph-CDOJZepc.js → graph-Dvru9CW9.js} +1 -1
  66. package/payload/server/public/assets/{graph-labels-6HKBjyXX.js → graph-labels-DLhR6Rit.js} +1 -1
  67. package/payload/server/public/assets/{maximize-2-DWmyXX8h.js → maximize-2-CyPKq7HZ.js} +1 -1
  68. package/payload/server/public/assets/{operator-D-s7jVnf.js → operator-Ql5iQXB-.js} +1 -1
  69. package/payload/server/public/assets/page-ClJOW2fk.js +32 -0
  70. package/payload/server/public/assets/{page-rDhp8G9q.js → page-YsTtZsn6.js} +1 -1
  71. package/payload/server/public/assets/{public-zK67N3z2.js → public-BlDxwcmS.js} +1 -1
  72. package/payload/server/public/assets/{rotate-ccw-BzBJxqBN.js → rotate-ccw-DSynJPZX.js} +1 -1
  73. package/payload/server/public/assets/{useSubAccountSwitcher-B-GuP1jj.css → useSubAccountSwitcher-CJelLQK-.css} +1 -1
  74. package/payload/server/public/browser.html +4 -4
  75. package/payload/server/public/calendar.html +5 -5
  76. package/payload/server/public/chat.html +9 -9
  77. package/payload/server/public/data.html +8 -8
  78. package/payload/server/public/graph.html +9 -9
  79. package/payload/server/public/index.html +11 -11
  80. package/payload/server/public/operator.html +11 -11
  81. package/payload/server/public/public.html +9 -9
  82. package/payload/server/server.js +82 -63
  83. package/payload/server/public/assets/chat-Qpyg5t65.js +0 -1
  84. package/payload/server/public/assets/chevron-left-DBUeR2CX.js +0 -1
  85. package/payload/server/public/assets/data-Ch5bne41.js +0 -1
  86. package/payload/server/public/assets/page-BcxrdxYL.js +0 -32
  87. /package/payload/server/public/assets/{useSubAccountSwitcher-Bg0RH3Gk.js → useSubAccountSwitcher-DGKQxXDD.js} +0 -0
@@ -10,7 +10,19 @@
10
10
  // package so `node --test dist/__tests__/*.test.js` picks it up after build.
11
11
  import test from "node:test";
12
12
  import assert from "node:assert/strict";
13
- import { renderPlist, buildSessionManagerWrapper, buildHeartbeatWrapper } from "../launchd-plist.js";
13
+ import { renderPlist, buildSessionManagerWrapper, buildHeartbeatWrapper, buildNeo4jWrapper } from "../launchd-plist.js";
14
+ test("neo4j wrapper exports NEO4J_CONF, prepends brew bins, execs neo4j console", () => {
15
+ const w = buildNeo4jWrapper({ confDir: "/Users/x/.realagent-code/neo4j/conf", brewPrefix: "/usr/local" });
16
+ assert.match(w, /^#!\/bin\/bash/);
17
+ assert.match(w, /export NEO4J_CONF="\/Users\/x\/\.realagent-code\/neo4j\/conf"/);
18
+ assert.match(w, /export PATH="\/usr\/local\/bin:\/usr\/local\/sbin:\$PATH"/);
19
+ assert.match(w, /exec \/usr\/local\/bin\/neo4j console\n$/);
20
+ });
21
+ test("neo4j wrapper honours the Apple Silicon brew prefix", () => {
22
+ const w = buildNeo4jWrapper({ confDir: "/Users/x/.sitedesk-code/neo4j/conf", brewPrefix: "/opt/homebrew" });
23
+ assert.match(w, /export PATH="\/opt\/homebrew\/bin:\/opt\/homebrew\/sbin:\$PATH"/);
24
+ assert.match(w, /exec \/opt\/homebrew\/bin\/neo4j console\n$/);
25
+ });
14
26
  // ---------------------------------------------------------------------------
15
27
  // Standard render — every required field present, no escaping needed.
16
28
  // ---------------------------------------------------------------------------
@@ -0,0 +1,52 @@
1
+ // Contract grid for the pure darwin dedicated-Neo4j resolvers. No fs/spawn —
2
+ // the wrapper in index.ts (setupDedicatedNeo4jDarwin / resetNeo4jAuth) owns the
3
+ // mkdir/cp/sed/launchctl. Runs via Node's built-in test runner.
4
+ import test from "node:test";
5
+ import assert from "node:assert/strict";
6
+ import { resolveDarwinDedicatedNeo4jPaths, resolveDarwinDedicatedNeo4jConfKeys, } from "../neo4j-darwin-dedicated.js";
7
+ test("paths derive the full per-brand neo4j tree + brew source/bins", () => {
8
+ const p = resolveDarwinDedicatedNeo4jPaths({ persistDir: "/Users/x/.realagent-code", brewPrefix: "/usr/local" });
9
+ assert.deepEqual(p, {
10
+ home: "/Users/x/.realagent-code/neo4j",
11
+ dataDir: "/Users/x/.realagent-code/neo4j/data",
12
+ confDir: "/Users/x/.realagent-code/neo4j/conf",
13
+ logsDir: "/Users/x/.realagent-code/neo4j/logs",
14
+ runDir: "/Users/x/.realagent-code/neo4j/run",
15
+ pluginsDir: "/Users/x/.realagent-code/neo4j/plugins",
16
+ importDir: "/Users/x/.realagent-code/neo4j/import",
17
+ srcConfDir: "/usr/local/opt/neo4j/libexec/conf",
18
+ neo4jBin: "/usr/local/bin/neo4j",
19
+ neo4jAdminBin: "/usr/local/bin/neo4j-admin",
20
+ });
21
+ });
22
+ test("home equals resolveNeo4jDataDir's darwin-dedicated root (reset-auth parity)", () => {
23
+ // resetNeo4jAuth clears `${dataDir}/data/...`, where its dataDir arg is `home`.
24
+ const p = resolveDarwinDedicatedNeo4jPaths({ persistDir: "/Users/x/.realagent-code", brewPrefix: "/usr/local" });
25
+ assert.equal(p.dataDir, `${p.home}/data`);
26
+ });
27
+ test("Apple Silicon prefix flows into src conf + bins", () => {
28
+ const p = resolveDarwinDedicatedNeo4jPaths({ persistDir: "/Users/x/.sitedesk-code", brewPrefix: "/opt/homebrew" });
29
+ assert.equal(p.srcConfDir, "/opt/homebrew/opt/neo4j/libexec/conf");
30
+ assert.equal(p.neo4jBin, "/opt/homebrew/bin/neo4j");
31
+ assert.equal(p.neo4jAdminBin, "/opt/homebrew/bin/neo4j-admin");
32
+ });
33
+ test("conf keys: bolt/http listen + five absolute directories, http = port-213", () => {
34
+ const p = resolveDarwinDedicatedNeo4jPaths({ persistDir: "/Users/x/.realagent-code", brewPrefix: "/usr/local" });
35
+ const keys = resolveDarwinDedicatedNeo4jConfKeys({
36
+ port: 7688, dataDir: p.dataDir, logsDir: p.logsDir, runDir: p.runDir, pluginsDir: p.pluginsDir, importDir: p.importDir,
37
+ });
38
+ assert.deepEqual(keys, [
39
+ ["server.bolt.listen_address", ":7688"],
40
+ ["server.http.listen_address", ":7475"],
41
+ ["server.directories.data", "/Users/x/.realagent-code/neo4j/data"],
42
+ ["server.directories.logs", "/Users/x/.realagent-code/neo4j/logs"],
43
+ ["server.directories.run", "/Users/x/.realagent-code/neo4j/run"],
44
+ ["server.directories.plugins", "/Users/x/.realagent-code/neo4j/plugins"],
45
+ ["server.directories.import", "/Users/x/.realagent-code/neo4j/import"],
46
+ ]);
47
+ });
48
+ test("sitedesk port maps http to 7476", () => {
49
+ const keys = resolveDarwinDedicatedNeo4jConfKeys({ port: 7689, dataDir: "d", logsDir: "l", runDir: "r", pluginsDir: "p", importDir: "i" });
50
+ assert.equal(keys[0][1], ":7689");
51
+ assert.equal(keys[1][1], ":7476");
52
+ });
@@ -1,14 +1,15 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import { resolveNeo4jDataDir } from "../neo4j-datadir.js";
4
- test("darwin resolves the Homebrew datadir under the brew prefix (Intel + Apple Silicon)", () => {
5
- assert.equal(resolveNeo4jDataDir({ platform: "darwin", brewPrefix: "/usr/local", dedicated: false, hostname: "maxy-code" }), "/usr/local/var/neo4j");
6
- assert.equal(resolveNeo4jDataDir({ platform: "darwin", brewPrefix: "/opt/homebrew", dedicated: false, hostname: "maxy-code" }), "/opt/homebrew/var/neo4j");
4
+ test("darwin shared resolves the Homebrew datadir under the brew prefix (Intel + Apple Silicon)", () => {
5
+ assert.equal(resolveNeo4jDataDir({ platform: "darwin", brewPrefix: "/usr/local", dedicated: false, hostname: "maxy-code", persistDir: "/Users/x/.maxy-code" }), "/usr/local/var/neo4j");
6
+ assert.equal(resolveNeo4jDataDir({ platform: "darwin", brewPrefix: "/opt/homebrew", dedicated: false, hostname: "maxy-code", persistDir: "/Users/x/.maxy-code" }), "/opt/homebrew/var/neo4j");
7
7
  });
8
- test("darwin ignores dedicated only the shared Homebrew instance exists on darwin", () => {
9
- assert.equal(resolveNeo4jDataDir({ platform: "darwin", brewPrefix: "/usr/local", dedicated: true, hostname: "realagent-code" }), "/usr/local/var/neo4j");
8
+ test("darwin dedicated resolves a per-brand datadir under the persist tree", () => {
9
+ assert.equal(resolveNeo4jDataDir({ platform: "darwin", brewPrefix: "/usr/local", dedicated: true, hostname: "realagent-code", persistDir: "/Users/x/.realagent-code" }), "/Users/x/.realagent-code/neo4j");
10
+ assert.equal(resolveNeo4jDataDir({ platform: "darwin", brewPrefix: "/opt/homebrew", dedicated: true, hostname: "sitedesk-code", persistDir: "/Users/x/.sitedesk-code" }), "/Users/x/.sitedesk-code/neo4j");
10
11
  });
11
12
  test("linux shared and dedicated datadirs are unchanged", () => {
12
- assert.equal(resolveNeo4jDataDir({ platform: "linux", brewPrefix: "", dedicated: false, hostname: "maxy-code" }), "/var/lib/neo4j");
13
- assert.equal(resolveNeo4jDataDir({ platform: "linux", brewPrefix: "", dedicated: true, hostname: "realagent" }), "/var/lib/neo4j-realagent");
13
+ assert.equal(resolveNeo4jDataDir({ platform: "linux", brewPrefix: "", dedicated: false, hostname: "maxy-code", persistDir: "/root/.maxy-code" }), "/var/lib/neo4j");
14
+ assert.equal(resolveNeo4jDataDir({ platform: "linux", brewPrefix: "", dedicated: true, hostname: "realagent", persistDir: "/root/.realagent" }), "/var/lib/neo4j-realagent");
14
15
  });
@@ -8,8 +8,24 @@
8
8
  // convention — see peer-brand-detect.test.ts).
9
9
  import test from "node:test";
10
10
  import assert from "node:assert/strict";
11
- import { resolveDedicatedNeo4jTeardown, SHARED_NEO4J_PORT } from "../neo4j-teardown.js";
11
+ import { resolveDedicatedNeo4jTeardown, resolveDarwinDedicatedNeo4jTeardown, SHARED_NEO4J_PORT } from "../neo4j-teardown.js";
12
12
  const HOST = "sitedesk-code";
13
+ test("darwin dedicated → label + plist + per-brand datadir", () => {
14
+ const plan = resolveDarwinDedicatedNeo4jTeardown({
15
+ brandHostname: "sitedesk-code", neo4jPort: 7689, home: "/Users/x", persistDir: "/Users/x/.sitedesk-code",
16
+ });
17
+ assert.deepEqual(plan, {
18
+ label: "com.rubytech.sitedesk-code-neo4j",
19
+ plistPath: "/Users/x/Library/LaunchAgents/com.rubytech.sitedesk-code-neo4j.plist",
20
+ dataDir: "/Users/x/.sitedesk-code/neo4j",
21
+ });
22
+ });
23
+ test("darwin teardown guards: shared 7687 / undefined / empty hostname → null", () => {
24
+ const base = { home: "/Users/x", persistDir: "/Users/x/.maxy-code" };
25
+ assert.equal(resolveDarwinDedicatedNeo4jTeardown({ brandHostname: "maxy-code", neo4jPort: 7687, ...base }), null);
26
+ assert.equal(resolveDarwinDedicatedNeo4jTeardown({ brandHostname: "maxy-code", neo4jPort: undefined, ...base }), null);
27
+ assert.equal(resolveDarwinDedicatedNeo4jTeardown({ brandHostname: "", neo4jPort: 7689, ...base }), null);
28
+ });
13
29
  test("dedicated port → full teardown plan with install-parity names", () => {
14
30
  const plan = resolveDedicatedNeo4jTeardown({ brandHostname: HOST, neo4jPort: 7689 });
15
31
  assert.notEqual(plan, null);
package/dist/index.js CHANGED
@@ -16,7 +16,8 @@ import { requireSupportedPlatform, detectPlatform } from "./platform-detect.js";
16
16
  import { decideClaudeUpgradePrivilege } from "./claude-upgrade-privilege.js";
17
17
  import { resolveNeo4jDataDir } from "./neo4j-datadir.js";
18
18
  import { protectCommands, unprotectCommands, lsattrShowsImmutable } from "./install-immutability.js";
19
- import { renderPlist, buildSessionManagerWrapper, buildHeartbeatWrapper } from "./launchd-plist.js";
19
+ import { renderPlist, buildSessionManagerWrapper, buildHeartbeatWrapper, buildNeo4jWrapper } from "./launchd-plist.js";
20
+ import { resolveDarwinDedicatedNeo4jPaths, resolveDarwinDedicatedNeo4jConfKeys } from "./neo4j-darwin-dedicated.js";
20
21
  import { bootstrapLaunchAgent } from "./launchd-bootstrap.js";
21
22
  import { enumerateClaudeCandidates, pickCanonicalClaude } from "./claude-bin.js";
22
23
  import { installAllBrewPackages } from "./brew-install.js";
@@ -1262,14 +1263,51 @@ function resetNeo4jAuth(port = DEFAULT_NEO4J_PORT, dataDir = "/var/lib/neo4j", p
1262
1263
  `${dataDir}/data/databases/system`,
1263
1264
  `${dataDir}/data/transactions/system`,
1264
1265
  ];
1265
- if (platform === "darwin") {
1266
- // Homebrew Neo4j runs under a user launchd agent (brew services) and its
1267
- // datadir is user-owned no systemctl, no sudo. This branch handles the
1268
- // shared (default-brand) instance. Dedicated brands (realagent-code etc.)
1269
- // set NEO4J_DEDICATED, but their dedicated provisioning (setupDedicatedNeo4j)
1270
- // is Linux-only and aborts a dedicated-brand darwin install upstream, so a
1271
- // non-default port never reaches here — see Task 1396 for darwin dedicated
1272
- // support.
1266
+ if (platform === "darwin" && dedicated) {
1267
+ // Dedicated darwin brand: the per-brand launchd-supervised instance (Task
1268
+ // 1396). No brew services (that is the shared instance) bootout the
1269
+ // brand's LaunchAgent, clear its auth db (clearPaths are rooted at the
1270
+ // per-brand dataDir), re-seed the password against the per-brand NEO4J_CONF,
1271
+ // and bootstrap the LaunchAgent back. The trailing bolt-port health loop
1272
+ // below already targets `port`.
1273
+ const brewPrefix = resolveBrewPrefix();
1274
+ const persistDir = resolve(process.env.HOME ?? "/", BRAND.configDir);
1275
+ const p = resolveDarwinDedicatedNeo4jPaths({ persistDir, brewPrefix });
1276
+ const label = `${launchdLabel()}-neo4j`;
1277
+ console.log(" Resetting dedicated Neo4j auth (launchd)...");
1278
+ spawnSync("launchctl", ["bootout", `${gui()}/${label}`], { stdio: "pipe" });
1279
+ // launchctl bootout returns once the job leaves the domain, but neo4j does a
1280
+ // graceful multi-second store flush on SIGTERM — so the JVM can still hold
1281
+ // the port (and the store) after bootout returns. Wait for the bolt port to
1282
+ // free before clearing the auth db and re-seeding: set-initial-password only
1283
+ // takes effect against a stopped store, and rm must not race a live flush.
1284
+ // Unlike the Linux sibling's synchronous `systemctl stop`, darwin needs this
1285
+ // explicit wait. Bounded — after 30s, proceed anyway (loud path continues to
1286
+ // the health check below).
1287
+ for (let i = 0; i < 15; i++) {
1288
+ const c = spawnSync("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN"], { stdio: "pipe", timeout: 5000 });
1289
+ if (!(c.status === 0 && (c.stdout?.toString() ?? "").includes("LISTEN")))
1290
+ break;
1291
+ spawnSync("sleep", ["2"]);
1292
+ }
1293
+ spawnSync("rm", ["-rf", ...clearPaths], { stdio: "inherit" });
1294
+ const setPw = spawnSync(p.neo4jAdminBin, ["dbms", "set-initial-password", "--", password], {
1295
+ stdio: "inherit",
1296
+ env: { ...process.env, NEO4J_CONF: p.confDir },
1297
+ });
1298
+ if (setPw.status !== 0) {
1299
+ throw new Error(`neo4j-admin set-initial-password failed (exit ${setPw.status}) during dedicated darwin auth reset`);
1300
+ }
1301
+ const plistPath = join(launchAgentsDir(), `${label}.plist`);
1302
+ const boot = bootstrapLaunchAgent((a) => { const r = spawnSync("launchctl", a, { stdio: "pipe", encoding: "utf-8", timeout: 15_000 }); return { status: r.status, stderr: (r.stderr ?? "").toString() }; }, { gui: gui(), label, plistPath, logger: (l) => { console.log(` ${l}`); logFile(` ${l}`); } });
1303
+ if (!boot.ok) {
1304
+ throw new Error(`launchctl bootstrap ${label} failed (exit ${boot.bootstrapStatus}) after ${boot.attempts} attempt(s) during dedicated darwin auth reset: ${boot.stderr}`);
1305
+ }
1306
+ logFile(` [neo4j-darwin-auth-reset] dedicated datadir=${dataDir} conf=${p.confDir} label=${label}`);
1307
+ }
1308
+ else if (platform === "darwin") {
1309
+ // Shared (default-brand) darwin instance under brew services — user-owned
1310
+ // datadir, no systemctl, no sudo.
1273
1311
  console.log(" Resetting Neo4j auth with fresh password (brew services)...");
1274
1312
  spawnSync("brew", ["services", "stop", "neo4j"], { stdio: "inherit" });
1275
1313
  spawnSync("rm", ["-rf", ...clearPaths], { stdio: "inherit" });
@@ -1351,6 +1389,7 @@ function ensureNeo4jPassword() {
1351
1389
  brewPrefix: platform === "darwin" ? resolveBrewPrefix() : "",
1352
1390
  dedicated: NEO4J_DEDICATED,
1353
1391
  hostname: BRAND.hostname,
1392
+ persistDir,
1354
1393
  });
1355
1394
  logFile(`[create-maxy] neo4j-datadir=${dataDir} platform=${platform}`);
1356
1395
  // 1. Same-brand check: if our own password file exists and works, done (upgrade path).
@@ -1408,6 +1447,16 @@ function installNeo4j() {
1408
1447
  // step ends after `brew install neo4j` + initial-password seeding so the
1409
1448
  // payload deploy step finds the shared password file.
1410
1449
  installAllBrewPackages(["neo4j"], logFile);
1450
+ if (NEO4J_DEDICATED) {
1451
+ // Dedicated brands get their own launchd-supervised instance in
1452
+ // setupDedicatedNeo4j() (darwin branch, next in the pipeline). Do NOT seed
1453
+ // or start the shared brew instance here — that would bind a pointless
1454
+ // 7687 and seed a shared password the dedicated path never uses. The brew
1455
+ // binary install above is all a dedicated brand needs from this step.
1456
+ logFile(" [neo4j-darwin] dedicated brand — brew binary installed; dedicated instance provisioned in setupDedicatedNeo4j()");
1457
+ console.log(" Neo4j installed (Homebrew). Dedicated instance will be provisioned next.");
1458
+ return;
1459
+ }
1411
1460
  // Generate strong random password — stored in persistent location (~/{configDir}/)
1412
1461
  const password = randomBytes(24).toString("base64url");
1413
1462
  const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
@@ -1510,6 +1559,124 @@ function peerBrandUsingSystemUnit() {
1510
1559
  peerEnvContents,
1511
1560
  });
1512
1561
  }
1562
+ /**
1563
+ * Darwin dedicated Neo4j (Task 1396): a per-brand launchd-supervised instance
1564
+ * sharing the brew neo4j binary, isolated under `~/.<brand>/neo4j/`. Proven
1565
+ * approach — the brew `neo4j` wrapper force-sets NEO4J_HOME, so isolation is
1566
+ * driven entirely by a per-brand NEO4J_CONF whose server.directories.* are all
1567
+ * absolute (including run, which keeps this instance's pidfile off the shared
1568
+ * one). Copies the brew default conf, delete-then-appends the per-brand keys
1569
+ * (verifying exactly one active line each, the same guard as the Linux path),
1570
+ * seeds the password on first install, writes + bootstraps the LaunchAgent, and
1571
+ * health-checks the brand's bolt port. Idempotent: reruns re-apply conf state and
1572
+ * re-bootstrap without rotating an existing password. Never touches the shared
1573
+ * brew instance on 7687.
1574
+ */
1575
+ function setupDedicatedNeo4jDarwin() {
1576
+ const brewPrefix = resolveBrewPrefix();
1577
+ const persistDir = resolve(process.env.HOME ?? "/", BRAND.configDir);
1578
+ const p = resolveDarwinDedicatedNeo4jPaths({ persistDir, brewPrefix });
1579
+ const label = `${launchdLabel()}-neo4j`;
1580
+ const confPath = `${p.confDir}/neo4j.conf`;
1581
+ const confExists = existsSync(confPath);
1582
+ for (const dir of [p.dataDir, p.logsDir, p.runDir, p.pluginsDir, p.importDir, p.confDir]) {
1583
+ mkdirSync(dir, { recursive: true });
1584
+ }
1585
+ if (!confExists) {
1586
+ console.log(` Setting up dedicated Neo4j for ${BRAND.productName} on bolt://localhost:${NEO4J_PORT}...`);
1587
+ if (!existsSync(p.srcConfDir)) {
1588
+ throw new Error(`Brew Neo4j default conf not found at ${p.srcConfDir}. Is Neo4j installed via Homebrew?`);
1589
+ }
1590
+ // Seed the per-brand conf from the brew default (neo4j.conf + the logs xml).
1591
+ cpSync(p.srcConfDir, p.confDir, { recursive: true });
1592
+ }
1593
+ else {
1594
+ console.log(` Dedicated Neo4j for ${BRAND.productName} already configured at ${p.confDir} — re-applying per-brand state`);
1595
+ }
1596
+ // Delete-then-append each per-brand key. The brew default ships some directory
1597
+ // keys twice (a commented default plus an active /usr/local/var line), so
1598
+ // delete-all-then-append guarantees exactly one active line. BSD sed needs the
1599
+ // empty in-place suffix argument (`-i ''`).
1600
+ const keys = resolveDarwinDedicatedNeo4jConfKeys({
1601
+ port: NEO4J_PORT, dataDir: p.dataDir, logsDir: p.logsDir, runDir: p.runDir, pluginsDir: p.pluginsDir, importDir: p.importDir,
1602
+ });
1603
+ for (const [key, value] of keys) {
1604
+ const keyRe = key.replace(/\./g, "\\.");
1605
+ spawnSync("sed", ["-i", "", `/^#\\{0,1\\}${keyRe}=/d`, confPath], { stdio: "inherit" });
1606
+ appendFileSync(confPath, `${key}=${value}\n`);
1607
+ }
1608
+ // Verify exactly one active line per key — n!=1 aborts BEFORE bootstrap.
1609
+ const activeCounts = [];
1610
+ for (const [key] of keys) {
1611
+ const r = spawnSync("grep", ["-c", `^${key}=`, confPath], { stdio: "pipe" });
1612
+ const n = parseInt((r.stdout?.toString() ?? "0").trim(), 10) || 0;
1613
+ activeCounts.push(`${key}:${n}`);
1614
+ if (n !== 1) {
1615
+ const msg = `[neo4j-conf] FATAL ${key} active_count=${n} in ${confPath}`;
1616
+ console.error(` ${msg}`);
1617
+ logFile(` ${msg}`);
1618
+ throw new Error(msg);
1619
+ }
1620
+ }
1621
+ logFile(` [neo4j-conf] keys_active=${activeCounts.join(",")}`);
1622
+ if (!confExists) {
1623
+ // Set the initial password before first start (first install only — rotating
1624
+ // on a rerun would brick an existing DB whose password is already stored).
1625
+ const password = randomBytes(24).toString("base64url");
1626
+ mkdirSync(persistDir, { recursive: true });
1627
+ writeFileSync(join(persistDir, ".neo4j-password"), password, { mode: 0o600 });
1628
+ const cfgDir = resolve(INSTALL_DIR, "platform/config");
1629
+ mkdirSync(cfgDir, { recursive: true });
1630
+ writeFileSync(join(cfgDir, ".neo4j-password"), password, { mode: 0o600 });
1631
+ const setPw = spawnSync(p.neo4jAdminBin, ["dbms", "set-initial-password", "--", password], {
1632
+ stdio: "inherit",
1633
+ env: { ...process.env, NEO4J_CONF: p.confDir },
1634
+ });
1635
+ if (setPw.status !== 0) {
1636
+ throw new Error(`neo4j-admin set-initial-password failed (exit ${setPw.status}) for dedicated darwin instance`);
1637
+ }
1638
+ }
1639
+ // Write the wrapper + plist and bootstrap the LaunchAgent (bounded retry on the
1640
+ // transient launchctl EIO, same helper installServiceDarwin uses).
1641
+ const wrapperPath = join(persistDir, "neo4j-wrapper.sh");
1642
+ writeFileSync(wrapperPath, buildNeo4jWrapper({ confDir: p.confDir, brewPrefix }));
1643
+ chmodSync(wrapperPath, 0o755);
1644
+ const plist = renderPlist({
1645
+ label,
1646
+ programArguments: ["/bin/bash", wrapperPath],
1647
+ stdoutPath: `${p.logsDir}/neo4j-console.log`,
1648
+ stderrPath: `${p.logsDir}/neo4j-console.log`,
1649
+ keepAlive: true,
1650
+ runAtLoad: true,
1651
+ workingDirectory: p.home,
1652
+ });
1653
+ mkdirSync(launchAgentsDir(), { recursive: true });
1654
+ const plistPath = join(launchAgentsDir(), `${label}.plist`);
1655
+ writeFileSync(plistPath, plist);
1656
+ logFile(` ${plistPath} written (${plist.length} bytes)`);
1657
+ const boot = bootstrapLaunchAgent((a) => { const r = spawnSync("launchctl", a, { stdio: "pipe", encoding: "utf-8", timeout: 15_000 }); return { status: r.status, stderr: (r.stderr ?? "").toString() }; }, { gui: gui(), label, plistPath, logger: (l) => { console.log(` ${l}`); logFile(` ${l}`); } });
1658
+ if (!boot.ok) {
1659
+ throw new Error(`launchctl bootstrap ${label} failed (exit ${boot.bootstrapStatus}) after ${boot.attempts} attempt(s): ${boot.stderr}`);
1660
+ }
1661
+ // Health-check the brand's bolt port — loud failure, no silent fallback to the
1662
+ // shared instance (matches the Linux dedicated bind check).
1663
+ console.log(` Waiting for dedicated Neo4j on port ${NEO4J_PORT}...`);
1664
+ let up = false;
1665
+ for (let i = 0; i < 20; i++) {
1666
+ const c = spawnSync("lsof", ["-nP", `-iTCP:${NEO4J_PORT}`, "-sTCP:LISTEN"], { stdio: "pipe", timeout: 5000 });
1667
+ if (c.status === 0 && (c.stdout?.toString() ?? "").includes("LISTEN")) {
1668
+ up = true;
1669
+ break;
1670
+ }
1671
+ spawnSync("sleep", ["2"]);
1672
+ }
1673
+ if (!up) {
1674
+ logFile(` Neo4j dedicated (darwin): failed to bind port ${NEO4J_PORT}`);
1675
+ throw new Error(`Dedicated Neo4j ${label} did not bind bolt://localhost:${NEO4J_PORT} within 40s. Check ${p.logsDir}/neo4j-console.log`);
1676
+ }
1677
+ logFile(` Neo4j dedicated (darwin): conf=${p.confDir} data=${p.dataDir} label=${label} bolt=:${NEO4J_PORT}`);
1678
+ console.log(` Dedicated Neo4j ready on bolt://localhost:${NEO4J_PORT}`);
1679
+ }
1513
1680
  /**
1514
1681
  * Create a dedicated Neo4j instance for this brand when NEO4J_DEDICATED is true.
1515
1682
  * Produces: separate config dir, data dir, log dir, systemd service, and password.
@@ -1526,6 +1693,13 @@ function peerBrandUsingSystemUnit() {
1526
1693
  function setupDedicatedNeo4j() {
1527
1694
  if (!NEO4J_DEDICATED)
1528
1695
  return;
1696
+ // darwin has no systemd/apt neo4j — provision a per-brand launchd-supervised
1697
+ // instance instead (Task 1396). Linux falls through to the systemd body below.
1698
+ const platform = requireSupportedPlatform(process.platform);
1699
+ if (platform === "darwin") {
1700
+ setupDedicatedNeo4jDarwin();
1701
+ return;
1702
+ }
1529
1703
  const brandSuffix = BRAND.hostname; // e.g., "realagent"
1530
1704
  const confDir = `/etc/neo4j-${brandSuffix}`;
1531
1705
  const dataDir = `/var/lib/neo4j-${brandSuffix}`;
@@ -75,6 +75,27 @@ export function buildHeartbeatWrapper(o) {
75
75
  "",
76
76
  ].join("\n");
77
77
  }
78
+ /**
79
+ * Bash wrapper for the darwin dedicated-Neo4j LaunchAgent (Task 1396). The
80
+ * launchd analogue of the Linux dedicated neo4j systemd unit. The brew `neo4j`
81
+ * wrapper force-sets NEO4J_HOME to the Cellar, so per-brand isolation is driven
82
+ * entirely by NEO4J_CONF — the per-brand conf's absolute server.directories.*
83
+ * (including run) keep the data and pidfile off the shared instance. PATH gets
84
+ * the brew prefix bins so the neo4j launcher's `java` resolves under the bare
85
+ * launchd PATH.
86
+ */
87
+ export function buildNeo4jWrapper(o) {
88
+ return [
89
+ "#!/bin/bash",
90
+ "# generated by create-maxy setupDedicatedNeo4jDarwin().",
91
+ "# Exports NEO4J_CONF (per-brand conf → isolated data/run) and prepends the",
92
+ "# brew prefix bins so java resolves, then execs the brew-wrapped neo4j.",
93
+ `export NEO4J_CONF="${o.confDir}"`,
94
+ `export PATH="${o.brewPrefix}/bin:${o.brewPrefix}/sbin:$PATH"`,
95
+ `exec ${o.brewPrefix}/bin/neo4j console`,
96
+ "",
97
+ ].join("\n");
98
+ }
78
99
  export function renderPlist(spec) {
79
100
  const lines = [];
80
101
  lines.push(`<?xml version="1.0" encoding="UTF-8"?>`);
@@ -0,0 +1,39 @@
1
+ // Pure path + conf-key resolution for the darwin dedicated Neo4j instance
2
+ // (Task 1396). No fs, no spawn — the caller (setupDedicatedNeo4jDarwin in
3
+ // index.ts) wraps these with mkdir/cp/sed/launchctl. Mirrors neo4j-datadir.ts /
4
+ // launchd-plist.ts: inputs in, decision out.
5
+ //
6
+ // The darwin dedicated instance shares the brew neo4j binary but is isolated by
7
+ // a per-brand NEO4J_CONF whose server.directories.* are ALL absolute (including
8
+ // run, so its pidfile never collides with the shared instance — the brew wrapper
9
+ // force-sets NEO4J_HOME, so the Linux NEO4J_HOME trick is unavailable here).
10
+ export function resolveDarwinDedicatedNeo4jPaths(input) {
11
+ const home = `${input.persistDir}/neo4j`;
12
+ return {
13
+ home,
14
+ dataDir: `${home}/data`,
15
+ confDir: `${home}/conf`,
16
+ logsDir: `${home}/logs`,
17
+ runDir: `${home}/run`,
18
+ pluginsDir: `${home}/plugins`,
19
+ importDir: `${home}/import`,
20
+ srcConfDir: `${input.brewPrefix}/opt/neo4j/libexec/conf`,
21
+ neo4jBin: `${input.brewPrefix}/bin/neo4j`,
22
+ neo4jAdminBin: `${input.brewPrefix}/bin/neo4j-admin`,
23
+ };
24
+ }
25
+ /** The conf keys to delete-then-append into the per-brand neo4j.conf. Order is
26
+ * stable for grep-ability. httpPort = port - 213 preserves the 7687/7474 offset
27
+ * (matching the Linux dedicated path). */
28
+ export function resolveDarwinDedicatedNeo4jConfKeys(input) {
29
+ const httpPort = input.port - 213;
30
+ return [
31
+ ["server.bolt.listen_address", `:${input.port}`],
32
+ ["server.http.listen_address", `:${httpPort}`],
33
+ ["server.directories.data", input.dataDir],
34
+ ["server.directories.logs", input.logsDir],
35
+ ["server.directories.run", input.runDir],
36
+ ["server.directories.plugins", input.pluginsDir],
37
+ ["server.directories.import", input.importDir],
38
+ ];
39
+ }
@@ -2,14 +2,18 @@
2
2
  // No spawnSync, no fs — the caller injects the brew prefix (from `brew --prefix`)
3
3
  // on darwin. Peer of the other install decision helpers.
4
4
  //
5
- // darwin: Homebrew installs Neo4j's datadir at `<brew prefix>/var/neo4j`
6
- // (/usr/local on Intel, /opt/homebrew on Apple Silicon), user-owned. Only the
7
- // shared instance exists on darwin dedicated (systemd) instances are Linux
8
- // only so `dedicated` is not consulted here.
5
+ // darwin shared: Homebrew installs Neo4j's datadir at `<brew prefix>/var/neo4j`
6
+ // (/usr/local on Intel, /opt/homebrew on Apple Silicon), user-owned.
7
+ // darwin dedicated: a per-brand launchd-supervised instance isolated under the
8
+ // brand persist tree at `<persistDir>/neo4j` (Task 1396). The brew `neo4j`
9
+ // wrapper force-sets NEO4J_HOME, so isolation is driven by a per-brand
10
+ // NEO4J_CONF with absolute server.directories.*; this root is the data home
11
+ // that the conf's `server.directories.data=<root>/data` and resetNeo4jAuth's
12
+ // `${root}/data/...` clear paths both derive from.
9
13
  // linux: /var/lib/neo4j (shared) or /var/lib/neo4j-<hostname> (dedicated).
10
14
  export function resolveNeo4jDataDir(input) {
11
15
  if (input.platform === "darwin") {
12
- return `${input.brewPrefix}/var/neo4j`;
16
+ return input.dedicated ? `${input.persistDir}/neo4j` : `${input.brewPrefix}/var/neo4j`;
13
17
  }
14
18
  return input.dedicated ? `/var/lib/neo4j-${input.hostname}` : "/var/lib/neo4j";
15
19
  }
@@ -43,3 +43,18 @@ export function resolveDedicatedNeo4jTeardown(args) {
43
43
  logDir: `/var/log/neo4j-${brandHostname}`,
44
44
  };
45
45
  }
46
+ export function resolveDarwinDedicatedNeo4jTeardown(args) {
47
+ const { brandHostname, neo4jPort, home, persistDir } = args;
48
+ if (neo4jPort === undefined)
49
+ return null;
50
+ if (neo4jPort === SHARED_NEO4J_PORT)
51
+ return null;
52
+ if (!brandHostname)
53
+ return null;
54
+ const label = `com.rubytech.${brandHostname}-neo4j`;
55
+ return {
56
+ label,
57
+ plistPath: `${home}/Library/LaunchAgents/${label}.plist`,
58
+ dataDir: `${persistDir}/neo4j`,
59
+ };
60
+ }
package/dist/uninstall.js CHANGED
@@ -5,7 +5,7 @@ import { homedir } from "node:os";
5
5
  import { createInterface } from "node:readline";
6
6
  import { removeBrandStanza, hasAnyBrandStanza } from "./samba-provision.js";
7
7
  import { stripCronBlock } from "./cron-registration.js";
8
- import { resolveDedicatedNeo4jTeardown } from "./neo4j-teardown.js";
8
+ import { resolveDedicatedNeo4jTeardown, resolveDarwinDedicatedNeo4jTeardown, SHARED_NEO4J_PORT } from "./neo4j-teardown.js";
9
9
  import { KNOWN_BRAND_HOSTNAMES } from "./known-brands.js";
10
10
  const HOME = homedir();
11
11
  const PAYLOAD_DIR = resolve(import.meta.dirname, "../payload");
@@ -80,13 +80,18 @@ function launchdLabel() {
80
80
  }
81
81
  /** Every LaunchAgent label installServiceDarwin() creates for this brand: the
82
82
  * main server, the claude-session-manager (Task 1395), and the scheduling
83
- * heartbeat (Task 1403). Uninstall boots out and removes all three. */
83
+ * heartbeat (Task 1403). A non-default (dedicated) brand also runs the
84
+ * per-brand Neo4j LaunchAgent (Task 1396). Uninstall boots out and removes all. */
84
85
  function darwinLaunchdLabels() {
85
- return [
86
+ const labels = [
86
87
  launchdLabel(),
87
88
  `${launchdLabel()}-claude-session-manager`,
88
89
  `${launchdLabel()}-heartbeat`,
89
90
  ];
91
+ if ((BRAND.neo4jPort ?? SHARED_NEO4J_PORT) !== SHARED_NEO4J_PORT) {
92
+ labels.push(`${launchdLabel()}-neo4j`);
93
+ }
94
+ return labels;
90
95
  }
91
96
  function plistPathFor(label) {
92
97
  return resolve(HOME, "Library/LaunchAgents", `${label}.plist`);
@@ -483,12 +488,33 @@ function removeAppDirs() {
483
488
  // ---------------------------------------------------------------------------
484
489
  function removeNeo4jData() {
485
490
  log("5", "Removing Neo4j data...");
486
- // This function's teardown is Linux-shaped: `sudo systemctl daemon-reload`
487
- // and `sudo rm -rf /var/lib/neo4j/*`. On darwin that raises a spurious sudo
488
- // prompt and logs a FAILED. Skip here (Task 1403); darwin Neo4j data teardown
489
- // belongs to Task 1396 (dedicated Neo4j on darwin).
491
+ // darwin: a dedicated brand's Neo4j is a per-brand launchd instance under
492
+ // ~/.<brand>/neo4j (Task 1396). Its LaunchAgent was already booted out in
493
+ // stopServices() via darwinLaunchdLabels(); here we remove the plist and the
494
+ // per-brand home. Never touch the shared brew instance under
495
+ // <brewPrefix>/var/neo4j — the teardown resolver returns null for a shared
496
+ // (7687), unreadable, or hostname-less brand.
497
+ if (isDarwin()) {
498
+ const plan = resolveDarwinDedicatedNeo4jTeardown({
499
+ brandHostname: BRAND.hostname,
500
+ neo4jPort: readNeo4jPortFromEnv() ?? BRAND.neo4jPort,
501
+ home: HOME,
502
+ persistDir: resolve(HOME, BRAND.configDir),
503
+ });
504
+ if (plan) {
505
+ rmSync(plan.plistPath, { force: true });
506
+ rmSync(plan.dataDir, { recursive: true, force: true });
507
+ console.log(` Removed dedicated Neo4j (${plan.label}) + datadir`);
508
+ }
509
+ else {
510
+ console.log(" Neo4j: shared instance — left in place.");
511
+ }
512
+ return;
513
+ }
514
+ // Linux teardown below is systemd-shaped (`sudo systemctl daemon-reload`,
515
+ // `sudo rm -rf /var/lib/neo4j*`).
490
516
  if (!isLinux()) {
491
- console.log(" Not Linux — skipping.");
517
+ console.log(" Unsupported platform — skipping.");
492
518
  return;
493
519
  }
494
520
  // Brand isolation: remove only THIS brand's Neo4j data. Shared
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.404",
3
+ "version": "0.1.406",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: platform-architecture
3
3
  description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
4
- content-hash: sha256:55075f40e6c0ae401f2b98548b0e707b2274f48553193c33a96fd2bd9a2e231b
4
+ content-hash: sha256:69d2ec47598e75fac6f763994a077fd39ccda77767ddb90404dac2d556f5791c
5
5
  brand: maxy-code
6
6
  product-name: Maxy
7
7
  ---
@@ -605,7 +605,7 @@ To run, for example, `realagent-code` alongside the default install, repeat step
605
605
  npx -y @rubytech/create-realagent-code@latest
606
606
  ```
607
607
 
608
- The persist directory becomes `$HOME/.realagent-code`, the LaunchAgent becomes `com.rubytech.realagent-code`, and the admin URL switches to `http://realagent-code.local:<port>`. Every brand has its own isolated tree — there is no shared state, and `CLAUDE_CONFIG_DIR` is always `$HOME/.<brand>/.claude` for that brand, never the default `~/.claude`.
608
+ The persist directory becomes `$HOME/.realagent-code`, the LaunchAgent becomes `com.rubytech.realagent-code`, and the admin URL switches to `http://realagent-code.local:<port>`. Because a non-default brand runs its own Neo4j on a dedicated bolt port (`realagent-code` → 7688, `sitedesk-code` → 7689; the default `maxy-code` keeps 7687), the installer provisions a second, isolated Neo4j supervised by its own LaunchAgent `com.rubytech.realagent-code-neo4j`, with data under `$HOME/.realagent-code/neo4j/`. That instance shares the Homebrew `neo4j` binary but reads a per-brand config, so the default brand's shared Neo4j on 7687 is left untouched. Every brand has its own isolated tree — there is no shared state, and `CLAUDE_CONFIG_DIR` is always `$HOME/.<brand>/.claude` for that brand, never the default `~/.claude`.
609
609
 
610
610
  ## 3. Confirm the LaunchAgent is up
611
611
 
@@ -129,6 +129,8 @@ An `action` invokes one pre-encoded MCP tool with no reasoning. When a due event
129
129
 
130
130
  An event carries **either** an `action` **or** an `agentDispatch`, never both — setting one clears the other. Agent dispatch is stored as `agentChannel`, `agentDestination`, `agentPrompt`. Use `schedule-update` with `agentDispatch` to modify or `agentDispatch: null` to clear. On WhatsApp, a destination bound as an account-manager scopes the spawned session to its sub-account; on Telegram the session runs on the house account.
131
131
 
132
+ A WhatsApp `agentDispatch` destination may therefore be an account manager, not only an admin phone — this is the supported way to send a manager a proactive or scheduled WhatsApp message. The reply is delivered over the house's paired line regardless of which sub-account the session is scoped to; socket ownership never restricts which number the dispatch can reach.
133
+
132
134
  Dispatch results are recorded on the Event node (`lastDispatchResult`, `lastDispatchAt`) exactly as for tool actions. Every heartbeat tick also audits stored agent dispatches and logs `[schedule-audit] op=stale-dispatch` for any whose destination has stopped being an admin or whose bound sub-account is gone, so a binding that rots between scheduling and firing is surfaced before it mis-dispatches.
133
135
 
134
136
  ## Reconciling public bookings (D1 → graph)
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=agent-dispatch-manager-grounding.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-dispatch-manager-grounding.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/agent-dispatch-manager-grounding.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,19 @@
1
+ // Task 1435 — content-presence gate for the scheduling side of the WhatsApp
2
+ // deliverability grounding. `isValidAgentDestination` accepts an account
3
+ // manager (adminPhones OR accountManagers), so scheduled dispatch is the
4
+ // supported way to reach a manager. PLUGIN.md must say so plainly, and deliver-
5
+ // over-the-house-line must be stated, so the agent cites it instead of inventing
6
+ // a line-ownership restriction. Mirrors time-resolve-description.test.ts.
7
+ import { describe, it, expect } from "vitest";
8
+ import { readFileSync } from "fs";
9
+ import { resolve } from "path";
10
+ const PLUGIN = readFileSync(resolve(__dirname, "../../../PLUGIN.md"), "utf8");
11
+ describe("agent-dispatch account-manager grounding", () => {
12
+ it("PLUGIN.md states a WhatsApp dispatch destination may be an account manager", () => {
13
+ expect(PLUGIN).toContain("A WhatsApp `agentDispatch` destination may therefore be an account manager");
14
+ });
15
+ it("PLUGIN.md states delivery is over the house line regardless of scope", () => {
16
+ expect(PLUGIN).toContain("socket ownership never restricts which number the dispatch can reach");
17
+ });
18
+ });
19
+ //# sourceMappingURL=agent-dispatch-manager-grounding.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-dispatch-manager-grounding.test.js","sourceRoot":"","sources":["../../src/__tests__/agent-dispatch-manager-grounding.test.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,yEAAyE;AACzE,yEAAyE;AACzE,gFAAgF;AAChF,iFAAiF;AACjF,0EAA0E;AAC1E,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAE/B,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,oBAAoB,CAAC,EAAE,MAAM,CAAC,CAAC;AAE9E,QAAQ,CAAC,0CAA0C,EAAE,GAAG,EAAE;IACxD,EAAE,CAAC,4EAA4E,EAAE,GAAG,EAAE;QACpF,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CACtB,4EAA4E,CAC7E,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sEAAsE,EAAE,GAAG,EAAE;QAC9E,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CACtB,sEAAsE,CACvE,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=deliverability-grounding.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deliverability-grounding.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/deliverability-grounding.test.ts"],"names":[],"mappings":""}