@vectorize-io/self-driving-agents 0.0.10 → 0.0.12
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/cli.js +46 -19
- package/dist/tests/cli.test.js +139 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -858,13 +858,20 @@ async function main() {
|
|
|
858
858
|
}
|
|
859
859
|
}
|
|
860
860
|
else {
|
|
861
|
-
//
|
|
862
|
-
p.log.info("Updating hindsight-memory plugin...");
|
|
861
|
+
// Refresh marketplace catalog and update plugin to latest
|
|
862
|
+
p.log.info("Updating hindsight-memory plugin to latest version...");
|
|
863
863
|
try {
|
|
864
|
-
execSync(`claude plugin update ${
|
|
865
|
-
|
|
864
|
+
execSync(`claude plugin marketplace update ${MARKETPLACE_NAME}`, { stdio: "pipe" });
|
|
865
|
+
}
|
|
866
|
+
catch { /* ignore — marketplace update is best-effort */ }
|
|
867
|
+
try {
|
|
868
|
+
execSync(`claude plugin update ${PLUGIN_NAME}@${MARKETPLACE_NAME}`, { stdio: "inherit" });
|
|
869
|
+
p.log.success("Plugin updated to latest");
|
|
870
|
+
}
|
|
871
|
+
catch (err) {
|
|
872
|
+
const msg = err?.stderr?.toString?.()?.trim() || err?.message || String(err);
|
|
873
|
+
p.log.warn(`Failed to update plugin: ${msg}`);
|
|
866
874
|
}
|
|
867
|
-
catch { /* ignore */ }
|
|
868
875
|
}
|
|
869
876
|
// Step 3: Configure Hindsight connection in ~/.hindsight/claude-code.json
|
|
870
877
|
const ccConfigDir = join(homedir(), ".hindsight");
|
|
@@ -881,16 +888,35 @@ async function main() {
|
|
|
881
888
|
const claudeConfig = await promptClaudeConfig(agentId);
|
|
882
889
|
ccConfig.hindsightApiUrl = claudeConfig.apiUrl;
|
|
883
890
|
ccConfig.hindsightApiToken = claudeConfig.apiToken;
|
|
884
|
-
ccConfig.enableKnowledgeTools = true;
|
|
885
|
-
mkdirSync(ccConfigDir, { recursive: true });
|
|
886
|
-
writeFileSync(ccConfigPath, JSON.stringify(ccConfig, null, 2) + "\n");
|
|
887
|
-
p.log.success(`Hindsight connection saved to ${color.dim(ccConfigPath)}`);
|
|
888
891
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
892
|
+
// Verify/set bank granularity. SDA agents need per-project isolation —
|
|
893
|
+
// bank derived as agentName::projectBasename. If the user has a conflicting
|
|
894
|
+
// config (dynamicBankId: false with a static bankId), bail with instructions.
|
|
895
|
+
if (ccConfig.dynamicBankId === false && ccConfig.bankId) {
|
|
896
|
+
p.cancel(`Conflicting plugin config in ${ccConfigPath}:\n` +
|
|
897
|
+
` Found: dynamicBankId=false, bankId="${ccConfig.bankId}"\n` +
|
|
898
|
+
` Self-driving agents need dynamicBankId=true with granularity ["agent", "project"].\n` +
|
|
899
|
+
` Remove "dynamicBankId" and "bankId" from the config, or set them to:\n` +
|
|
900
|
+
` "dynamicBankId": true,\n` +
|
|
901
|
+
` "dynamicBankGranularity": ["agent", "project"]`);
|
|
902
|
+
process.exit(1);
|
|
903
|
+
}
|
|
904
|
+
const expectedGranularity = ["agent", "project"];
|
|
905
|
+
const currentGranularity = ccConfig.dynamicBankGranularity;
|
|
906
|
+
if (currentGranularity &&
|
|
907
|
+
JSON.stringify(currentGranularity) !== JSON.stringify(expectedGranularity)) {
|
|
908
|
+
p.cancel(`Conflicting plugin config in ${ccConfigPath}:\n` +
|
|
909
|
+
` Found: dynamicBankGranularity=${JSON.stringify(currentGranularity)}\n` +
|
|
910
|
+
` Self-driving agents require: ${JSON.stringify(expectedGranularity)}\n` +
|
|
911
|
+
` Update the config to match, or remove the field to let the installer set it.`);
|
|
912
|
+
process.exit(1);
|
|
893
913
|
}
|
|
914
|
+
ccConfig.dynamicBankId = true;
|
|
915
|
+
ccConfig.dynamicBankGranularity = expectedGranularity;
|
|
916
|
+
ccConfig.enableKnowledgeTools = true;
|
|
917
|
+
mkdirSync(ccConfigDir, { recursive: true });
|
|
918
|
+
writeFileSync(ccConfigPath, JSON.stringify(ccConfig, null, 2) + "\n");
|
|
919
|
+
p.log.success(`Plugin config: ${color.dim(ccConfigPath)}`);
|
|
894
920
|
// Step 4: Save content locally for the agent
|
|
895
921
|
const contentDir = join(homedir(), ".self-driving-agents", "claude-code", agentId);
|
|
896
922
|
mkdirSync(contentDir, { recursive: true });
|
|
@@ -937,13 +963,14 @@ async function main() {
|
|
|
937
963
|
writeFileSync(userSettingsPath, JSON.stringify(userSettings, null, 2) + "\n");
|
|
938
964
|
p.log.success("Auto-approved hindsight tools in Claude Code");
|
|
939
965
|
}
|
|
940
|
-
const
|
|
941
|
-
const prompt = hasBankTemplate
|
|
942
|
-
? `Use /hindsight-memory:create-agent to create a "${agentId}" agent. Then ingest all files from ${contentDir}/ (skip bank-template.json). Read ${contentDir}/bank-template.json and create the exact mental models (knowledge pages) defined in its "mental_models" array using agent_knowledge_create_page for each one.`
|
|
943
|
-
: `Use /hindsight-memory:create-agent to create a "${agentId}" agent. Then ingest all files from ${contentDir}/ and create 3 knowledge pages that make sense based on the content.`;
|
|
966
|
+
const prompt = `/hindsight-memory:create-agent ${agentId} from ${contentDir}`;
|
|
944
967
|
p.note([
|
|
945
|
-
`${color.
|
|
946
|
-
|
|
968
|
+
`${color.yellow("⚠")} ${color.bold(`Important:`)} the agent's memory is scoped to the directory where you start ${color.cyan("claude")}.`,
|
|
969
|
+
` Always start your Claude Code sessions from the same project directory.`,
|
|
970
|
+
``,
|
|
971
|
+
`${color.dim("1.")} ${color.cyan("cd")} into your project directory`,
|
|
972
|
+
`${color.dim("2.")} Run: ${color.cyan("claude")}`,
|
|
973
|
+
`${color.dim("3.")} Say: ${color.cyan(prompt)}`,
|
|
947
974
|
].join("\n"), "Next steps");
|
|
948
975
|
p.outro(color.green(`'${agentId}' content ready`));
|
|
949
976
|
cleanup?.();
|
package/dist/tests/cli.test.js
CHANGED
|
@@ -527,3 +527,142 @@ describe("harness validation", () => {
|
|
|
527
527
|
expect(SUPPORTED_HARNESSES.includes("")).toBe(false);
|
|
528
528
|
});
|
|
529
529
|
});
|
|
530
|
+
// ── Claude Code plugin lifecycle decisions ──
|
|
531
|
+
describe("claude-code plugin install/update logic", () => {
|
|
532
|
+
// Mirrors the decision logic in cli.ts:
|
|
533
|
+
// - if plugin not in `claude plugin list` output → install
|
|
534
|
+
// - else → run `claude plugin marketplace update` + `claude plugin update`
|
|
535
|
+
function shouldInstall(pluginListOutput, pluginName) {
|
|
536
|
+
return !pluginListOutput.includes(pluginName);
|
|
537
|
+
}
|
|
538
|
+
function shouldUpdate(pluginListOutput, pluginName) {
|
|
539
|
+
return pluginListOutput.includes(pluginName);
|
|
540
|
+
}
|
|
541
|
+
it("installs when plugin not present", () => {
|
|
542
|
+
const out = "Installed plugins:\n ❯ rust-analyzer-lsp@claude-plugins-official\n Version: 1.0.0";
|
|
543
|
+
expect(shouldInstall(out, "hindsight-memory")).toBe(true);
|
|
544
|
+
expect(shouldUpdate(out, "hindsight-memory")).toBe(false);
|
|
545
|
+
});
|
|
546
|
+
it("updates when plugin already present", () => {
|
|
547
|
+
const out = "Installed plugins:\n ❯ hindsight-memory@vectorize-io-hindsight\n Version: 0.5.0";
|
|
548
|
+
expect(shouldInstall(out, "hindsight-memory")).toBe(false);
|
|
549
|
+
expect(shouldUpdate(out, "hindsight-memory")).toBe(true);
|
|
550
|
+
});
|
|
551
|
+
it("detects plugin regardless of installed scope", () => {
|
|
552
|
+
const userScope = " ❯ hindsight-memory@vectorize-io-hindsight\n Scope: user";
|
|
553
|
+
const localScope = " ❯ hindsight-memory@vectorize-io-hindsight\n Scope: local";
|
|
554
|
+
expect(shouldUpdate(userScope, "hindsight-memory")).toBe(true);
|
|
555
|
+
expect(shouldUpdate(localScope, "hindsight-memory")).toBe(true);
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
describe("claude-code marketplace detection", () => {
|
|
559
|
+
// Mirrors the marketplace-add decision: if neither name nor repo is in the
|
|
560
|
+
// `claude plugin marketplace list` output, we run `claude plugin marketplace add`.
|
|
561
|
+
function hasMarketplace(out, name, repo) {
|
|
562
|
+
return out.includes(name) || out.includes(repo);
|
|
563
|
+
}
|
|
564
|
+
const MARKETPLACE_NAME = "vectorize-io-hindsight";
|
|
565
|
+
const MARKETPLACE_REPO = "vectorize-io/hindsight";
|
|
566
|
+
it("detects when marketplace already added by name", () => {
|
|
567
|
+
const out = "Configured marketplaces:\n vectorize-io-hindsight (github: vectorize-io/hindsight)";
|
|
568
|
+
expect(hasMarketplace(out, MARKETPLACE_NAME, MARKETPLACE_REPO)).toBe(true);
|
|
569
|
+
});
|
|
570
|
+
it("detects when marketplace already added by repo", () => {
|
|
571
|
+
const out = "Configured marketplaces:\n some-other-name (github: vectorize-io/hindsight)";
|
|
572
|
+
expect(hasMarketplace(out, MARKETPLACE_NAME, MARKETPLACE_REPO)).toBe(true);
|
|
573
|
+
});
|
|
574
|
+
it("returns false when marketplace not added", () => {
|
|
575
|
+
const out = "Configured marketplaces:\n claude-plugins-official";
|
|
576
|
+
expect(hasMarketplace(out, MARKETPLACE_NAME, MARKETPLACE_REPO)).toBe(false);
|
|
577
|
+
});
|
|
578
|
+
it("returns false on empty marketplace list", () => {
|
|
579
|
+
expect(hasMarketplace("", MARKETPLACE_NAME, MARKETPLACE_REPO)).toBe(false);
|
|
580
|
+
});
|
|
581
|
+
});
|
|
582
|
+
describe("claude-code allowed-tools merge", () => {
|
|
583
|
+
// Mirrors the auto-approve logic that merges entries into ~/.claude/settings.json's allowedTools.
|
|
584
|
+
function mergeAllowed(existing, toAdd) {
|
|
585
|
+
const merged = [...existing];
|
|
586
|
+
let updated = false;
|
|
587
|
+
for (const tool of toAdd) {
|
|
588
|
+
if (!merged.includes(tool)) {
|
|
589
|
+
merged.push(tool);
|
|
590
|
+
updated = true;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return { merged, updated };
|
|
594
|
+
}
|
|
595
|
+
const HINDSIGHT_TOOLS = [
|
|
596
|
+
"mcp__hindsight__*",
|
|
597
|
+
"Skill(hindsight-memory:create-agent)",
|
|
598
|
+
"Bash(ls ~/.self-driving-agents/*)",
|
|
599
|
+
"Bash(cat ~/.self-driving-agents/*)",
|
|
600
|
+
];
|
|
601
|
+
it("adds all tools to empty allowedTools", () => {
|
|
602
|
+
const { merged, updated } = mergeAllowed([], HINDSIGHT_TOOLS);
|
|
603
|
+
expect(updated).toBe(true);
|
|
604
|
+
expect(merged).toEqual(HINDSIGHT_TOOLS);
|
|
605
|
+
});
|
|
606
|
+
it("preserves existing entries", () => {
|
|
607
|
+
const { merged } = mergeAllowed(["Bash(npm *)", "Read"], HINDSIGHT_TOOLS);
|
|
608
|
+
expect(merged).toContain("Bash(npm *)");
|
|
609
|
+
expect(merged).toContain("Read");
|
|
610
|
+
expect(merged).toContain("mcp__hindsight__*");
|
|
611
|
+
});
|
|
612
|
+
it("does not duplicate when already present", () => {
|
|
613
|
+
const existing = [...HINDSIGHT_TOOLS];
|
|
614
|
+
const { merged, updated } = mergeAllowed(existing, HINDSIGHT_TOOLS);
|
|
615
|
+
expect(updated).toBe(false);
|
|
616
|
+
expect(merged).toHaveLength(HINDSIGHT_TOOLS.length);
|
|
617
|
+
});
|
|
618
|
+
it("only adds missing entries", () => {
|
|
619
|
+
const existing = ["mcp__hindsight__*"];
|
|
620
|
+
const { merged, updated } = mergeAllowed(existing, HINDSIGHT_TOOLS);
|
|
621
|
+
expect(updated).toBe(true);
|
|
622
|
+
expect(merged).toHaveLength(HINDSIGHT_TOOLS.length);
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
describe("claude-code Hindsight config persistence", () => {
|
|
626
|
+
// Mirrors the config-write logic: if existing config has a connection
|
|
627
|
+
// (hindsightApiUrl or llmProvider), don't prompt; otherwise prompt.
|
|
628
|
+
function shouldPromptConnection(config) {
|
|
629
|
+
return !config.hindsightApiUrl && !config.llmProvider;
|
|
630
|
+
}
|
|
631
|
+
function applyClaudeConfig(existing, prompted) {
|
|
632
|
+
return {
|
|
633
|
+
...existing,
|
|
634
|
+
hindsightApiUrl: prompted.apiUrl,
|
|
635
|
+
hindsightApiToken: prompted.apiToken,
|
|
636
|
+
enableKnowledgeTools: true,
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
it("prompts on first install (empty config)", () => {
|
|
640
|
+
expect(shouldPromptConnection({})).toBe(true);
|
|
641
|
+
});
|
|
642
|
+
it("skips prompt when hindsightApiUrl already set", () => {
|
|
643
|
+
expect(shouldPromptConnection({ hindsightApiUrl: "https://api.example.com" })).toBe(false);
|
|
644
|
+
});
|
|
645
|
+
it("skips prompt when llmProvider already set (local daemon mode)", () => {
|
|
646
|
+
expect(shouldPromptConnection({ llmProvider: "openai" })).toBe(false);
|
|
647
|
+
});
|
|
648
|
+
it("writes Cloud connection on first install", () => {
|
|
649
|
+
const result = applyClaudeConfig({}, {
|
|
650
|
+
apiUrl: "https://api.hindsight.vectorize.io",
|
|
651
|
+
apiToken: "hsk_abc",
|
|
652
|
+
});
|
|
653
|
+
expect(result.hindsightApiUrl).toBe("https://api.hindsight.vectorize.io");
|
|
654
|
+
expect(result.hindsightApiToken).toBe("hsk_abc");
|
|
655
|
+
expect(result.enableKnowledgeTools).toBe(true);
|
|
656
|
+
});
|
|
657
|
+
it("preserves other settings when applying connection", () => {
|
|
658
|
+
const existing = { debug: true, retainEveryNTurns: 1 };
|
|
659
|
+
const result = applyClaudeConfig(existing, { apiUrl: "https://x.com", apiToken: "t" });
|
|
660
|
+
expect(result.debug).toBe(true);
|
|
661
|
+
expect(result.retainEveryNTurns).toBe(1);
|
|
662
|
+
expect(result.hindsightApiUrl).toBe("https://x.com");
|
|
663
|
+
});
|
|
664
|
+
it("always sets enableKnowledgeTools=true", () => {
|
|
665
|
+
const result = applyClaudeConfig({ enableKnowledgeTools: false }, { apiUrl: "https://x.com", apiToken: "t" });
|
|
666
|
+
expect(result.enableKnowledgeTools).toBe(true);
|
|
667
|
+
});
|
|
668
|
+
});
|