openviking-opencode 0.1.0 → 0.2.1

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.
Files changed (3) hide show
  1. package/README.md +148 -0
  2. package/index.mjs +84 -18
  3. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # openviking-opencode
2
+
3
+ [OpenViking](https://github.com/OpenVikingDB/OpenViking) official plugin for [OpenCode](https://opencode.ai).
4
+
5
+ Automatically injects your indexed code repositories into the AI assistant's context, so it proactively searches them without you having to ask.
6
+
7
+ ## What It Does
8
+
9
+ - **Auto-installs the `openviking` skill** into `~/.config/opencode/skills/openviking/` on first load
10
+ - **Injects indexed repo summaries** into the system prompt so the AI knows what's available
11
+ - **Auto-starts the OpenViking server** if it's installed and configured but not running
12
+ - **Shows clear toast notifications** if setup is incomplete (missing install, missing config, failed start)
13
+
14
+ ## Prerequisites
15
+
16
+ ### 1. Install OpenViking
17
+
18
+ ```bash
19
+ pip install openviking
20
+ ```
21
+
22
+ ### 2. Configure OpenViking
23
+
24
+ OpenViking requires an embedding model and a VLM (for generating directory summaries). Create `~/.openviking/ov.conf`:
25
+
26
+ ```bash
27
+ cp /path/to/OpenViking/examples/ov.conf.example ~/.openviking/ov.conf
28
+ ```
29
+
30
+ Then fill in your API keys. Minimum required fields:
31
+
32
+ ```json
33
+ {
34
+ "embedding": {
35
+ "provider": "openai",
36
+ "api_key": "sk-..."
37
+ },
38
+ "vlm": {
39
+ "provider": "openai",
40
+ "model": "gpt-4o-mini",
41
+ "api_key": "sk-..."
42
+ }
43
+ }
44
+ ```
45
+
46
+ > See the [OpenViking docs](https://github.com/OpenVikingDB/OpenViking) for all supported providers.
47
+
48
+ ### 3. Install OpenCode
49
+
50
+ ```bash
51
+ npm install -g opencode-ai
52
+ ```
53
+
54
+ ## Installation
55
+
56
+ Add the plugin to your OpenCode config at `~/.config/opencode/opencode.json`:
57
+
58
+ ```json
59
+ {
60
+ "plugin": ["openviking-opencode"]
61
+ }
62
+ ```
63
+
64
+ OpenCode will automatically install the plugin via bun on next startup. No other steps needed — the skill is installed automatically.
65
+
66
+ ## Usage
67
+
68
+ ### Index a repository
69
+
70
+ Once OpenViking is running, index any GitHub repo or local project:
71
+
72
+ ```
73
+ # In OpenCode, just ask:
74
+ "Add https://github.com/tiangolo/fastapi to OpenViking"
75
+ ```
76
+
77
+ Indexing runs in the background. Small repos (~100 files) take 2–5 min; large repos (500+ files) can take 20–60 min. Search works progressively as files are indexed.
78
+
79
+ ### Search automatically
80
+
81
+ Once repos are indexed, the AI will proactively search them when relevant — no need to ask:
82
+
83
+ ```
84
+ "How does fastapi handle dependency injection?"
85
+ → AI automatically searches viking://resources/fastapi/ before answering
86
+
87
+ "How is authentication implemented in my-project?"
88
+ → AI automatically searches viking://resources/my-project/ before answering
89
+ ```
90
+
91
+ ### Trigger manually
92
+
93
+ You can also explicitly invoke the skill:
94
+
95
+ ```
96
+ "Use openviking to find how JWT tokens are verified in my projects"
97
+ ```
98
+
99
+ ## Best Practices
100
+
101
+ **Keep the server running persistently**
102
+
103
+ The plugin auto-starts the server when OpenCode launches, but for the best experience start it as a background service:
104
+
105
+ ```bash
106
+ openviking serve --config ~/.openviking/ov.conf > /tmp/openviking.log 2>&1 &
107
+ ```
108
+
109
+ Or add it to your shell profile (`~/.zshrc` / `~/.bashrc`):
110
+
111
+ ```bash
112
+ # Auto-start OpenViking if not already running
113
+ openviking health 2>/dev/null || openviking serve --config ~/.openviking/ov.conf > /tmp/openviking.log 2>&1 &
114
+ ```
115
+
116
+ **Use `viking://resources/` for all code repos**
117
+
118
+ Always index under `viking://resources/` — this is the scope the plugin and skill are configured to search:
119
+
120
+ ```bash
121
+ openviking add-resource https://github.com/owner/repo --to viking://resources/
122
+ openviking add-resource /path/to/project --to viking://resources/
123
+ ```
124
+
125
+ **Index repos you reference often**
126
+
127
+ The more repos indexed, the more useful the AI becomes. Good candidates:
128
+ - Internal libraries your project depends on
129
+ - Open source libraries you frequently look up
130
+ - Other projects in your monorepo
131
+
132
+ **Don't index everything**
133
+
134
+ Avoid indexing repos you rarely touch — it adds noise to search results. Use `openviking rm viking://resources/repo --recursive` to remove repos you no longer need.
135
+
136
+ ## Troubleshooting
137
+
138
+ | Symptom | Fix |
139
+ |---------|-----|
140
+ | Toast: "openviking 未安装" | Run `pip install openviking` |
141
+ | Toast: "未找到 ov.conf" | Create and configure `~/.openviking/ov.conf` |
142
+ | Toast: "服务启动失败" | Check logs: `cat /tmp/openviking.log` |
143
+ | AI doesn't search automatically | Ask "what repos do you have in openviking?" — if it can't answer, restart OpenCode |
144
+ | Stale repo list | Restart OpenCode to refresh the repo cache |
145
+
146
+ ## License
147
+
148
+ Apache-2.0
package/index.mjs CHANGED
@@ -7,23 +7,54 @@ import { fileURLToPath } from "url"
7
7
 
8
8
  const execAsync = promisify(exec)
9
9
  const __dirname = dirname(fileURLToPath(import.meta.url))
10
+ const OV_CONF = join(homedir(), ".openviking", "ov.conf")
11
+
12
+ // ── Helpers ───────────────────────────────────────────────────────────────────
13
+
14
+ async function run(cmd, opts = {}) {
15
+ return execAsync(cmd, { timeout: 10000, ...opts })
16
+ }
17
+
18
+ async function isInstalled() {
19
+ try {
20
+ await run("openviking --version")
21
+ return true
22
+ } catch {
23
+ return false
24
+ }
25
+ }
26
+
27
+ async function isHealthy() {
28
+ try {
29
+ await run("openviking health")
30
+ return true
31
+ } catch {
32
+ return false
33
+ }
34
+ }
35
+
36
+ async function startServer() {
37
+ // Start in background, wait up to 30s for healthy
38
+ await run("openviking serve --config " + OV_CONF + " > /tmp/openviking.log 2>&1 &")
39
+ for (let i = 0; i < 10; i++) {
40
+ await new Promise((r) => setTimeout(r, 3000))
41
+ if (await isHealthy()) return true
42
+ }
43
+ return false
44
+ }
10
45
 
11
46
  // ── Skill auto-install ────────────────────────────────────────────────────────
12
47
 
13
48
  function installSkill() {
14
49
  const src = join(__dirname, "skills", "openviking", "SKILL.md")
15
50
  const dest = join(homedir(), ".config", "opencode", "skills", "openviking", "SKILL.md")
16
- const destDir = dirname(dest)
17
51
  try {
18
- if (!existsSync(destDir)) mkdirSync(destDir, { recursive: true })
52
+ if (!existsSync(dirname(dest))) mkdirSync(dirname(dest), { recursive: true })
19
53
  const content = readFileSync(src, "utf8")
20
- // Only overwrite if content changed (avoid unnecessary disk writes)
21
54
  if (!existsSync(dest) || readFileSync(dest, "utf8") !== content) {
22
55
  writeFileSync(dest, content, "utf8")
23
56
  }
24
- } catch {
25
- // Non-fatal — skill stays as-is or isn't installed
26
- }
57
+ } catch {}
27
58
  }
28
59
 
29
60
  // ── Repo context cache ────────────────────────────────────────────────────────
@@ -37,12 +68,10 @@ async function loadRepos() {
37
68
  if (cachedRepos !== null && now - lastFetchTime < CACHE_TTL_MS) return
38
69
 
39
70
  try {
40
- const { stdout } = await execAsync(
41
- "openviking --output json ls viking://resources/ --abs-limit 2000",
42
- { timeout: 8000 }
71
+ const { stdout } = await run(
72
+ "openviking --output json ls viking://resources/ --abs-limit 2000"
43
73
  )
44
- const parsed = JSON.parse(stdout)
45
- const items = parsed?.result ?? []
74
+ const items = JSON.parse(stdout)?.result ?? []
46
75
  const repos = items
47
76
  .filter((item) => item.uri?.startsWith("viking://resources/"))
48
77
  .map((item) => {
@@ -51,14 +80,43 @@ async function loadRepos() {
51
80
  ? `- **${name}** (${item.uri})\n ${item.abstract}`
52
81
  : `- **${name}** (${item.uri})`
53
82
  })
54
-
55
83
  if (repos.length > 0) {
56
84
  cachedRepos = repos.join("\n")
57
85
  lastFetchTime = now
58
86
  }
59
- } catch {
60
- // openviking not running — plugin is a no-op until it is
87
+ } catch {}
88
+ }
89
+
90
+ // ── Init: check deps, start server if needed ─────────────────────────────────
91
+
92
+ async function init(client) {
93
+ const toast = (message, variant = "warning") =>
94
+ client.tui.showToast({
95
+ body: { title: "OpenViking", message, variant, duration: 8000 },
96
+ }).catch(() => {})
97
+
98
+ // 1. openviking 没装
99
+ if (!(await isInstalled())) {
100
+ await toast("openviking 未安装,请运行: pip install openviking", "error")
101
+ return false
61
102
  }
103
+
104
+ // 2. ov.conf 不存在(装了但没配置)
105
+ if (!existsSync(OV_CONF)) {
106
+ await toast("未找到 ~/.openviking/ov.conf,请先配置 API keys 后再启动服务", "warning")
107
+ return false
108
+ }
109
+
110
+ // 3. 服务没跑 → 静默自动启动
111
+ if (!(await isHealthy())) {
112
+ const started = await startServer()
113
+ if (!started) {
114
+ await toast("openviking 服务启动失败,查看日志: /tmp/openviking.log", "error")
115
+ return false
116
+ }
117
+ }
118
+
119
+ return true
62
120
  }
63
121
 
64
122
  // ── Plugin export ─────────────────────────────────────────────────────────────
@@ -66,9 +124,14 @@ async function loadRepos() {
66
124
  /**
67
125
  * @type {import('@opencode-ai/plugin').Plugin}
68
126
  */
69
- export async function OpenVikingPlugin() {
127
+ export async function OpenVikingPlugin({ client }) {
70
128
  installSkill()
71
- await loadRepos()
129
+
130
+ // 后台初始化,不阻塞 opencode 启动
131
+ Promise.resolve().then(async () => {
132
+ const ready = await init(client)
133
+ if (ready) await loadRepos()
134
+ })
72
135
 
73
136
  return {
74
137
  "experimental.chat.system.transform": (_input, output) => {
@@ -83,8 +146,11 @@ export async function OpenVikingPlugin() {
83
146
  },
84
147
 
85
148
  "session.created": async () => {
86
- cachedRepos = null
87
- await loadRepos()
149
+ const ready = await init(client)
150
+ if (ready) {
151
+ cachedRepos = null
152
+ await loadRepos()
153
+ }
88
154
  },
89
155
  }
90
156
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openviking-opencode",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "OpenCode plugin for OpenViking — injects indexed repo context into the AI assistant and auto-installs the openviking skill",
5
5
  "type": "module",
6
6
  "main": "index.mjs",