@membank/cli 0.3.0 → 0.4.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.
Files changed (3) hide show
  1. package/README.md +13 -5
  2. package/dist/index.mjs +136 -232
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -33,7 +33,7 @@ Options:
33
33
  --json Machine-readable output
34
34
  ```
35
35
 
36
- Supported harnesses: `claude-code`, `copilot`, `codex`, `opencode`
36
+ Supported harnesses: `claude-code`, `copilot`, `codex`, `opencode` (see `membank setup` for harness-specific setup instructions)
37
37
 
38
38
  ## Commands
39
39
 
@@ -125,6 +125,16 @@ Output session context formatted for a harness. Called automatically by session
125
125
  membank inject --harness claude-code --scope <project-scope>
126
126
  ```
127
127
 
128
+ ### `membank dashboard`
129
+
130
+ Start the web dashboard for browsing and managing memories.
131
+
132
+ ```bash
133
+ membank dashboard
134
+ ```
135
+
136
+ Opens http://localhost:3847 by default. Features: full-text search, filtering by type/scope/pin status, edit memory metadata, view dedup reviews, and storage statistics.
137
+
128
138
  ## Global flags
129
139
 
130
140
  ```
@@ -143,11 +153,9 @@ Starts the stdio MCP server. This is what harnesses connect to — `setup` write
143
153
 
144
154
  ## Session hooks
145
155
 
146
- `setup` installs two hooks:
147
-
148
- **Session start** — calls `membank inject` to prepend pinned memories into the LLM context at the beginning of every session.
156
+ `setup` installs a hook for Claude Code that injects memories at the start of each session:
149
157
 
150
- **Session stop (Claude Code only)** — prompts the LLM to review the session and call `save_memory` for any notable corrections, preferences, or decisions.
158
+ **SessionStart** — calls `membank inject` to prepend pinned memories into the LLM context at the beginning of every session.
151
159
 
152
160
  ## Requirements
153
161
 
package/dist/index.mjs CHANGED
@@ -134,7 +134,7 @@ async function importCommand(filePath, db, formatter, prompt) {
134
134
  }
135
135
  //#endregion
136
136
  //#region src/commands/inject.ts
137
- const MEMORY_GUIDANCE = "[Memory Guidance]: query_memory before answering on topics where past preferences, corrections, or decisions may apply; save_memory when user corrects you, states a preference, makes a decision, or shares something worth retaining across sessions; update_memory to refine an existing memory (query first to find it) or to set pinned=true/false; delete_memory when a memory is wrong or no longer relevant; pin high-value memories that should always appear at session start";
137
+ const MEMORY_GUIDANCE = "[Memory Guidance]: Persistent memory is available via query_memory, save_memory, update_memory, delete_memory. Skipping save_memory when the user gives a correction or preference means they have to repeat themselves next session that is the failure mode to avoid. Skipping query_memory on topics that touch prior decisions means contradicting yourself. Default to saving (type: correction|preference|decision|learning|fact) when in doubt; rely on dedup to handle redundancy. Pin anything that should appear at every session start.";
138
138
  function formatContext(ctx) {
139
139
  const lines = [];
140
140
  const statParts = Object.entries(ctx.stats).filter(([, count]) => count > 0).map(([type, count]) => `${count} ${type}${count !== 1 ? "s" : ""}`);
@@ -160,51 +160,6 @@ function outputAdditionalContext(text, harness, eventName) {
160
160
  }
161
161
  process.stdout.write(`${text}\n`);
162
162
  }
163
- async function readStdin() {
164
- if (process.stdin.isTTY) return "";
165
- return new Promise((resolve) => {
166
- const chunks = [];
167
- const timeout = setTimeout(() => resolve(""), 1e3);
168
- process.stdin.on("data", (chunk) => chunks.push(chunk));
169
- process.stdin.on("end", () => {
170
- clearTimeout(timeout);
171
- resolve(Buffer.concat(chunks).toString("utf8"));
172
- });
173
- process.stdin.on("error", () => {
174
- clearTimeout(timeout);
175
- resolve("");
176
- });
177
- });
178
- }
179
- const FEEDBACK_PATTERNS = [
180
- /\bdon'?t\b/i,
181
- /\bstop\b/i,
182
- /\bnever\b/i,
183
- /\balways\b/i,
184
- /\bremember\b/i,
185
- /\bprefer\b/i,
186
- /\bi (like|want|hate|dislike)\b/i,
187
- /\bfrom now on\b/i,
188
- /\bkeep in mind\b/i,
189
- /\bnote that\b/i,
190
- /\bstop doing\b/i,
191
- /\bstop using\b/i,
192
- /\bthat'?s wrong\b/i,
193
- /\bno,?\s+(actually|that'?s)\b/i,
194
- /\bplease (don'?t|stop|always|never)\b/i
195
- ];
196
- function looksLikeFeedback(prompt) {
197
- return FEEDBACK_PATTERNS.some((p) => p.test(prompt));
198
- }
199
- function isToolFailure(data) {
200
- if (data.hook_event_name === "PostToolUseFailure") return true;
201
- if (typeof data.error_message === "string" && data.error_message.length > 0) return true;
202
- const response = data.tool_result ?? data.tool_response;
203
- if (typeof response === "object" && response !== null) {
204
- if (response.is_error === true) return true;
205
- }
206
- return false;
207
- }
208
163
  async function handleSessionStart(opts) {
209
164
  const projectScope = opts.scope ?? await resolveScope();
210
165
  const db = DatabaseManager.open();
@@ -218,41 +173,8 @@ async function handleSessionStart(opts) {
218
173
  const harness = opts.harness;
219
174
  outputAdditionalContext(text, harness, "SessionStart");
220
175
  }
221
- async function handleUserPrompt(harness) {
222
- const raw = await readStdin();
223
- if (!raw) process.exit(0);
224
- let data;
225
- try {
226
- data = JSON.parse(raw);
227
- } catch {
228
- process.exit(0);
229
- }
230
- if (!looksLikeFeedback(typeof data.prompt === "string" ? data.prompt : "")) process.exit(0);
231
- outputAdditionalContext("User prompt may contain a correction, preference, or decision worth saving. After responding, evaluate: should this be saved as a memory? If yes, call save_memory with the appropriate type (correction/preference/decision/learning) and scope (global or project).", harness, "UserPromptSubmit");
232
- }
233
- async function handleToolFailure(harness) {
234
- const raw = await readStdin();
235
- if (!raw) process.exit(0);
236
- let data;
237
- try {
238
- data = JSON.parse(raw);
239
- } catch {
240
- process.exit(0);
241
- }
242
- if (!isToolFailure(data)) process.exit(0);
243
- outputAdditionalContext(`Tool "${typeof data.tool_name === "string" ? data.tool_name : "unknown"}" failed. If this reveals a non-obvious constraint, environment issue, or repeatable failure pattern, call save_memory with type "learning" to prevent repeating it.`, harness, "PostToolUseFailure");
244
- }
245
176
  async function injectCommand(opts) {
246
- const harness = opts.harness;
247
- const event = opts.event ?? "session-start";
248
- if (event === "user-prompt") {
249
- await handleUserPrompt(harness);
250
- return;
251
- }
252
- if (event === "tool-failure") {
253
- await handleToolFailure(harness);
254
- return;
255
- }
177
+ if (opts.event !== void 0 && opts.event !== "session-start") process.exit(0);
256
178
  await handleSessionStart(opts);
257
179
  }
258
180
  //#endregion
@@ -632,10 +554,10 @@ function findMembankHookCommand(hooks, pattern) {
632
554
  return "";
633
555
  }
634
556
  function containsMembankInject(hooks) {
635
- return findMembankHookCommand(hooks, "@membank/cli inject") !== "";
557
+ return findMembankHookCommand(hooks, "@membank/cli") !== "";
636
558
  }
637
559
  function extractInjectCommand(hooks) {
638
- return findMembankHookCommand(hooks, "@membank/cli inject");
560
+ return findMembankHookCommand(hooks, "@membank/cli");
639
561
  }
640
562
  function filterOutMembank(groups) {
641
563
  return groups.filter((g) => !containsMembankInject(getHooksArray(g)));
@@ -643,154 +565,139 @@ function filterOutMembank(groups) {
643
565
  function filterOutMembankFlat(hooks) {
644
566
  return hooks.filter((h) => !containsMembankInject([h]));
645
567
  }
568
+ function pruneNestedEvent(hooks, eventKey) {
569
+ const existing = hooks[eventKey];
570
+ if (!Array.isArray(existing)) return;
571
+ const cleaned = filterOutMembank(existing);
572
+ if (cleaned.length === 0) delete hooks[eventKey];
573
+ else hooks[eventKey] = cleaned;
574
+ }
575
+ function pruneFlatEvent(hooks, eventKey) {
576
+ const existing = hooks[eventKey];
577
+ if (!Array.isArray(existing)) return;
578
+ const cleaned = filterOutMembankFlat(existing);
579
+ if (cleaned.length === 0) delete hooks[eventKey];
580
+ else hooks[eventKey] = cleaned;
581
+ }
646
582
  const writers = {
647
583
  "claude-code": {
648
- replacement: "npx @membank/cli inject --harness claude-code",
649
- write(resolver, overwrite = false) {
584
+ inspect(resolver) {
585
+ const hooks = readJson(join(resolver.home(), ".claude", "settings.json")).hooks ?? {};
586
+ return {
587
+ status: "ready",
588
+ hooks: [{
589
+ event: "SessionStart",
590
+ command: "npx @membank/cli@latest inject --harness claude-code",
591
+ existingCommand: extractInjectCommand((Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []).flatMap(getHooksArray)) || null
592
+ }]
593
+ };
594
+ },
595
+ write(resolver, events) {
650
596
  const cfgPath = join(resolver.home(), ".claude", "settings.json");
651
597
  const cfg = readJson(cfgPath);
652
- const hooks = cfg.hooks;
653
- const existingSessionStart = Array.isArray(hooks?.SessionStart) ? hooks.SessionStart : [];
654
- const innerHooks = existingSessionStart.flatMap(getHooksArray);
655
- if (!overwrite && containsMembankInject(innerHooks)) return {
656
- status: "already-configured",
657
- existing: extractInjectCommand(innerHooks),
658
- replacement: this.replacement
659
- };
660
- const filteredSessionStart = overwrite ? filterOutMembank(existingSessionStart) : existingSessionStart;
661
- const existingUserPrompt = Array.isArray(hooks?.UserPromptSubmit) ? hooks.UserPromptSubmit : [];
662
- const existingToolFailure = Array.isArray(hooks?.PostToolUseFailure) ? hooks.PostToolUseFailure : [];
598
+ const hooks = cfg.hooks ?? {};
599
+ const newHooks = { ...hooks };
600
+ pruneNestedEvent(newHooks, "UserPromptSubmit");
601
+ pruneNestedEvent(newHooks, "PostToolUseFailure");
602
+ if (events.includes("SessionStart")) newHooks.SessionStart = [...filterOutMembank(Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []), {
603
+ matcher: "",
604
+ hooks: [{
605
+ type: "command",
606
+ command: "npx @membank/cli@latest inject --harness claude-code"
607
+ }]
608
+ }];
663
609
  writeJsonAtomic(cfgPath, {
664
610
  ...cfg,
665
- hooks: {
666
- ...hooks ?? {},
667
- SessionStart: [...filteredSessionStart, {
668
- matcher: "",
669
- hooks: [{
670
- type: "command",
671
- command: "npx @membank/cli inject --harness claude-code"
672
- }]
673
- }],
674
- UserPromptSubmit: [...filterOutMembank(existingUserPrompt), {
675
- matcher: "",
676
- hooks: [{
677
- type: "command",
678
- command: "npx @membank/cli inject --event user-prompt --harness claude-code"
679
- }]
680
- }],
681
- PostToolUseFailure: [...filterOutMembank(existingToolFailure), {
682
- matcher: "",
683
- hooks: [{
684
- type: "command",
685
- command: "npx @membank/cli inject --event tool-failure --harness claude-code"
686
- }]
687
- }]
688
- }
611
+ hooks: newHooks
689
612
  });
690
613
  return { status: "written" };
691
614
  }
692
615
  },
693
616
  "copilot-cli": {
694
- replacement: "npx @membank/cli inject --harness copilot-cli",
695
- write(resolver, overwrite = false) {
617
+ inspect(resolver) {
618
+ const hooks = readJson(join(resolver.home(), ".copilot", "settings.json")).hooks ?? {};
619
+ return {
620
+ status: "ready",
621
+ hooks: [{
622
+ event: "sessionStart",
623
+ command: "npx @membank/cli@latest inject --harness copilot-cli",
624
+ existingCommand: extractInjectCommand(Array.isArray(hooks.sessionStart) ? hooks.sessionStart : []) || null
625
+ }]
626
+ };
627
+ },
628
+ write(resolver, events) {
696
629
  const cfgPath = join(resolver.home(), ".copilot", "settings.json");
697
630
  const cfg = readJson(cfgPath);
698
- const hooks = cfg.hooks;
699
- const existingSessionStart = Array.isArray(hooks?.sessionStart) ? hooks.sessionStart : [];
700
- if (!overwrite && containsMembankInject(existingSessionStart)) return {
701
- status: "already-configured",
702
- existing: extractInjectCommand(existingSessionStart),
703
- replacement: this.replacement
704
- };
705
- const filteredSessionStart = overwrite ? filterOutMembankFlat(existingSessionStart) : existingSessionStart;
706
- const existingUserPrompt = Array.isArray(hooks?.userPromptSubmitted) ? hooks.userPromptSubmitted : [];
707
- const existingToolFailure = Array.isArray(hooks?.postToolUseFailure) ? hooks.postToolUseFailure : [];
631
+ const hooks = cfg.hooks ?? {};
632
+ const newHooks = { ...hooks };
633
+ pruneFlatEvent(newHooks, "userPromptSubmitted");
634
+ pruneFlatEvent(newHooks, "postToolUseFailure");
635
+ if (events.includes("sessionStart")) newHooks.sessionStart = [...filterOutMembankFlat(Array.isArray(hooks.sessionStart) ? hooks.sessionStart : []), {
636
+ type: "command",
637
+ bash: "npx @membank/cli@latest inject --harness copilot-cli",
638
+ timeoutSec: 30
639
+ }];
708
640
  writeJsonAtomic(cfgPath, {
709
641
  version: cfg.version ?? 1,
710
642
  ...cfg,
711
- hooks: {
712
- ...hooks ?? {},
713
- sessionStart: [...filteredSessionStart, {
714
- type: "command",
715
- bash: "npx @membank/cli inject --harness copilot-cli",
716
- timeoutSec: 30
717
- }],
718
- userPromptSubmitted: [...filterOutMembankFlat(existingUserPrompt), {
719
- type: "command",
720
- bash: "npx @membank/cli inject --event user-prompt --harness copilot-cli",
721
- timeoutSec: 30
722
- }],
723
- postToolUseFailure: [...filterOutMembankFlat(existingToolFailure), {
724
- type: "command",
725
- bash: "npx @membank/cli inject --event tool-failure --harness copilot-cli",
726
- timeoutSec: 30
727
- }]
728
- }
643
+ hooks: newHooks
729
644
  });
730
645
  return { status: "written" };
731
646
  }
732
647
  },
733
648
  codex: {
734
- replacement: "npx @membank/cli inject --harness codex",
735
- write(resolver, overwrite = false) {
649
+ inspect(resolver) {
650
+ const hooks = readJson(join(resolver.home(), ".codex", "hooks.json")).hooks ?? {};
651
+ return {
652
+ status: "ready",
653
+ hooks: [{
654
+ event: "SessionStart",
655
+ command: "npx @membank/cli@latest inject --harness codex",
656
+ existingCommand: extractInjectCommand((Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []).flatMap(getHooksArray)) || null
657
+ }]
658
+ };
659
+ },
660
+ write(resolver, events) {
736
661
  const cfgPath = join(resolver.home(), ".codex", "hooks.json");
737
662
  const cfg = readJson(cfgPath);
738
- const hooks = cfg.hooks;
739
- const existingSessionStart = Array.isArray(hooks?.SessionStart) ? hooks.SessionStart : [];
740
- const innerHooks = existingSessionStart.flatMap(getHooksArray);
741
- if (!overwrite && containsMembankInject(innerHooks)) return {
742
- status: "already-configured",
743
- existing: extractInjectCommand(innerHooks),
744
- replacement: this.replacement
745
- };
746
- const filteredSessionStart = overwrite ? filterOutMembank(existingSessionStart) : existingSessionStart;
747
- const existingUserPrompt = Array.isArray(hooks?.UserPromptSubmit) ? hooks.UserPromptSubmit : [];
748
- const existingToolFailure = Array.isArray(hooks?.PostToolUse) ? hooks.PostToolUse : [];
663
+ const hooks = cfg.hooks ?? {};
664
+ const newHooks = { ...hooks };
665
+ pruneNestedEvent(newHooks, "UserPromptSubmit");
666
+ pruneNestedEvent(newHooks, "PostToolUse");
667
+ if (events.includes("SessionStart")) newHooks.SessionStart = [...filterOutMembank(Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []), {
668
+ matcher: "",
669
+ hooks: [{
670
+ type: "command",
671
+ command: "npx @membank/cli@latest inject --harness codex",
672
+ timeout: 30
673
+ }]
674
+ }];
749
675
  writeJsonAtomic(cfgPath, {
750
676
  ...cfg,
751
- hooks: {
752
- ...hooks ?? {},
753
- SessionStart: [...filteredSessionStart, {
754
- matcher: "",
755
- hooks: [{
756
- type: "command",
757
- command: "npx @membank/cli inject --harness codex",
758
- timeout: 30
759
- }]
760
- }],
761
- UserPromptSubmit: [...filterOutMembank(existingUserPrompt), {
762
- matcher: "",
763
- hooks: [{
764
- type: "command",
765
- command: "npx @membank/cli inject --event user-prompt --harness codex",
766
- timeout: 30
767
- }]
768
- }],
769
- PostToolUse: [...filterOutMembank(existingToolFailure), {
770
- matcher: "",
771
- hooks: [{
772
- type: "command",
773
- command: "npx @membank/cli inject --event tool-failure --harness codex",
774
- timeout: 30
775
- }]
776
- }]
777
- }
677
+ hooks: newHooks
778
678
  });
779
679
  return { status: "written" };
780
680
  }
781
681
  },
782
682
  opencode: {
783
- replacement: "npx @membank/cli inject",
784
- write(resolver, overwrite = false) {
683
+ inspect(resolver) {
785
684
  const pluginPath = join(resolver.home(), ".config", "opencode", "plugins", "membank.js");
786
- if (!overwrite && existsSync(pluginPath)) {
787
- const existing = readFileSync(pluginPath, "utf8");
788
- if (existing.includes("@membank/cli inject")) return {
789
- status: "already-configured",
790
- existing: existing.trim(),
791
- replacement: newOpencodePlugin()
792
- };
685
+ let existingCommand = null;
686
+ if (existsSync(pluginPath)) {
687
+ if (readFileSync(pluginPath, "utf8").includes("@membank/cli")) existingCommand = pluginPath;
793
688
  }
689
+ return {
690
+ status: "ready",
691
+ hooks: [{
692
+ event: "plugin",
693
+ command: pluginPath,
694
+ existingCommand
695
+ }]
696
+ };
697
+ },
698
+ write(resolver, events) {
699
+ if (events.length === 0) return { status: "written" };
700
+ const pluginPath = join(resolver.home(), ".config", "opencode", "plugins", "membank.js");
794
701
  mkdirSync(dirname(pluginPath), { recursive: true });
795
702
  writeFileSync(pluginPath, `${newOpencodePlugin()}\n`, "utf8");
796
703
  return { status: "written" };
@@ -802,19 +709,7 @@ function newOpencodePlugin() {
802
709
  "export default {",
803
710
  " hooks: {",
804
711
  " \"session.start\": async ({ $ }) => {",
805
- " return await $`npx @membank/cli inject`.text();",
806
- " },",
807
- " \"chat.message\": async ({ $, message }) => {",
808
- " const input = JSON.stringify({ prompt: message?.content ?? \"\" });",
809
- " return await $`npx @membank/cli inject --event user-prompt`.stdin(input).text();",
810
- " },",
811
- " \"tool.execute.after\": async ({ $, result }) => {",
812
- " if (!result?.exitCode && !result?.error) return;",
813
- " const payload = JSON.stringify({",
814
- " tool_name: result.tool ?? \"unknown\",",
815
- " error_message: result.error ?? (\"exit code \" + result.exitCode),",
816
- " });",
817
- " return await $`npx @membank/cli inject --event tool-failure`.stdin(payload).text();",
712
+ " return await $`npx @membank/cli@latest inject`.text();",
818
713
  " },",
819
714
  " },",
820
715
  "};"
@@ -825,10 +720,15 @@ var InjectionHookWriter = class {
825
720
  constructor(resolver = defaultPathResolver) {
826
721
  this.#resolver = resolver;
827
722
  }
828
- write(harness, overwrite) {
723
+ inspect(harness) {
829
724
  const writer = writers[harness];
830
725
  if (!writer) return { status: "not-supported" };
831
- return writer.write(this.#resolver, overwrite);
726
+ return writer.inspect(this.#resolver);
727
+ }
728
+ write(harness, events) {
729
+ const writer = writers[harness];
730
+ if (!writer) return { status: "not-supported" };
731
+ return writer.write(this.#resolver, events);
832
732
  }
833
733
  };
834
734
  //#endregion
@@ -1062,8 +962,7 @@ var SetupOrchestrator = class {
1062
962
  out("");
1063
963
  const injectionHooksConfigured = [];
1064
964
  if (this.#hookWriter) {
1065
- const w = this.#hookWriter;
1066
- injectionHooksConfigured.push(...await this.#runHookLoop(detected, "injection hook", (h, ow) => w.write(h, ow), yes, out));
965
+ injectionHooksConfigured.push(...await this.#runHookSetup(detected, yes, out));
1067
966
  out("");
1068
967
  }
1069
968
  let modelDownloaded = false;
@@ -1086,28 +985,33 @@ var SetupOrchestrator = class {
1086
985
  }
1087
986
  return results;
1088
987
  }
1089
- async #runHookLoop(detected, label, write, yes, out) {
988
+ async #runHookSetup(detected, yes, out) {
1090
989
  const configured = [];
990
+ const w = this.#hookWriter;
1091
991
  for (const h of detected) try {
1092
- const result = write(h.name);
1093
- if (result.status === "not-supported") continue;
1094
- if (result.status === "written") {
1095
- out(` ✓ ${h.name}: ${label} written`);
1096
- configured.push(h.name);
992
+ const inspected = w.inspect(h.name);
993
+ if (inspected.status === "not-supported") continue;
994
+ const toWrite = [];
995
+ for (const hook of inspected.hooks) if (hook.existingCommand === null) {
996
+ out(` ${h.name}: ${hook.event} injection hook`);
997
+ out(` Command: ${hook.command}`);
998
+ if (yes || await this.#prompter(` Configure ${hook.event} injection hook for ${h.name}?`)) toWrite.push(hook.event);
1097
999
  } else {
1098
- out(` ⚠ ${h.name}: ${label} already configured`);
1099
- out(` Current: ${result.existing}`);
1100
- out(` New: ${result.replacement}`);
1101
- if (yes || await this.#prompter(` Replace ${label} for ${h.name}?`)) {
1102
- if (write(h.name, true).status === "written") {
1103
- out(` ✓ ${h.name}: ${label} replaced`);
1104
- configured.push(h.name);
1105
- }
1106
- }
1000
+ out(` ⚠ ${h.name}: ${hook.event} injection hook already configured`);
1001
+ out(` Current: ${hook.existingCommand}`);
1002
+ if (hook.existingCommand !== hook.command) out(` New: ${hook.command}`);
1003
+ if (yes || await this.#prompter(` Replace ${hook.event} injection hook for ${h.name}?`)) toWrite.push(hook.event);
1004
+ }
1005
+ if (toWrite.length > 0) {
1006
+ w.write(h.name, toWrite);
1007
+ const skippedCount = inspected.hooks.length - toWrite.length;
1008
+ const label = skippedCount > 0 ? `${toWrite.length} injection hook(s) written, ${skippedCount} skipped` : `${toWrite.length} injection hook(s) written`;
1009
+ out(` ✓ ${h.name}: ${label}`);
1010
+ configured.push(h.name);
1107
1011
  }
1108
1012
  } catch (err) {
1109
1013
  const msg = err instanceof Error ? err.message : String(err);
1110
- out(` ✗ ${h.name} ${label}: ${msg}`);
1014
+ out(` ✗ ${h.name} injection hooks: ${msg}`);
1111
1015
  }
1112
1016
  return configured;
1113
1017
  }
@@ -1241,7 +1145,7 @@ program.command("import <file>").description("import memories from a JSON export
1241
1145
  db.close();
1242
1146
  }
1243
1147
  });
1244
- program.command("inject").description("output session context for harness injection (used by setup hooks)").option("--harness <name>", "format output for a specific harness (claude-code|copilot-cli|codex|opencode)").option("--scope <scope>", "project scope override (default: auto-detect from git remote)").option("--event <event>", "hook event type (session-start|user-prompt|tool-failure)", "session-start").action(async (cmdOptions) => {
1148
+ program.command("inject").description("output session context for harness injection (used by setup hooks)").option("--harness <name>", "format output for a specific harness (claude-code|copilot-cli|codex|opencode)").option("--scope <scope>", "project scope override (default: auto-detect from git remote)").option("--event <event>", "hook event type (only session-start is supported; other values no-op for legacy hook compatibility)", "session-start").action(async (cmdOptions) => {
1245
1149
  try {
1246
1150
  await injectCommand(cmdOptions);
1247
1151
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@membank/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,9 +17,9 @@
17
17
  "@huggingface/transformers": "^4.2.0",
18
18
  "commander": "^14.0.3",
19
19
  "ora": "^9.4.0",
20
- "@membank/dashboard": "0.1.0",
21
- "@membank/mcp": "0.3.0",
22
- "@membank/core": "0.3.0"
20
+ "@membank/core": "0.4.0",
21
+ "@membank/dashboard": "0.2.0",
22
+ "@membank/mcp": "0.4.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^25.6.0",