open-mem 0.13.0 → 0.14.0

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/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.14.0] - 2026-02-23
9
+
10
+ ### Added
11
+ - **Interactive setup wizard** — `npx open-mem` now launches a beautiful interactive onboarding wizard (powered by `@clack/prompts`) that guides users through provider selection, dashboard, workflow mode, and context file configuration. Use `--quick` to skip the wizard.
12
+ - **OpenCode session bridge** — when no API key is configured, open-mem automatically routes AI compression through OpenCode's own session model. Zero-config AI compression for all users, regardless of auth method (env vars, OAuth, Copilot, etc.).
13
+ - **Smart provider detection** — at plugin init, queries OpenCode's configured providers via the SDK client and gives actionable guidance instead of generic warnings.
14
+
15
+ ### Changed
16
+ - `chat.message` hook now accepts the new OpenCode model format (`{ providerID, modelID }` object alongside legacy string).
17
+ - Auto-detect provider option updated to reflect automatic OpenCode fallback — no separate API key warning removed.
18
+ - Wizard messaging reflects that AI compression works out-of-the-box without extra configuration.
19
+
8
20
  ## [0.13.0] - 2026-02-23
9
21
 
10
22
  ### Added
package/bin/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // open-mem — Plugin Installer for OpenCode
4
- // Usage: npx open-mem [--global] [--force] [--uninstall] [--dry-run] [--help] [--version]
4
+ // Usage: npx open-mem [--global] [--force] [--quick] [--uninstall] [--dry-run] [--help] [--version]
5
5
 
6
6
  import fs from "node:fs";
7
7
  import os from "node:os";
@@ -36,6 +36,7 @@ const flagDryRun = args.includes("--dry-run");
36
36
  const flagForce = args.includes("--force");
37
37
  const flagHelp = args.includes("--help") || args.includes("-h");
38
38
  const flagVersion = args.includes("--version") || args.includes("-v");
39
+ const flagQuick = args.includes("--quick");
39
40
 
40
41
  // ── unknown flag validation ─────────────────────────────────────────
