@meyverick/omnicode 0.0.4 → 0.0.5
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 +4 -3
- package/package.json +1 -1
- package/src/bin/omnicode-runtime.js +192 -0
- package/src/bin/omnicode.js +31 -36
- package/src/installer/lib.js +12 -26
- package/src/bin/omnicode-runtime.sh +0 -121
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# omnicode
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The cross-platform command-line entrypoint for running OpenCode through OmniRoute.
|
|
4
4
|
|
|
5
5
|
## What is omnicode?
|
|
6
6
|
|
|
7
|
-
`omnicode` is a thin wrapper that launches OpenCode through OmniRoute
|
|
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
|
|
|
@@ -16,7 +16,8 @@ So I wrote this wrapper. It starts OmniRoute, inits GrayMatter and OpenSpec quie
|
|
|
16
16
|
|
|
17
17
|
## Features
|
|
18
18
|
|
|
19
|
-
- Thin npm global command
|
|
19
|
+
- Thin npm global command written in Node.js (no bash needed).
|
|
20
|
+
- Cross-platform by design — developed and tested on Ubuntu Linux; untested on Windows, macOS, and other Linux distributions.
|
|
20
21
|
- Automatic session resume per project using the OpenCode database.
|
|
21
22
|
- Background OmniRoute lifecycle with cleanup when OpenCode exits.
|
|
22
23
|
- Quiet GrayMatter and OpenSpec initialization with captured logs.
|
package/package.json
CHANGED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { spawn, execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, openSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
|
|
6
|
+
import { commandExists, getDataDir } from "../installer/lib.js";
|
|
7
|
+
|
|
8
|
+
const isWindows = process.platform === "win32";
|
|
9
|
+
const MAX_OMNI_WAIT = 30;
|
|
10
|
+
const OMNI_CHECK_DELAY = 1000;
|
|
11
|
+
|
|
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
|
+
function sleep(ms) {
|
|
38
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function initTool(name, args, logPath) {
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
if (!commandExists(name)) {
|
|
44
|
+
console.log(`[omnicode] ${name}: not installed, skipping`);
|
|
45
|
+
resolve();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
console.log(`[omnicode] ${name}: initializing`);
|
|
49
|
+
const log = openSync(logPath, "w");
|
|
50
|
+
const child = spawn(name, args, { stdio: ["ignore", log, log] });
|
|
51
|
+
child.on("close", (code) => {
|
|
52
|
+
if (code === 0) {
|
|
53
|
+
console.log(`[omnicode] ${name}: ready`);
|
|
54
|
+
} else {
|
|
55
|
+
console.log(`[omnicode] WARNING: ${name} init failed; continuing. Log: ${logPath}`);
|
|
56
|
+
}
|
|
57
|
+
resolve();
|
|
58
|
+
});
|
|
59
|
+
child.on("error", () => {
|
|
60
|
+
console.log(`[omnicode] WARNING: ${name} init failed; continuing. Log: ${logPath}`);
|
|
61
|
+
resolve();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function initTools(dataDir) {
|
|
67
|
+
const graymatterLog = join(dataDir, "graymatter-init.log");
|
|
68
|
+
const openspecLog = join(dataDir, "openspec-init.log");
|
|
69
|
+
|
|
70
|
+
await Promise.all([
|
|
71
|
+
initTool("graymatter", ["init", "--only", "opencode"], graymatterLog),
|
|
72
|
+
initTool("openspec", ["init", "--force", "--tools", "opencode"], openspecLog),
|
|
73
|
+
]);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function startOmniroute(dataDir, logFile, pidFile) {
|
|
77
|
+
if (isProcessRunning("omniroute")) {
|
|
78
|
+
console.log("[omnicode] omniroute already running");
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (existsSync(pidFile) || existsSync(pidFile)) {
|
|
83
|
+
let pid = null;
|
|
84
|
+
try {
|
|
85
|
+
pid = parseInt(readFileSync(pidFile, "utf8").trim(), 10);
|
|
86
|
+
} catch {}
|
|
87
|
+
if (pid && isPidAlive(pid)) {
|
|
88
|
+
console.log(`[omnicode] omniroute already running (pid: ${pid})`);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
try { unlinkSync(pidFile); } catch {}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log("[omnicode] starting omniroute...");
|
|
95
|
+
const log = openSync(logFile, "w");
|
|
96
|
+
const child = spawn("omniroute", ["--no-open"], {
|
|
97
|
+
detached: true,
|
|
98
|
+
stdio: ["ignore", log, log],
|
|
99
|
+
});
|
|
100
|
+
child.unref();
|
|
101
|
+
const pid = child.pid;
|
|
102
|
+
writeFileSync(pidFile, String(pid), { mode: 0o600 });
|
|
103
|
+
|
|
104
|
+
return pid;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function waitForOmniroute(pid, logFile) {
|
|
108
|
+
let waited = 0;
|
|
109
|
+
while (waited < MAX_OMNI_WAIT * 1000) {
|
|
110
|
+
await sleep(OMNI_CHECK_DELAY);
|
|
111
|
+
waited += OMNI_CHECK_DELAY;
|
|
112
|
+
|
|
113
|
+
if (!isPidAlive(pid)) {
|
|
114
|
+
console.error(`[omnicode] ERROR: omniroute exited during startup. Log: ${logFile}`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (isProcessRunning("omniroute")) {
|
|
119
|
+
console.log(`[omnicode] omniroute started (pid: ${pid})`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.error(`[omnicode] ERROR: omniroute did not become ready. Log: ${logFile}`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function stopOmnirouteIfIdle(pidFile) {
|
|
129
|
+
if (isProcessRunning("opencode")) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (existsSync(pidFile)) {
|
|
134
|
+
let pid = null;
|
|
135
|
+
try {
|
|
136
|
+
pid = parseInt(readFileSync(pidFile, "utf8").trim(), 10);
|
|
137
|
+
} catch {}
|
|
138
|
+
if (pid && isPidAlive(pid)) {
|
|
139
|
+
console.log(`[omnicode] no opencode left -> stopping omniroute (pid: ${pid})`);
|
|
140
|
+
try { process.kill(pid, "SIGTERM"); } catch {}
|
|
141
|
+
const deadline = Date.now() + 5000;
|
|
142
|
+
while (isPidAlive(pid) && Date.now() < deadline) {
|
|
143
|
+
try { process.kill(pid, 0); } catch { break; }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
try { unlinkSync(pidFile); } catch {}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function runRuntime(mode) {
|
|
151
|
+
const dataDir = getDataDir();
|
|
152
|
+
mkdirSync(dataDir, { recursive: true });
|
|
153
|
+
|
|
154
|
+
const logFile = join(dataDir, "omniroute.log");
|
|
155
|
+
const pidFile = join(dataDir, "omniroute.pid");
|
|
156
|
+
|
|
157
|
+
if (!isWindows) {
|
|
158
|
+
process.umask(0o077);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const cleanup = () => stopOmnirouteIfIdle(pidFile);
|
|
162
|
+
process.on("exit", cleanup);
|
|
163
|
+
process.on("SIGINT", () => { cleanup(); process.exit(0); });
|
|
164
|
+
process.on("SIGTERM", () => { cleanup(); process.exit(0); });
|
|
165
|
+
|
|
166
|
+
await initTools(dataDir);
|
|
167
|
+
|
|
168
|
+
const pid = startOmniroute(dataDir, logFile, pidFile);
|
|
169
|
+
if (pid !== null) {
|
|
170
|
+
await waitForOmniroute(pid, logFile);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (mode.flag === "-s" && mode.id) {
|
|
174
|
+
console.log(`[omnicode] launching opencode (session: ${mode.id})`);
|
|
175
|
+
await new Promise((resolve) => {
|
|
176
|
+
const child = spawn("opencode", ["-s", mode.id], {
|
|
177
|
+
stdio: "inherit",
|
|
178
|
+
cwd: process.cwd(),
|
|
179
|
+
});
|
|
180
|
+
child.on("close", () => resolve());
|
|
181
|
+
});
|
|
182
|
+
} else {
|
|
183
|
+
console.log("[omnicode] launching opencode (new session)");
|
|
184
|
+
await new Promise((resolve) => {
|
|
185
|
+
const child = spawn("opencode", [], {
|
|
186
|
+
stdio: "inherit",
|
|
187
|
+
cwd: process.cwd(),
|
|
188
|
+
});
|
|
189
|
+
child.on("close", () => resolve());
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
package/src/bin/omnicode.js
CHANGED
|
@@ -1,29 +1,47 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { execFileSync
|
|
3
|
-
import {
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import os from "node:os";
|
|
7
6
|
|
|
8
|
-
import { commandExists } from "../installer/lib.js";
|
|
7
|
+
import { commandExists, getOpencodeDbPath } from "../installer/lib.js";
|
|
8
|
+
import { runRuntime } from "./omnicode-runtime.js";
|
|
9
9
|
|
|
10
10
|
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
11
|
-
const runtimeScript = join(__dirname, "omnicode-runtime.sh");
|
|
12
11
|
const packageJsonPath = join(__dirname, "..", "..", "package.json");
|
|
13
12
|
|
|
14
13
|
const SESSION_ID_RE = /^[a-zA-Z0-9_-]+$/;
|
|
14
|
+
const isWindows = process.platform === "win32";
|
|
15
|
+
|
|
16
|
+
let DatabaseSync;
|
|
17
|
+
try {
|
|
18
|
+
({ DatabaseSync } = await import("node:sqlite"));
|
|
19
|
+
} catch {
|
|
20
|
+
DatabaseSync = null;
|
|
21
|
+
}
|
|
15
22
|
|
|
16
23
|
export function printUsage() {
|
|
17
24
|
console.log(`Usage: omnicode [-s <session_id>] [-c] [--status] [--version]`);
|
|
18
25
|
}
|
|
19
26
|
|
|
27
|
+
let _cachedVersion = null;
|
|
28
|
+
|
|
20
29
|
export function getVersion() {
|
|
30
|
+
if (_cachedVersion !== null) return _cachedVersion;
|
|
21
31
|
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
22
|
-
|
|
32
|
+
_cachedVersion = pkg.version;
|
|
33
|
+
return _cachedVersion;
|
|
23
34
|
}
|
|
24
35
|
|
|
25
36
|
export function isProcessRunning(name) {
|
|
26
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
|
+
}
|
|
27
45
|
execFileSync("pgrep", ["-x", name], { stdio: "ignore" });
|
|
28
46
|
return true;
|
|
29
47
|
} catch {
|
|
@@ -78,7 +96,7 @@ export function parseArgs(argv) {
|
|
|
78
96
|
process.exit(2);
|
|
79
97
|
}
|
|
80
98
|
|
|
81
|
-
if (sessionId
|
|
99
|
+
if (!sessionId || !SESSION_ID_RE.test(sessionId)) {
|
|
82
100
|
console.error(`[omnicode] ERROR: invalid session ID format`);
|
|
83
101
|
process.exit(2);
|
|
84
102
|
}
|
|
@@ -87,11 +105,11 @@ export function parseArgs(argv) {
|
|
|
87
105
|
}
|
|
88
106
|
|
|
89
107
|
export async function getLatestSessionId(directory = realpathSync(process.cwd())) {
|
|
90
|
-
const dbPath =
|
|
91
|
-
if (!
|
|
108
|
+
const dbPath = getOpencodeDbPath();
|
|
109
|
+
if (!dbPath) return null;
|
|
92
110
|
|
|
93
111
|
try {
|
|
94
|
-
|
|
112
|
+
if (!DatabaseSync) return null;
|
|
95
113
|
const db = new DatabaseSync(dbPath, { readOnly: true });
|
|
96
114
|
const row = db.prepare(
|
|
97
115
|
"SELECT id FROM session WHERE directory = ? ORDER BY time_updated DESC LIMIT 1"
|
|
@@ -103,21 +121,13 @@ export async function getLatestSessionId(directory = realpathSync(process.cwd())
|
|
|
103
121
|
}
|
|
104
122
|
}
|
|
105
123
|
|
|
106
|
-
export async function resolveSessionMode(sessionId,
|
|
124
|
+
export async function resolveSessionMode(sessionId, latestSessionId = null) {
|
|
107
125
|
if (sessionId) return { flag: "-s", id: sessionId };
|
|
108
126
|
if (latestSessionId === null) latestSessionId = await getLatestSessionId();
|
|
109
127
|
if (latestSessionId) return { flag: "-s", id: latestSessionId };
|
|
110
128
|
return { flag: null, id: null };
|
|
111
129
|
}
|
|
112
130
|
|
|
113
|
-
export function buildRuntimeArgs(mode) {
|
|
114
|
-
const args = [runtimeScript];
|
|
115
|
-
if (mode.flag === "-s") {
|
|
116
|
-
args.push("-s", mode.id);
|
|
117
|
-
}
|
|
118
|
-
return args;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
131
|
async function main() {
|
|
122
132
|
const args = parseArgs(process.argv);
|
|
123
133
|
|
|
@@ -128,23 +138,8 @@ async function main() {
|
|
|
128
138
|
process.exit(1);
|
|
129
139
|
}
|
|
130
140
|
|
|
131
|
-
const mode = await resolveSessionMode(args.sessionId
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
return new Promise((resolve, reject) => {
|
|
135
|
-
const child = spawn("bash", childArgs, {
|
|
136
|
-
stdio: "inherit",
|
|
137
|
-
cwd: process.cwd(),
|
|
138
|
-
});
|
|
139
|
-
child.on("error", reject);
|
|
140
|
-
child.on("close", (code) => {
|
|
141
|
-
if (code !== 0) {
|
|
142
|
-
process.exit(code);
|
|
143
|
-
} else {
|
|
144
|
-
resolve();
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
});
|
|
141
|
+
const mode = await resolveSessionMode(args.sessionId);
|
|
142
|
+
await runRuntime(mode);
|
|
148
143
|
}
|
|
149
144
|
|
|
150
145
|
function isMainModule() {
|
package/src/installer/lib.js
CHANGED
|
@@ -1,40 +1,26 @@
|
|
|
1
|
-
import { execFileSync
|
|
2
|
-
import { existsSync
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
|
|
6
|
+
const isWindows = process.platform === "win32";
|
|
7
|
+
|
|
6
8
|
export function commandExists(command) {
|
|
9
|
+
const tool = isWindows ? "where" : "which";
|
|
7
10
|
try {
|
|
8
|
-
execFileSync(
|
|
11
|
+
execFileSync(tool, [command], { stdio: "ignore" });
|
|
9
12
|
return true;
|
|
10
13
|
} catch {
|
|
11
14
|
return false;
|
|
12
15
|
}
|
|
13
16
|
}
|
|
14
17
|
|
|
15
|
-
export function
|
|
16
|
-
|
|
17
|
-
console.log(`[omnicode] $ ${quoted}`);
|
|
18
|
-
return new Promise((resolve, reject) => {
|
|
19
|
-
const child = spawn(command, args, {
|
|
20
|
-
stdio: "inherit",
|
|
21
|
-
shell: false,
|
|
22
|
-
cwd: process.cwd(),
|
|
23
|
-
...opts,
|
|
24
|
-
});
|
|
25
|
-
child.on("error", reject);
|
|
26
|
-
child.on("close", (code) => {
|
|
27
|
-
if (code !== 0) {
|
|
28
|
-
reject(new Error(`Command failed with exit code ${code}: ${quoted}`));
|
|
29
|
-
} else {
|
|
30
|
-
resolve();
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
});
|
|
18
|
+
export function getDataDir() {
|
|
19
|
+
return join(os.homedir(), ".local", "share", "omnicode");
|
|
34
20
|
}
|
|
35
21
|
|
|
36
|
-
export function
|
|
37
|
-
const
|
|
38
|
-
if (!existsSync(
|
|
39
|
-
return
|
|
22
|
+
export function getOpencodeDbPath() {
|
|
23
|
+
const dbPath = join(os.homedir(), ".local", "share", "opencode", "opencode.db");
|
|
24
|
+
if (!existsSync(dbPath)) return null;
|
|
25
|
+
return dbPath;
|
|
40
26
|
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
SESSION_FLAG="${1:-}"
|
|
5
|
-
SESSION_ID="${2:-}"
|
|
6
|
-
RUNTIME_DIR="$HOME/.local/share/omnicode"
|
|
7
|
-
LOG_FILE="$RUNTIME_DIR/omniroute.log"
|
|
8
|
-
GRAYMATTER_LOG="$RUNTIME_DIR/graymatter-init.log"
|
|
9
|
-
OPENSPEC_LOG="$RUNTIME_DIR/openspec-init.log"
|
|
10
|
-
PID_FILE="$RUNTIME_DIR/omniroute.pid"
|
|
11
|
-
MAX_OMNI_WAIT=30
|
|
12
|
-
OMNI_CHECK_DELAY=1
|
|
13
|
-
|
|
14
|
-
export PATH="$PATH:$HOME/.local/bin:/usr/local/bin:/usr/bin:/bin"
|
|
15
|
-
|
|
16
|
-
umask 0077
|
|
17
|
-
mkdir -p "$RUNTIME_DIR"
|
|
18
|
-
|
|
19
|
-
is_pid_alive() {
|
|
20
|
-
local pid="${1:-}"
|
|
21
|
-
[[ -n "$pid" ]] && kill -0 "$pid" >/dev/null 2>&1
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
is_omniroute_running() {
|
|
25
|
-
pgrep -f "omniroute" >/dev/null 2>&1
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
is_opencode_running() {
|
|
29
|
-
pgrep -f "opencode" >/dev/null 2>&1
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
start_omniroute() {
|
|
33
|
-
if is_omniroute_running; then
|
|
34
|
-
echo "[omnicode] omniroute already running"
|
|
35
|
-
return 0
|
|
36
|
-
fi
|
|
37
|
-
|
|
38
|
-
echo "[omnicode] starting omniroute..."
|
|
39
|
-
: > "$LOG_FILE"
|
|
40
|
-
nohup omniroute --no-open >>"$LOG_FILE" 2>&1 &
|
|
41
|
-
local pid=$!
|
|
42
|
-
if [[ -e "$PID_FILE" ]]; then
|
|
43
|
-
echo "[omnicode] ERROR: PID file already exists at $PID_FILE" >&2
|
|
44
|
-
exit 1
|
|
45
|
-
fi
|
|
46
|
-
touch "$PID_FILE"
|
|
47
|
-
chmod 600 "$PID_FILE"
|
|
48
|
-
printf '%s\n' "$pid" > "$PID_FILE"
|
|
49
|
-
|
|
50
|
-
local waited=0
|
|
51
|
-
while [[ "$waited" -lt "$MAX_OMNI_WAIT" ]]; do
|
|
52
|
-
sleep "$OMNI_CHECK_DELAY"
|
|
53
|
-
waited=$((waited + OMNI_CHECK_DELAY))
|
|
54
|
-
|
|
55
|
-
if ! is_pid_alive "$pid"; then
|
|
56
|
-
echo "[omnicode] ERROR: omniroute exited during startup. Log: $LOG_FILE" >&2
|
|
57
|
-
exit 1
|
|
58
|
-
fi
|
|
59
|
-
|
|
60
|
-
if is_omniroute_running; then
|
|
61
|
-
echo "[omnicode] omniroute started (pid: $pid)"
|
|
62
|
-
return 0
|
|
63
|
-
fi
|
|
64
|
-
done
|
|
65
|
-
|
|
66
|
-
echo "[omnicode] ERROR: omniroute did not become ready. Log: $LOG_FILE" >&2
|
|
67
|
-
exit 1
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
stop_omniroute_if_idle() {
|
|
71
|
-
if is_opencode_running; then
|
|
72
|
-
return 0
|
|
73
|
-
fi
|
|
74
|
-
|
|
75
|
-
if [[ -f "$PID_FILE" ]]; then
|
|
76
|
-
local pid
|
|
77
|
-
pid="$(cat "$PID_FILE" 2>/dev/null || true)"
|
|
78
|
-
if is_pid_alive "$pid"; then
|
|
79
|
-
echo "[omnicode] no opencode left -> stopping omniroute (pid: $pid)"
|
|
80
|
-
kill "$pid" 2>/dev/null || true
|
|
81
|
-
fi
|
|
82
|
-
rm -f "$PID_FILE"
|
|
83
|
-
fi
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
cleanup() {
|
|
87
|
-
stop_omniroute_if_idle
|
|
88
|
-
}
|
|
89
|
-
trap cleanup EXIT INT TERM
|
|
90
|
-
|
|
91
|
-
if command -v graymatter >/dev/null 2>&1; then
|
|
92
|
-
echo "[omnicode] graymatter: initializing"
|
|
93
|
-
if graymatter init --only opencode >"$GRAYMATTER_LOG" 2>&1; then
|
|
94
|
-
echo "[omnicode] graymatter: ready"
|
|
95
|
-
else
|
|
96
|
-
echo "[omnicode] WARNING: graymatter init failed; continuing. Log: $GRAYMATTER_LOG"
|
|
97
|
-
fi
|
|
98
|
-
else
|
|
99
|
-
echo "[omnicode] graymatter: not installed, skipping"
|
|
100
|
-
fi
|
|
101
|
-
|
|
102
|
-
if command -v openspec >/dev/null 2>&1; then
|
|
103
|
-
echo "[omnicode] openspec: initializing"
|
|
104
|
-
if openspec init --force --tools opencode >"$OPENSPEC_LOG" 2>&1; then
|
|
105
|
-
echo "[omnicode] openspec: ready"
|
|
106
|
-
else
|
|
107
|
-
echo "[omnicode] WARNING: openspec init failed; continuing. Log: $OPENSPEC_LOG"
|
|
108
|
-
fi
|
|
109
|
-
else
|
|
110
|
-
echo "[omnicode] openspec: not installed, skipping"
|
|
111
|
-
fi
|
|
112
|
-
|
|
113
|
-
start_omniroute
|
|
114
|
-
|
|
115
|
-
if [[ "$SESSION_FLAG" == "-s" && -n "$SESSION_ID" ]]; then
|
|
116
|
-
echo "[omnicode] launching opencode (session: $SESSION_ID)"
|
|
117
|
-
opencode -s "$SESSION_ID"
|
|
118
|
-
else
|
|
119
|
-
echo "[omnicode] launching opencode (new session)"
|
|
120
|
-
opencode
|
|
121
|
-
fi
|