@rubytech/create-realagent-code 0.1.16 → 0.1.18
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__/onboarding-state-readback.test.js +61 -0
- package/dist/__tests__/plugin-install.test.js +105 -0
- package/dist/index.js +175 -0
- package/dist/lib/plugin-install.js +108 -0
- package/dist/onboarding-readback.js +27 -0
- package/package.json +1 -1
- package/payload/platform/config/brand.json +6 -1
- package/payload/platform/plugins/.claude-plugin/marketplace.json +103 -0
- package/payload/platform/plugins/admin/.claude-plugin/plugin.json +17 -0
- package/payload/platform/plugins/anthropic/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/business-assistant/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/cloudflare/.claude-plugin/plugin.json +17 -0
- package/payload/platform/plugins/contacts/.claude-plugin/plugin.json +17 -0
- package/payload/platform/plugins/deep-research/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/docs/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/docs/references/deployment.md +73 -0
- package/payload/platform/plugins/docs/references/platform.md +1 -1
- package/payload/platform/plugins/email/.claude-plugin/plugin.json +17 -0
- package/payload/platform/plugins/linkedin-import/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/memory/.claude-plugin/plugin.json +17 -0
- package/payload/platform/plugins/outlook/.claude-plugin/plugin.json +17 -0
- package/payload/platform/plugins/projects/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/replicate/.claude-plugin/plugin.json +17 -0
- package/payload/platform/plugins/sales/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/scheduling/.claude-plugin/plugin.json +17 -0
- package/payload/platform/plugins/tasks/.claude-plugin/plugin.json +13 -13
- package/payload/platform/plugins/waitlist/.claude-plugin/plugin.json +17 -0
- package/payload/platform/plugins/whatsapp/.claude-plugin/plugin.json +17 -0
- package/payload/platform/plugins/workflows/.claude-plugin/plugin.json +13 -12
- package/payload/platform/scripts/seed-neo4j.sh +35 -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 +13 -2
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +6 -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 +9 -3
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/premium-plugins/.claude-plugin/marketplace.json +18 -0
- package/payload/premium-plugins/real-agency/plugins/.claude-plugin/marketplace.json +63 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/.claude-plugin/plugin.json +8 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/.claude-plugin/plugin.json +8 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/.claude-plugin/plugin.json +8 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/.claude-plugin/plugin.json +8 -0
- package/payload/premium-plugins/real-agency/plugins/estate-onboarding/.claude-plugin/plugin.json +8 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/.claude-plugin/plugin.json +8 -0
- package/payload/premium-plugins/real-agency/plugins/estate-teaching/.claude-plugin/plugin.json +8 -0
- package/payload/premium-plugins/real-agency/plugins/leads/.claude-plugin/plugin.json +8 -0
- package/payload/premium-plugins/real-agency/plugins/listings/.claude-plugin/plugin.json +8 -0
- package/payload/premium-plugins/real-agency/plugins/loop/.claude-plugin/plugin.json +17 -0
- package/payload/premium-plugins/real-agency/plugins/vendors/.claude-plugin/plugin.json +8 -0
- package/payload/premium-plugins/teaching/.claude-plugin/plugin.json +8 -0
- package/payload/premium-plugins/writer-craft/.claude-plugin/plugin.json +8 -0
- package/payload/server/public/assets/{Checkbox-YcNN7jq1.js → Checkbox-BmlrSRF8.js} +1 -1
- package/payload/server/public/assets/admin-Drvl0Okz.js +216 -0
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-96vgHxqG.js → architectureDiagram-Q4EWVU46-DU8MnqOj.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-BdH2Cb_g.js → blockDiagram-DXYQGD6D-DwBZc1na.js} +1 -1
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-N93PeJeZ.js → c4Diagram-AHTNJAMY-C9ZHreVX.js} +1 -1
- package/payload/server/public/assets/channel-Boz4UHN2.js +1 -0
- package/payload/server/public/assets/{chunk-336JU56O--ZhC91-8.js → chunk-336JU56O-DNXqHk42.js} +2 -2
- package/payload/server/public/assets/{chunk-426QAEUC-NreYOhuj.js → chunk-426QAEUC-DOLKkQGn.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-D8tdHM-B.js → chunk-4TB4RGXK-l8iZx-gQ.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-0x7lCysy.js → chunk-5FUZZQ4R-DQwRtUdO.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-B8aFD0v2.js → chunk-5PVQY5BW-CvvoBJnn.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-DcGxS2xv.js → chunk-EDXVE4YY-DV1J4-LR.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-CGa-VGZR.js → chunk-ENJZ2VHE-Co6qpIQl.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-jOlAtTf8.js → chunk-ICPOFSXX-9v9O_mwr.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-DYDZSTbO.js → chunk-OYMX7WX6-D2X1NuKZ.js} +1 -1
- package/payload/server/public/assets/{chunk-U2HBQHQK-CZ-7SbAa.js → chunk-U2HBQHQK-DmqWEsN2.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP-DE9wpb9Q.js → chunk-X2U36JSP-C0b2a6Bq.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-B6klGA8S.js → chunk-YZCP3GAM-BdBAquWg.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-BtC_su1V.js → chunk-ZZ45TVLE-C1AFspLM.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-CRA6E97A.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-VnieTvEm.js +1 -0
- package/payload/server/public/assets/clone-6YhZHo8b.js +1 -0
- package/payload/server/public/assets/{dagre-DJ6RkHVs.js → dagre-7eiQzAHj.js} +1 -1
- package/payload/server/public/assets/{dagre-KV5264BT-BL-5O-QR.js → dagre-KV5264BT-a88ylEXY.js} +1 -1
- package/payload/server/public/assets/data-Dq_if_B4.js +1 -0
- package/payload/server/public/assets/{device-url-actions-BN_tQerV.js → device-url-actions-_s-UgHa3.js} +1 -1
- package/payload/server/public/assets/{diagram-5BDNPKRD-tmZhEAU-.js → diagram-5BDNPKRD-XuINnSy-.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-Cm8Qa00o.js → diagram-G4DWMVQ6-B93iSQ8a.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-gpFqrqSd.js → diagram-MMDJMWI5-Gcoj967k.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-Dhui1_oR.js → diagram-TYMM5635-C2BKnsAF.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-DVwK_CfW.js → erDiagram-SMLLAGMA-DwbOuflI.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-BElR60Ob.js → flowDiagram-DWJPFMVM-CEExhdxC.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-8NGZhLA3.js → ganttDiagram-T4ZO3ILL-kYNYeUK-.js} +1 -1
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-DGVw9ymM.js → gitGraphDiagram-UUTBAWPF-D3X2eJPE.js} +1 -1
- package/payload/server/public/assets/graph-CNFkzAKU.js +1 -0
- package/payload/server/public/assets/{graph-labels-piCPPdFq.js → graph-labels-C6ZZPglH.js} +1 -1
- package/payload/server/public/assets/{graphlib-uE-sp1ee.js → graphlib-_S6i_Jn2.js} +1 -1
- package/payload/server/public/assets/{infoDiagram-42DDH7IO-B-Vuz8L0.js → infoDiagram-42DDH7IO-CQx0vDcC.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-DlcYgX27.js → ishikawaDiagram-UXIWVN3A-0eZUgqUP.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-C-19XR7D.js → journeyDiagram-VCZTEJTY-D-B2Kd_J.js} +1 -1
- package/payload/server/public/assets/jsx-runtime-CtqEPPN5.css +1 -0
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-CEsBB-l2.js → kanban-definition-6JOO6SKY-BmLn-OEz.js} +1 -1
- package/payload/server/public/assets/{line-B_WJJGNO.js → line-h25nWPBw.js} +1 -1
- package/payload/server/public/assets/{mermaid-parser.core-DHnCk5T5.js → mermaid-parser.core-CXXeaSZi.js} +1 -1
- package/payload/server/public/assets/{mermaid.core-DhZqC4uG.js → mermaid.core-BjFfgEHL.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-B2tHowcI.js → mindmap-definition-QFDTVHPH-Dql4ILoK.js} +1 -1
- package/payload/server/public/assets/{page-CjxybpTy.js → page-CdWWweCx.js} +1 -1
- package/payload/server/public/assets/{page-CBA4FUGo.js → page-D20UnO_r.js} +1 -1
- package/payload/server/public/assets/{pieDiagram-DEJITSTG-xopsRxKN.js → pieDiagram-DEJITSTG-nYaoTCKZ.js} +1 -1
- package/payload/server/public/assets/{public-BBqroGXK.js → public-CXgyLdJU.js} +3 -3
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-B19ZbtY0.js → quadrantDiagram-34T5L4WZ-XG8xivm9.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-BMcM9iam.js → requirementDiagram-MS252O5E-C2GzfT-6.js} +1 -1
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-DW5Hs7Av.js → sankeyDiagram-XADWPNL6-3aI78p2X.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-Cn7z1Q3s.js → sequenceDiagram-FGHM5R23-CvaNRYuS.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-DR7At7js.js → stateDiagram-FHFEXIEX-DHT2Zyvj.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-YmtuS9BN.js +1 -0
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-Bm8JLRpB.js → timeline-definition-GMOUNBTQ-DqPjDUku.js} +1 -1
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-6XqALjnd.js → vennDiagram-DHZGUBPP-lQB1LgjH.js} +1 -1
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-DhdYZjeI.js → wardleyDiagram-NUSXRM2D-sxSMxkwm.js} +1 -1
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-CBmEo9Oi.js → xychartDiagram-5P7HB3ND-4-dvvfSD.js} +1 -1
- package/payload/server/public/data.html +5 -5
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +8 -8
- package/payload/server/public/public.html +5 -5
- package/payload/server/server.js +91 -6
- package/payload/server/public/assets/admin-B1JZ0uth.js +0 -216
- package/payload/server/public/assets/channel-TAr3X5oz.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-DjmusLvy.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-TIJEjUyl.js +0 -1
- package/payload/server/public/assets/clone-O8K8fxhJ.js +0 -1
- package/payload/server/public/assets/data-DcMPzQFt.js +0 -1
- package/payload/server/public/assets/graph-oLjrPnCQ.js +0 -1
- package/payload/server/public/assets/jsx-runtime-jZDyLD_2.css +0 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CvqZ4uTP.js +0 -1
- /package/payload/server/public/assets/{jsx-runtime-HrrfyPcI.js → jsx-runtime-BSJRynxp.js} +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Task 033 — install-invariant for OnboardingState seed.
|
|
2
|
+
//
|
|
3
|
+
// The installer re-reads Neo4j after seed-neo4j.sh to verify the
|
|
4
|
+
// OnboardingState node landed. The parser turns cypher-shell's plain-
|
|
5
|
+
// format output into a tri-state outcome consumed by the loud-fail
|
|
6
|
+
// branch in src/index.ts. These tests fence the three outcomes the
|
|
7
|
+
// installer logs name: present / missing / unreachable.
|
|
8
|
+
import test from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { classifyOnboardingReadBack } from "../onboarding-readback.js";
|
|
11
|
+
test("non-zero exit → unreachable, with stderr tail captured", () => {
|
|
12
|
+
const outcome = classifyOnboardingReadBack({
|
|
13
|
+
status: 1,
|
|
14
|
+
stdout: "",
|
|
15
|
+
stderr: "Connection refused: bolt://localhost:7687",
|
|
16
|
+
});
|
|
17
|
+
assert.equal(outcome.kind, "unreachable");
|
|
18
|
+
if (outcome.kind === "unreachable") {
|
|
19
|
+
assert.match(outcome.stderrTail, /Connection refused/);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
test("header row only (no match) → missing", () => {
|
|
23
|
+
// cypher-shell --format plain emits the column header even when zero rows
|
|
24
|
+
// matched. The seed-or-fail contract treats header-only as the loud-fail.
|
|
25
|
+
const outcome = classifyOnboardingReadBack({
|
|
26
|
+
status: 0,
|
|
27
|
+
stdout: "currentStep\n",
|
|
28
|
+
stderr: "",
|
|
29
|
+
});
|
|
30
|
+
assert.equal(outcome.kind, "missing");
|
|
31
|
+
});
|
|
32
|
+
test("data row present → present, currentStep relayed verbatim", () => {
|
|
33
|
+
const outcome = classifyOnboardingReadBack({
|
|
34
|
+
status: 0,
|
|
35
|
+
stdout: "currentStep\n0\n",
|
|
36
|
+
stderr: "",
|
|
37
|
+
});
|
|
38
|
+
assert.equal(outcome.kind, "present");
|
|
39
|
+
if (outcome.kind === "present") {
|
|
40
|
+
assert.equal(outcome.currentStep, "0");
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
test("empty stdout + zero exit → missing (defensive: cypher-shell flushed nothing)", () => {
|
|
44
|
+
const outcome = classifyOnboardingReadBack({
|
|
45
|
+
status: 0,
|
|
46
|
+
stdout: "",
|
|
47
|
+
stderr: "",
|
|
48
|
+
});
|
|
49
|
+
assert.equal(outcome.kind, "missing");
|
|
50
|
+
});
|
|
51
|
+
test("trailing whitespace tolerated on present row", () => {
|
|
52
|
+
const outcome = classifyOnboardingReadBack({
|
|
53
|
+
status: 0,
|
|
54
|
+
stdout: "currentStep\n0 \n\n",
|
|
55
|
+
stderr: "",
|
|
56
|
+
});
|
|
57
|
+
assert.equal(outcome.kind, "present");
|
|
58
|
+
if (outcome.kind === "present") {
|
|
59
|
+
assert.equal(outcome.currentStep, "0");
|
|
60
|
+
}
|
|
61
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Unit tests for the installer's plugin-install pure functions.
|
|
2
|
+
// Tests are ephemeral per the sprint workflow — they prove behaviour
|
|
3
|
+
// during the sprint; the installer's runtime exercise on a real Pi is the
|
|
4
|
+
// permanent verification artifact.
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import assert from "node:assert/strict";
|
|
7
|
+
import { parsePluginList, computeInstallActions, computeConfigureActions, parseExternalPlugins, } from "../lib/plugin-install.js";
|
|
8
|
+
test("parsePluginList extracts name@marketplace tuples", () => {
|
|
9
|
+
const stdout = `
|
|
10
|
+
❯ tasks@maxy-platform
|
|
11
|
+
Version: 0.1.0
|
|
12
|
+
Scope: local
|
|
13
|
+
|
|
14
|
+
❯ telegram@claude-plugins-official
|
|
15
|
+
Version: 1.2.3
|
|
16
|
+
`;
|
|
17
|
+
const out = parsePluginList(stdout);
|
|
18
|
+
assert.deepEqual(out, [
|
|
19
|
+
{ name: "tasks", marketplace: "maxy-platform" },
|
|
20
|
+
{ name: "telegram", marketplace: "claude-plugins-official" },
|
|
21
|
+
]);
|
|
22
|
+
});
|
|
23
|
+
test("parsePluginList returns empty array for empty stdout", () => {
|
|
24
|
+
assert.deepEqual(parsePluginList(""), []);
|
|
25
|
+
assert.deepEqual(parsePluginList("No plugins installed.\n"), []);
|
|
26
|
+
});
|
|
27
|
+
test("computeInstallActions: every desired plugin needs installing when none present", () => {
|
|
28
|
+
const desired = [
|
|
29
|
+
{ name: "admin", marketplace: "maxy-platform" },
|
|
30
|
+
{ name: "telegram", marketplace: "claude-plugins-official" },
|
|
31
|
+
];
|
|
32
|
+
const { toInstall, alreadyInstalled } = computeInstallActions(desired, []);
|
|
33
|
+
assert.equal(toInstall.length, 2);
|
|
34
|
+
assert.equal(alreadyInstalled.length, 0);
|
|
35
|
+
});
|
|
36
|
+
test("computeInstallActions: idempotent skip for exact name@marketplace match", () => {
|
|
37
|
+
const desired = [
|
|
38
|
+
{ name: "admin", marketplace: "maxy-platform" },
|
|
39
|
+
{ name: "telegram", marketplace: "claude-plugins-official" },
|
|
40
|
+
];
|
|
41
|
+
const installed = [
|
|
42
|
+
{ name: "admin", marketplace: "maxy-platform" },
|
|
43
|
+
];
|
|
44
|
+
const { toInstall, alreadyInstalled } = computeInstallActions(desired, installed);
|
|
45
|
+
assert.deepEqual(toInstall.map(p => p.name), ["telegram"]);
|
|
46
|
+
assert.deepEqual(alreadyInstalled.map(p => p.name), ["admin"]);
|
|
47
|
+
});
|
|
48
|
+
test("computeInstallActions: name-only match skips install (legacy install state)", () => {
|
|
49
|
+
const desired = [{ name: "admin", marketplace: "maxy-platform" }];
|
|
50
|
+
const installed = [{ name: "admin", marketplace: "" }];
|
|
51
|
+
const { toInstall, alreadyInstalled } = computeInstallActions(desired, installed);
|
|
52
|
+
assert.equal(toInstall.length, 0);
|
|
53
|
+
assert.equal(alreadyInstalled.length, 1);
|
|
54
|
+
});
|
|
55
|
+
test("computeConfigureActions: skip when no configureSecret declared", () => {
|
|
56
|
+
const installed = [{ name: "admin", marketplace: "maxy-platform" }];
|
|
57
|
+
const actions = computeConfigureActions(installed, {});
|
|
58
|
+
assert.equal(actions.length, 1);
|
|
59
|
+
assert.equal(actions[0].kind, "skip-no-secret-name");
|
|
60
|
+
});
|
|
61
|
+
test("computeConfigureActions: skip when env var absent", () => {
|
|
62
|
+
const installed = [{
|
|
63
|
+
name: "telegram",
|
|
64
|
+
marketplace: "claude-plugins-official",
|
|
65
|
+
configureSecret: "TELEGRAM_BOT_TOKEN",
|
|
66
|
+
}];
|
|
67
|
+
const actions = computeConfigureActions(installed, {});
|
|
68
|
+
assert.equal(actions.length, 1);
|
|
69
|
+
assert.equal(actions[0].kind, "skip-no-secret-value");
|
|
70
|
+
});
|
|
71
|
+
test("computeConfigureActions: configure when env var present", () => {
|
|
72
|
+
const installed = [{
|
|
73
|
+
name: "telegram",
|
|
74
|
+
marketplace: "claude-plugins-official",
|
|
75
|
+
configureSecret: "TELEGRAM_BOT_TOKEN",
|
|
76
|
+
}];
|
|
77
|
+
const actions = computeConfigureActions(installed, { TELEGRAM_BOT_TOKEN: "abc123" });
|
|
78
|
+
assert.equal(actions.length, 1);
|
|
79
|
+
assert.equal(actions[0].kind, "configure");
|
|
80
|
+
if (actions[0].kind === "configure") {
|
|
81
|
+
assert.equal(actions[0].secretValue, "abc123");
|
|
82
|
+
assert.equal(actions[0].plugin.name, "telegram");
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
test("parseExternalPlugins: undefined returns []", () => {
|
|
86
|
+
assert.deepEqual(parseExternalPlugins(undefined), []);
|
|
87
|
+
assert.deepEqual(parseExternalPlugins(null), []);
|
|
88
|
+
});
|
|
89
|
+
test("parseExternalPlugins: well-formed array round-trips", () => {
|
|
90
|
+
const raw = [
|
|
91
|
+
{ name: "telegram", marketplace: "claude-plugins-official", configureSecret: "TELEGRAM_BOT_TOKEN", channelPlugin: true },
|
|
92
|
+
{ name: "discord", marketplace: "claude-plugins-official" },
|
|
93
|
+
];
|
|
94
|
+
const out = parseExternalPlugins(raw);
|
|
95
|
+
assert.deepEqual(out, [
|
|
96
|
+
{ name: "telegram", marketplace: "claude-plugins-official", configureSecret: "TELEGRAM_BOT_TOKEN", channelPlugin: true },
|
|
97
|
+
{ name: "discord", marketplace: "claude-plugins-official" },
|
|
98
|
+
]);
|
|
99
|
+
});
|
|
100
|
+
test("parseExternalPlugins: missing marketplace throws", () => {
|
|
101
|
+
assert.throws(() => parseExternalPlugins([{ name: "telegram" }]));
|
|
102
|
+
});
|
|
103
|
+
test("parseExternalPlugins: missing name throws", () => {
|
|
104
|
+
assert.throws(() => parseExternalPlugins([{ marketplace: "x" }]));
|
|
105
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { existsSync, mkdirSync, writeFileSync, cpSync, readFileSync, rmSync, rea
|
|
|
4
4
|
import { resolve, join, dirname } from "node:path";
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
6
|
import { resolveInstallPortFromFs, buildMaxyUnitFile, buildClaudeSessionManagerUnitFile } from "./port-resolution.js";
|
|
7
|
+
import { classifyOnboardingReadBack } from "./onboarding-readback.js";
|
|
7
8
|
import { parseOsRelease, isUbuntuLike as isUbuntuLikePure, parseAptCacheCandidate, decideAptResolution, } from "./apt-resolve.js";
|
|
8
9
|
import { findPeerBrandOnDefaultNeo4jPort } from "./peer-brand-detect.js";
|
|
9
10
|
import { requireSupportedPlatform } from "./platform-detect.js";
|
|
@@ -12,6 +13,7 @@ import { installAllBrewPackages } from "./brew-install.js";
|
|
|
12
13
|
import { parseSwVers, isSupportedMacosVersion } from "./macos-version.js";
|
|
13
14
|
import { decideChromiumAction, isSnapConfinedPath } from "./snap-chromium.js";
|
|
14
15
|
import { classifyPortHolder } from "./preflight-port-classifier.js";
|
|
16
|
+
import { parsePluginList, computeInstallActions, computeConfigureActions, parseExternalPlugins, } from "./lib/plugin-install.js";
|
|
15
17
|
const PAYLOAD_DIR = resolve(import.meta.dirname, "../payload");
|
|
16
18
|
// Brand manifest — read from payload to derive all brand-specific installation values.
|
|
17
19
|
// The bundler stamps brand.json into the payload at build time.
|
|
@@ -1875,6 +1877,137 @@ function deployPayload() {
|
|
|
1875
1877
|
writeFileSync(join(configDir, `.${BRAND.hostname}-version`), PKG_VERSION, "utf-8");
|
|
1876
1878
|
console.log(` Deployed to ${INSTALL_DIR}`);
|
|
1877
1879
|
}
|
|
1880
|
+
// Task 030 — register the local + external Claude Code plugins after the
|
|
1881
|
+
// payload is on disk. The bundler stamps `.claude-plugin/marketplace.json`
|
|
1882
|
+
// at each plugin-tree root (platform/plugins/, premium-plugins/real-agency/
|
|
1883
|
+
// plugins/, premium-plugins/) so this function discovers them generically
|
|
1884
|
+
// from the install directory rather than hardcoding names.
|
|
1885
|
+
//
|
|
1886
|
+
// Idempotent on every step: marketplace add is skipped when already in
|
|
1887
|
+
// `marketplace list`; `plugin install` is skipped when the plugin is
|
|
1888
|
+
// already in `plugin list` (matched by `<name>@<marketplace>` or by name
|
|
1889
|
+
// alone for legacy install state). One `[plugin-install] <name>@<src>
|
|
1890
|
+
// idempotent=<bool>` log line per attempt; failures log
|
|
1891
|
+
// `[plugin-install] ERROR <name>@<src> exit=<n> stderr=<short>` and do
|
|
1892
|
+
// NOT abort — one plugin failing must not block the rest, mirroring the
|
|
1893
|
+
// marketplace-add behaviour at src/index.ts:1018-1019.
|
|
1894
|
+
function registerLocalAndExternalPlugins() {
|
|
1895
|
+
console.log(" Registering local + external Claude Code plugins...");
|
|
1896
|
+
const localTrees = [];
|
|
1897
|
+
// The three known marketplace.json locations the bundler emits. Missing
|
|
1898
|
+
// ones are silently skipped (e.g. a brand that ships no premium plugins).
|
|
1899
|
+
const candidates = [
|
|
1900
|
+
join(INSTALL_DIR, "platform", "plugins"),
|
|
1901
|
+
join(INSTALL_DIR, "premium-plugins", "real-agency", "plugins"),
|
|
1902
|
+
join(INSTALL_DIR, "premium-plugins"),
|
|
1903
|
+
];
|
|
1904
|
+
for (const dir of candidates) {
|
|
1905
|
+
const mkPath = join(dir, ".claude-plugin", "marketplace.json");
|
|
1906
|
+
if (!existsSync(mkPath))
|
|
1907
|
+
continue;
|
|
1908
|
+
try {
|
|
1909
|
+
const parsed = JSON.parse(readFileSync(mkPath, "utf-8"));
|
|
1910
|
+
if (typeof parsed.name === "string" && parsed.name.length > 0) {
|
|
1911
|
+
localTrees.push({ name: parsed.name, dir });
|
|
1912
|
+
}
|
|
1913
|
+
else {
|
|
1914
|
+
logFile(`[plugin-marketplace] ERROR malformed marketplace at ${mkPath} reason=no-name`);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
catch (err) {
|
|
1918
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1919
|
+
logFile(`[plugin-marketplace] ERROR parse ${mkPath} error=${JSON.stringify(msg.slice(0, 200))}`);
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
// Add each local marketplace. Idempotence via `marketplace list` grep.
|
|
1923
|
+
const mkList = spawnSync("claude", ["plugin", "marketplace", "list"], { stdio: "pipe", encoding: "utf-8" });
|
|
1924
|
+
const mkListed = mkList.stdout ?? "";
|
|
1925
|
+
for (const { name, dir } of localTrees) {
|
|
1926
|
+
if (mkListed.includes(name)) {
|
|
1927
|
+
logFile(`[plugin-marketplace] added ${name} idempotent=true`);
|
|
1928
|
+
continue;
|
|
1929
|
+
}
|
|
1930
|
+
const add = spawnSync("claude", ["plugin", "marketplace", "add", dir], { stdio: "pipe", encoding: "utf-8", timeout: 60_000 });
|
|
1931
|
+
if (add.status === 0) {
|
|
1932
|
+
logFile(`[plugin-marketplace] added ${name} source=${dir} idempotent=false`);
|
|
1933
|
+
}
|
|
1934
|
+
else {
|
|
1935
|
+
const stderrShort = (add.stderr ?? "").split("\n")[0]?.slice(0, 200) ?? "";
|
|
1936
|
+
logFile(`[plugin-marketplace] ERROR add ${name} source=${dir} exit=${add.status} stderr=${JSON.stringify(stderrShort)}`);
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
// Build the desired plugin list = (every local marketplace's plugins) +
|
|
1940
|
+
// (brand.json#externalPlugins). The local entries' `marketplace` field
|
|
1941
|
+
// matches the marketplace.json `name`; the external entries declare
|
|
1942
|
+
// their own.
|
|
1943
|
+
const desired = [];
|
|
1944
|
+
for (const { name: mkName, dir } of localTrees) {
|
|
1945
|
+
const mkPath = join(dir, ".claude-plugin", "marketplace.json");
|
|
1946
|
+
try {
|
|
1947
|
+
const parsed = JSON.parse(readFileSync(mkPath, "utf-8"));
|
|
1948
|
+
for (const p of parsed.plugins ?? []) {
|
|
1949
|
+
if (typeof p.name === "string" && p.name.length > 0) {
|
|
1950
|
+
desired.push({ name: p.name, marketplace: mkName });
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
catch {
|
|
1955
|
+
// Already logged above.
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
let externals = [];
|
|
1959
|
+
try {
|
|
1960
|
+
externals = parseExternalPlugins(BRAND.externalPlugins);
|
|
1961
|
+
}
|
|
1962
|
+
catch (err) {
|
|
1963
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1964
|
+
logFile(`[plugin-install] ERROR brand.externalPlugins parse error=${JSON.stringify(msg.slice(0, 200))}`);
|
|
1965
|
+
}
|
|
1966
|
+
desired.push(...externals);
|
|
1967
|
+
// Snapshot what's installed to compute the install set.
|
|
1968
|
+
const pluginList = spawnSync("claude", ["plugin", "list"], { stdio: "pipe", encoding: "utf-8" });
|
|
1969
|
+
const installed = parsePluginList(pluginList.stdout ?? "");
|
|
1970
|
+
const { toInstall, alreadyInstalled } = computeInstallActions(desired, installed);
|
|
1971
|
+
for (const ref of alreadyInstalled) {
|
|
1972
|
+
logFile(`[plugin-install] ${ref.name}@${ref.marketplace} idempotent=true`);
|
|
1973
|
+
}
|
|
1974
|
+
for (const ref of toInstall) {
|
|
1975
|
+
const install = spawnSync("claude", ["plugin", "install", `${ref.name}@${ref.marketplace}`, "--scope", "user"], { stdio: "pipe", encoding: "utf-8", timeout: 120_000 });
|
|
1976
|
+
if (install.status === 0) {
|
|
1977
|
+
logFile(`[plugin-install] ${ref.name}@${ref.marketplace} idempotent=false`);
|
|
1978
|
+
}
|
|
1979
|
+
else {
|
|
1980
|
+
const stderrShort = (install.stderr ?? "").split("\n")[0]?.slice(0, 200) ?? "";
|
|
1981
|
+
logFile(`[plugin-install] ERROR ${ref.name}@${ref.marketplace} exit=${install.status} stderr=${JSON.stringify(stderrShort)}`);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
// Configure-token pass — only the externals carry configureSecret. For
|
|
1985
|
+
// each, pipe `/<name>:configure <secret>` into a one-shot `claude`
|
|
1986
|
+
// invocation. Missing env vars log SKIP and continue — pairing is a
|
|
1987
|
+
// manual per-operator step per the channels-reference docs.
|
|
1988
|
+
const configureActions = computeConfigureActions(externals, process.env);
|
|
1989
|
+
for (const action of configureActions) {
|
|
1990
|
+
if (action.kind === "skip-no-secret-name") {
|
|
1991
|
+
// Local plugins and externals without configureSecret — nothing to do.
|
|
1992
|
+
continue;
|
|
1993
|
+
}
|
|
1994
|
+
if (action.kind === "skip-no-secret-value") {
|
|
1995
|
+
logFile(`[plugin-configure] SKIP ${action.plugin.name} reason=no-secret-in-env env-var=${action.plugin.configureSecret}`);
|
|
1996
|
+
continue;
|
|
1997
|
+
}
|
|
1998
|
+
// The piped-stdin form: `echo '/<name>:configure <secret>' | claude --print`
|
|
1999
|
+
// is the documented one-shot configure path. We do not log the secret value.
|
|
2000
|
+
const cmd = `/${action.plugin.name}:configure ${action.secretValue}`;
|
|
2001
|
+
const configure = spawnSync("claude", ["--print", "--input-format", "text"], { input: cmd, stdio: ["pipe", "pipe", "pipe"], encoding: "utf-8", timeout: 60_000 });
|
|
2002
|
+
if (configure.status === 0) {
|
|
2003
|
+
logFile(`[plugin-configure] ${action.plugin.name} ok env-var=${action.plugin.configureSecret}`);
|
|
2004
|
+
}
|
|
2005
|
+
else {
|
|
2006
|
+
const stderrShort = (configure.stderr ?? "").split("\n")[0]?.slice(0, 200) ?? "";
|
|
2007
|
+
logFile(`[plugin-configure] ERROR ${action.plugin.name} exit=${configure.status} stderr=${JSON.stringify(stderrShort)}`);
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
1878
2011
|
function buildPlatform() {
|
|
1879
2012
|
log("9", TOTAL, "Installing dependencies and building...");
|
|
1880
2013
|
console.log(` Installing platform dependencies (${join(INSTALL_DIR, "platform")})...`);
|
|
@@ -2157,6 +2290,47 @@ function setupAccount() {
|
|
|
2157
2290
|
logFile(` [neo4j] passing NEO4J_URI=${neo4jUri} to seed`);
|
|
2158
2291
|
shell("bash", [seedScript], { cwd: INSTALL_DIR, env: neo4jEnv });
|
|
2159
2292
|
}
|
|
2293
|
+
// Task 033 — install-invariant: OnboardingState must exist post-seed.
|
|
2294
|
+
// Loud-fails (exit 1) on miss because the first admin session has nothing
|
|
2295
|
+
// to read otherwise: loadOnboardingStep returns -1 (no node), the chat
|
|
2296
|
+
// tries to drive the 9-step flow against missing state, and step
|
|
2297
|
+
// advancements have nowhere to land. Symmetric to Task 904's
|
|
2298
|
+
// [install-invariant] line — log-and-continue isn't enough here because
|
|
2299
|
+
// the gap silently breaks every fresh install. Idempotent on re-runs:
|
|
2300
|
+
// the seed's MERGE leaves the node untouched.
|
|
2301
|
+
const accountId = resolveInstallAccountId();
|
|
2302
|
+
if (accountId) {
|
|
2303
|
+
assertOnboardingStateSeeded(accountId, neo4jUri, password);
|
|
2304
|
+
}
|
|
2305
|
+
else {
|
|
2306
|
+
console.log(" [install-invariant] onboarding-state-check SKIPPED reason=no-account-discovered");
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
// Task 033 — Neo4j read-back assertion. Runs cypher-shell against the
|
|
2310
|
+
// brand's Neo4j and confirms an OnboardingState{accountId} node exists.
|
|
2311
|
+
// Loud-fail exit on miss / Neo4j unreachable so the installer logs name
|
|
2312
|
+
// the exact symptom the next run has to fix.
|
|
2313
|
+
function assertOnboardingStateSeeded(accountId, neo4jUri, password) {
|
|
2314
|
+
const TAG = "[install-invariant]";
|
|
2315
|
+
const cypher = `MATCH (o:OnboardingState {accountId: '${accountId}'}) RETURN o.currentStep AS currentStep;`;
|
|
2316
|
+
const result = spawnSync("cypher-shell", ["-u", "neo4j", "-p", password, "-a", neo4jUri, "--format", "plain", cypher], { stdio: "pipe", encoding: "utf-8", timeout: 10_000 });
|
|
2317
|
+
const outcome = classifyOnboardingReadBack({
|
|
2318
|
+
status: result.status,
|
|
2319
|
+
stdout: result.stdout ?? "",
|
|
2320
|
+
stderr: result.stderr ?? "",
|
|
2321
|
+
});
|
|
2322
|
+
if (outcome.kind === "unreachable") {
|
|
2323
|
+
console.error(` ${TAG} onboarding-state-UNREACHABLE accountId=${accountId} exit=${result.status} stderr=${JSON.stringify(outcome.stderrTail)}`);
|
|
2324
|
+
logFile(` ${TAG} onboarding-state-UNREACHABLE accountId=${accountId} stderr=${outcome.stderrTail}`);
|
|
2325
|
+
throw new Error(`OnboardingState read-back failed: cypher-shell exit=${result.status}`);
|
|
2326
|
+
}
|
|
2327
|
+
if (outcome.kind === "missing") {
|
|
2328
|
+
console.error(` ${TAG} onboarding-state-MISSING accountId=${accountId}`);
|
|
2329
|
+
logFile(` ${TAG} onboarding-state-MISSING accountId=${accountId}`);
|
|
2330
|
+
throw new Error(`OnboardingState missing for accountId=${accountId} after seed-neo4j.sh`);
|
|
2331
|
+
}
|
|
2332
|
+
console.log(` ${TAG} onboarding-state-present accountId=${accountId} currentStep=${outcome.currentStep}`);
|
|
2333
|
+
logFile(` ${TAG} onboarding-state-present accountId=${accountId} currentStep=${outcome.currentStep}`);
|
|
2160
2334
|
}
|
|
2161
2335
|
// ---------------------------------------------------------------------------
|
|
2162
2336
|
// Tunnel script shortcuts
|
|
@@ -3336,6 +3510,7 @@ try {
|
|
|
3336
3510
|
ensureNeo4jPassword(); // Now config/.neo4j-password is available if it existed before
|
|
3337
3511
|
provisionRemoteSessionSecret(); // Task 653: shared HMAC key readable by maxy-edge + maxy-ui
|
|
3338
3512
|
buildPlatform();
|
|
3513
|
+
registerLocalAndExternalPlugins(); // Task 030: install-time plugin registration
|
|
3339
3514
|
setupVncViewer();
|
|
3340
3515
|
setupAccount();
|
|
3341
3516
|
installTunnelScripts(); // ~/setup-tunnel.sh, ~/reset-tunnel.sh — the SKILL contract
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Pure-function support for the installer's plugin-install loop. The runtime
|
|
2
|
+
// callers in src/index.ts wrap these with the actual `spawnSync` / fs reads;
|
|
3
|
+
// keeping the logic side-effect-free here makes the install / configure
|
|
4
|
+
// branches unit-testable without stubbing every shell invocation.
|
|
5
|
+
/**
|
|
6
|
+
* `claude plugin list` prints rows like:
|
|
7
|
+
* ❯ tasks@maxy-platform
|
|
8
|
+
* Version: 0.1.0
|
|
9
|
+
* Scope: local
|
|
10
|
+
*
|
|
11
|
+
* Parse the `<name>@<marketplace>` line so the installer can skip already-
|
|
12
|
+
* installed plugins. Plugins whose row lacks the `@` suffix (legacy
|
|
13
|
+
* installer state) are returned with marketplace='' so a name-only check
|
|
14
|
+
* still de-dupes.
|
|
15
|
+
*/
|
|
16
|
+
export function parsePluginList(stdout) {
|
|
17
|
+
const out = [];
|
|
18
|
+
for (const raw of stdout.split("\n")) {
|
|
19
|
+
const line = raw.replace(/^\s*❯\s+/, "").trim();
|
|
20
|
+
if (!line || !/^[a-z0-9][a-z0-9-]*(@[a-zA-Z0-9-]+)?$/.test(line))
|
|
21
|
+
continue;
|
|
22
|
+
const atIdx = line.indexOf("@");
|
|
23
|
+
if (atIdx < 0)
|
|
24
|
+
out.push({ name: line, marketplace: "" });
|
|
25
|
+
else
|
|
26
|
+
out.push({ name: line.slice(0, atIdx), marketplace: line.slice(atIdx + 1) });
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Given the desired plugins and what's already installed, return the
|
|
32
|
+
* subset that still needs `claude plugin install <name>@<marketplace>`.
|
|
33
|
+
* Idempotence rule: a plugin is "already installed" if either its
|
|
34
|
+
* exact `<name>@<marketplace>` is in the installed set, OR a row exists
|
|
35
|
+
* with the same name and any marketplace (the spawn would re-attach in
|
|
36
|
+
* the operator's chosen scope). This mirrors the marketplace-add
|
|
37
|
+
* idempotence used in src/index.ts:1012.
|
|
38
|
+
*/
|
|
39
|
+
export function computeInstallActions(desired, installed) {
|
|
40
|
+
const byName = new Map();
|
|
41
|
+
for (const i of installed) {
|
|
42
|
+
if (!byName.has(i.name))
|
|
43
|
+
byName.set(i.name, new Set());
|
|
44
|
+
byName.get(i.name).add(i.marketplace);
|
|
45
|
+
}
|
|
46
|
+
const toInstall = [];
|
|
47
|
+
const alreadyInstalled = [];
|
|
48
|
+
for (const d of desired) {
|
|
49
|
+
const marketplaces = byName.get(d.name);
|
|
50
|
+
if (marketplaces && (marketplaces.has(d.marketplace) || marketplaces.has(""))) {
|
|
51
|
+
alreadyInstalled.push(d);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
toInstall.push(d);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { toInstall, alreadyInstalled };
|
|
58
|
+
}
|
|
59
|
+
export function computeConfigureActions(installed, env) {
|
|
60
|
+
const out = [];
|
|
61
|
+
for (const p of installed) {
|
|
62
|
+
if (!p.configureSecret) {
|
|
63
|
+
out.push({ kind: "skip-no-secret-name", plugin: p });
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const value = env[p.configureSecret];
|
|
67
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
68
|
+
out.push({ kind: "skip-no-secret-value", plugin: p });
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
out.push({ kind: "configure", plugin: p, secretValue: value });
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
/** Brand-config validator. brand.json#externalPlugins is optional; when
|
|
76
|
+
* present, each entry must declare name + marketplace as non-empty
|
|
77
|
+
* strings. Other fields are optional. Returns a normalised array; throws
|
|
78
|
+
* on any malformed entry (loud failure at install time beats silent
|
|
79
|
+
* drop). */
|
|
80
|
+
export function parseExternalPlugins(raw) {
|
|
81
|
+
if (raw === undefined || raw === null)
|
|
82
|
+
return [];
|
|
83
|
+
if (!Array.isArray(raw)) {
|
|
84
|
+
throw new Error("brand.json#externalPlugins must be an array");
|
|
85
|
+
}
|
|
86
|
+
const out = [];
|
|
87
|
+
for (const entry of raw) {
|
|
88
|
+
if (!entry || typeof entry !== "object") {
|
|
89
|
+
throw new Error("brand.json#externalPlugins entries must be objects");
|
|
90
|
+
}
|
|
91
|
+
const e = entry;
|
|
92
|
+
if (typeof e.name !== "string" || e.name.length === 0) {
|
|
93
|
+
throw new Error("brand.json#externalPlugins entry missing 'name'");
|
|
94
|
+
}
|
|
95
|
+
if (typeof e.marketplace !== "string" || e.marketplace.length === 0) {
|
|
96
|
+
throw new Error(`brand.json#externalPlugins[${e.name}] missing 'marketplace'`);
|
|
97
|
+
}
|
|
98
|
+
const ref = { name: e.name, marketplace: e.marketplace };
|
|
99
|
+
if (typeof e.configureSecret === "string" && e.configureSecret.length > 0) {
|
|
100
|
+
ref.configureSecret = e.configureSecret;
|
|
101
|
+
}
|
|
102
|
+
if (e.channelPlugin === true) {
|
|
103
|
+
ref.channelPlugin = true;
|
|
104
|
+
}
|
|
105
|
+
out.push(ref);
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Task 033 — pure parser for the OnboardingState read-back assertion.
|
|
2
|
+
//
|
|
3
|
+
// Extracted into a dedicated module (not src/index.ts) so the test
|
|
4
|
+
// suite can import it without triggering index's brand.json-at-load
|
|
5
|
+
// side effect. The installer's loud-fail branches at
|
|
6
|
+
// `assertOnboardingStateSeeded` consume the tri-state outcome.
|
|
7
|
+
/**
|
|
8
|
+
* Classify cypher-shell --format plain output into one of three states:
|
|
9
|
+
*
|
|
10
|
+
* - `unreachable` — non-zero exit (auth, network, syntax). Stderr tail
|
|
11
|
+
* (last 200 chars) is preserved so the install log carries the
|
|
12
|
+
* symptom verbatim.
|
|
13
|
+
* - `missing` — exit 0 but no data row beyond the header. cypher-shell
|
|
14
|
+
* prints the column name even on a zero-match query.
|
|
15
|
+
* - `present` — exit 0 with at least one data row. `currentStep` is the
|
|
16
|
+
* first field on row 2.
|
|
17
|
+
*/
|
|
18
|
+
export function classifyOnboardingReadBack(input) {
|
|
19
|
+
if (input.status !== 0) {
|
|
20
|
+
return { kind: "unreachable", stderrTail: input.stderr.trim().slice(-200) };
|
|
21
|
+
}
|
|
22
|
+
const lines = input.stdout.trim().split("\n").filter((l) => l.length > 0);
|
|
23
|
+
if (lines.length < 2) {
|
|
24
|
+
return { kind: "missing" };
|
|
25
|
+
}
|
|
26
|
+
return { kind: "present", currentStep: lines[1].trim() };
|
|
27
|
+
}
|
package/package.json
CHANGED
|
@@ -51,5 +51,10 @@
|
|
|
51
51
|
"defaultEnabled": ["sales"],
|
|
52
52
|
"available": ["deep-research", "waitlist", "projects", "whatsapp", "replicate", "linkedin-import"],
|
|
53
53
|
"excluded": ["telegram"]
|
|
54
|
-
}
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
"externalPlugins": [
|
|
57
|
+
{ "name": "discord", "marketplace": "claude-plugins-official", "configureSecret": "DISCORD_BOT_TOKEN", "channelPlugin": true },
|
|
58
|
+
{ "name": "imessage", "marketplace": "claude-plugins-official", "channelPlugin": true }
|
|
59
|
+
]
|
|
55
60
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "maxy-platform",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "Rubytech LLC"
|
|
5
|
+
},
|
|
6
|
+
"plugins": [
|
|
7
|
+
{
|
|
8
|
+
"name": "admin",
|
|
9
|
+
"source": "./admin",
|
|
10
|
+
"version": "0.1.0"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "anthropic",
|
|
14
|
+
"source": "./anthropic",
|
|
15
|
+
"version": "0.1.0"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "business-assistant",
|
|
19
|
+
"source": "./business-assistant",
|
|
20
|
+
"version": "0.1.0"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "cloudflare",
|
|
24
|
+
"source": "./cloudflare",
|
|
25
|
+
"version": "0.1.0"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "contacts",
|
|
29
|
+
"source": "./contacts",
|
|
30
|
+
"version": "0.1.0"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "deep-research",
|
|
34
|
+
"source": "./deep-research",
|
|
35
|
+
"version": "0.1.0"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "docs",
|
|
39
|
+
"source": "./docs",
|
|
40
|
+
"version": "0.1.0"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "email",
|
|
44
|
+
"source": "./email",
|
|
45
|
+
"version": "0.1.0"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "linkedin-import",
|
|
49
|
+
"source": "./linkedin-import",
|
|
50
|
+
"version": "0.1.0"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "memory",
|
|
54
|
+
"source": "./memory",
|
|
55
|
+
"version": "0.1.0"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "outlook",
|
|
59
|
+
"source": "./outlook",
|
|
60
|
+
"version": "0.1.0"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "projects",
|
|
64
|
+
"source": "./projects",
|
|
65
|
+
"version": "0.1.0"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "replicate",
|
|
69
|
+
"source": "./replicate",
|
|
70
|
+
"version": "0.1.0"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"name": "sales",
|
|
74
|
+
"source": "./sales",
|
|
75
|
+
"version": "0.1.0"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"name": "scheduling",
|
|
79
|
+
"source": "./scheduling",
|
|
80
|
+
"version": "0.1.0"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"name": "tasks",
|
|
84
|
+
"source": "./tasks",
|
|
85
|
+
"version": "0.1.0"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"name": "waitlist",
|
|
89
|
+
"source": "./waitlist",
|
|
90
|
+
"version": "0.1.0"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "whatsapp",
|
|
94
|
+
"source": "./whatsapp",
|
|
95
|
+
"version": "0.1.0"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "workflows",
|
|
99
|
+
"source": "./workflows",
|
|
100
|
+
"version": "0.1.0"
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
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), brand-settings, account-manage, account-update, 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), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), render-component, session-reset, session-resume, file-attach, wifi, and action-approval tools (action-pending, action-approve, action-reject, action-edit) for managing the Maxy platform.",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Rubytech LLC"
|
|
7
|
+
},
|
|
8
|
+
"mcpServers": {
|
|
9
|
+
"admin": {
|
|
10
|
+
"type": "stdio",
|
|
11
|
+
"command": "node",
|
|
12
|
+
"args": [
|
|
13
|
+
"${CLAUDE_PLUGIN_ROOT}/mcp/dist/index.js"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|