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 +21 -0
- package/README.md +99 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +175 -0
- package/package.json +46 -0
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.
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|