@mem9/opencode 0.1.1 → 0.1.2
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 +64 -101
- package/package.json +2 -5
- package/src/shared/setup-files.ts +19 -0
- package/src/tui/index.ts +51 -20
package/README.md
CHANGED
|
@@ -2,82 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
Persistent memory for [OpenCode](https://opencode.ai).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This package has two entrypoints:
|
|
6
6
|
|
|
7
7
|
- a server plugin for recall, auto-ingest, and memory tools
|
|
8
8
|
- a TUI plugin for interactive setup inside OpenCode
|
|
9
9
|
|
|
10
10
|
## Quick Start
|
|
11
11
|
|
|
12
|
-
### 1. Install
|
|
12
|
+
### 1. Install mem9 once at user scope
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
User scope:
|
|
17
|
-
|
|
18
|
-
File: `~/.config/opencode/opencode.json`
|
|
19
|
-
|
|
20
|
-
```json
|
|
21
|
-
{
|
|
22
|
-
"plugin": ["@mem9/opencode"]
|
|
23
|
-
}
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Project scope:
|
|
27
|
-
|
|
28
|
-
File: `<project>/.opencode/opencode.json`
|
|
29
|
-
|
|
30
|
-
```json
|
|
31
|
-
{
|
|
32
|
-
"plugin": ["@mem9/opencode"]
|
|
33
|
-
}
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Recommended pattern:
|
|
37
|
-
|
|
38
|
-
- install the server plugin once at user scope
|
|
39
|
-
- keep project-specific behavior in `<project>/.opencode/mem9.json`
|
|
40
|
-
|
|
41
|
-
`@mem9/opencode` resolves npm `latest`.
|
|
42
|
-
If you publish a prerelease channel, install it with an explicit npm specifier instead:
|
|
43
|
-
|
|
44
|
-
```json
|
|
45
|
-
{
|
|
46
|
-
"plugin": ["@mem9/opencode@rc"]
|
|
47
|
-
}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
You can also pin an exact prerelease version:
|
|
51
|
-
|
|
52
|
-
```json
|
|
53
|
-
{
|
|
54
|
-
"plugin": ["@mem9/opencode@0.1.0-rc.0"]
|
|
55
|
-
}
|
|
14
|
+
```bash
|
|
15
|
+
opencode plugin --global @mem9/opencode
|
|
56
16
|
```
|
|
57
17
|
|
|
58
|
-
|
|
18
|
+
That adds `@mem9/opencode` to these files:
|
|
59
19
|
|
|
60
|
-
|
|
61
|
-
- npm plugin plus local file plugin
|
|
20
|
+
macOS/Linux:
|
|
62
21
|
|
|
63
|
-
|
|
22
|
+
- `~/.config/opencode/opencode.json`
|
|
23
|
+
- `~/.config/opencode/tui.json`
|
|
64
24
|
|
|
65
|
-
|
|
25
|
+
Windows:
|
|
66
26
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"plugin": ["@mem9/opencode"]
|
|
70
|
-
}
|
|
71
|
-
```
|
|
27
|
+
- `%APPDATA%\\opencode\\opencode.json`
|
|
28
|
+
- `%APPDATA%\\opencode\\tui.json`
|
|
72
29
|
|
|
73
|
-
|
|
30
|
+
mem9 works best with one global plugin install plus project `mem9.json` overrides.
|
|
31
|
+
OpenCode merges plugin lists across scopes, so one install keeps recall, ingest, and tools predictable.
|
|
74
32
|
|
|
75
|
-
###
|
|
33
|
+
### 2. Restart OpenCode and run `/mem9-setup`
|
|
76
34
|
|
|
77
|
-
`/mem9-setup` is the
|
|
35
|
+
`/mem9-setup` is the main entrypoint for:
|
|
78
36
|
|
|
79
37
|
- shared mem9 credentials
|
|
80
|
-
- OpenCode
|
|
38
|
+
- OpenCode mem9 settings
|
|
81
39
|
|
|
82
40
|
When no usable profile exists, it shows two actions:
|
|
83
41
|
|
|
@@ -89,32 +47,45 @@ When usable profiles already exist, it shows four actions:
|
|
|
89
47
|
- `Get a mem9 API key automatically`
|
|
90
48
|
- `Add an existing mem9 API key`
|
|
91
49
|
- `Use an existing mem9 profile in a scope`
|
|
92
|
-
- `
|
|
50
|
+
- `Adjust scope settings`
|
|
93
51
|
|
|
94
|
-
|
|
52
|
+
The last two actions do different things:
|
|
95
53
|
|
|
96
|
-
|
|
54
|
+
- `Use an existing mem9 profile in a scope` changes which profile a user or project scope uses
|
|
55
|
+
- `Adjust scope settings` changes `debug`, `defaultTimeoutMs`, and `searchTimeoutMs` for a user or project scope
|
|
97
56
|
|
|
98
|
-
|
|
57
|
+
### 3. Add project overrides only when you need them
|
|
99
58
|
|
|
100
|
-
|
|
101
|
-
|
|
59
|
+
Keep the plugin install global.
|
|
60
|
+
Use `<project>/.opencode/mem9.json` when one repository needs a different profile, debug flag, or timeout.
|
|
102
61
|
|
|
103
|
-
|
|
62
|
+
Example:
|
|
104
63
|
|
|
105
|
-
|
|
106
|
-
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"schemaVersion": 1,
|
|
67
|
+
"profileId": "default",
|
|
68
|
+
"debug": false,
|
|
69
|
+
"defaultTimeoutMs": 8000,
|
|
70
|
+
"searchTimeoutMs": 15000
|
|
71
|
+
}
|
|
72
|
+
```
|
|
107
73
|
|
|
108
|
-
mem9
|
|
74
|
+
## Where mem9 stores data
|
|
109
75
|
|
|
110
|
-
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
-
|
|
114
|
-
|
|
115
|
-
|
|
76
|
+
- Shared credentials:
|
|
77
|
+
macOS/Linux: `$HOME/.mem9/.credentials.json`
|
|
78
|
+
Windows: `%USERPROFILE%\\.mem9\\.credentials.json`
|
|
79
|
+
- Global mem9 config:
|
|
80
|
+
macOS/Linux: `~/.config/opencode/mem9.json`
|
|
81
|
+
Windows: `%APPDATA%\\opencode\\mem9.json`
|
|
82
|
+
- Project mem9 override:
|
|
83
|
+
all platforms: `<project>/.opencode/mem9.json`
|
|
84
|
+
- Debug logs:
|
|
85
|
+
macOS/Linux: `~/.local/share/opencode/plugins/mem9/log/YYYY-MM-DD.jsonl`
|
|
86
|
+
Windows: `%LOCALAPPDATA%\\opencode\\plugins\\mem9\\log\\YYYY-MM-DD.jsonl`
|
|
116
87
|
|
|
117
|
-
`MEM9_HOME` defaults to `$HOME/.mem9
|
|
88
|
+
`MEM9_HOME` defaults to `$HOME/.mem9` on macOS/Linux and `%USERPROFILE%\\.mem9` on Windows.
|
|
118
89
|
|
|
119
90
|
## Credentials File
|
|
120
91
|
|
|
@@ -173,33 +144,33 @@ These environment variables still override disk config at runtime:
|
|
|
173
144
|
- `MEM9_DEBUG`
|
|
174
145
|
- `MEM9_HOME`
|
|
175
146
|
|
|
176
|
-
Legacy compatibility
|
|
147
|
+
Legacy compatibility:
|
|
177
148
|
|
|
178
149
|
- `MEM9_TENANT_ID`
|
|
179
150
|
|
|
180
151
|
`MEM9_TENANT_ID` is treated as the API key source for older setups.
|
|
181
152
|
|
|
182
|
-
##
|
|
153
|
+
## Upgrading
|
|
183
154
|
|
|
184
|
-
|
|
155
|
+
OpenCode caches npm plugins by package specifier.
|
|
156
|
+
When config points at `@mem9/opencode`, OpenCode resolves it as `@mem9/opencode@latest`.
|
|
185
157
|
|
|
186
|
-
|
|
158
|
+
Upgrade flow:
|
|
187
159
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
160
|
+
1. Quit OpenCode.
|
|
161
|
+
2. Delete the cached folder that matches the installed specifier.
|
|
162
|
+
macOS/Linux default: `~/.cache/opencode/packages/@mem9/opencode@latest`
|
|
163
|
+
Windows default: `%LOCALAPPDATA%\\opencode\\packages\\@mem9\\opencode@latest`
|
|
164
|
+
If you pinned an exact version such as `@mem9/opencode@0.1.1`, delete that exact folder name instead.
|
|
165
|
+
3. Run:
|
|
193
166
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
```json
|
|
197
|
-
{
|
|
198
|
-
"plugin": ["./.opencode/plugins/mem9/src/tui/index.ts"]
|
|
199
|
-
}
|
|
167
|
+
```bash
|
|
168
|
+
opencode plugin --force --global @mem9/opencode
|
|
200
169
|
```
|
|
201
170
|
|
|
202
|
-
|
|
171
|
+
4. Restart OpenCode.
|
|
172
|
+
|
|
173
|
+
For prerelease testing, install an explicit npm specifier such as `@mem9/opencode@rc` or an exact version.
|
|
203
174
|
|
|
204
175
|
## What the Plugin Does
|
|
205
176
|
|
|
@@ -211,7 +182,7 @@ The server plugin does three things:
|
|
|
211
182
|
|
|
212
183
|
### Hook Flow
|
|
213
184
|
|
|
214
|
-
OpenCode integration
|
|
185
|
+
OpenCode integration uses four runtime hooks:
|
|
215
186
|
|
|
216
187
|
| Hook | What mem9 does |
|
|
217
188
|
| --- | --- |
|
|
@@ -253,16 +224,8 @@ The plugin registers these tools:
|
|
|
253
224
|
## Troubleshooting
|
|
254
225
|
|
|
255
226
|
- `Setup pending` means the plugin could not find a usable runtime identity. Run `/mem9-setup`, add `MEM9_API_KEY`, or point the active `mem9.json` scope at a profile with a non-empty `apiKey`.
|
|
256
|
-
- If `/mem9-setup` is missing, confirm `@mem9/opencode` is listed in
|
|
227
|
+
- If `/mem9-setup` is missing, confirm `@mem9/opencode` is listed in your global `tui.json`.
|
|
257
228
|
- If recall or tools work in one project and not another, check whether the project has its own `.opencode/mem9.json` override.
|
|
258
229
|
- If recall, auto-ingest, or debug logs appear to run twice, check for duplicate plugin registration across user scope, project scope, npm, or local file paths.
|
|
259
230
|
- If the selected profile exists but has no `apiKey`, update that profile in `$MEM9_HOME/.credentials.json`.
|
|
260
231
|
- If debug logging is enabled and no file appears, confirm OpenCode can write to its state directory.
|
|
261
|
-
|
|
262
|
-
## Local Verification
|
|
263
|
-
|
|
264
|
-
```bash
|
|
265
|
-
pnpm test
|
|
266
|
-
pnpm run typecheck
|
|
267
|
-
pnpm run pack:check
|
|
268
|
-
```
|
package/package.json
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mem9/opencode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "OpenCode memory plugin — cloud-persistent AI agent memory with hybrid vector + keyword search via mem9",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
|
-
"oc-plugin": [
|
|
8
|
-
"server",
|
|
9
|
-
"tui"
|
|
10
|
-
],
|
|
11
7
|
"exports": {
|
|
12
8
|
".": "./src/index.ts",
|
|
9
|
+
"./server": "./src/index.ts",
|
|
13
10
|
"./tui": "./src/tui/index.ts"
|
|
14
11
|
},
|
|
15
12
|
"license": "Apache-2.0",
|
|
@@ -17,6 +17,7 @@ export interface SetupProfileSummary {
|
|
|
17
17
|
label: string;
|
|
18
18
|
baseUrl: string;
|
|
19
19
|
hasApiKey: boolean;
|
|
20
|
+
apiKeyPreview: string;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export interface ScopeConfigState {
|
|
@@ -112,6 +113,23 @@ function hasApiKey(profile: unknown): boolean {
|
|
|
112
113
|
return Boolean(normalizeOptionalString(profile.apiKey));
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
function summarizeApiKeyPreview(apiKey: string): string {
|
|
117
|
+
const normalized = apiKey.trim();
|
|
118
|
+
if (normalized.length === 0) {
|
|
119
|
+
return "";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (normalized.length <= 4) {
|
|
123
|
+
return `${normalized.slice(0, 1)}...`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (normalized.length <= 8) {
|
|
127
|
+
return `${normalized.slice(0, 2)}...${normalized.slice(-2)}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return `${normalized.slice(0, 4)}...${normalized.slice(-4)}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
115
133
|
function normalizeProfileRecord(
|
|
116
134
|
profileId: string,
|
|
117
135
|
profile: Mem9Profile | undefined,
|
|
@@ -188,6 +206,7 @@ function buildProfiles(
|
|
|
188
206
|
label: normalized.label,
|
|
189
207
|
baseUrl: normalized.baseUrl,
|
|
190
208
|
hasApiKey: normalized.apiKey.trim().length > 0,
|
|
209
|
+
apiKeyPreview: summarizeApiKeyPreview(normalized.apiKey),
|
|
191
210
|
};
|
|
192
211
|
})
|
|
193
212
|
.sort(sortProfileSummaries);
|
package/src/tui/index.ts
CHANGED
|
@@ -27,7 +27,7 @@ type SetupAction =
|
|
|
27
27
|
| "use-profile-in-scope"
|
|
28
28
|
| "configure-scope";
|
|
29
29
|
|
|
30
|
-
type ScopeFlowMode = "profile-only" | "
|
|
30
|
+
type ScopeFlowMode = "profile-only" | "settings-only";
|
|
31
31
|
|
|
32
32
|
interface ProfileDraft {
|
|
33
33
|
profileId: string;
|
|
@@ -50,6 +50,17 @@ export interface SetupActionOption {
|
|
|
50
50
|
description: string;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
export interface SetupProfileOption {
|
|
54
|
+
title: string;
|
|
55
|
+
value: string;
|
|
56
|
+
description: string;
|
|
57
|
+
disabled: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface SetupProfileOptionState {
|
|
61
|
+
currentProfileId?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
53
64
|
function getProjectDir(api: TuiPluginApi): string {
|
|
54
65
|
const worktree = api.state.path.worktree.trim();
|
|
55
66
|
if (worktree.length > 0) {
|
|
@@ -155,12 +166,12 @@ export function buildSetupActionOptions(
|
|
|
155
166
|
{
|
|
156
167
|
title: "Use an existing mem9 profile in a scope",
|
|
157
168
|
value: "use-profile-in-scope",
|
|
158
|
-
description: "Choose
|
|
169
|
+
description: "Choose which saved profile user or project settings should use.",
|
|
159
170
|
},
|
|
160
171
|
{
|
|
161
|
-
title: "
|
|
172
|
+
title: "Adjust scope settings",
|
|
162
173
|
value: "configure-scope",
|
|
163
|
-
description: "Change
|
|
174
|
+
description: "Change debug logging, request timeouts, and other mem9 settings for a user or project scope.",
|
|
164
175
|
},
|
|
165
176
|
);
|
|
166
177
|
}
|
|
@@ -168,6 +179,30 @@ export function buildSetupActionOptions(
|
|
|
168
179
|
return options;
|
|
169
180
|
}
|
|
170
181
|
|
|
182
|
+
function formatProfileTitle(profile: SetupProfileSummary): string {
|
|
183
|
+
return profile.label === profile.profileId
|
|
184
|
+
? profile.label
|
|
185
|
+
: `${profile.label} (${profile.profileId})`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function buildScopeProfileOptions(
|
|
189
|
+
state: Pick<SetupState, "profiles">,
|
|
190
|
+
optionState: SetupProfileOptionState = {},
|
|
191
|
+
): SetupProfileOption[] {
|
|
192
|
+
return state.profiles.map((profile) => ({
|
|
193
|
+
title: formatProfileTitle(profile),
|
|
194
|
+
value: profile.profileId,
|
|
195
|
+
description: [
|
|
196
|
+
profile.apiKeyPreview || "API key missing",
|
|
197
|
+
profile.baseUrl,
|
|
198
|
+
profile.profileId === optionState.currentProfileId ? "Current in this scope" : undefined,
|
|
199
|
+
]
|
|
200
|
+
.filter((value): value is string => Boolean(value))
|
|
201
|
+
.join(" | "),
|
|
202
|
+
disabled: !profile.hasApiKey,
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
205
|
+
|
|
171
206
|
function parsePositiveInteger(value: string, field: string): number | null {
|
|
172
207
|
const trimmed = value.trim();
|
|
173
208
|
if (trimmed.length === 0) {
|
|
@@ -279,7 +314,7 @@ function showActionDialog(
|
|
|
279
314
|
return;
|
|
280
315
|
}
|
|
281
316
|
|
|
282
|
-
showScopeDialog(api, paths, state, "
|
|
317
|
+
showScopeDialog(api, paths, state, "settings-only");
|
|
283
318
|
},
|
|
284
319
|
}),
|
|
285
320
|
);
|
|
@@ -459,7 +494,12 @@ function showScopeDialog(
|
|
|
459
494
|
],
|
|
460
495
|
onSelect: (option) => {
|
|
461
496
|
const draft = createScopeDraft(state, option.value);
|
|
462
|
-
|
|
497
|
+
if (mode === "profile-only") {
|
|
498
|
+
showScopeProfileDialog(api, paths, state, draft);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
showScopeDebugDialog(api, paths, state, draft);
|
|
463
503
|
},
|
|
464
504
|
}),
|
|
465
505
|
);
|
|
@@ -469,27 +509,18 @@ function showScopeProfileDialog(
|
|
|
469
509
|
api: TuiPluginApi,
|
|
470
510
|
paths: Mem9ResolvedPaths,
|
|
471
511
|
state: SetupState,
|
|
472
|
-
mode: ScopeFlowMode,
|
|
473
512
|
draft: ScopeDraft,
|
|
474
513
|
): void {
|
|
475
514
|
api.ui.dialog.replace(() =>
|
|
476
515
|
api.ui.DialogSelect<string>({
|
|
477
516
|
title: "Which mem9 profile should this scope use?",
|
|
478
517
|
current: draft.profileId,
|
|
479
|
-
options: state
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
description: `${profile.baseUrl} • ${profile.hasApiKey ? "API key configured" : "API key missing"}`,
|
|
483
|
-
disabled: !profile.hasApiKey,
|
|
484
|
-
})),
|
|
518
|
+
options: buildScopeProfileOptions(state, {
|
|
519
|
+
currentProfileId: draft.profileId,
|
|
520
|
+
}),
|
|
485
521
|
onSelect: (option) => {
|
|
486
522
|
draft.profileId = option.value;
|
|
487
|
-
|
|
488
|
-
void submitScopeConfigDraft(api, paths, draft);
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
showScopeDebugDialog(api, paths, state, draft);
|
|
523
|
+
void submitScopeConfigDraft(api, paths, draft);
|
|
493
524
|
},
|
|
494
525
|
}),
|
|
495
526
|
);
|
|
@@ -550,7 +581,7 @@ function showDefaultTimeoutDialog(
|
|
|
550
581
|
showSearchTimeoutDialog(api, paths, state, draft);
|
|
551
582
|
},
|
|
552
583
|
onCancel: () => {
|
|
553
|
-
|
|
584
|
+
showScopeDebugDialog(api, paths, state, draft);
|
|
554
585
|
},
|
|
555
586
|
}),
|
|
556
587
|
);
|