@ephemushroom/opencode-claude-mem 0.4.3 → 0.5.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/README.md CHANGED
@@ -3,6 +3,9 @@
3
3
  Persistent memory for [OpenCode](https://opencode.ai), powered by
4
4
  [Claude-Mem](https://github.com/thedotmack/claude-mem).
5
5
 
6
+ **Supports OpenCode 1 and OpenCode 2 with the same package name from v0.5.0.**
7
+ No `/v2` or `/cli` suffix is required. Existing version-specific entries remain compatible.
8
+
6
9
  Share the same Claude-Mem worker, database, and memory across your coding
7
10
  agents: memories written by Claude Code are visible to OpenCode, and vice
8
11
  versa. Previous observations and summaries are injected into new OpenCode
@@ -24,7 +27,10 @@ flowchart LR
24
27
  ## Quick Start
25
28
 
26
29
  1. Install and configure Claude-Mem in Claude Code.
27
- 2. Add this plugin to your `opencode.json`:
30
+ 2. Add this plugin to your `opencode.json` — pick the section matching your
31
+ OpenCode version:
32
+
33
+ ### OpenCode 1
28
34
 
29
35
  ```json
30
36
  {
@@ -32,15 +38,69 @@ flowchart LR
32
38
  }
33
39
  ```
34
40
 
41
+ ### OpenCode 2
42
+
43
+ ```json
44
+ {
45
+ "plugins": ["@ephemushroom/opencode-claude-mem"]
46
+ }
47
+ ```
48
+
35
49
  3. Restart OpenCode.
36
50
  4. Start a session — memory context is injected automatically, the `mem-*`
37
51
  tools become available, and a collapsible **Memory** section appears in the
38
- sidebar.
52
+ sidebar (both versions).
39
53
 
40
54
  Using [oh-my-openagent](https://github.com/code-yeongyu/oh-my-openagent)?
41
55
  See [Disable the Claude Code bridge](#using-with-oh-my-openagent-disable-the-claude-code-bridge)
42
56
  to avoid running two claude-mem integrations at once.
43
57
 
58
+ ## OpenCode 1 and 2
59
+
60
+ OpenCode 1 and OpenCode 2 install side by side (`opencode` vs `opencode2`) and
61
+ their plugin APIs are incompatible. This package exposes **one bare package
62
+ name for both runtimes**, with thin bridges that select the existing adapter
63
+ contract. No `/v2` or `/cli` suffix is needed:
64
+
65
+ | | OpenCode 1 | OpenCode 2 |
66
+ |---|---|---|
67
+ | **Binary** | `opencode` | `opencode2` |
68
+ | **Server plugin config** | `opencode.json` → `plugin` | `opencode.json` → `plugins` |
69
+ | **Server plugin entry** | `@ephemushroom/opencode-claude-mem` | `@ephemushroom/opencode-claude-mem` |
70
+ | **Sidebar config** | `~/.config/opencode/tui.json` → `plugin` (self-healed) | `~/.config/opencode/cli.json` → `plugins` (self-healed) |
71
+ | **Sidebar config entry** | `@ephemushroom/opencode-claude-mem` | `@ephemushroom/opencode-claude-mem` |
72
+ | **Sidebar resolution** | package `./tui` export, `tui()` | package `./tui` export, `setup()` |
73
+
74
+ Both adapters talk to the same Claude-Mem worker, so memory written from V1,
75
+ V2, and Claude Code is shared. The V2 entrypoint covers the same behavior —
76
+ context injection, tool observation capture, the `mem-search`/`mem-timeline`/
77
+ `mem-get-observations` tools, session init/summarize/cleanup via the event
78
+ stream.
79
+
80
+ The bare package is tested with `opencode 1.18.29` and
81
+ `opencode2 0.0.0-beta-19151`; the V2 SDK is pinned because preview APIs can
82
+ change. Unified routing requires **v0.5.0 or later**; the older `0.4.4` package
83
+ does not provide it. Upgrade the package in both server and sidebar configurations.
84
+ Existing `/server`, `/tui`, `/v2`, and `/cli` exports remain supported; replace
85
+ an old config entry rather than adding the bare name beside it.
86
+
87
+ After upgrading, finish active tasks, close the client, and restart the V2
88
+ background service with `opencode2 service restart` before reopening `opencode2`.
89
+ For V1, restart `opencode`. Verify server activation with `opencode2 plugin list`.
90
+
91
+ In V2, installing the server plugin
92
+ (`"plugins": ["@ephemushroom/opencode-claude-mem"]`) auto-appends the same
93
+ bare package to `~/.config/opencode/cli.json` on first load. Restart OpenCode
94
+ after that first load if the sidebar is not yet present. Beta `19151` still
95
+ uses a separate CLI config; this is not a promise of same-launch discovery
96
+ from the server config alone. To register manually, use:
97
+
98
+ ```json
99
+ {
100
+ "plugins": ["@ephemushroom/opencode-claude-mem"]
101
+ }
102
+ ```
103
+
44
104
  ## What You Get
45
105
 
46
106
  | Surface | What it looks like |
@@ -79,11 +139,17 @@ MCP/Context sections — borderless, click-to-toggle:
79
139
  loop cheap (stats every 5s).
80
140
  - Fails open: worker offline → shows the offline state, never blocks the TUI.
81
141
 
82
- The sidebar loads via the package's `./tui` export. On startup the plugin
83
- **self-heals `~/.config/opencode/tui.json`**: if it is registered as a server
84
- plugin in `opencode.json` but missing from the TUI plugin list, it appends
85
- itself — no manual configuration. Symlinked `tui.json` files are written
86
- through, preserving dotfiles setups.
142
+ The sidebar loads via the package's `./tui` bridge in both versions (OpenCode 2
143
+ registers the bare name in `~/.config/opencode/cli.json`). On startup
144
+ the plugin **self-heals the TUI config**: if it is registered as a server
145
+ plugin but missing from the TUI plugin list, it appends itself — no manual
146
+ configuration. OpenCode 1 writes into `tui.json` (symlinked files are written
147
+ through, preserving dotfiles setups); OpenCode 2 appends
148
+ `@ephemushroom/opencode-claude-mem` to `cli.json`'s `plugins` array.
149
+ Existing bare, `/cli`, or `/tui` sidebar entries (including versions and
150
+ object-form options) are left untouched, without adding another entry.
151
+ Malformed or comment-bearing CLI config is left untouched; add the bare name
152
+ manually in that case.
87
153
 
88
154
  ### Memory Tools
89
155
 
@@ -111,27 +177,46 @@ sequenceDiagram
111
177
  participant W as Claude-Mem Worker
112
178
 
113
179
  OC->>P: plugin loads
114
- P->>P: self-heal tui.json
180
+ P->>P: self-heal tui.json / cli.json
115
181
  P->>W: health check (auto-start via bunx if down)
116
- OC->>P: chat.message (first user prompt)
182
+ OC->>P: first user prompt (chat.message / context hook)
117
183
  P->>W: POST /api/sessions/init
118
- OC->>P: system.transform
184
+ OC->>P: system prompt transform / context hook
119
185
  W-->>P: GET /api/context/inject (cached per session)
120
186
  P-->>OC: inject <claude-mem-context> into system prompt
121
187
  loop during the session
122
- OC->>P: tool.execute.after / message.updated / file.edited
188
+ OC->>P: tool.execute.after / assistant text / file.edited
123
189
  P->>W: POST /api/sessions/observations
124
190
  end
125
- OC->>P: session.idle / session.compacted
191
+ OC->>P: session idle / execution succeeded / compaction ended
126
192
  P->>W: POST /api/sessions/summarize
127
193
  OC->>P: session.deleted
128
- P->>W: POST /api/sessions/complete
194
+ P->>P: flush pending observations and release local state
129
195
  ```
130
196
 
131
197
  The plugin is intentionally small: it only adapts OpenCode hook events to the
132
198
  Claude-Mem worker HTTP API. All indexing, summarization, memory search, and
133
199
  storage stay in upstream Claude-Mem.
134
200
 
201
+ ### Hook Mapping (V1 ↔ V2)
202
+
203
+ The V1 and V2 entrypoints adapt the same behavior onto two different plugin
204
+ APIs:
205
+
206
+ | Behavior | OpenCode 1 | OpenCode 2 |
207
+ |---|---|---|
208
+ | Inject memory context | `experimental.chat.system.transform` | `ctx.session.hook('context')` |
209
+ | Preserve memory on compaction | `experimental.session.compacting` | _(context hook re-runs per dispatch)_ |
210
+ | Session init with user prompt | `chat.message` | `ctx.session.hook('context')` (from `event.messages`) |
211
+ | Capture tool observations | `tool.execute.after` | `ctx.tool.hook('execute.after')` |
212
+ | Custom memory tools | `tool` (`mem-search`, …) | `ctx.tool.transform` (`tools.add`) |
213
+ | Assistant text capture | `event` (`message.updated`, debounced 250ms) | `session.text.ended` event (complete text) |
214
+ | File edit observations | `event` (`file.edited`) | `ctx.tool.hook('execute.after')` |
215
+ | Summarize on idle | `event` (`session.idle`) | `session.execution.succeeded` event |
216
+ | Summarize after compaction | `event` (`session.compacted`) | `session.compaction.ended` event |
217
+ | Release session tracking | `event` (`session.deleted`) | `session.deleted` event |
218
+ | Sidebar slot | `api.slots.register` (`sidebar_content`) | `ctx.ui.slot({ append: 'sidebar.content', render })` |
219
+
135
220
  ### Cross-Tool Memory Sharing
136
221
 
137
222
  Writes are attributed (`platformSource: "opencode"`), reads are shared:
@@ -169,12 +254,25 @@ Restart Claude Code so the worker can start and initialize its data directory.
169
254
 
170
255
  Add this plugin to your project or global `opencode.json`:
171
256
 
257
+ **OpenCode 1** (`opencode`):
258
+
172
259
  ```json
173
260
  {
174
261
  "plugin": ["@ephemushroom/opencode-claude-mem"]
175
262
  }
176
263
  ```
177
264
 
265
+ **OpenCode 2** (`opencode2`):
266
+
267
+ ```json
268
+ {
269
+ "plugins": ["@ephemushroom/opencode-claude-mem"]
270
+ }
271
+ ```
272
+
273
+ The two runtimes can be installed side by side and use the same package name.
274
+ Their config keys remain different: `plugin` in V1, `plugins` in V2.
275
+
178
276
  Then restart OpenCode.
179
277
 
180
278
  ### Step 3: Verify
@@ -183,9 +281,9 @@ Then restart OpenCode.
183
281
  curl -s http://127.0.0.1:37777/api/health
184
282
  ```
185
283
 
186
- If the worker is healthy, OpenCode shows a toast like
187
- `Memory active · <project>` when a session starts, and the sidebar shows
188
- `▶ Memory (online, … obs)`.
284
+ If the worker is healthy, the sidebar shows `▶ Memory (online, … obs)`.
285
+ OpenCode 1 also shows a `Memory active · <project>` toast when a session
286
+ starts (OpenCode 2 only toasts a warning when the worker is offline).
189
287
 
190
288
  ## Using with oh-my-openagent: Disable the Claude Code Bridge
191
289
 
@@ -227,20 +325,23 @@ worker. Everything the bridge provided is covered natively by this plugin:
227
325
 
228
326
  ### Hook Mapping
229
327
 
230
- | Claude Code | OpenCode plugin | Purpose |
328
+ Claude Code hooks → this plugin's adapters (see [Hook Mapping (V1 ↔ V2)](#hook-mapping-v1--v2)
329
+ for the per-runtime API):
330
+
331
+ | Claude Code | OpenCode (both runtimes) | Purpose |
231
332
  |---|---|---|
232
- | `SessionStart` | `experimental.chat.system.transform` | Inject memory context |
233
- | `SessionStart` | `experimental.session.compacting` | Preserve memory during compaction |
234
- | `UserPromptSubmit` | `chat.message` | Initialize session with real user prompt |
235
- | `PostToolUse` | `tool.execute.after` | Capture tool observations |
236
- | Claude-Mem MCP `search` | `tool` (`mem-search`) | Search memory from OpenCode |
237
- | Claude-Mem MCP `timeline` | `tool` (`mem-timeline`) | Chronological context around an observation |
238
- | Claude-Mem MCP `get_observations` | `tool` (`mem-get-observations`) | Fetch full observation details by ID |
239
- | _(streaming)_ | `event` (`message.updated`) | Capture assistant text (debounced 250ms) |
240
- | _(streaming)_ | `event` (`file.edited`) | Record file edit observations |
241
- | _(compaction)_ | `event` (`session.compacted`) | Summarize after OpenCode compacts |
242
- | `Stop` | `event` (`session.idle`) | Flush + summarize |
243
- | `SessionEnd` | `event` (`session.deleted`) | Flush + complete (no zombie active rows) |
333
+ | `SessionStart` | system prompt injection (`system.transform` / `context` hook) | Inject memory context |
334
+ | `SessionStart` | compaction injection (`session.compacting` / context hook) | Preserve memory during compaction |
335
+ | `UserPromptSubmit` | session init (`chat.message` / context hook) | Initialize session with real user prompt |
336
+ | `PostToolUse` | observation capture (`tool.execute.after` / `execute.after` hook) | Capture tool observations |
337
+ | Claude-Mem MCP `search` | `mem-search` tool | Search memory from OpenCode |
338
+ | Claude-Mem MCP `timeline` | `mem-timeline` tool | Chronological context around an observation |
339
+ | Claude-Mem MCP `get_observations` | `mem-get-observations` tool | Fetch full observation details by ID |
340
+ | _(streaming)_ | assistant text (`message.updated` / `session.text.ended`) | Capture assistant text |
341
+ | _(streaming)_ | file edits (`file.edited` / tool hook) | Record file edit observations |
342
+ | _(compaction)_ | `session.compacted` / `session.compaction.ended` | Summarize after OpenCode compacts |
343
+ | `Stop` | `session.idle` / `session.execution.succeeded` | Flush + summarize |
344
+ | `SessionEnd` | `session.deleted` | Flush observations and release local state; Worker self-completes |
244
345
 
245
346
  ### Worker API Endpoints Used
246
347
 
@@ -251,7 +352,6 @@ worker. Everything the bridge provided is covered natively by this plugin:
251
352
  | `POST` | `/api/sessions/init` | Initialize session |
252
353
  | `POST` | `/api/sessions/observations` | Store tool observation |
253
354
  | `POST` | `/api/sessions/summarize` | Trigger summarization |
254
- | `POST` | `/api/sessions/complete` | Complete session |
255
355
  | `GET` | `/api/search?query=...&project=...&dateStart=...&dateEnd=...` | `mem-search`; also supports `limit`, `platformSource`, `type`, `obs_type`, `offset`, and `orderBy` |
256
356
  | `GET` | `/api/timeline?project={name}&anchor={id}` | `mem-timeline` |
257
357
  | `POST` | `/api/observations/batch` | `mem-get-observations` |
@@ -264,11 +364,22 @@ The worker endpoint is resolved in this order: `CLAUDE_MEM_WORKER_HOST` /
264
364
 
265
365
  ### Key Implementation Details
266
366
 
267
- - **Thin client architecture** — `src/index.ts` handles OpenCode hooks and
268
- session state; `src/worker-client.ts` is a static HTTP client; `src/tui.ts`
269
- renders the sidebar; `src/tui-registration.ts` self-heals `tui.json`.
270
- - **Zero runtime dependencies** — the OpenCode plugin SDK is bundled into
271
- `dist/`, keeping the install footprint under ~500 KB.
367
+ - **Dual-runtime architecture** — `src/index.ts` is the OpenCode 1 plugin,
368
+ `src/v2.ts` the OpenCode 2 plugin (`Plugin.define`), `src/tui.ts` the V1
369
+ sidebar, `src/cli.ts` the V2 sidebar; `src/shared.ts` and
370
+ `src/sidebar-model.ts` hold the shared pure helpers; `src/worker-client.ts`
371
+ is a static HTTP client; `src/tui-registration.ts` self-heals `tui.json`
372
+ (V1) and `cli.json` (V2).
373
+ - **Static package bridges** — `src/package-server.ts` exposes `{ id, server,
374
+ setup }` at both `.` and `./server`; `src/package-tui.ts` exposes `{ id, tui,
375
+ setup }` at `./tui`. V1 reads `server`/`tui`; V2 reads `setup`. Neither bridge
376
+ invokes both adapters. Both installed server loaders prefer `./server`, so
377
+ routing only the root export to V2 is insufficient. Never combine `server`
378
+ and `tui` in one object: V1 rejects that shape.
379
+ - **Zero runtime dependencies for server plugins** — the OpenCode plugin SDK
380
+ is bundled into `dist/` (V1 `index.js` ~480 KB, V2 `v2.js` ~30 KB via a deep
381
+ `@opencode-ai/plugin` import that skips effect). The TUI entrypoints keep
382
+ `@opentui/solid` external (provided by the host TUI).
272
383
  - **Reactive sidebar** — collapse state and view data are solid-js signals
273
384
  (shared with OpenCode's own solid instance via `--external solid-js`), so
274
385
  clicking the header re-renders reliably; falls back to plain closures if
@@ -279,9 +390,10 @@ The worker endpoint is resolved in this order: `CLAUDE_MEM_WORKER_HOST` /
279
390
  avoiding startup crashes caused by early TUI access.
280
391
  - **Auto-start** — if the worker is down on load, spawns
281
392
  `bunx claude-mem start` once per OpenCode process (skipped if `bun` is not
282
- on `PATH`).
393
+ on `PATH`). Health is rechecked on subsequent hooks, so a recovered Worker
394
+ does not require restarting OpenCode.
283
395
  - **Context caching** — memory context is fetched once per session and reused
284
- across prompt injection and compaction.
396
+ across prompt injection and compaction. Failed fetches are not cached.
285
397
  - **Circular memory protection** — injected context is wrapped in
286
398
  `<claude-mem-context>` tags, Claude-Mem search tools are skipped from
287
399
  observation capture, and memory tags are stripped before storage.
@@ -289,9 +401,13 @@ The worker endpoint is resolved in this order: `CLAUDE_MEM_WORKER_HOST` /
289
401
  truncated by UTF-8 byte size (24 KB cap).
290
402
  - **Field name correctness** — worker payloads use `contentSessionId`, not
291
403
  `claudeSessionId` (the wrong name fails silently).
292
- - **Session lifecycle hygiene** — `session.deleted` triggers
293
- `completeSession`, preventing zombie `active` rows from accumulating stale
294
- `pending_messages`.
404
+ - **Session lifecycle hygiene** — `session.deleted` flushes pending observations
405
+ and releases local tracking. Claude-Mem self-completes processing; the removed
406
+ `/api/sessions/complete` endpoint is not called.
407
+ - **Attribution** — observations include the OpenCode call ID as `tool_use_id`;
408
+ summaries include the latest assistant's `observedModel` when available.
409
+ - **Write status** — failed HTTP writes return failure without throwing; a summary
410
+ toast is shown only after the Worker accepts the request, not after generation.
295
411
 
296
412
  ## Troubleshooting
297
413
 
@@ -320,9 +436,13 @@ plugin load. If the toast still appears:
320
436
 
321
437
  ### Sidebar Memory section is missing
322
438
 
323
- - Check `~/.config/opencode/tui.json` contains this plugin in its `plugin`
324
- array — the plugin self-heals this file on load, so restarting OpenCode
325
- twice (once to heal, once to load) fixes a missing entry.
439
+ - **OpenCode 1**: check `~/.config/opencode/tui.json` contains this plugin in
440
+ its `plugin` array — the plugin self-heals this file on load, so restarting
441
+ OpenCode twice (once to heal, once to load) fixes a missing entry.
442
+ - **OpenCode 2**: check `~/.config/opencode/cli.json` contains
443
+ `@ephemushroom/opencode-claude-mem` in its `plugins` array — self-healed
444
+ the same way (comment-bearing cli.json files are left untouched, so remove
445
+ the comments or add the entry manually).
326
446
  - The sidebar requires OpenCode's `@opentui/solid` runtime; if unavailable the
327
447
  section is skipped silently while hooks and tools keep working.
328
448
 
@@ -346,15 +466,29 @@ plugin load. If the toast still appears:
346
466
 
347
467
  ```bash
348
468
  bun install
349
- bun run build # bundle dist/index.js + dist/tui.js + emit declarations
350
- bun test # bun's built-in runner
469
+ bun run build # four adapters + package-server/package-tui bridges + declarations
470
+ bun test # build first: export tests import the actual dist artifacts
351
471
  bun run lint # oxlint
352
472
  bun run fmt:check # oxfmt
473
+ bun src/test-fixtures/v2-runtime.ts # installed V2: packed bare package, local model + Worker
474
+ bun src/test-fixtures/v2-runtime.ts v1 # installed V1: same package and behavior assertions
475
+ bun src/test-fixtures/tui-runtime.ts # installed V2 TUI: bare CLI entry loads the sidebar
476
+ bun --conditions=browser src/test-fixtures/cli-driver.ts render # real rendering + mouse click
353
477
  ```
354
478
 
355
479
  If you edit source code locally, rebuild and restart OpenCode to pick up the
356
480
  new plugin bundle.
357
481
 
482
+ The runtime QA drivers never write to your real config or memory database and
483
+ never use paid models. They pack the current build, serve it through a temporary
484
+ local registry, and ask the installed host to resolve the bare npm name in an
485
+ isolated home/cache. Registry access is needed for package dependencies. Server
486
+ QA drives injection, exactly-once tool capture, all three memory tools, and
487
+ summarization. The V2 TUI probe proves sidebar setup through Worker polling and
488
+ unchanged bare CLI config, not whole-window visual layout (its terminal is piped).
489
+ The separate OpenTUI driver exercises actual bridge rendering and clicks. V1's
490
+ interactive host is not verified through pipes; its server runtime is verified.
491
+
358
492
  ## License
359
493
 
360
494
  MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * OpenCode V2 TUI plugin for Claude-Mem (the Memory sidebar).
3
+ *
4
+ * Loaded from `~/.config/opencode/cli.json` via the package's `./cli`
5
+ * export:
6
+ *
7
+ * ```json
8
+ * { "plugins": ["@ephemushroom/opencode-claude-mem/cli"] }
9
+ * ```
10
+ *
11
+ * V2 TUI plugins are `Plugin.define({ id, setup })` modules rendered inside
12
+ * the TUI process with @opentui/solid elements — the same element model as the
13
+ * V1 sidebar, but registered through a `sidebar.content` slot claim with
14
+ * reactive state from `ctx.storage.memory` (survives hot reloads).
15
+ */
16
+ declare const _default: import("@opencode-ai/plugin-v2/tui/plugin").Definition;
17
+ export default _default;