@meyverick/omnicode 0.0.5 → 0.0.6
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 +2 -2
- package/package.json +1 -1
- package/src/bin/omnicode-runtime.js +43 -60
- package/src/bin/omnicode.js +2 -20
- package/src/installer/lib.js +29 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ The cross-platform command-line entrypoint for running OpenCode through OmniRout
|
|
|
4
4
|
|
|
5
5
|
## What is omnicode?
|
|
6
6
|
|
|
7
|
-
`omnicode` is a thin wrapper that launches OpenCode through OmniRoute. It expects you to install the underlying tools yourself, then handles optional GrayMatter and OpenSpec initialization, background OmniRoute lifecycle, and project-local OpenCode sessions. Developed and tested on Ubuntu Linux
|
|
7
|
+
`omnicode` is a thin wrapper that launches OpenCode through OmniRoute. It expects you to install the underlying tools yourself, then handles optional GrayMatter and OpenSpec initialization, background OmniRoute lifecycle, and project-local OpenCode sessions. Developed and tested on Ubuntu Linux; cross-platform by design but untested on Windows, macOS, and other Linux distributions.
|
|
8
8
|
|
|
9
9
|
## Why
|
|
10
10
|
|
|
@@ -17,7 +17,7 @@ So I wrote this wrapper. It starts OmniRoute, inits GrayMatter and OpenSpec quie
|
|
|
17
17
|
## Features
|
|
18
18
|
|
|
19
19
|
- Thin npm global command written in Node.js (no bash needed).
|
|
20
|
-
- Cross-platform by design
|
|
20
|
+
- Cross-platform by design: developed and tested on Ubuntu Linux; untested on Windows, macOS, and other Linux distributions.
|
|
21
21
|
- Automatic session resume per project using the OpenCode database.
|
|
22
22
|
- Background OmniRoute lifecycle with cleanup when OpenCode exits.
|
|
23
23
|
- Quiet GrayMatter and OpenSpec initialization with captured logs.
|
package/package.json
CHANGED
|
@@ -1,66 +1,45 @@
|
|
|
1
|
-
import { spawn
|
|
2
|
-
import { existsSync, mkdirSync, openSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import os from "node:os";
|
|
5
4
|
|
|
6
|
-
import { commandExists, getDataDir } from "../installer/lib.js";
|
|
5
|
+
import { commandExists, getDataDir, isProcessRunning, isPidAlive } from "../installer/lib.js";
|
|
7
6
|
|
|
8
|
-
const isWindows = process.platform === "win32";
|
|
9
7
|
const MAX_OMNI_WAIT = 30;
|
|
10
8
|
const OMNI_CHECK_DELAY = 1000;
|
|
11
9
|
|
|
12
|
-
function isProcessRunning(name) {
|
|
13
|
-
try {
|
|
14
|
-
if (isWindows) {
|
|
15
|
-
const out = execFileSync("tasklist", ["/FI", `IMAGENAME eq ${name}.exe`, "/NH"], {
|
|
16
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
17
|
-
encoding: "utf8",
|
|
18
|
-
});
|
|
19
|
-
return out.includes(`${name}.exe`);
|
|
20
|
-
}
|
|
21
|
-
execFileSync("pgrep", ["-x", name], { stdio: "ignore" });
|
|
22
|
-
return true;
|
|
23
|
-
} catch {
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function isPidAlive(pid) {
|
|
29
|
-
try {
|
|
30
|
-
process.kill(pid, 0);
|
|
31
|
-
return true;
|
|
32
|
-
} catch {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
10
|
function sleep(ms) {
|
|
38
11
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
39
12
|
}
|
|
40
13
|
|
|
41
|
-
function initTool(name, args, logPath) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
14
|
+
async function initTool(name, args, logPath) {
|
|
15
|
+
if (!commandExists(name)) {
|
|
16
|
+
console.log(`[omnicode] ${name}: not installed, skipping`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
console.log(`[omnicode] ${name}: initializing`);
|
|
20
|
+
let log;
|
|
21
|
+
try {
|
|
22
|
+
log = openSync(logPath, "w");
|
|
50
23
|
const child = spawn(name, args, { stdio: ["ignore", log, log] });
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
24
|
+
const result = await Promise.race([
|
|
25
|
+
new Promise((resolve) => {
|
|
26
|
+
child.on("close", resolve);
|
|
27
|
+
child.on("error", () => resolve(null));
|
|
28
|
+
}),
|
|
29
|
+
new Promise((resolve) => {
|
|
30
|
+
setTimeout(() => { child.kill(); resolve(null); }, 30000);
|
|
31
|
+
}),
|
|
32
|
+
]);
|
|
33
|
+
if (result === null) {
|
|
34
|
+
console.log(`[omnicode] WARNING: ${name} init timed out; continuing. Log: ${logPath}`);
|
|
35
|
+
} else if (result === 0) {
|
|
36
|
+
console.log(`[omnicode] ${name}: ready`);
|
|
37
|
+
} else {
|
|
60
38
|
console.log(`[omnicode] WARNING: ${name} init failed; continuing. Log: ${logPath}`);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
if (log !== undefined) try { closeSync(log); } catch {}
|
|
42
|
+
}
|
|
64
43
|
}
|
|
65
44
|
|
|
66
45
|
async function initTools(dataDir) {
|
|
@@ -137,9 +116,13 @@ function stopOmnirouteIfIdle(pidFile) {
|
|
|
137
116
|
} catch {}
|
|
138
117
|
if (pid && isPidAlive(pid)) {
|
|
139
118
|
console.log(`[omnicode] no opencode left -> stopping omniroute (pid: ${pid})`);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
119
|
+
if (process.platform === "win32") {
|
|
120
|
+
spawn("taskkill", ["/T", "/F", "/PID", String(pid)], { stdio: "ignore" });
|
|
121
|
+
} else {
|
|
122
|
+
try { process.kill(-pid, "SIGTERM"); } catch {}
|
|
123
|
+
}
|
|
124
|
+
for (let i = 0; i < 10; i++) {
|
|
125
|
+
if (!isPidAlive(pid)) break;
|
|
143
126
|
try { process.kill(pid, 0); } catch { break; }
|
|
144
127
|
}
|
|
145
128
|
}
|
|
@@ -149,7 +132,7 @@ function stopOmnirouteIfIdle(pidFile) {
|
|
|
149
132
|
|
|
150
133
|
export async function runRuntime(mode) {
|
|
151
134
|
const dataDir = getDataDir();
|
|
152
|
-
mkdirSync(dataDir, { recursive: true });
|
|
135
|
+
mkdirSync(dataDir, { recursive: true, mode: 0o700 });
|
|
153
136
|
|
|
154
137
|
const logFile = join(dataDir, "omniroute.log");
|
|
155
138
|
const pidFile = join(dataDir, "omniroute.pid");
|
|
@@ -163,12 +146,12 @@ export async function runRuntime(mode) {
|
|
|
163
146
|
process.on("SIGINT", () => { cleanup(); process.exit(0); });
|
|
164
147
|
process.on("SIGTERM", () => { cleanup(); process.exit(0); });
|
|
165
148
|
|
|
166
|
-
await initTools(dataDir);
|
|
167
|
-
|
|
168
149
|
const pid = startOmniroute(dataDir, logFile, pidFile);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
150
|
+
|
|
151
|
+
await Promise.all([
|
|
152
|
+
pid !== null ? waitForOmniroute(pid, logFile) : Promise.resolve(),
|
|
153
|
+
initTools(dataDir),
|
|
154
|
+
]);
|
|
172
155
|
|
|
173
156
|
if (mode.flag === "-s" && mode.id) {
|
|
174
157
|
console.log(`[omnicode] launching opencode (session: ${mode.id})`);
|
package/src/bin/omnicode.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { execFileSync } from "node:child_process";
|
|
3
2
|
import { readFileSync, realpathSync } from "node:fs";
|
|
4
3
|
import { join } from "node:path";
|
|
5
4
|
import { fileURLToPath } from "node:url";
|
|
6
5
|
|
|
7
|
-
import { commandExists, getOpencodeDbPath } from "../installer/lib.js";
|
|
6
|
+
import { commandExists, getOpencodeDbPath, isProcessRunning } from "../installer/lib.js";
|
|
8
7
|
import { runRuntime } from "./omnicode-runtime.js";
|
|
9
8
|
|
|
10
9
|
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
11
10
|
const packageJsonPath = join(__dirname, "..", "..", "package.json");
|
|
12
11
|
|
|
13
|
-
const SESSION_ID_RE = /^[a-zA-Z0-9_-]+$/;
|
|
14
|
-
const isWindows = process.platform === "win32";
|
|
12
|
+
const SESSION_ID_RE = /^(?=.{1,128}$)[a-zA-Z0-9_-]+$/;
|
|
15
13
|
|
|
16
14
|
let DatabaseSync;
|
|
17
15
|
try {
|
|
@@ -33,22 +31,6 @@ export function getVersion() {
|
|
|
33
31
|
return _cachedVersion;
|
|
34
32
|
}
|
|
35
33
|
|
|
36
|
-
export function isProcessRunning(name) {
|
|
37
|
-
try {
|
|
38
|
-
if (isWindows) {
|
|
39
|
-
const out = execFileSync("tasklist", ["/FI", `IMAGENAME eq ${name}.exe`, "/NH"], {
|
|
40
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
41
|
-
encoding: "utf8",
|
|
42
|
-
});
|
|
43
|
-
return out.includes(`${name}.exe`);
|
|
44
|
-
}
|
|
45
|
-
execFileSync("pgrep", ["-x", name], { stdio: "ignore" });
|
|
46
|
-
return true;
|
|
47
|
-
} catch {
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
34
|
export function getProcessStatus() {
|
|
53
35
|
return {
|
|
54
36
|
opencode: isProcessRunning("opencode"),
|
package/src/installer/lib.js
CHANGED
|
@@ -15,6 +15,35 @@ export function commandExists(command) {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
export function isProcessRunning(name) {
|
|
19
|
+
try {
|
|
20
|
+
if (isWindows) {
|
|
21
|
+
const extensions = [".exe", ".cmd", ".bat"];
|
|
22
|
+
for (const ext of extensions) {
|
|
23
|
+
const out = execFileSync("tasklist", ["/FI", `IMAGENAME eq ${name}${ext}`, "/NH"], {
|
|
24
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
25
|
+
encoding: "utf8",
|
|
26
|
+
});
|
|
27
|
+
if (out.includes(`${name}${ext}`)) return true;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
execFileSync("pgrep", ["-x", name], { stdio: "ignore" });
|
|
32
|
+
return true;
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function isPidAlive(pid) {
|
|
39
|
+
try {
|
|
40
|
+
process.kill(pid, 0);
|
|
41
|
+
return true;
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
18
47
|
export function getDataDir() {
|
|
19
48
|
return join(os.homedir(), ".local", "share", "omnicode");
|
|
20
49
|
}
|