@soffits/opencode-rummage 0.2.0 → 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/package.json +1 -1
- package/skills/rummage/SKILL.md +10 -0
- package/tui.js +64 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soffits/opencode-rummage",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "OpenCode plugin for rummage: recall from your own files on each prompt, a skill for searching them, and indexing progress in the footer.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"author": "Sakina",
|
package/skills/rummage/SKILL.md
CHANGED
|
@@ -45,3 +45,13 @@ Credentials in results print as `[secret]`. Add `--show-secrets` only when the u
|
|
|
45
45
|
|
|
46
46
|
Quote the file and lines you relied on. A miss doesn't prove the thing isn't there: try other words, exact
|
|
47
47
|
names, or the folder directly before saying so.
|
|
48
|
+
|
|
49
|
+
While the index is still being built, output carries an `index` note saying how much is in; treat a miss then as
|
|
50
|
+
"maybe not indexed yet", and `rummage status --line` tells the user how far along it is.
|
|
51
|
+
|
|
52
|
+
## When rummage itself is broken
|
|
53
|
+
|
|
54
|
+
If you've confirmed a problem is rummage's own (reproduced it, checked `rummage doctor` and the version, ruled out
|
|
55
|
+
the user's config), tell the user and offer a ready issue draft: steps to reproduce, rummage version, OS, the exact
|
|
56
|
+
error, with private content (file names, paths, text from their files) removed. Link
|
|
57
|
+
https://github.com/soffits/rummage/issues and let them decide whether to file it; never open issues or PRs yourself.
|
package/tui.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// OpenCode TUI part of the rummage plugin: shows rummage's index state in the footer while it is building, paused
|
|
2
|
-
// or broken
|
|
2
|
+
// or broken (or memory is paused), toasts when indexing starts, finishes or fails, a one-time note on what the plugin
|
|
3
|
+
// does, and /rummage-pause and /rummage-resume.
|
|
3
4
|
//
|
|
4
5
|
// All state comes from `rummage status --line --json`, which reads one small progress file. Between runs this part
|
|
5
6
|
// only checks that file's modification time, so an idle index costs a stat every few seconds. Written as plain JS
|
|
@@ -15,11 +16,17 @@ const RECHECK_BUSY_MS = 30000
|
|
|
15
16
|
const FOOTER_CHARS = 60
|
|
16
17
|
const KEY = Symbol.for("rummage.opencode.tui")
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
const INTRO =
|
|
20
|
+
"rummage remembers the project you open and OpenCode's own chats, and adds passages that clearly match to your " +
|
|
21
|
+
"prompts. /rummage-pause stops it for a while; removing the plugin turns it off."
|
|
22
|
+
|
|
23
|
+
function run(command, args, timeout, input) {
|
|
19
24
|
return new Promise((resolve) => {
|
|
20
|
-
execFile(command, args, { timeout, maxBuffer: 1 << 20, windowsHide: true }, (error, stdout, stderr) =>
|
|
25
|
+
const child = execFile(command, args, { timeout, maxBuffer: 1 << 20, windowsHide: true }, (error, stdout, stderr) =>
|
|
21
26
|
resolve({ ok: !error, stdout: String(stdout ?? ""), stderr: String(stderr ?? "") }),
|
|
22
27
|
)
|
|
28
|
+
child.stdin.on("error", () => {})
|
|
29
|
+
child.stdin.end(input ?? "")
|
|
23
30
|
})
|
|
24
31
|
}
|
|
25
32
|
|
|
@@ -83,7 +90,7 @@ export default {
|
|
|
83
90
|
return line.length > FOOTER_CHARS ? line.slice(0, FOOTER_CHARS - 3) + "..." : line
|
|
84
91
|
})
|
|
85
92
|
effect((prev) => {
|
|
86
|
-
const bad = ["error", "stopped", "paused"].includes(info().state)
|
|
93
|
+
const bad = ["error", "stopped", "paused"].includes(info().state) || Boolean(info().paused_memory)
|
|
87
94
|
const theme = context.theme.text
|
|
88
95
|
return setProp(el, "fg", bad ? (theme.feedback?.warning?.base ?? theme.base) : theme.muted, prev)
|
|
89
96
|
})
|
|
@@ -95,11 +102,64 @@ export default {
|
|
|
95
102
|
context.ui.slot({ append: "home.footer.status", render: label }),
|
|
96
103
|
]
|
|
97
104
|
|
|
105
|
+
const directory = () => (context.location ?? context.data.location.default())?.directory
|
|
106
|
+
const sessionID = () => {
|
|
107
|
+
const route = context.ui.router.current()
|
|
108
|
+
return route.type === "session" ? route.sessionID : undefined
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// The same words as Claude Code's /rummage:pause: recall|index|all, a time like 2h, here, session, private.
|
|
112
|
+
async function pauseCommand(verb, input) {
|
|
113
|
+
const event = { command_args: String(input ?? ""), cwd: directory(), session_id: sessionID() }
|
|
114
|
+
const result = await run(command, [verb, "--hook"], 10000, JSON.stringify(event))
|
|
115
|
+
let reason = ""
|
|
116
|
+
try {
|
|
117
|
+
reason = JSON.parse(result.stdout).reason ?? ""
|
|
118
|
+
} catch {}
|
|
119
|
+
context.ui.toast.show({
|
|
120
|
+
title: "rummage",
|
|
121
|
+
message: reason || result.stderr.trim() || `${verb} failed`,
|
|
122
|
+
variant: result.ok ? "info" : "error",
|
|
123
|
+
})
|
|
124
|
+
void refresh()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const unkeymap = context.keymap.layer(() => ({
|
|
128
|
+
mode: "global",
|
|
129
|
+
commands: [
|
|
130
|
+
{
|
|
131
|
+
id: "rummage.pause",
|
|
132
|
+
title: "Pause rummage (recall, index or all; a time like 2h; here or session)",
|
|
133
|
+
group: "rummage",
|
|
134
|
+
palette: true,
|
|
135
|
+
slash: { name: "rummage-pause", arguments: true },
|
|
136
|
+
run: (input) => pauseCommand("pause", input),
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: "rummage.resume",
|
|
140
|
+
title: "Resume rummage",
|
|
141
|
+
group: "rummage",
|
|
142
|
+
palette: true,
|
|
143
|
+
slash: { name: "rummage-resume", arguments: true },
|
|
144
|
+
run: (input) => pauseCommand("resume", input),
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
}))
|
|
148
|
+
|
|
149
|
+
const [seen, setSeen] = context.storage.store("rummage.intro", { initial: { shown: false } })
|
|
150
|
+
if (!seen.shown) {
|
|
151
|
+
context.ui.toast.show({ title: "rummage", message: INTRO, variant: "info" })
|
|
152
|
+
void setSeen((draft) => {
|
|
153
|
+
draft.shown = true
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
98
157
|
const dispose = () => {
|
|
99
158
|
if (stopped) return
|
|
100
159
|
stopped = true
|
|
101
160
|
clearTimeout(timer)
|
|
102
161
|
for (const off of unslot) off?.()
|
|
162
|
+
unkeymap?.()
|
|
103
163
|
if (globalThis[KEY] === dispose) delete globalThis[KEY]
|
|
104
164
|
}
|
|
105
165
|
globalThis[KEY] = dispose
|