opencode-memelord 0.0.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 +74 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +154 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# opencode-memelord
|
|
2
|
+
|
|
3
|
+
[OpenCode](https://opencode.ai) plugin for [memelord](https://github.com/glommer/memelord) -- persistent memory for coding agents.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
Gives your OpenCode agent persistent memory that improves over time. Memories from past sessions are injected at session start, tool failures are tracked, and transcripts are analyzed for self-corrections and discoveries.
|
|
8
|
+
|
|
9
|
+
| OpenCode event | memelord hook | Purpose |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `session.created` | `session-start` | Inject top memories into context |
|
|
12
|
+
| `tool.execute.after` | `post-tool-use` | Record tool failures for pattern detection |
|
|
13
|
+
| `session.idle` | `stop` | Analyze transcript for corrections and discoveries |
|
|
14
|
+
| `session.deleted` | `session-end` | Embed pending memories, run weight decay |
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
### Quick start (with memelord init)
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g memelord
|
|
22
|
+
cd your-project
|
|
23
|
+
memelord init
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
This configures both the MCP server (for memory tools) and the plugin (for hooks).
|
|
27
|
+
|
|
28
|
+
### Manual setup
|
|
29
|
+
|
|
30
|
+
Add to your `opencode.json`:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"plugin": ["opencode-memelord"],
|
|
35
|
+
"mcp": {
|
|
36
|
+
"memelord": {
|
|
37
|
+
"type": "local",
|
|
38
|
+
"command": ["memelord", "serve"],
|
|
39
|
+
"environment": { "MEMELORD_DIR": ".memelord" },
|
|
40
|
+
"enabled": true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Make sure memelord is installed globally:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install -g memelord
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Then initialize the project database:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
memelord init
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## How it works
|
|
59
|
+
|
|
60
|
+
This plugin is a thin wrapper around the `memelord hook` CLI. It translates OpenCode's plugin events into the same hook commands that memelord uses for Claude Code, so all the analysis, storage, embedding, and decay logic stays in memelord.
|
|
61
|
+
|
|
62
|
+
- **No logic is duplicated** -- the plugin delegates everything to the memelord CLI
|
|
63
|
+
- **Automatic upstream updates** -- when memelord improves its hooks, this plugin benefits without changes
|
|
64
|
+
- **MCP tools still work** -- the plugin handles lifecycle hooks, the MCP server provides memory tools (`memory_start_task`, `memory_report`, `memory_end_task`, `memory_contradict`, `memory_status`)
|
|
65
|
+
|
|
66
|
+
## Requirements
|
|
67
|
+
|
|
68
|
+
- [memelord](https://github.com/glommer/memelord) installed globally (`npm install -g memelord`)
|
|
69
|
+
- [OpenCode](https://opencode.ai) v1.0+
|
|
70
|
+
- A `.memelord/` directory in your project (created by `memelord init`)
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* opencode-memelord: OpenCode plugin for memelord persistent memory.
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper that delegates to `memelord hook <event>` CLI commands.
|
|
5
|
+
* No business logic is ported — all analysis, storage, embedding, and
|
|
6
|
+
* decay happens in the memelord CLI.
|
|
7
|
+
*
|
|
8
|
+
* Hook mapping:
|
|
9
|
+
* session.created → memelord hook session-start
|
|
10
|
+
* tool.execute.after → memelord hook post-tool-use
|
|
11
|
+
* session.idle → memelord hook stop
|
|
12
|
+
* session.deleted → memelord hook session-end
|
|
13
|
+
*/
|
|
14
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
15
|
+
export declare const MemelordPlugin: Plugin;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { dirname, join } from "path";
|
|
3
|
+
import { createRequire } from "module";
|
|
4
|
+
import { writeFileSync, unlinkSync, existsSync } from "fs";
|
|
5
|
+
import { tmpdir } from "os";
|
|
6
|
+
function resolveMemelordBin() {
|
|
7
|
+
try {
|
|
8
|
+
const require2 = createRequire(import.meta.url);
|
|
9
|
+
const pkgPath = require2.resolve("memelord/package.json");
|
|
10
|
+
return join(dirname(pkgPath), "dist", "cli.mjs");
|
|
11
|
+
} catch {
|
|
12
|
+
return "";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
var MemelordPlugin = async ({ client, directory, worktree, $ }) => {
|
|
16
|
+
const cwd = worktree || directory;
|
|
17
|
+
const memelordBin = resolveMemelordBin();
|
|
18
|
+
let currentSessionId = "";
|
|
19
|
+
async function callHook(event, stdinData) {
|
|
20
|
+
const json = JSON.stringify(stdinData);
|
|
21
|
+
try {
|
|
22
|
+
if (memelordBin) {
|
|
23
|
+
return await $`echo ${json} | node ${memelordBin} hook ${event}`.quiet().nothrow().text();
|
|
24
|
+
}
|
|
25
|
+
return await $`echo ${json} | memelord hook ${event}`.quiet().nothrow().text();
|
|
26
|
+
} catch {
|
|
27
|
+
return "";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function convertTranscript(messages) {
|
|
31
|
+
const lines = [];
|
|
32
|
+
for (const { info, parts } of messages) {
|
|
33
|
+
const content = [];
|
|
34
|
+
for (const part of parts) {
|
|
35
|
+
if (part.type === "text") {
|
|
36
|
+
content.push({ type: "text", text: part.text });
|
|
37
|
+
} else if (part.type === "tool") {
|
|
38
|
+
content.push({
|
|
39
|
+
type: "tool_use",
|
|
40
|
+
name: part.tool,
|
|
41
|
+
input: part.state.status !== "pending" ? part.state.input : {}
|
|
42
|
+
});
|
|
43
|
+
if (part.state.status === "completed") {
|
|
44
|
+
content.push({ type: "tool_result", content: part.state.output });
|
|
45
|
+
} else if (part.state.status === "error") {
|
|
46
|
+
content.push({
|
|
47
|
+
type: "tool_result",
|
|
48
|
+
content: part.state.error,
|
|
49
|
+
is_error: true
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const msg = { role: info.role, content };
|
|
55
|
+
if (info.role === "assistant") {
|
|
56
|
+
const a = info;
|
|
57
|
+
msg.usage = {
|
|
58
|
+
input_tokens: a.tokens.input,
|
|
59
|
+
output_tokens: a.tokens.output,
|
|
60
|
+
cache_creation_input_tokens: a.tokens.cache.write
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
lines.push(JSON.stringify(msg));
|
|
64
|
+
}
|
|
65
|
+
return lines.join(`
|
|
66
|
+
`);
|
|
67
|
+
}
|
|
68
|
+
async function onSessionCreated(sessionId) {
|
|
69
|
+
currentSessionId = sessionId;
|
|
70
|
+
if (!existsSync(join(cwd, ".memelord")))
|
|
71
|
+
return;
|
|
72
|
+
const stdout = await callHook("session-start", {
|
|
73
|
+
session_id: sessionId,
|
|
74
|
+
cwd
|
|
75
|
+
});
|
|
76
|
+
if (!stdout.trim())
|
|
77
|
+
return;
|
|
78
|
+
try {
|
|
79
|
+
const parsed = JSON.parse(stdout);
|
|
80
|
+
const context = parsed?.hookSpecificOutput?.additionalContext;
|
|
81
|
+
if (context) {
|
|
82
|
+
await client.session.prompt({
|
|
83
|
+
path: { id: sessionId },
|
|
84
|
+
body: {
|
|
85
|
+
noReply: true,
|
|
86
|
+
parts: [{ type: "text", text: context }]
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
} catch {}
|
|
91
|
+
}
|
|
92
|
+
async function onSessionIdle(sessionId) {
|
|
93
|
+
if (!existsSync(join(cwd, ".memelord")))
|
|
94
|
+
return;
|
|
95
|
+
const stdinData = {
|
|
96
|
+
session_id: sessionId,
|
|
97
|
+
cwd
|
|
98
|
+
};
|
|
99
|
+
try {
|
|
100
|
+
const response = await client.session.messages({
|
|
101
|
+
path: { id: sessionId }
|
|
102
|
+
});
|
|
103
|
+
const messages = response.data;
|
|
104
|
+
if (messages && messages.length > 0) {
|
|
105
|
+
const transcript = convertTranscript(messages);
|
|
106
|
+
const tmpFile = join(tmpdir(), `memelord-transcript-${sessionId}.jsonl`);
|
|
107
|
+
writeFileSync(tmpFile, transcript);
|
|
108
|
+
stdinData.transcript_path = tmpFile;
|
|
109
|
+
await callHook("stop", stdinData);
|
|
110
|
+
try {
|
|
111
|
+
unlinkSync(tmpFile);
|
|
112
|
+
} catch {}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
} catch {}
|
|
116
|
+
await callHook("stop", stdinData);
|
|
117
|
+
}
|
|
118
|
+
async function onSessionDeleted(sessionId) {
|
|
119
|
+
if (!existsSync(join(cwd, ".memelord")))
|
|
120
|
+
return;
|
|
121
|
+
await callHook("session-end", {
|
|
122
|
+
session_id: sessionId,
|
|
123
|
+
cwd
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
"tool.execute.after": async (input, output) => {
|
|
128
|
+
if (!existsSync(join(cwd, ".memelord")))
|
|
129
|
+
return;
|
|
130
|
+
if (!currentSessionId)
|
|
131
|
+
return;
|
|
132
|
+
callHook("post-tool-use", {
|
|
133
|
+
session_id: currentSessionId,
|
|
134
|
+
cwd,
|
|
135
|
+
tool_name: input.tool,
|
|
136
|
+
tool_input: input.args,
|
|
137
|
+
tool_response: output.output ?? ""
|
|
138
|
+
}).catch(() => {});
|
|
139
|
+
},
|
|
140
|
+
event: async (input) => {
|
|
141
|
+
const { event } = input;
|
|
142
|
+
if (event.type === "session.created") {
|
|
143
|
+
await onSessionCreated(event.properties.info.id);
|
|
144
|
+
} else if (event.type === "session.idle") {
|
|
145
|
+
onSessionIdle(event.properties.sessionID).catch(() => {});
|
|
146
|
+
} else if (event.type === "session.deleted") {
|
|
147
|
+
await onSessionDeleted(event.properties.info.id);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
export {
|
|
153
|
+
MemelordPlugin
|
|
154
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-memelord",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "OpenCode plugin for memelord - persistent memory for coding agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target node && tsc --emitDeclarationOnly",
|
|
10
|
+
"typecheck": "tsc --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"opencode",
|
|
14
|
+
"plugin",
|
|
15
|
+
"memelord",
|
|
16
|
+
"memory",
|
|
17
|
+
"ai",
|
|
18
|
+
"coding-agent"
|
|
19
|
+
],
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/buzinas/opencode-memelord"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@opencode-ai/plugin": "^1.2.10",
|
|
27
|
+
"memelord": "^0.1.4"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@opencode-ai/sdk": "^1.2.10",
|
|
31
|
+
"@types/bun": "latest",
|
|
32
|
+
"typescript": "^5.0.0"
|
|
33
|
+
},
|
|
34
|
+
"opencode": {
|
|
35
|
+
"type": "plugin",
|
|
36
|
+
"hooks": [
|
|
37
|
+
"event",
|
|
38
|
+
"tool.execute.after"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist"
|
|
43
|
+
]
|
|
44
|
+
}
|