@vectorize-io/self-driving-agents 0.0.9 → 0.0.11

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 CHANGED
@@ -809,7 +809,96 @@ async function main() {
809
809
  apiToken = claudeConfig.apiToken;
810
810
  }
811
811
  else if (harness === "claude-code") {
812
- // Claude Code: just save content locally, Claude handles the rest via skill
812
+ // Step 1: Ensure Claude Code CLI is available
813
+ try {
814
+ execSync("which claude", { stdio: "pipe" });
815
+ }
816
+ catch {
817
+ p.cancel("Claude Code CLI not found. Install it:\n npm install -g @anthropic-ai/claude-code");
818
+ process.exit(1);
819
+ }
820
+ // Step 2: Ensure marketplace + plugin installed
821
+ const MARKETPLACE_REPO = "vectorize-io/hindsight";
822
+ const MARKETPLACE_NAME = "vectorize-io-hindsight";
823
+ const PLUGIN_NAME = "hindsight-memory";
824
+ let hasMarketplace = false;
825
+ try {
826
+ const out = execSync("claude plugin marketplace list", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
827
+ hasMarketplace = out.includes(MARKETPLACE_NAME) || out.includes(MARKETPLACE_REPO);
828
+ }
829
+ catch { /* ignore */ }
830
+ if (!hasMarketplace) {
831
+ p.log.info("Adding Hindsight marketplace...");
832
+ try {
833
+ execSync(`claude plugin marketplace add ${MARKETPLACE_REPO}`, { stdio: "inherit" });
834
+ p.log.success("Marketplace added");
835
+ }
836
+ catch (err) {
837
+ const msg = err?.stderr?.toString?.()?.trim() || err?.message || String(err);
838
+ if (!msg.includes("already")) {
839
+ p.log.warn(`Failed to add marketplace: ${msg}`);
840
+ }
841
+ }
842
+ }
843
+ let pluginInstalled = false;
844
+ try {
845
+ const out = execSync("claude plugin list", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
846
+ pluginInstalled = out.includes(PLUGIN_NAME);
847
+ }
848
+ catch { /* ignore */ }
849
+ if (!pluginInstalled) {
850
+ p.log.info("Installing hindsight-memory plugin...");
851
+ try {
852
+ execSync(`claude plugin install ${PLUGIN_NAME}@${MARKETPLACE_NAME}`, { stdio: "inherit" });
853
+ p.log.success("Plugin installed");
854
+ }
855
+ catch (err) {
856
+ const msg = err?.stderr?.toString?.()?.trim() || err?.message || String(err);
857
+ p.log.warn(`Failed to install plugin: ${msg}`);
858
+ }
859
+ }
860
+ else {
861
+ // Refresh marketplace catalog and update plugin to latest
862
+ p.log.info("Updating hindsight-memory plugin to latest version...");
863
+ try {
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}`);
874
+ }
875
+ }
876
+ // Step 3: Configure Hindsight connection in ~/.hindsight/claude-code.json
877
+ const ccConfigDir = join(homedir(), ".hindsight");
878
+ const ccConfigPath = join(ccConfigDir, "claude-code.json");
879
+ let ccConfig = {};
880
+ if (existsSync(ccConfigPath)) {
881
+ try {
882
+ ccConfig = JSON.parse(readFileSync(ccConfigPath, "utf-8"));
883
+ }
884
+ catch { /* ignore */ }
885
+ }
886
+ const hasConnection = ccConfig.hindsightApiUrl || ccConfig.llmProvider;
887
+ if (!hasConnection && process.stdin.isTTY) {
888
+ const claudeConfig = await promptClaudeConfig(agentId);
889
+ ccConfig.hindsightApiUrl = claudeConfig.apiUrl;
890
+ ccConfig.hindsightApiToken = claudeConfig.apiToken;
891
+ ccConfig.enableKnowledgeTools = true;
892
+ mkdirSync(ccConfigDir, { recursive: true });
893
+ writeFileSync(ccConfigPath, JSON.stringify(ccConfig, null, 2) + "\n");
894
+ p.log.success(`Hindsight connection saved to ${color.dim(ccConfigPath)}`);
895
+ }
896
+ else if (!ccConfig.enableKnowledgeTools) {
897
+ ccConfig.enableKnowledgeTools = true;
898
+ mkdirSync(ccConfigDir, { recursive: true });
899
+ writeFileSync(ccConfigPath, JSON.stringify(ccConfig, null, 2) + "\n");
900
+ }
901
+ // Step 4: Save content locally for the agent
813
902
  const contentDir = join(homedir(), ".self-driving-agents", "claude-code", agentId);
814
903
  mkdirSync(contentDir, { recursive: true });
815
904
  // Copy content files to the local dir
@@ -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
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectorize-io/self-driving-agents",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "Install self-driving agents with portable memory on any harness",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",