@zilliz/memsearch-opencode 0.2.0 → 0.2.1

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
@@ -143,6 +143,14 @@ memsearch config set embedding.provider openai
143
143
  export OPENAI_API_KEY=sk-...
144
144
  ```
145
145
 
146
+ To override only the OpenCode capture summarization model:
147
+
148
+ ```bash
149
+ memsearch config set plugins.opencode.summarize.model anthropic/claude-haiku
150
+ ```
151
+
152
+ Leave it empty or unset to keep the current `small_model` / plugin default behavior. This setting does not fall back to `llm.model`.
153
+
146
154
  ## How It Works
147
155
 
148
156
  1. **Capture**: After each conversation turn, the plugin extracts the user+assistant exchange, summarizes it via LLM, and appends to a daily markdown file.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zilliz/memsearch-opencode",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "memsearch plugin for OpenCode — semantic memory search across sessions",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -9,7 +9,8 @@
9
9
  },
10
10
  "files": [
11
11
  "index.ts",
12
- "scripts/",
12
+ "scripts/*.py",
13
+ "scripts/*.sh",
13
14
  "skills/",
14
15
  "prompts/",
15
16
  "README.md"
@@ -11,19 +11,26 @@ extracts the last turn, summarizes it as bullet points, and writes to
11
11
  It also triggers memsearch indexing after writing.
12
12
  """
13
13
 
14
- import sqlite3
14
+ import argparse
15
+ import contextlib
15
16
  import json
16
- import sys
17
17
  import os
18
- import time
18
+ import shlex
19
19
  import shutil
20
- import subprocess
21
- import argparse
22
20
  import signal
21
+ import sqlite3
22
+ import subprocess
23
+ import sys
24
+ import time
23
25
  from datetime import datetime
24
26
  from pathlib import Path
25
27
 
26
28
 
29
+ def split_memsearch_cmd(memsearch_cmd):
30
+ """Split the configured memsearch command preserving quoted arguments."""
31
+ return shlex.split(memsearch_cmd)
32
+
33
+
27
34
  def get_small_model():
28
35
  """Read small_model from opencode.json config (fallback to model, then empty)."""
29
36
  config_paths = [
@@ -41,6 +48,20 @@ def get_small_model():
41
48
  return ""
42
49
 
43
50
 
51
+ def get_plugin_summarize_model(memsearch_cmd=None):
52
+ """Read the memsearch OpenCode summarize model override."""
53
+ if not memsearch_cmd:
54
+ return ""
55
+ try:
56
+ result = subprocess.run(
57
+ [*split_memsearch_cmd(memsearch_cmd), "config", "get", "plugins.opencode.summarize.model"],
58
+ capture_output=True, text=True, timeout=5,
59
+ )
60
+ return result.stdout.strip()
61
+ except Exception:
62
+ return ""
63
+
64
+
44
65
  def ensure_isolated_config():
45
66
  """Create isolated config dir without plugins/ to prevent recursion."""
46
67
  isolated = "/tmp/opencode-memsearch-summarize/opencode"
@@ -48,6 +69,9 @@ def ensure_isolated_config():
48
69
  # Copy opencode.json (provider config) but NOT plugins/
49
70
  src = os.path.expanduser("~/.config/opencode/opencode.json")
50
71
  dst = os.path.join(isolated, "opencode.json")
72
+ if os.path.islink(dst) or (os.path.lexists(dst) and not os.path.isfile(dst)):
73
+ with contextlib.suppress(OSError):
74
+ os.remove(dst)
51
75
  if os.path.exists(src) and not os.path.exists(dst):
52
76
  shutil.copy2(src, dst)
53
77
  return os.path.dirname(isolated) # /tmp/opencode-memsearch-summarize
@@ -59,7 +83,7 @@ def _load_summarize_prompt(agent_name, memsearch_cmd=None):
59
83
  if memsearch_cmd:
60
84
  try:
61
85
  result = subprocess.run(
62
- memsearch_cmd.split() + ["config", "get", "prompts.summarize"],
86
+ [*split_memsearch_cmd(memsearch_cmd), "config", "get", "prompts.summarize"],
63
87
  capture_output=True, text=True, timeout=5,
64
88
  )
65
89
  custom_path = result.stdout.strip()
@@ -91,9 +115,10 @@ def summarize_with_llm(turn_text, small_model, memsearch_cmd=None):
91
115
  )
92
116
  isolated_dir = ensure_isolated_config()
93
117
 
118
+ summarize_model = get_plugin_summarize_model(memsearch_cmd) or small_model
94
119
  cmd = ["opencode", "run"]
95
- if small_model:
96
- cmd += ["-m", small_model]
120
+ if summarize_model:
121
+ cmd += ["-m", summarize_model]
97
122
  cmd.append(full_prompt)
98
123
 
99
124
  try:
@@ -110,7 +135,7 @@ def summarize_with_llm(turn_text, small_model, memsearch_cmd=None):
110
135
  output = result.stdout.strip()
111
136
  # Extract bullet points (skip any opencode run header lines)
112
137
  lines = output.split("\n")
113
- bullets = [l for l in lines if l.strip().startswith("- ")]
138
+ bullets = [line for line in lines if line.strip().startswith("- ")]
114
139
  if bullets:
115
140
  return "\n".join(bullets)
116
141
  except Exception:
@@ -260,7 +285,7 @@ def main():
260
285
 
261
286
  db_path = get_db_path()
262
287
  if not os.path.exists(db_path):
263
- print(f"OpenCode database not found at {db_path}", file=sys.stderr)
288
+ sys.stderr.write(f"OpenCode database not found at {db_path}\n")
264
289
  sys.exit(1)
265
290
 
266
291
  memory_dir = os.path.join(args.project_dir, ".memsearch", "memory")
@@ -273,10 +298,8 @@ def main():
273
298
 
274
299
  # Clean up on exit
275
300
  def cleanup(signum=None, frame=None):
276
- try:
301
+ with contextlib.suppress(OSError):
277
302
  os.remove(pid_file)
278
- except OSError:
279
- pass
280
303
  sys.exit(0)
281
304
 
282
305
  signal.signal(signal.SIGTERM, cleanup)
@@ -287,10 +310,8 @@ def main():
287
310
  state_file = os.path.join(args.project_dir, ".memsearch", ".last_msg_time")
288
311
  last_msg_time = 0
289
312
  if os.path.exists(state_file):
290
- try:
291
- last_msg_time = int(open(state_file).read().strip())
292
- except (ValueError, OSError):
293
- pass
313
+ with contextlib.suppress(ValueError, OSError), open(state_file) as f:
314
+ last_msg_time = int(f.read().strip())
294
315
 
295
316
  while True:
296
317
  try: