openviking-opencode 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.mjs ADDED
@@ -0,0 +1,90 @@
1
+ import { exec } from "child_process"
2
+ import { promisify } from "util"
3
+ import { readFileSync, mkdirSync, writeFileSync, existsSync } from "fs"
4
+ import { homedir } from "os"
5
+ import { join, dirname } from "path"
6
+ import { fileURLToPath } from "url"
7
+
8
+ const execAsync = promisify(exec)
9
+ const __dirname = dirname(fileURLToPath(import.meta.url))
10
+
11
+ // ── Skill auto-install ────────────────────────────────────────────────────────
12
+
13
+ function installSkill() {
14
+ const src = join(__dirname, "skills", "openviking", "SKILL.md")
15
+ const dest = join(homedir(), ".config", "opencode", "skills", "openviking", "SKILL.md")
16
+ const destDir = dirname(dest)
17
+ try {
18
+ if (!existsSync(destDir)) mkdirSync(destDir, { recursive: true })
19
+ const content = readFileSync(src, "utf8")
20
+ // Only overwrite if content changed (avoid unnecessary disk writes)
21
+ if (!existsSync(dest) || readFileSync(dest, "utf8") !== content) {
22
+ writeFileSync(dest, content, "utf8")
23
+ }
24
+ } catch {
25
+ // Non-fatal — skill stays as-is or isn't installed
26
+ }
27
+ }
28
+
29
+ // ── Repo context cache ────────────────────────────────────────────────────────
30
+
31
+ let cachedRepos = null
32
+ let lastFetchTime = 0
33
+ const CACHE_TTL_MS = 60 * 1000
34
+
35
+ async function loadRepos() {
36
+ const now = Date.now()
37
+ if (cachedRepos !== null && now - lastFetchTime < CACHE_TTL_MS) return
38
+
39
+ try {
40
+ const { stdout } = await execAsync(
41
+ "openviking --output json ls viking://resources/ --abs-limit 2000",
42
+ { timeout: 8000 }
43
+ )
44
+ const parsed = JSON.parse(stdout)
45
+ const items = parsed?.result ?? []
46
+ const repos = items
47
+ .filter((item) => item.uri?.startsWith("viking://resources/"))
48
+ .map((item) => {
49
+ const name = item.uri.replace("viking://resources/", "").replace(/\/$/, "")
50
+ return item.abstract
51
+ ? `- **${name}** (${item.uri})\n ${item.abstract}`
52
+ : `- **${name}** (${item.uri})`
53
+ })
54
+
55
+ if (repos.length > 0) {
56
+ cachedRepos = repos.join("\n")
57
+ lastFetchTime = now
58
+ }
59
+ } catch {
60
+ // openviking not running — plugin is a no-op until it is
61
+ }
62
+ }
63
+
64
+ // ── Plugin export ─────────────────────────────────────────────────────────────
65
+
66
+ /**
67
+ * @type {import('@opencode-ai/plugin').Plugin}
68
+ */
69
+ export async function OpenVikingPlugin() {
70
+ installSkill()
71
+ await loadRepos()
72
+
73
+ return {
74
+ "experimental.chat.system.transform": (_input, output) => {
75
+ if (!cachedRepos) return
76
+ output.system.push(
77
+ `## OpenViking — Indexed Code Repositories\n\n` +
78
+ `The following repos are semantically indexed and searchable.\n` +
79
+ `When the user asks about any of these projects or their internals, ` +
80
+ `you MUST proactively call skill("openviking") before answering.\n\n` +
81
+ cachedRepos
82
+ )
83
+ },
84
+
85
+ "session.created": async () => {
86
+ cachedRepos = null
87
+ await loadRepos()
88
+ },
89
+ }
90
+ }
@@ -0,0 +1,60 @@
1
+ import { exec } from "child_process"
2
+ import { promisify } from "util"
3
+
4
+ const execAsync = promisify(exec)
5
+
6
+ let cachedRepos: string | null = null
7
+
8
+ async function loadRepos(): Promise<void> {
9
+ try {
10
+ const { stdout } = await execAsync(
11
+ "openviking --output json ls viking://resources/ --abs-limit 2000",
12
+ { timeout: 8000 }
13
+ )
14
+ const parsed = JSON.parse(stdout)
15
+ const items: Array<{ uri: string; abstract?: string }> = parsed?.result ?? []
16
+ const repos = items
17
+ .filter((item) => item.uri?.startsWith("viking://resources/"))
18
+ .map((item) => {
19
+ const name = item.uri.replace("viking://resources/", "").replace(/\/$/, "")
20
+ return item.abstract
21
+ ? `- **${name}** (${item.uri})\n ${item.abstract}`
22
+ : `- **${name}** (${item.uri})`
23
+ })
24
+ if (repos.length > 0) {
25
+ cachedRepos = repos.join("\n")
26
+ }
27
+ } catch {
28
+ // openviking not available — plugin is a no-op
29
+ }
30
+ }
31
+
32
+ /**
33
+ * @type {import('@opencode-ai/plugin').Plugin}
34
+ */
35
+ export async function OpenVikingContextPlugin() {
36
+ // Fetch repos at startup so the cache is ready before any request
37
+ await loadRepos()
38
+
39
+ return {
40
+ // Inject repo list into every LLM request's system prompt (sync — no await)
41
+ "experimental.chat.system.transform": (
42
+ _input: unknown,
43
+ output: { system: string[] }
44
+ ) => {
45
+ if (!cachedRepos) return
46
+ output.system.push(
47
+ `## OpenViking — Indexed Code Repositories\n\n` +
48
+ `The following repos are semantically indexed and searchable.\n` +
49
+ `When the user asks about any of these projects or their internals, ` +
50
+ `you MUST proactively call skill("openviking") before answering.\n\n` +
51
+ cachedRepos
52
+ )
53
+ },
54
+
55
+ // Refresh repo list when a new session starts
56
+ "session.created": async () => {
57
+ await loadRepos()
58
+ },
59
+ }
60
+ }
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "openviking-opencode",
3
+ "version": "0.1.0",
4
+ "description": "OpenCode plugin for OpenViking — injects indexed repo context into the AI assistant and auto-installs the openviking skill",
5
+ "type": "module",
6
+ "main": "index.mjs",
7
+ "exports": {
8
+ ".": "./index.mjs"
9
+ },
10
+ "keywords": [
11
+ "opencode",
12
+ "opencode-plugin",
13
+ "openviking",
14
+ "rag",
15
+ "code-search"
16
+ ],
17
+ "license": "Apache-2.0",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/OpenVikingDB/OpenViking"
21
+ }
22
+ }
@@ -0,0 +1,187 @@
1
+ ---
2
+ name: openviking
3
+ description: "Activate when the user's question involves external libraries, frameworks, or logic that may not exist in the current repo. SEARCH PRIORITY: (1) Always search the local codebase first (Glob/Grep/Read); (2) Only search OpenViking if local results are insufficient or the code clearly isn't in the current repo; (3) Stop if OpenViking also returns no relevant results — do not keep searching. Trigger even when only part of the question involves an external library or another project. Also activate when the user wants to add or index a GitHub repo or local project."
4
+ license: MIT
5
+ compatibility: opencode
6
+ ---
7
+
8
+ # OpenViking Code Repository Search
9
+
10
+ Index code repos into OpenViking and search them semantically. URIs use the `viking://` namespace. Directories have VLM-generated summaries (`abstract` / `overview`); files are read with `read`.
11
+
12
+ **IMPORTANT: Execute openviking commands directly and immediately — no warmup, no pre-checks, no test commands before the real one. If a command fails, handle the error at that point.**
13
+
14
+ ## Error Handling
15
+
16
+ Run commands directly. Handle errors only when they occur:
17
+
18
+ **`command not found: openviking`** → Tell user to run `pip install openviking` and configure `~/.openviking/ov.conf`. Stop.
19
+
20
+ **`url is required` / `CLI_CONFIG` error** → Auto-create config and retry:
21
+ ```bash
22
+ mkdir -p ~/.openviking && echo '{"url": "http://localhost:1933"}' > ~/.openviking/ovcli.conf
23
+ ```
24
+
25
+ **`CONNECTION_ERROR` / failed to connect:**
26
+ - `~/.openviking/ov.conf` **exists** → auto-start server, wait until healthy, retry:
27
+ ```bash
28
+ openviking serve --config ~/.openviking/ov.conf > /tmp/openviking.log 2>&1 &
29
+ for i in $(seq 1 10); do openviking health 2>/dev/null && break; sleep 3; done
30
+ ```
31
+ - **Does not exist** → Tell user to configure `~/.openviking/ov.conf` first (API keys for embedding model and VLM required; see `examples/ov.conf.example`). Stop.
32
+
33
+ ---
34
+
35
+ ## Commands
36
+
37
+ ### Add a Repository
38
+
39
+ ```bash
40
+ openviking add-resource https://github.com/owner/repo --to viking://resources/
41
+ openviking add-resource /path/to/project --to viking://resources/
42
+ ```
43
+
44
+ **`--to` is the parent scope** — repo name is auto-appended from source:
45
+ - `https://github.com/tiangolo/fastapi` → `viking://resources/fastapi/`
46
+ - `/path/to/myproject` → `viking://resources/myproject/`
47
+
48
+ Always use `viking://resources/` for code repos. **Never use `--wait`.**
49
+
50
+ **After submitting — always do this:**
51
+ 1. Run `openviking observer queue` once to get queue status
52
+ 2. Report to user: files queued, estimated time (see table), and that search works progressively
53
+ 3. End the task — do not poll or wait
54
+
55
+ | Repo Size | Files | Est. Time |
56
+ |-----------|-------|-----------|
57
+ | Small | < 100 | 2–5 min |
58
+ | Medium | 100–500 | 5–20 min |
59
+ | Large | 500+ | 20–60+ min |
60
+
61
+ ---
62
+
63
+ ### Search
64
+
65
+ ```bash
66
+ # Semantic search within a repo
67
+ openviking find "<query>" --uri viking://resources/repo --limit 10
68
+
69
+ # Semantic search across all repos
70
+ openviking find "<query>" --limit 10
71
+
72
+ # Stricter similarity threshold (0.0–1.0)
73
+ openviking find "<query>" --uri viking://resources/repo --threshold 0.6
74
+
75
+ # Regex search
76
+ openviking grep "class.*Repository" --uri viking://resources/repo
77
+ openviking grep "TODO" --ignore-case --uri viking://resources/repo
78
+ ```
79
+
80
+ When the user names a specific repo, run `openviking ls viking://resources/` first to find the exact URI.
81
+
82
+ ---
83
+
84
+ ### Read File Content
85
+
86
+ **`abstract` / `overview` — directories only.** VLM generates summaries per directory, not per file. Calling on a file URI will error.
87
+
88
+ **`read` — files only.** Reads raw file content with optional line range.
89
+
90
+ ```bash
91
+ # Directories: get VLM-generated summaries
92
+ openviking abstract viking://resources/repo/src/ # one-line summary of directory
93
+ openviking overview viking://resources/repo/src/ # detailed summary of directory
94
+
95
+ # Files: read actual content
96
+ openviking read viking://resources/repo/src/auth.py # full content
97
+ openviking read viking://resources/repo/src/auth.py --offset 100 --limit 50 # lines 100-149
98
+ openviking read viking://resources/repo/src/auth.py --offset 100 # line 100 to end
99
+ ```
100
+
101
+ ---
102
+
103
+ ### Browse & Find Files
104
+
105
+ ```bash
106
+ openviking ls viking://resources/ # list all indexed repos
107
+ openviking ls viking://resources/repo # list repo contents
108
+ openviking tree viking://resources/repo # directory tree
109
+
110
+ # --uri is required; viking:// root is invalid
111
+ openviking glob "**/*.py" --uri viking://resources/repo # by pattern
112
+ openviking glob "**/*.py" --uri viking://resources/ # across all repos
113
+ ```
114
+
115
+ ---
116
+
117
+ ### Manage
118
+
119
+ ```bash
120
+ # Check import progress
121
+ openviking observer queue
122
+ openviking observer system
123
+
124
+ # Delete a repo (irreversible)
125
+ openviking rm viking://resources/repo --recursive
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Combining Commands for Complex Tasks
131
+
132
+ Commands are building blocks — chain them for deeper investigation:
133
+
134
+ **Progressive drill-down:** `find` → `abstract`/`overview` (dirs) → `read` (files)
135
+ - `find`: locate candidates by concept
136
+ - `abstract`: one-line summary of a directory to judge relevance
137
+ - `overview`: detailed summary of a directory for deeper understanding
138
+ - `read`: fetch full file content only when needed
139
+ - `grep`: trace call sites or find all usages of a symbol
140
+ - `glob`: enumerate all files of a type for full coverage
141
+
142
+ **Example: "How does project X handle authentication?"**
143
+ ```
144
+ 1. openviking find "authentication login token" --uri viking://resources/x --limit 10
145
+ → returns file URIs like viking://resources/x/src/auth/jwt.py
146
+ 2. openviking abstract viking://resources/x/src/auth/ → understand the auth directory
147
+ 3. openviking read viking://resources/x/src/auth/jwt.py → read the specific file
148
+ 4. openviking grep "verify_token" --uri viking://resources/x → trace call sites
149
+ ```
150
+
151
+ **Example: "What tests exist for the parser module?"**
152
+ ```
153
+ 1. openviking glob "**/test*parser*" --uri viking://resources/x
154
+ 2. openviking abstract viking://resources/x/tests/ → understand test directory structure
155
+ 3. openviking read <specific test file>
156
+ ```
157
+
158
+ ---
159
+
160
+ ## Workflow Examples
161
+
162
+ **Adding a repo:**
163
+ ```
164
+ User: "Add fastapi so I can search how they handle dependencies"
165
+ 1. openviking add-resource https://github.com/tiangolo/fastapi --to viking://resources/
166
+ → stored at viking://resources/fastapi/ | on error: see Error Handling above
167
+ 2. openviking observer queue
168
+ 3. Tell user: indexing in background, ~10 min, files searchable progressively
169
+ 4. End task
170
+ ```
171
+
172
+ **Searching a specific repo:**
173
+ ```
174
+ User: "How does fastapi handle dependency injection?"
175
+ 1. openviking ls viking://resources/ → find URI matching "fastapi"
176
+ 2. openviking find "dependency injection" --uri viking://resources/fastapi --limit 10
177
+ → returns file URIs, e.g. viking://resources/fastapi/fastapi/dependencies/utils.py
178
+ 3. openviking abstract viking://resources/fastapi/fastapi/dependencies/ → understand the directory
179
+ 4. openviking read viking://resources/fastapi/fastapi/dependencies/utils.py → read the file
180
+ ```
181
+
182
+ **Searching across all repos:**
183
+ ```
184
+ User: "How is JWT authentication implemented in my projects?"
185
+ 1. openviking find "JWT authentication" --limit 10
186
+ 2. Results include source URIs — group by repo, read relevant files
187
+ ```