@meyverick/omnicode 0.1.3 → 0.2.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/README.md +17 -35
- package/package.json +6 -3
- package/src/bin/omnicode-runtime.js +66 -20
- package/src/bin/omnicode.js +59 -8
- package/src/installer/AGENTS.template.md +13 -0
- package/src/installer/lib.js +917 -22
- package/src/installer/mineru-client.js +164 -0
- package/src/installer/tree-sitter.js +270 -0
package/README.md
CHANGED
|
@@ -1,49 +1,31 @@
|
|
|
1
1
|
# omnicode
|
|
2
2
|
|
|
3
|
-
The cross-platform command-line entrypoint for running OpenCode through OmniRoute.
|
|
3
|
+
The cross-platform command-line entrypoint for running OpenCode through OmniRoute, backed by AI-powered reference indexing with Qdrant.
|
|
4
4
|
|
|
5
5
|
## What is omnicode?
|
|
6
6
|
|
|
7
|
-
`omnicode` is a
|
|
7
|
+
`omnicode` is a wrapper that launches OpenCode through OmniRoute with zero friction. It handles GrayMatter memory, OpenSpec change tracking, and OmniRoute lifecycle automatically. On top of that, it indexes your `./references/` directory into a Qdrant vector database — unlocking semantic search for AI agents working in your codebase.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Because I'm lazy. Not "I'll automate this repetitive task" lazy, but "I keep opening a second terminal just to run `omniroute`, then manually init GrayMatter, then manually init OpenSpec, then forget which session ID I was using" lazy.
|
|
9
|
+
Developed and tested on Ubuntu Linux; cross-platform by design (Node.js, no bash).
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
## Why
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
Because I kept opening a second terminal to run `omniroute`, manually init GrayMatter, manually init OpenSpec, and forget which session ID I was using. I wrote a wrapper so I can run one command and pretend I'm a responsible developer with proper infrastructure.
|
|
16
14
|
|
|
17
15
|
## Features
|
|
18
16
|
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
- Print version with `omnicode --version`.
|
|
26
|
-
|
|
27
|
-
## Requirements
|
|
28
|
-
|
|
29
|
-
- Node.js 22 or later (developed and tested on Node 26; OmniRoute requires Node >=22 <=24).
|
|
30
|
-
|
|
31
|
-
## Explore
|
|
32
|
-
|
|
33
|
-
- [Getting Started](https://github.com/meyverick/omnicode/wiki/Getting-Started) - install, run, and uninstall.
|
|
34
|
-
- [How it works](https://github.com/meyverick/omnicode/wiki/How-it-works) - runtime flow, session handling, and lifecycle.
|
|
35
|
-
- [Configuration](https://github.com/meyverick/omnicode/wiki/Configuration) - paths and environment overrides.
|
|
36
|
-
- [Troubleshooting](https://github.com/meyverick/omnicode/wiki/Troubleshooting) - common issues and rollback.
|
|
37
|
-
|
|
38
|
-
## Acknowledgments
|
|
39
|
-
|
|
40
|
-
Built on top of:
|
|
17
|
+
- **Single command** — launches OmniRoute + OpenCode + optional tools in one shot.
|
|
18
|
+
- **Automatic session resume** — remembers your session per project via the OpenCode database.
|
|
19
|
+
- **Background OmniRoute lifecycle** — starts with OpenCode, stops when no sessions remain.
|
|
20
|
+
- **Quiet initialization** — GrayMatter and OpenSpec init run in the background with captured logs.
|
|
21
|
+
- **Semantic reference indexing** — indexes `./references/` into Qdrant with BGE embeddings for AI-agent search.
|
|
22
|
+
- **Cross-platform runtime** — pure Node.js, no bash dependency.
|
|
41
23
|
|
|
42
|
-
|
|
43
|
-
- [OmniRoute](https://github.com/diegosouzapw/OmniRoute) - model routing layer
|
|
44
|
-
- [OpenSpec](https://github.com/Fission-AI/OpenSpec) - AI agent workflow framework
|
|
45
|
-
- [GrayMatter](https://github.com/angelnicolasc/graymatter) - persistent memory for AI agents
|
|
24
|
+
## Quick start
|
|
46
25
|
|
|
47
|
-
|
|
26
|
+
```bash
|
|
27
|
+
npm install -g @meyverick/omnicode
|
|
28
|
+
omnicode
|
|
29
|
+
```
|
|
48
30
|
|
|
49
|
-
[
|
|
31
|
+
See the [wiki](https://github.com/meyverick/omnicode/wiki) for detailed setup, configuration, and troubleshooting.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meyverick/omnicode",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Cross-platform
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Cross-platform CLI for OpenCode through OmniRoute with Qdrant semantic reference indexing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"omnicode": "src/bin/omnicode.js"
|
|
@@ -22,5 +22,8 @@
|
|
|
22
22
|
"cli"
|
|
23
23
|
],
|
|
24
24
|
"author": "meyverick",
|
|
25
|
-
"license": "Apache-2.0"
|
|
25
|
+
"license": "Apache-2.0",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"web-tree-sitter": "0.20.8"
|
|
28
|
+
}
|
|
26
29
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync, writeSync, unlinkSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
5
|
-
import { commandExists, getDataDir, isPidAlive, isProcessRunningAsync } from "../installer/lib.js";
|
|
5
|
+
import { commandExists, getDataDir, isPidAlive, isProcessRunningAsync, detectQdrantMcp, generateQdrantConfig, ensureOpencodeConfig, ensureQdrantAgentInstructions, indexReferences, startMcpServer, stopMcpServer, getQdrantStoreEnv, getQdrantPidFile, isQdrantRunning, startQdrantContainer, stopQdrantContainer } from "../installer/lib.js";
|
|
6
|
+
|
|
7
|
+
process.on("unhandledRejection", (err) => {
|
|
8
|
+
console.error("[omnicode] runtime: UNHANDLED REJECTION:", err?.message || err);
|
|
9
|
+
});
|
|
6
10
|
|
|
7
11
|
const MAX_OMNI_WAIT = 30;
|
|
8
12
|
const OMNI_CHECK_DELAY = 1000;
|
|
@@ -111,6 +115,8 @@ async function stopOmnirouteIfIdle(pidFile) {
|
|
|
111
115
|
return;
|
|
112
116
|
}
|
|
113
117
|
|
|
118
|
+
stopQdrantContainer();
|
|
119
|
+
|
|
114
120
|
if (existsSync(pidFile)) {
|
|
115
121
|
let pid = null;
|
|
116
122
|
try {
|
|
@@ -143,8 +149,13 @@ export async function runRuntime(mode) {
|
|
|
143
149
|
process.umask(0o077);
|
|
144
150
|
}
|
|
145
151
|
|
|
146
|
-
|
|
152
|
+
let startedIndexing = false;
|
|
153
|
+
const cleanup = async () => {
|
|
154
|
+
if (startedIndexing) try { unlinkSync(join(process.cwd(), ".qdrant", ".indexing")); } catch {}
|
|
155
|
+
await stopOmnirouteIfIdle(pidFile);
|
|
156
|
+
};
|
|
147
157
|
process.on("exit", () => {
|
|
158
|
+
if (startedIndexing) try { unlinkSync(join(process.cwd(), ".qdrant", ".indexing")); } catch {}
|
|
148
159
|
try {
|
|
149
160
|
if (!existsSync(pidFile)) return;
|
|
150
161
|
const pid = parseInt(readFileSync(pidFile, "utf8").trim(), 10);
|
|
@@ -161,26 +172,61 @@ export async function runRuntime(mode) {
|
|
|
161
172
|
|
|
162
173
|
await Promise.all([
|
|
163
174
|
pid !== null ? waitForOmniroute(pid, logFile) : Promise.resolve(),
|
|
175
|
+
startQdrantContainer(),
|
|
164
176
|
initTools(dataDir),
|
|
165
177
|
]);
|
|
166
178
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
179
|
+
const hasQdrant = detectQdrantMcp();
|
|
180
|
+
const refsDir = join(process.cwd(), "references");
|
|
181
|
+
let qdrantConfig = null;
|
|
182
|
+
|
|
183
|
+
if (hasQdrant) {
|
|
184
|
+
qdrantConfig = generateQdrantConfig();
|
|
185
|
+
ensureOpencodeConfig(qdrantConfig);
|
|
186
|
+
ensureQdrantAgentInstructions();
|
|
187
|
+
console.log("[omnicode] qdrant MCP configured");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const ac = new AbortController();
|
|
191
|
+
if (hasQdrant && existsSync(refsDir) && qdrantConfig) {
|
|
192
|
+
if (existsSync(join(process.cwd(), ".qdrant", ".indexing"))) {
|
|
193
|
+
console.log("[omnicode] background indexing is already running, skipping");
|
|
194
|
+
} else {
|
|
195
|
+
startedIndexing = true;
|
|
196
|
+
console.log("[omnicode] background indexing started");
|
|
197
|
+
indexReferences(refsDir, qdrantConfig, null, false, ac.signal).catch((err) => {
|
|
198
|
+
console.error(`[omnicode] background index failed: ${err.message}`);
|
|
182
199
|
});
|
|
183
|
-
|
|
184
|
-
});
|
|
200
|
+
}
|
|
185
201
|
}
|
|
202
|
+
|
|
203
|
+
const launchOpencode = () => new Promise((resolve) => {
|
|
204
|
+
const args = mode.flag === "-s" && mode.id ? ["-s", mode.id] : [];
|
|
205
|
+
if (mode.flag === "-s" && mode.id) {
|
|
206
|
+
console.log(`[omnicode] launching opencode (session: ${mode.id})`);
|
|
207
|
+
} else {
|
|
208
|
+
console.log("[omnicode] launching opencode (new session)");
|
|
209
|
+
}
|
|
210
|
+
const child = spawn("opencode", args, { stdio: "inherit", cwd: process.cwd() });
|
|
211
|
+
const logFd = openSync(join(dataDir, "background.log"), "w");
|
|
212
|
+
const origLog = console.log;
|
|
213
|
+
const origWarn = console.warn;
|
|
214
|
+
const origError = console.error;
|
|
215
|
+
console.log = (...args) => writeSync(logFd, args.join(" ") + "\n");
|
|
216
|
+
console.warn = (...args) => writeSync(logFd, "WARN: " + args.join(" ") + "\n");
|
|
217
|
+
console.error = (...args) => writeSync(logFd, "ERROR: " + args.join(" ") + "\n");
|
|
218
|
+
child.on("close", () => {
|
|
219
|
+
setTimeout(() => {
|
|
220
|
+
console.log = origLog;
|
|
221
|
+
console.warn = origWarn;
|
|
222
|
+
console.error = origError;
|
|
223
|
+
try { closeSync(logFd); } catch {}
|
|
224
|
+
resolve();
|
|
225
|
+
}, 100);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
await launchOpencode();
|
|
230
|
+
ac.abort();
|
|
231
|
+
await cleanup();
|
|
186
232
|
}
|
package/src/bin/omnicode.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFileSync, realpathSync } from "node:fs";
|
|
2
|
+
import { readFileSync, realpathSync, existsSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
process.on("unhandledRejection", (err) => {
|
|
7
|
+
console.error("[omnicode] UNHANDLED REJECTION:", err?.message || err);
|
|
8
|
+
process.exit(1);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
import { commandExists, getOpencodeDbPath, isProcessRunning, detectQdrantMcp, generateQdrantConfig, ensureOpencodeConfig, indexReferences, isQdrantRunning, countProcesses, getQdrantRunningCount, countActiveIndexers } from "../installer/lib.js";
|
|
12
|
+
import { downloadLanguageCmd } from "../installer/tree-sitter.js";
|
|
7
13
|
import { runRuntime } from "./omnicode-runtime.js";
|
|
8
14
|
|
|
9
15
|
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
@@ -19,7 +25,7 @@ try {
|
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
export function printUsage() {
|
|
22
|
-
console.log(`Usage: omnicode [-s <session_id>] [-c] [--status] [--version]`);
|
|
28
|
+
console.log(`Usage: omnicode [-s <session_id>] [-c] [--status] [--version] [index]`);
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
let _cachedVersion = null;
|
|
@@ -32,20 +38,28 @@ export function getVersion() {
|
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
export function getProcessStatus() {
|
|
41
|
+
const indexingLockPath = join(process.cwd(), ".qdrant", ".indexing");
|
|
35
42
|
return {
|
|
36
|
-
opencode:
|
|
37
|
-
omniroute:
|
|
43
|
+
opencode: countProcesses("opencode"),
|
|
44
|
+
omniroute: countProcesses("omniroute"),
|
|
45
|
+
qdrant: getQdrantRunningCount(),
|
|
46
|
+
indexing: existsSync(indexingLockPath),
|
|
47
|
+
indexingCount: countActiveIndexers(),
|
|
38
48
|
};
|
|
39
49
|
}
|
|
40
50
|
|
|
41
51
|
export function printStatus(status = getProcessStatus()) {
|
|
42
|
-
console.log(`[omnicode] opencode: ${status.opencode ?
|
|
43
|
-
console.log(`[omnicode] omniroute: ${status.omniroute ?
|
|
52
|
+
console.log(`[omnicode] opencode: ${status.opencode > 0 ? `running (${status.opencode})` : "stopped"}`);
|
|
53
|
+
console.log(`[omnicode] omniroute: ${status.omniroute > 0 ? `running (${status.omniroute})` : "stopped"}`);
|
|
54
|
+
console.log(`[omnicode] qdrant: ${status.qdrant > 0 ? `running (${status.qdrant})` : "stopped"}`);
|
|
55
|
+
console.log(`[omnicode] indexing: ${status.indexing ? `true (${status.indexingCount})` : "false"}`);
|
|
44
56
|
}
|
|
45
57
|
|
|
46
58
|
export function parseArgs(argv) {
|
|
47
59
|
let sessionId = null;
|
|
48
60
|
let continueSession = false;
|
|
61
|
+
let index = false;
|
|
62
|
+
let forceReindex = false;
|
|
49
63
|
for (let i = 2; i < argv.length; i++) {
|
|
50
64
|
const arg = argv[i];
|
|
51
65
|
if (arg === "-h" || arg === "--help") {
|
|
@@ -64,6 +78,22 @@ export function parseArgs(argv) {
|
|
|
64
78
|
continueSession = true;
|
|
65
79
|
continue;
|
|
66
80
|
}
|
|
81
|
+
if (arg === "--force-reindex") {
|
|
82
|
+
forceReindex = true;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (arg === "--index" || arg === "index") {
|
|
86
|
+
index = true;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (arg === "download-language") {
|
|
90
|
+
const language = argv[++i];
|
|
91
|
+
if (!language) {
|
|
92
|
+
console.error("[omnicode] ERROR: download-language requires a language argument");
|
|
93
|
+
process.exit(2);
|
|
94
|
+
}
|
|
95
|
+
return { downloadLanguage: language };
|
|
96
|
+
}
|
|
67
97
|
if (arg === "-s") {
|
|
68
98
|
sessionId = argv[++i];
|
|
69
99
|
if (!sessionId) {
|
|
@@ -83,7 +113,7 @@ export function parseArgs(argv) {
|
|
|
83
113
|
process.exit(2);
|
|
84
114
|
}
|
|
85
115
|
}
|
|
86
|
-
return { sessionId, continueSession };
|
|
116
|
+
return { sessionId, continueSession, index, forceReindex };
|
|
87
117
|
}
|
|
88
118
|
|
|
89
119
|
export async function getLatestSessionId(directory = realpathSync(process.cwd())) {
|
|
@@ -113,6 +143,27 @@ export async function resolveSessionMode(sessionId, latestSessionId = null) {
|
|
|
113
143
|
async function main() {
|
|
114
144
|
const args = parseArgs(process.argv);
|
|
115
145
|
|
|
146
|
+
if (args.downloadLanguage) {
|
|
147
|
+
const success = await downloadLanguageCmd(args.downloadLanguage);
|
|
148
|
+
process.exit(success ? 0 : 1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (args.index) {
|
|
152
|
+
if (!detectQdrantMcp()) {
|
|
153
|
+
console.error("[omnicode] ERROR: uvx mcp-server-qdrant not found. Install uvx first.");
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
if (isQdrantRunning()) {
|
|
157
|
+
console.error("[omnicode] ERROR: a qdrant MCP server is already running for this project.");
|
|
158
|
+
console.error("[omnicode] Close the opencode session first, or wait for indexing to finish.");
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
const qdrantConfig = generateQdrantConfig();
|
|
162
|
+
ensureOpencodeConfig(qdrantConfig);
|
|
163
|
+
await indexReferences(join(process.cwd(), "references"), qdrantConfig, null, args.forceReindex);
|
|
164
|
+
process.exit(0);
|
|
165
|
+
}
|
|
166
|
+
|
|
116
167
|
const missing = ["opencode", "omniroute"].filter((cmd) => !commandExists(cmd));
|
|
117
168
|
if (missing.length > 0) {
|
|
118
169
|
console.error(`[omnicode] ERROR: missing required tool(s): ${missing.join(", ")}`);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!-- qdrant:instructions:begin — managed by `omnicode`; edits inside this block are overwritten -->
|
|
2
|
+
## Qdrant MCP (Semantic Search)
|
|
3
|
+
|
|
4
|
+
Qdrant MCP [configured] -> execute semantic search over `./references/`.
|
|
5
|
+
Index [maintained] via omnicode. Storage [isolated] -> `./.qdrant/` (local, zero external servers).
|
|
6
|
+
Model -> `BAAI/bge-small-en-v1.5` (384 dimensions).
|
|
7
|
+
|
|
8
|
+
- `qdrant-store(information, metadata?, collection_name?)` -> embed && store text chunk. `information` -> target text. Default collection -> `references`.
|
|
9
|
+
- `qdrant-find(query, collection_name?)` -> execute semantic search via natural-language `query` -> retrieve optimal matches.
|
|
10
|
+
|
|
11
|
+
**Execution Trigger [CRITICAL]:**
|
|
12
|
+
Execute `qdrant-find` prior to codebase mutations -> retrieve reference context, architectural patterns, && documentation -> prevent hallucination.
|
|
13
|
+
<!-- qdrant:instructions:end -->
|