@meyverick/omnicode 0.0.5 → 0.0.7
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 -59
- 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,46 @@
|
|
|
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
7
|
const isWindows = process.platform === "win32";
|
|
9
8
|
const MAX_OMNI_WAIT = 30;
|
|
10
9
|
const OMNI_CHECK_DELAY = 1000;
|
|
11
10
|
|
|
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
11
|
function sleep(ms) {
|
|
38
12
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
39
13
|
}
|
|
40
14
|
|
|
41
|
-
function initTool(name, args, logPath) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
15
|
+
async function initTool(name, args, logPath) {
|
|
16
|
+
if (!commandExists(name)) {
|
|
17
|
+
console.log(`[omnicode] ${name}: not installed, skipping`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
console.log(`[omnicode] ${name}: initializing`);
|
|
21
|
+
let log;
|
|
22
|
+
try {
|
|
23
|
+
log = openSync(logPath, "w");
|
|
50
24
|
const child = spawn(name, args, { stdio: ["ignore", log, log] });
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
25
|
+
const result = await Promise.race([
|
|
26
|
+
new Promise((resolve) => {
|
|
27
|
+
child.on("close", resolve);
|
|
28
|
+
child.on("error", () => resolve(null));
|
|
29
|
+
}),
|
|
30
|
+
new Promise((resolve) => {
|
|
31
|
+
setTimeout(() => { child.kill(); resolve(null); }, 30000);
|
|
32
|
+
}),
|
|
33
|
+
]);
|
|
34
|
+
if (result === null) {
|
|
35
|
+
console.log(`[omnicode] WARNING: ${name} init timed out; continuing. Log: ${logPath}`);
|
|
36
|
+
} else if (result === 0) {
|
|
37
|
+
console.log(`[omnicode] ${name}: ready`);
|
|
38
|
+
} else {
|
|
60
39
|
console.log(`[omnicode] WARNING: ${name} init failed; continuing. Log: ${logPath}`);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
if (log !== undefined) try { closeSync(log); } catch {}
|
|
43
|
+
}
|
|
64
44
|
}
|
|
65
45
|
|
|
66
46
|
async function initTools(dataDir) {
|
|
@@ -137,9 +117,13 @@ function stopOmnirouteIfIdle(pidFile) {
|
|
|
137
117
|
} catch {}
|
|
138
118
|
if (pid && isPidAlive(pid)) {
|
|
139
119
|
console.log(`[omnicode] no opencode left -> stopping omniroute (pid: ${pid})`);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
120
|
+
if (process.platform === "win32") {
|
|
121
|
+
spawn("taskkill", ["/T", "/F", "/PID", String(pid)], { stdio: "ignore" });
|
|
122
|
+
} else {
|
|
123
|
+
try { process.kill(-pid, "SIGTERM"); } catch {}
|
|
124
|
+
}
|
|
125
|
+
for (let i = 0; i < 10; i++) {
|
|
126
|
+
if (!isPidAlive(pid)) break;
|
|
143
127
|
try { process.kill(pid, 0); } catch { break; }
|
|
144
128
|
}
|
|
145
129
|
}
|
|
@@ -149,7 +133,7 @@ function stopOmnirouteIfIdle(pidFile) {
|
|
|
149
133
|
|
|
150
134
|
export async function runRuntime(mode) {
|
|
151
135
|
const dataDir = getDataDir();
|
|
152
|
-
mkdirSync(dataDir, { recursive: true });
|
|
136
|
+
mkdirSync(dataDir, { recursive: true, mode: 0o700 });
|
|
153
137
|
|
|
154
138
|
const logFile = join(dataDir, "omniroute.log");
|
|
155
139
|
const pidFile = join(dataDir, "omniroute.pid");
|
|
@@ -163,12 +147,12 @@ export async function runRuntime(mode) {
|
|
|
163
147
|
process.on("SIGINT", () => { cleanup(); process.exit(0); });
|
|
164
148
|
process.on("SIGTERM", () => { cleanup(); process.exit(0); });
|
|
165
149
|
|
|
166
|
-
await initTools(dataDir);
|
|
167
|
-
|
|
168
150
|
const pid = startOmniroute(dataDir, logFile, pidFile);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
151
|
+
|
|
152
|
+
await Promise.all([
|
|
153
|
+
pid !== null ? waitForOmniroute(pid, logFile) : Promise.resolve(),
|
|
154
|
+
initTools(dataDir),
|
|
155
|
+
]);
|
|
172
156
|
|
|
173
157
|
if (mode.flag === "-s" && mode.id) {
|
|
174
158
|
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", ["-f", 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
|
}
|