opencode-deja 0.1.0 → 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 +9 -2
- package/index.js +41 -4
- package/lib.js +84 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,6 +20,12 @@ embeddings, nothing leaves the machine.
|
|
|
20
20
|
opencode installs the package on start. The deja binary comes with it, but a
|
|
21
21
|
deja you installed yourself always wins — see [Which binary](#which-binary).
|
|
22
22
|
|
|
23
|
+
`deja install --auto` wires opencode too, and is the shorter path if you have
|
|
24
|
+
the CLI: it adds deja's MCP server and writes a plugin of its own. Having both
|
|
25
|
+
is fine. This package reads what the installer left — the MCP entry in
|
|
26
|
+
`opencode.json`, the plugin file in `plugins/deja.js` — and contributes only
|
|
27
|
+
what is missing, so nothing is registered or recalled twice.
|
|
28
|
+
|
|
23
29
|
## What you get
|
|
24
30
|
|
|
25
31
|
Six tools the model can call:
|
|
@@ -51,8 +57,9 @@ And recall nobody has to ask for:
|
|
|
51
57
|
|
|
52
58
|
- `autoRecall` (default `true`) — the three hooks above. Turn off to keep only
|
|
53
59
|
the tools.
|
|
54
|
-
- `tools` (default `true`) — the six tools.
|
|
55
|
-
|
|
60
|
+
- `tools` (default `true`) — the six tools. They are skipped on their own when
|
|
61
|
+
`deja install` already wired the MCP server; set this to `false` to drop them
|
|
62
|
+
when you reach deja some other way.
|
|
56
63
|
- `bin` — path to a specific deja binary.
|
|
57
64
|
|
|
58
65
|
## Which binary
|
package/index.js
CHANGED
|
@@ -11,9 +11,17 @@ import { execFile } from "node:child_process"
|
|
|
11
11
|
import { promisify } from "node:util"
|
|
12
12
|
import { homedir } from "node:os"
|
|
13
13
|
import { join } from "node:path"
|
|
14
|
-
import { accessSync, constants } from "node:fs"
|
|
14
|
+
import { accessSync, constants, existsSync, readFileSync } from "node:fs"
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
clampLimit,
|
|
18
|
+
cliPluginPath,
|
|
19
|
+
configPaths,
|
|
20
|
+
contextText,
|
|
21
|
+
contributions,
|
|
22
|
+
lastUserText,
|
|
23
|
+
mcpWired,
|
|
24
|
+
} from "./lib.js"
|
|
17
25
|
|
|
18
26
|
const require = createRequire(import.meta.url)
|
|
19
27
|
const run_ = promisify(execFile)
|
|
@@ -99,6 +107,27 @@ const MISSING =
|
|
|
99
107
|
"deja is not installed on this machine, so there is no history to search. " +
|
|
100
108
|
"Install it with: curl -fsSL https://raw.githubusercontent.com/vshulcz/deja-vu/main/install.sh | sh"
|
|
101
109
|
|
|
110
|
+
// installerWiring reads what `deja install` left on disk for opencode: the MCP
|
|
111
|
+
// server in the config, and the plugin file `--auto` writes. Both reads are
|
|
112
|
+
// wrapped — a config we cannot read means "not wired", so the worst case is
|
|
113
|
+
// this package doing the work twice rather than not at all.
|
|
114
|
+
function installerWiring() {
|
|
115
|
+
const wiring = { mcp: false, recall: false }
|
|
116
|
+
try {
|
|
117
|
+
wiring.recall = existsSync(cliPluginPath(process.env, homedir()))
|
|
118
|
+
} catch {}
|
|
119
|
+
try {
|
|
120
|
+
for (const path of configPaths(process.env, homedir())) {
|
|
121
|
+
if (!existsSync(path)) continue
|
|
122
|
+
if (mcpWired(readFileSync(path, "utf8"))) {
|
|
123
|
+
wiring.mcp = true
|
|
124
|
+
break
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} catch {}
|
|
128
|
+
return wiring
|
|
129
|
+
}
|
|
130
|
+
|
|
102
131
|
export const DejaPlugin = async ({ client, directory }, options = {}) => {
|
|
103
132
|
const config = options || {}
|
|
104
133
|
const bin = resolveDeja(typeof config.bin === "string" ? config.bin : "")
|
|
@@ -117,7 +146,15 @@ export const DejaPlugin = async ({ client, directory }, options = {}) => {
|
|
|
117
146
|
|
|
118
147
|
const hooks = {}
|
|
119
148
|
|
|
120
|
-
|
|
149
|
+
// What `deja install` already wired for opencode is not repeated here. It
|
|
150
|
+
// writes an MCP server (the same six answers, under their own names) and,
|
|
151
|
+
// with --auto, a plugin file of its own that opencode loads beside this
|
|
152
|
+
// package — verified with `opencode debug config`, which resolves both into
|
|
153
|
+
// one plugin list. Whatever the installer wrote wins, because that is the
|
|
154
|
+
// copy `deja install` keeps current; this package fills the gaps.
|
|
155
|
+
const adds = contributions(installerWiring(), config)
|
|
156
|
+
|
|
157
|
+
if (adds.tools && tool) {
|
|
121
158
|
const schema = tool.schema
|
|
122
159
|
hooks.tool = {
|
|
123
160
|
deja_recall: tool({
|
|
@@ -187,7 +224,7 @@ export const DejaPlugin = async ({ client, directory }, options = {}) => {
|
|
|
187
224
|
}
|
|
188
225
|
}
|
|
189
226
|
|
|
190
|
-
if (
|
|
227
|
+
if (!adds.recall) return hooks
|
|
191
228
|
|
|
192
229
|
// opencode has no session-start hook. The system prompt is assembled on
|
|
193
230
|
// every request, so the session digest is fetched once and pushed there —
|
package/lib.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { join } from "node:path"
|
|
2
|
+
|
|
1
3
|
// The pure half of the plugin: everything that reads deja's output or
|
|
2
4
|
// opencode's message shape, with no process and no host. Kept apart from
|
|
3
5
|
// index.js because opencode loads every function a plugin module exports —
|
|
@@ -34,6 +36,88 @@ export function lastUserText(messages) {
|
|
|
34
36
|
return { parts: [], prompt: "" }
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
// cliPluginPath is where `deja install opencode-auto` writes its own plugin.
|
|
40
|
+
// opencode loads that file and this package side by side — both are entries in
|
|
41
|
+
// the resolved `plugin` list — and both push recall onto the system prompt, so
|
|
42
|
+
// whoever finds the other has to stand down. Mirrors opencodeConfigHome() in
|
|
43
|
+
// the installer: XDG_CONFIG_HOME wins, else ~/.config.
|
|
44
|
+
export function cliPluginPath(env, home) {
|
|
45
|
+
const base = (env && env.XDG_CONFIG_HOME) || join(home || "", ".config")
|
|
46
|
+
return join(base, "opencode", "plugins", "deja.js")
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// configPaths are the two names opencode accepts for the global config, in the
|
|
50
|
+
// order `deja install opencode` looks for them.
|
|
51
|
+
export function configPaths(env, home) {
|
|
52
|
+
const base = (env && env.XDG_CONFIG_HOME) || join(home || "", ".config")
|
|
53
|
+
return [join(base, "opencode", "opencode.json"), join(base, "opencode", "opencode.jsonc")]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// mcpWired reports whether that config already runs deja as an MCP server,
|
|
57
|
+
// which is what `deja install opencode` writes. The tools this package
|
|
58
|
+
// registers do the same six things, so when the server is there they are a
|
|
59
|
+
// second copy of it in the model's tool list.
|
|
60
|
+
//
|
|
61
|
+
// The file is JSONC — opencode ships it with comments — so comments come out
|
|
62
|
+
// before the parse. A config this cannot read is treated as not wired: losing
|
|
63
|
+
// the tools on a file we misread is worse than listing them twice.
|
|
64
|
+
export function mcpWired(text) {
|
|
65
|
+
try {
|
|
66
|
+
const config = JSON.parse(stripJSONComments(String(text || "")))
|
|
67
|
+
return Boolean(config && config.mcp && config.mcp.deja)
|
|
68
|
+
} catch {
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// stripJSONComments removes // and /* */ outside strings. Small on purpose: it
|
|
74
|
+
// only has to survive the file our own installer writes.
|
|
75
|
+
export function stripJSONComments(text) {
|
|
76
|
+
let out = ""
|
|
77
|
+
let inString = false
|
|
78
|
+
for (let i = 0; i < text.length; i++) {
|
|
79
|
+
const c = text[i]
|
|
80
|
+
if (inString) {
|
|
81
|
+
out += c
|
|
82
|
+
if (c === "\\") {
|
|
83
|
+
out += text[++i] || ""
|
|
84
|
+
} else if (c === '"') {
|
|
85
|
+
inString = false
|
|
86
|
+
}
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
if (c === '"') {
|
|
90
|
+
inString = true
|
|
91
|
+
out += c
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
if (c === "/" && text[i + 1] === "/") {
|
|
95
|
+
while (i < text.length && text[i] !== "\n") i++
|
|
96
|
+
out += "\n"
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
if (c === "/" && text[i + 1] === "*") {
|
|
100
|
+
i += 2
|
|
101
|
+
while (i + 1 < text.length && !(text[i] === "*" && text[i + 1] === "/")) i++
|
|
102
|
+
i++
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
out += c
|
|
106
|
+
}
|
|
107
|
+
return out
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// contributions decides what this package adds, given what `deja install`
|
|
111
|
+
// already wired and what the user turned off in their config. It is the whole
|
|
112
|
+
// rule in one place: fill the gaps, never repeat the installer.
|
|
113
|
+
export function contributions(wiring, config = {}) {
|
|
114
|
+
const wired = wiring || {}
|
|
115
|
+
return {
|
|
116
|
+
tools: config.tools !== false && !wired.mcp,
|
|
117
|
+
recall: config.autoRecall !== false && !wired.recall,
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
37
121
|
// clampLimit keeps a model that asks for a hundred sessions from spending the
|
|
38
122
|
// window on a tail nobody reads.
|
|
39
123
|
export function clampLimit(value, fallback = 5) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-deja",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Brings the session history of nineteen other coding agents into opencode: recall, session digest and per-file history tools over a local index, plus automatic recall on every request.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|