41
42
  const KNOWN_FLAGS = new Set([
@@ -43,6 +44,7 @@ const KNOWN_FLAGS = new Set([
43
44
  "--uninstall",
44
45
  "--dry-run",
45
46
  "--force",
47
+ "--quick",
46
48
  "--help",
47
49
  "-h",
48
50
  "--version",
@@ -91,7 +93,8 @@ ${BOLD}USAGE${RESET}
91
93
  npx open-mem [flags]
92
94
 
93
95
  ${BOLD}FLAGS${RESET}
94
- ${DIM}(none)${RESET} Add open-mem to local .opencode/opencode.json
96
+ ${DIM}(none)${RESET} Interactive setup wizard (TTY only)
97
+ --quick Skip wizard, install directly (non-interactive)
95
98
  --global Target ~/.config/opencode/opencode.json instead
96
99
  --uninstall Remove open-mem from all discovered config files
97
100
  --dry-run Preview changes without writing anything
@@ -627,19 +630,239 @@ ${BOLD}Next steps:${RESET}
627
630
  process.stdout.write(`\n${BOLD}Docs:${RESET} ${DOCS_URL}\n`);
628
631
  }
629
632
 
633
+ // ── wizard flow ────────────────────────────────────────────────────────
634
+
635
+ async function runWizard() {
636
+ const p = await import("@clack/prompts");
637
+
638
+ p.intro("open-mem — setup wizard");
639
+
640
+ // Detect if plugin is already installed somewhere
641
+ const existingLocal = findUp(process.cwd());
642
+ const globalDir = path.join(os.homedir(), ".config", "opencode");
643
+ const existingGlobal = ["opencode.jsonc", "opencode.json"]
644
+ .map((n) => path.join(globalDir, n))
645
+ .find((p) => fs.existsSync(p));
646
+
647
+ let alreadyInstalled = false;
648
+ for (const cfgPath of [existingLocal, existingGlobal].filter(Boolean)) {
649
+ const cfg = readConfig(cfgPath);
650
+ if (cfg && cfg.data && findPluginEntry(cfg.data) !== -1) {
651
+ alreadyInstalled = true;
652
+ p.log.info(`Plugin already configured in ${cfgPath} — reconfiguring settings.`);
653
+ break;
654
+ }
655
+ }
656
+
657
+ const answers = await p.group({
658
+ scope: () => p.select({
659
+ message: "Where to install?",
660
+ options: [
661
+ { value: "local", label: "This project", hint: ".opencode/opencode.json" },
662
+ { value: "global", label: "Global", hint: "~/.config/opencode/opencode.json" },
663
+ ],
664
+ }),
665
+
666
+ provider: () => p.select({
667
+ message: "AI compression provider",
668
+ options: [
669
+ { value: "auto", label: "Auto-detect", hint: "uses your environment keys, or OpenCode's model as fallback — recommended" },
670
+ { value: "google", label: "Google Gemini", hint: "free tier available" },
671
+ { value: "anthropic", label: "Anthropic", hint: "Claude models" },
672
+ { value: "bedrock", label: "AWS Bedrock", hint: "uses AWS credentials" },
673
+ { value: "openai", label: "OpenAI", hint: "GPT models" },
674
+ { value: "openrouter", label: "OpenRouter", hint: "100+ models" },
675
+ { value: "none", label: "None", hint: "basic metadata only, no AI key needed" },
676
+ ],
677
+ }),
678
+
679
+ dashboard: () => p.confirm({
680
+ message: "Enable web dashboard?",
681
+ initialValue: false,
682
+ }),
683
+
684
+ mode: () => p.select({
685
+ message: "Workflow mode",
686
+ options: [
687
+ { value: "code", label: "Code", hint: "optimized for coding sessions — default" },
688
+ { value: "research", label: "Research", hint: "broader knowledge capture" },
689
+ ],
690
+ }),
691
+
692
+ folderContext: () => p.select({
693
+ message: "Auto-generate context files per folder?",
694
+ options: [
695
+ { value: "AGENTS.md", label: "AGENTS.md", hint: "OpenCode default" },
696
+ { value: "CLAUDE.md", label: "CLAUDE.md", hint: "for Claude Code users" },
697
+ { value: "disabled", label: "Disabled", hint: "no auto-generated files" },
698
+ ],
699
+ }),
700
+ }, {
701
+ onCancel: () => {
702
+ p.cancel("Setup cancelled.");
703
+ process.exit(0);
704
+ },
705
+ });
706
+
707
+ // Apply the configuration
708
+ const s = p.spinner();
709
+
710
+ // 1. Add plugin to opencode config (unless already installed)
711
+ if (!alreadyInstalled) {
712
+ s.start("Adding open-mem to OpenCode config");
713
+ const isGlobal = answers.scope === "global";
714
+
715
+ // Determine target path based on wizard scope answer
716
+ let target;
717
+ if (isGlobal) {
718
+ const gDir = path.join(os.homedir(), ".config", "opencode");
719
+ target = ["opencode.jsonc", "opencode.json"]
720
+ .map((n) => path.join(gDir, n))
721
+ .find((p) => fs.existsSync(p)) || path.join(gDir, "opencode.json");
722
+ } else {
723
+ const found = findUp(process.cwd());
724
+ target = found || path.join(process.cwd(), ".opencode", "opencode.json");
725
+ }
726
+
727
+ const config = readConfig(target);
728
+ if (!config) {
729
+ writeNewConfig(target);
730
+ } else if (config.data) {
731
+ addPluginEntry(target, config.raw, config.data);
732
+ } else {
733
+ p.log.error(`Could not parse ${target} — fix the JSON/JSONC syntax first.`);
734
+ process.exit(1);
735
+ }
736
+ s.stop("Plugin configured");
737
+ }
738
+
739
+ // 2. Write .open-mem/config.json (only if non-default choices)
740
+ const projectConfig = {};
741
+ if (answers.provider !== "auto" && answers.provider !== "none" && answers.provider !== "google") {
742
+ projectConfig.provider = answers.provider;
743
+ }
744
+ if (answers.dashboard) {
745
+ projectConfig.dashboardEnabled = true;
746
+ }
747
+ if (answers.mode !== "code") {
748
+ projectConfig.mode = answers.mode;
749
+ }
750
+ if (answers.folderContext === "disabled") {
751
+ projectConfig.folderContextEnabled = false;
752
+ } else if (answers.folderContext === "CLAUDE.md") {
753
+ projectConfig.folderContextFilename = "CLAUDE.md";
754
+ }
755
+
756
+ if (Object.keys(projectConfig).length > 0) {
757
+ s.start("Writing project config");
758
+ const configDir = path.join(process.cwd(), ".open-mem");
759
+ const configPath = path.join(configDir, "config.json");
760
+
761
+ // Read existing config if present, merge
762
+ let existing = {};
763
+ if (fs.existsSync(configPath)) {
764
+ try { existing = JSON.parse(fs.readFileSync(configPath, "utf8")); } catch {}
765
+ }
766
+
767
+ const merged = { ...existing, ...projectConfig };
768
+
769
+ if (flagDryRun) {
770
+ s.stop("Project config (dry-run)");
771
+ p.log.info(`Would write to ${configPath}:`);
772
+ p.log.message(JSON.stringify(merged, null, 2));
773
+ } else {
774
+ fs.mkdirSync(configDir, { recursive: true });
775
+ fs.writeFileSync(configPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
776
+ s.stop("Project config saved");
777
+ }
778
+ }
779
+
780
+ // 3. Show summary
781
+ const providerLabels = {
782
+ auto: "Auto-detect",
783
+ google: "Google Gemini",
784
+ anthropic: "Anthropic",
785
+ bedrock: "AWS Bedrock",
786
+ openai: "OpenAI",
787
+ openrouter: "OpenRouter",
788
+ none: "None (basic extractor)",
789
+ };
790
+
791
+ const summaryLines = [
792
+ `Provider: ${providerLabels[answers.provider]}`,
793
+ `Dashboard: ${answers.dashboard ? "enabled" : "disabled"}`,
794
+ `Mode: ${answers.mode}`,
795
+ `Context: ${answers.folderContext === "disabled" ? "disabled" : answers.folderContext}`,
796
+ ];
797
+ p.note(summaryLines.join("\n"), "Configuration");
798
+ if (answers.provider === "auto") {
799
+ const detected = detectAIProviders();
800
+ if (detected.length > 0) {
801
+ const names = detected.map((d) => d.name).join(", ");
802
+ p.log.info(`AI compression: ${names} detected \u2014 will use directly`);
803
+ } else {
804
+ p.log.info("AI compression: will use your OpenCode session model automatically");
805
+ p.log.message(" No separate API key needed \u2014 open-mem piggybacks on your active provider.");
806
+ p.log.message(" Optional: set GOOGLE_GENERATIVE_AI_API_KEY for a dedicated (free) compression model.");
807
+ }
808
+ }
809
+
810
+ // 4. Show env var instructions for chosen provider
811
+ const envVarInstructions = {
812
+ auto: null,
813
+ google: "export GOOGLE_GENERATIVE_AI_API_KEY=... # free key: https://aistudio.google.com/apikey",
814
+ anthropic: "export ANTHROPIC_API_KEY=sk-ant-...",
815
+ bedrock: "# Configure AWS credentials (AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY or AWS_PROFILE)",
816
+ openai: "export OPENAI_API_KEY=sk-...",
817
+ openrouter: "export OPENROUTER_API_KEY=sk-or-...",
818
+ none: null,
819
+ };
820
+
821
+ if (envVarInstructions[answers.provider]) {
822
+ p.note(envVarInstructions[answers.provider], "Set this environment variable");
823
+ }
824
+
825
+ if (answers.dashboard) {
826
+ p.log.info("Dashboard will be available at http://localhost:3737");
827
+ }
828
+
829
+ p.outro("open-mem is ready! Start OpenCode to begin.");
830
+ }
831
+
630
832
  // ── main ────────────────────────────────────────────────────────────
631
833
 
632
- async function main() {
633
- const version = getVersion();
834
+ function printHeader(version) {
634
835
  process.stdout.write(
635
836
  `\n${BOLD}open-mem${RESET}${version ? ` ${DIM}v${version}${RESET}` : ""} ${DIM}— Persistent memory plugin for OpenCode${RESET}\n\n`,
636
837
  );
637
-
638
- if (flagDryRun) info(`${YELLOW}Dry-run mode${RESET} — no files will be modified.\n`);
639
-
838
+ }
839
+ async function main() {
840
+ const version = getVersion();
841
+ // Flags that bypass the wizard entirely
640
842
  if (flagUninstall) {
843
+ printHeader(version);
844
+ if (flagDryRun) info(`${YELLOW}Dry-run mode${RESET} — no files will be modified.\n`);
641
845
  await uninstall();
846
+ return;
847
+ }
848
+
849
+ // Interactive wizard: default when TTY and no bypass flags
850
+ const useWizard = process.stdout.isTTY && process.stdin.isTTY && !flagQuick && !flagForce && !flagGlobal;
851
+
852
+ if (useWizard) {
853
+ try {
854
+ await runWizard();
855
+ } catch (e) {
856
+ // If clack fails to load or crashes, fall back to quick install
857
+ warn(`Wizard failed (${e.message}), falling back to quick install.`);
858
+ printHeader(version);
859
+ if (flagDryRun) info(`${YELLOW}Dry-run mode${RESET} — no files will be modified.\n`);
860
+ await install();
861
+ }
642
862
  } else {
863
+ // Quick install (existing behavior)
864
+ printHeader(version);
865
+ if (flagDryRun) info(`${YELLOW}Dry-run mode${RESET} — no files will be modified.\n`);
643
866
  await install();
644
867
  }
645
868
  }
@@ -0,0 +1,27 @@
1
+ import type { LanguageModel } from "ai";
2
+ /**
3
+ * Create a generateText-compatible function that routes through OpenCode's session API.
4
+ *
5
+ * This creates a dedicated background session for open-mem's AI calls,
6
+ * reuses it across all compression/summarization requests, and extracts
7
+ * the text response.
8
+ */
9
+ export declare function createOpenCodeBridge(client: unknown): {
10
+ generateText: (options: {
11
+ model: LanguageModel;
12
+ prompt?: string;
13
+ system?: string;
14
+ maxOutputTokens?: number;
15
+ [key: string]: any;
16
+ }) => Promise<{
17
+ text: string;
18
+ finishReason: string;
19
+ usage: {
20
+ promptTokens: number;
21
+ completionTokens: number;
22
+ totalTokens: number;
23
+ };
24
+ }>;
25
+ cleanup: () => Promise<void>;
26
+ } | null;
27
+ //# sourceMappingURL=opencode-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode-bridge.d.ts","sourceRoot":"","sources":["../../src/ai/opencode-bridge.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAWxC;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO;4BAuCR;QAC1C,KAAK,EAAE,aAAa,CAAC;QACrB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACnB,KAAG,OAAO,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,EAAE,MAAM,CAAC;QACrB,KAAK,EAAE;YAAE,YAAY,EAAE,MAAM,CAAC;YAAC,gBAAgB,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAE,CAAC;KAC/E,CAAC;mBAgCwB,OAAO,CAAC,IAAI,CAAC;SAYvC"}
@@ -22,8 +22,12 @@ export declare function persistChatMessage(input: ChatCaptureInput): boolean;
22
22
  export declare function createChatCaptureHook(observations: ObservationRepository, sessions: SessionRepository, projectPath: string, sensitivePatterns?: string[]): (input: {
23
23
  sessionID: string;
24
24
  agent?: string;
25
- model?: string;
25
+ model?: string | {
26
+ providerID: string;
27
+ modelID: string;
28
+ };
26
29
  messageID?: string;
30
+ variant?: string;
27
31
  }, output: {
28
32
  message: unknown;
29
33
  parts: unknown[];
@@ -1 +1 @@
1
- {"version":3,"file":"chat-capture.d.ts","sourceRoot":"","sources":["../../src/hooks/chat-capture.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AA6CxD,MAAM,WAAW,gBAAgB;IAChC,YAAY,EAAE,qBAAqB,CAAC;IACpC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,8DAA8D;AAC9D,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAgDnE;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACpC,YAAY,EAAE,qBAAqB,EACnC,QAAQ,EAAE,iBAAiB,EAC3B,WAAW,EAAE,MAAM,EACnB,iBAAiB,GAAE,MAAM,EAAO,IAG/B,OAAO;IACN,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB,EACD,QAAQ;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,EAAE,CAAA;CAAE,KAC5C,OAAO,CAAC,IAAI,CAAC,CAqBhB"}
1
+ {"version":3,"file":"chat-capture.d.ts","sourceRoot":"","sources":["../../src/hooks/chat-capture.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AA6CxD,MAAM,WAAW,gBAAgB;IAChC,YAAY,EAAE,qBAAqB,CAAC;IACpC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,8DAA8D;AAC9D,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAgDnE;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACpC,YAAY,EAAE,qBAAqB,EACnC,QAAQ,EAAE,iBAAiB,EAC3B,WAAW,EAAE,MAAM,EACnB,iBAAiB,GAAE,MAAM,EAAO,IAG/B,OAAO;IACN,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,GAAG;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,EACD,QAAQ;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,EAAE,CAAA;CAAE,KAC5C,OAAO,CAAC,IAAI,CAAC,CAqBhB"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAgDA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AA2ClD;;;GAGG;AACH,wBAA8B,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,CA4RvE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAgDA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AA8FlD;;;GAGG;AACH,wBAA8B,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,CAuTvE"}