@soffits/opencode-rummage 0.0.0-stage → 0.1.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/index.js ADDED
@@ -0,0 +1,77 @@
1
+ // OpenCode plugin for rummage: recall from the user's files on each prompt, plus the rummage skill.
2
+ //
3
+ // The prompt hook runs `rummage recall` once per prompt and keeps the block in the message's metadata, so it is
4
+ // stored with the message and never re-run. The context hook adds that block to its user message in every model
5
+ // request, so the system prompt stays the same and the request prefix stays cacheable. A recall that fails or
6
+ // takes longer than `timeout` adds nothing.
7
+
8
+ import { execFile } from "node:child_process"
9
+ import { readFileSync } from "node:fs"
10
+
11
+ const KEY = "rummage.recall"
12
+
13
+ function recall(command, args, prompt, sessionID, timeout) {
14
+ return new Promise((resolve) => {
15
+ const child = execFile(
16
+ command,
17
+ ["recall", "--hook", ...args],
18
+ { timeout, maxBuffer: 1 << 20, windowsHide: true },
19
+ (error, stdout) => {
20
+ if (error || !stdout.trim()) return resolve("")
21
+ try {
22
+ resolve(JSON.parse(stdout).hookSpecificOutput?.additionalContext ?? "")
23
+ } catch {
24
+ resolve("")
25
+ }
26
+ },
27
+ )
28
+ child.stdin.on("error", () => {})
29
+ child.stdin.end(JSON.stringify({ prompt, session_id: sessionID }))
30
+ })
31
+ }
32
+
33
+ function bundledSkill() {
34
+ try {
35
+ const path = new URL("./skills/rummage/SKILL.md", import.meta.url)
36
+ const text = readFileSync(path, "utf8")
37
+ const [, front = "", body = text] = text.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/) ?? []
38
+ const field = (name) => front.match(new RegExp(`^${name}:\\s*(.*)$`, "m"))?.[1].trim()
39
+ return { id: field("name") ?? "rummage", description: field("description"), path: path.pathname, content: body }
40
+ } catch {
41
+ return undefined
42
+ }
43
+ }
44
+
45
+ export default {
46
+ id: "rummage",
47
+ async setup(ctx) {
48
+ const options = ctx.options ?? {}
49
+ const command = options.command ?? "rummage"
50
+ const args = options.args ?? []
51
+ const timeout = options.timeout ?? 5000
52
+
53
+ await ctx.session.hook("prompt", async (event) => {
54
+ const text = event.prompt.text ?? ""
55
+ if (!text.trim()) return
56
+ const block = await recall(command, args, text, String(event.sessionID), timeout)
57
+ if (block) event.metadata = { ...event.metadata, [KEY]: block }
58
+ })
59
+
60
+ await ctx.session.hook("context", (event) => {
61
+ event.messages.forEach((message, index) => {
62
+ const block = message.role === "user" && message.metadata?.[KEY]
63
+ if (typeof block !== "string") return
64
+ event.messages[index] = Object.assign(Object.create(Object.getPrototypeOf(message)), message, {
65
+ content: [...message.content, { type: "text", text: block }],
66
+ })
67
+ })
68
+ })
69
+
70
+ const skill = options.skill === false ? undefined : bundledSkill()
71
+ if (skill) {
72
+ await ctx.skill.transform((editor) => {
73
+ if (!editor.get(skill.id)) editor.add({ ...skill, name: skill.id })
74
+ })
75
+ }
76
+ },
77
+ }
package/package.json CHANGED
@@ -1,6 +1,16 @@
1
1
  {
2
2
  "name": "@soffits/opencode-rummage",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "OpenCode plugin for rummage: recall from your own files on each prompt, and a skill for searching them.",
5
+ "license": "AGPL-3.0-or-later",
6
+ "author": "Sakina",
7
+ "homepage": "https://github.com/soffits/rummage/blob/main/docs/integrations.md#opencode",
8
+ "repository": { "type": "git", "url": "git+https://github.com/soffits/rummage.git", "directory": "integrations/opencode" },
9
+ "keywords": ["opencode", "opencode-plugin", "memory", "recall", "search"],
10
+ "type": "module",
11
+ "exports": { ".": "./index.js" },
12
+ "files": ["index.js", "skills"],
13
+ "scripts": {
14
+ "prepack": "node -e \"require('fs').cpSync('../../skills/rummage', 'skills/rummage', { recursive: true })\""
15
+ }
16
+ }
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: rummage
3
+ description: Search the user's own local files (notes, documents, PDFs, pictures, chats) by meaning or exact words with the rummage CLI, then open the passages it finds. Use when the answer may be in the user's files and you don't know where, or the exact words.
4
+ ---
5
+
6
+ # rummage
7
+
8
+ `rummage` keeps a local index of the user's files and finds things by what they mean and by the exact words in
9
+ them. It runs on the user's machine; nothing is sent anywhere.
10
+
11
+ ## Search
12
+
13
+ ```bash
14
+ rummage search "what the user is looking for, in their own words" --json -n 10
15
+ rummage search "INV-20931" --words-only --json # names, IDs, numbers: exact words, no model, instant
16
+ rummage search "..." --json --path notes/work --glob '*.md' -s notes # narrow by folder, pattern, source
17
+ rummage search "..." --json --since 2026-10-01 --until 2026-10-31 # a time window (dates you work out)
18
+ rummage search "Ana Souza" --json --sort newest -n 30 # the newest matches first
19
+ rummage search "..." --json --budget-tokens 1500 # shorter output
20
+ ```
21
+
22
+ The JSON has `status`:
23
+
24
+ - `ok`: `results` are ranked. `summary.strong` says how many score near the top; the rest are weak.
25
+ - `no_results`: nothing matched. Rephrase by meaning, try exact words with `--words-only`, or drop a scope
26
+ filter.
27
+ - `empty_index` or an error with status `index_missing`: nothing is indexed yet (`rummage index`).
28
+
29
+ Each result has `id`, `source`, `title`, `ref` (where it is), `lines` for text files, `score`, `snippet`.
30
+ `next_actions` holds the commands that show what the output left out (masked credentials, the rest of a partially
31
+ indexed file); the next query is yours to choose.
32
+
33
+ ## Open what you found
34
+
35
+ ```bash
36
+ rummage get '#3f2a9c01be' # the whole item's text
37
+ rummage get '#3f2a9c01be' --loc 1800 # only the matched passage (a result's `loc`)
38
+ rummage get 'notes/lease.md' --head 40
39
+ ```
40
+
41
+ For a chat result, `rummage get '#id' --around 3` shows the turns before and after it; for a file passage it shows
42
+ the lines around it. `rummage recent --minutes 30 --json` lists what was said or changed lately, newest first.
43
+
44
+ Credentials in results print as `[secret]`. Add `--show-secrets` only when the user asks for a value.
45
+
46
+ Quote the file and lines you relied on. A miss doesn't prove the thing isn't there: try other words, exact
47
+ names, or the folder directly before saying so.
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.