opencode-mnemoteca 0.3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 gandazgul
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # opencode-mnemoteca
2
+
3
+ OpenCode plugin for **local persistent memory** using
4
+ [Mnemoteca](https://github.com/gandazgul/mnemoteca). It gives your AI coding
5
+ agent memory that persists across sessions. It is offline and does not use cloud
6
+ APIs.
7
+
8
+ ## Prerequisites
9
+
10
+ Install the `mnemoteca` binary first:
11
+
12
+ ```bash
13
+ curl -fsSL https://raw.githubusercontent.com/gandazgul/mnemoteca/main/install.sh | sh
14
+ mnemoteca setup
15
+ ```
16
+
17
+ See the [Mnemoteca README](https://github.com/gandazgul/mnemoteca#installation)
18
+ for detailed setup instructions. On first use, Mnemoteca downloads its ML models,
19
+ approximately 500 MB one time.
20
+
21
+ Make sure the `mnemoteca` binary is in your `PATH`.
22
+
23
+ ## Installation
24
+
25
+ Add the plugin to your OpenCode configuration:
26
+
27
+ ```json
28
+ {
29
+ "plugin": ["opencode-mnemoteca"]
30
+ }
31
+ ```
32
+
33
+ For local development, install from this repository checkout:
34
+
35
+ ```bash
36
+ npm install
37
+ npm run build
38
+ ```
39
+
40
+ ## Upgrade from opencode-mnemosyne
41
+
42
+ If you already used the old OpenCode plugin, stop OpenCode before you change the
43
+ configuration.
44
+
45
+ 1. Migrate CLI data first if needed. Use the
46
+ [Mnemoteca migration guide](https://github.com/gandazgul/mnemoteca/blob/main/docs/migrate-from-mnemosyne.md).
47
+ 2. Add `opencode-mnemoteca` to the same OpenCode configuration scope that used
48
+ `opencode-mnemosyne`.
49
+ 3. Restart OpenCode and verify that the memory tools work. Store and recall a
50
+ harmless test memory if needed.
51
+ 4. Remove `opencode-mnemosyne` from that same configuration scope.
52
+ 5. Restart OpenCode again.
53
+
54
+ Do not load the old and new plugins together for normal use. The agent-facing
55
+ `memory_*` tool names stay stable; only the plugin package and CLI command names
56
+ change.
57
+
58
+ Windows users must finish this replacement before restarting OpenCode. There is
59
+ no Windows `mnemosyne` compatibility shim, alias, copied executable, or renamed
60
+ executable.
61
+
62
+ ## Memory tools
63
+
64
+ The agent-facing tool names stay stable. They describe memory capabilities, not
65
+ product branding.
66
+
67
+ | Tool | Purpose |
68
+ | --- | --- |
69
+ | `memory_recall` | Search project memory. |
70
+ | `memory_recall_global` | Search global memory. |
71
+ | `memory_store` | Store a project memory. Set `core=true` to tag it as core. |
72
+ | `memory_store_global` | Store a global memory. Set `core=true` to tag it as core. |
73
+ | `memory_delete` | Delete a memory by the numeric document ID shown in recall or list output. |
74
+
75
+ Project memory uses a collection name derived from the project directory name.
76
+ If that name is empty or `global`, the plugin uses `default`.
77
+
78
+ The project collection is initialized when the plugin loads. The global
79
+ collection is created on first use of `mnemoteca add -g` or the equivalent
80
+ global store tool.
81
+
82
+ ## Commands taught to the agent
83
+
84
+ - Use `mnemoteca search -f plain [query]` and `mnemoteca search -g -f plain [query]` to search relevant memories.
85
+ - After significant decisions, use `mnemoteca add "memory content"` to save a concise fact. Use `mnemoteca add -g "memory content"` for cross-project preferences.
86
+ - Delete contradicted memories with `mnemoteca delete [memory id]` after storing the updated memory.
87
+ - Mark critical, always-relevant context as core with `-t core`. You can use repeated tags, such as `mnemoteca add "database is sqlite" -t core -t tech-stack`.
88
+
89
+ ## How it works
90
+
91
+ Mnemoteca is a local document store with hybrid search:
92
+
93
+ - SQLite storage on your machine.
94
+ - BM25 plus vector search.
95
+ - Local ONNX Runtime inference.
96
+ - No cloud API calls.
97
+
98
+ The plugin calls the `mnemoteca` executable with argument arrays. It does not
99
+ own data storage, select databases, or run migrations.
@@ -0,0 +1,3 @@
1
+ import { type Plugin } from "@opencode-ai/plugin";
2
+ export declare const MnemotecaPlugin: Plugin;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,qBAAqB,CAAC;AAExD,eAAO,MAAM,eAAe,EAAE,MAwN7B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,175 @@
1
+ import path from "node:path";
2
+ import { tool } from "@opencode-ai/plugin";
3
+ export const MnemotecaPlugin = async (ctx) => {
4
+ const { directory, worktree, client } = ctx;
5
+ const targetDir = directory || worktree || process.cwd();
6
+ const log = {
7
+ debug: (msg) => client.app
8
+ .log({ body: { service: "mnemoteca", level: "debug", message: msg } })
9
+ .catch(() => { }),
10
+ info: (msg) => client.app
11
+ .log({ body: { service: "mnemoteca", level: "info", message: msg } })
12
+ .catch(() => { }),
13
+ warn: (msg) => client.app
14
+ .log({ body: { service: "mnemoteca", level: "warn", message: msg } })
15
+ .catch(() => { }),
16
+ error: (msg) => client.app
17
+ .log({ body: { service: "mnemoteca", level: "error", message: msg } })
18
+ .catch(() => { }),
19
+ };
20
+ // Strip trailing slashes but keep the root slash if it is just "/".
21
+ let projectDir = targetDir.replace(/(.+?)\/+$/, "$1");
22
+ const projectRaw = path.basename(projectDir);
23
+ const project = projectRaw === "global" ? "default" : (projectRaw || "default");
24
+ await log.debug(`Plugin loaded for project: ${project} (dir: ${targetDir})`);
25
+ /**
26
+ * Run the Mnemoteca CLI binary gracefully using Bun.spawn.
27
+ * Avoid shell interpolation entirely by passing args as an array.
28
+ */
29
+ async function mnemoteca(...args) {
30
+ await log.debug(`Executing: mnemoteca ${args.join(" ")}`);
31
+ try {
32
+ // @ts-ignore - Bun is globally available in the OpenCode environment.
33
+ const proc = Bun.spawn(["mnemoteca", ...args], {
34
+ cwd: targetDir,
35
+ stdout: "pipe",
36
+ stderr: "pipe",
37
+ });
38
+ const [stdout, stderr, exitCode] = await Promise.all([
39
+ new Response(proc.stdout).text(),
40
+ new Response(proc.stderr).text(),
41
+ proc.exited,
42
+ ]);
43
+ if (exitCode !== 0) {
44
+ await log.error(`Execution failed (code ${exitCode}): ${stderr}`);
45
+ throw new Error(stderr.trim() || `mnemoteca ${args[0]} failed`);
46
+ }
47
+ // Mnemoteca can write output to stderr in older compatible paths. Use whichever has content.
48
+ const output = stdout || stderr;
49
+ await log.debug(`Execution successful. Output size: ${output.length}`);
50
+ return output;
51
+ }
52
+ catch (e) {
53
+ await log.error(`Execution error: ${e instanceof Error ? e.stack : String(e)}`);
54
+ const msg = e instanceof Error ? e.message : String(e);
55
+ if (msg.includes("not found") ||
56
+ msg.includes("ENOENT") ||
57
+ msg.includes("No such file")) {
58
+ return "Error: mnemoteca binary not found. Install it: https://github.com/gandazgul/mnemoteca#install";
59
+ }
60
+ throw e;
61
+ }
62
+ }
63
+ // Auto-init the project collection (idempotent).
64
+ try {
65
+ // @ts-ignore - Bun is globally available in the OpenCode environment.
66
+ await Bun.spawn(["mnemoteca", "init", "--name", project], {
67
+ cwd: targetDir,
68
+ stdout: "ignore", // Silence "collection already exists" logs.
69
+ stderr: "pipe", // Keep stderr for critical errors.
70
+ }).exited;
71
+ await log.info(`Ensured collection exists: ${project}`);
72
+ }
73
+ catch (e) {
74
+ await log.warn(`Failed to auto-init collection: ${e}`);
75
+ }
76
+ return {
77
+ tool: {
78
+ memory_recall: tool({
79
+ description: "Search project memory for relevant context, past decisions, and preferences. Use this at the start of conversations and whenever past context would help.",
80
+ args: {
81
+ query: tool.schema.string().describe("Semantic search query"),
82
+ },
83
+ async execute(args) {
84
+ await log.info(`Searching project memory for: ${args.query}`);
85
+ // Quote the query to prevent SQLite FTS errors with hyphens and special characters.
86
+ const safeQuery = `"${args.query.replaceAll('"', '""')}"`;
87
+ const result = await mnemoteca("search", "--name", project, "--format", "plain", safeQuery);
88
+ return result.trim() || "No memories found.";
89
+ },
90
+ }),
91
+ memory_recall_global: tool({
92
+ description: "Search global memory for cross-project preferences, decisions and patterns.",
93
+ args: {
94
+ query: tool.schema.string().describe("Semantic search query"),
95
+ },
96
+ async execute(args) {
97
+ await log.info(`Searching global memory for: ${args.query}`);
98
+ const safeQuery = `"${args.query.replaceAll('"', '""')}"`;
99
+ const result = await mnemoteca("search", "--global", "--format", "plain", safeQuery);
100
+ return result.trim() || "No global memories found.";
101
+ },
102
+ }),
103
+ memory_store: tool({
104
+ description: "Store a project memory: a decision, preference, or important context. One concise concept per memory. Set core=true for critical context that should always be available in every session (use sparingly).",
105
+ args: {
106
+ content: tool.schema.string().describe("Concise memory to store"),
107
+ core: tool.schema.boolean().optional().describe("If true, this memory is always injected into context (like AGENTS.md). Use sparingly."),
108
+ },
109
+ async execute(args) {
110
+ await log.info(`Storing project memory: ${args.content}`);
111
+ const cmdArgs = ["add", "--name", project];
112
+ if (args.core) {
113
+ cmdArgs.push("--tag", "core");
114
+ }
115
+ cmdArgs.push(args.content);
116
+ return (await mnemoteca(...cmdArgs)).trim();
117
+ },
118
+ }),
119
+ memory_store_global: tool({
120
+ description: "Store a cross-project memory: personal preferences, coding style, tool choices. Set core=true for critical cross-project context that should always be available.",
121
+ args: {
122
+ content: tool.schema.string().describe("Global memory to store"),
123
+ core: tool.schema.boolean().optional().describe("If true, this memory is always injected into context. Use sparingly."),
124
+ },
125
+ async execute(args) {
126
+ await log.info(`Storing global memory: ${args.content}`);
127
+ // Ensure the global collection exists.
128
+ try {
129
+ // @ts-ignore - Bun is globally available in the OpenCode environment.
130
+ await Bun.spawn(["mnemoteca", "init", "--global"], {
131
+ cwd: targetDir,
132
+ stdout: "ignore", // Silence "collection already exists" logs.
133
+ stderr: "pipe", // Keep stderr for critical errors.
134
+ }).exited;
135
+ await log.info("Ensured global collection exists.");
136
+ }
137
+ catch (e) {
138
+ await log.warn(`Failed to auto-init global collection: ${e}`);
139
+ }
140
+ const cmdArgs = ["add", "--global"];
141
+ if (args.core) {
142
+ cmdArgs.push("--tag", "core");
143
+ }
144
+ cmdArgs.push(args.content);
145
+ return (await mnemoteca(...cmdArgs)).trim();
146
+ },
147
+ }),
148
+ memory_delete: tool({
149
+ description: "Delete an outdated or incorrect memory by its document ID (shown in [brackets] in recall/list results).",
150
+ args: {
151
+ id: tool.schema.number().describe("Document ID to delete"),
152
+ },
153
+ async execute(args) {
154
+ await log.info(`Deleting memory document ID: ${args.id}`);
155
+ return (await mnemoteca("delete", String(args.id))).trim();
156
+ },
157
+ }),
158
+ },
159
+ // Inject memory instructions into compaction so they survive context window resets.
160
+ "experimental.session.compacting": async (_input, output) => {
161
+ output.context.push(`## Persistent Memory (Mnemoteca)
162
+
163
+ You have persistent memory tools: memory_recall, memory_store, memory_delete,
164
+ memory_recall_global, memory_store_global.
165
+
166
+ When to use memory:
167
+ - Search memory when past context would help answer the user's request.
168
+ - Store concise summaries of important decisions, preferences, and patterns.
169
+ - Delete outdated memories when new decisions contradict them.
170
+ - Use **core** for facts that should always be in context (project architecture, key conventions, user preferences).
171
+ - Use **global** variants for cross-project preferences (coding style, tool choices).
172
+ - At the end of a session, store any relevant memories for future sessions.`);
173
+ },
174
+ };
175
+ };
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "opencode-mnemoteca",
3
+ "version": "0.3.0",
4
+ "description": "OpenCode plugin for local persistent memory using Mnemoteca — offline semantic search, no cloud required",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "dev": "tsc --watch",
11
+ "typecheck": "tsc --noEmit",
12
+ "ci": "tsc --noEmit && tsc",
13
+ "test": "tsc --noEmit -p tsconfig.test.json && tsx src/index.test.ts"
14
+ },
15
+ "keywords": [
16
+ "opencode",
17
+ "opencode-plugin",
18
+ "mnemoteca",
19
+ "memory",
20
+ "local",
21
+ "offline",
22
+ "semantic-search",
23
+ "ai",
24
+ "coding-agent"
25
+ ],
26
+ "author": "gandazgul",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/gandazgul/opencode-mnemoteca.git"
31
+ },
32
+ "dependencies": {
33
+ "@opencode-ai/plugin": "^1.2.24"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^25.4.0",
37
+ "typescript": "^5.9.3",
38
+ "tsx": "^4.20.6"
39
+ },
40
+ "opencode": {
41
+ "type": "plugin"
42
+ },
43
+ "files": [
44
+ "dist"
45
+ ]
46
+ }