@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soffits/opencode-rummage",
3
- "version": "0.2.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",
@@ -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, and a toast when indexing starts, finishes or fails.
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
- function run(command, args, timeout) {
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