@ours.network/codex 1.1.1 → 1.2.0-nightly.10
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/.codex-plugin/plugin.json +1 -1
- package/.mcp.json +2 -2
- package/README.md +26 -15
- package/bin/codex-config-install.mjs +3 -3
- package/bin/container-file-transfer.mjs +147 -0
- package/bin/network-watch.mjs +8 -0
- package/bin/ours-codex-install.mjs +4 -31
- package/bin/proxy.mjs +25 -20
- package/dist/hooks-runner.mjs +562 -0
- package/dist/host-hooks.mjs +386 -0
- package/dist/monitor-mcp.mjs +32 -7
- package/dist/network-watch.mjs +287 -0
- package/dist/profile.mjs +56 -0
- package/hooks/hooks.json +3 -3
- package/install.sh +14 -33
- package/package.json +5 -5
- package/skills/ours/SKILL.md +8 -7
- package/skills/ours/references/configuration.md +20 -22
- package/src/hooks/runner.mjs +20 -35
- package/src/host-hooks.mjs +25 -0
- package/src/launcher.mjs +41 -15
- package/src/monitor-mcp.mjs +19 -6
- package/src/network-watch.mjs +45 -0
- package/src/profile.mjs +15 -39
- package/src/watcher.mjs +29 -3
package/.mcp.json
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
"command": "node",
|
|
5
5
|
"args": ["bin/proxy.mjs"],
|
|
6
6
|
"cwd": ".",
|
|
7
|
-
"env_vars": ["OURS_PORT", "OURS_CONFIG", "OURS_API_TOKEN", "OURS_STATE_DIR", "OURS_AUTOSTART", "OURS_CODEX_LIVE", "OURS_CLIENT_PID"]
|
|
7
|
+
"env_vars": ["OURS_PORT", "OURS_CONFIG", "OURS_MCP_CONFIG", "OURS_API_TOKEN", "OURS_STATE_DIR", "OURS_AUTOSTART", "OURS_CODEX_LIVE", "OURS_CLIENT_PID"]
|
|
8
8
|
},
|
|
9
9
|
"ours_monitor": {
|
|
10
10
|
"command": "node",
|
|
11
11
|
"args": ["bin/monitor-mcp.mjs"],
|
|
12
12
|
"cwd": ".",
|
|
13
13
|
"tool_timeout_sec": 86400,
|
|
14
|
-
"env_vars": ["OURS_CODEX_CONTROL_SOCKET", "OURS_CODEX_CAPABILITY", "OURS_PORT", "OURS_CONFIG", "OURS_API_TOKEN", "OURS_STATE_DIR"]
|
|
14
|
+
"env_vars": ["OURS_CODEX_CONTROL_SOCKET", "OURS_CODEX_CAPABILITY", "OURS_PORT", "OURS_CONFIG", "OURS_MCP_CONFIG", "OURS_API_TOKEN", "OURS_STATE_DIR"]
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
}
|
package/README.md
CHANGED
|
@@ -51,23 +51,26 @@ The launcher never starts, stops, restarts, or reconfigures the ours daemon. If
|
|
|
51
51
|
selected daemon is absent or incompatible, it exits with an error and leaves standard
|
|
52
52
|
`codex` available.
|
|
53
53
|
|
|
54
|
-
## Selecting a
|
|
54
|
+
## Selecting a gateway
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
3. the config selected by `OURS_CONFIG`
|
|
62
|
-
4. `~/.ours/config.json`
|
|
63
|
-
5. port `3050`
|
|
64
|
-
|
|
65
|
-
All MCP, hooks, unread, and watcher calls inherit the same selected profile. Example:
|
|
56
|
+
Every client uses `~/.ours-client/profile.json`; `OURS_CONFIG` can select a complete
|
|
57
|
+
alternate profile. It contains `serverUrl`, `expectedInstanceId`, and `credentialPath`.
|
|
58
|
+
The daemon endpoint is derived as `serverUrl + /daemon`, including gateway path prefixes.
|
|
59
|
+
Missing, unsafe, or invalid profiles fail closed. Remove legacy port/state/token
|
|
60
|
+
selectors; `--ours-port` is rejected. Example after provisioning with `ours-install client`:
|
|
66
61
|
|
|
67
62
|
```sh
|
|
68
|
-
OURS_CONFIG="$HOME/.ours/testing.json" ours-codex
|
|
63
|
+
OURS_CONFIG="$HOME/.ours-client/testing.json" ours-codex
|
|
69
64
|
```
|
|
70
65
|
|
|
66
|
+
MCP, hooks, unread and watcher requests inherit that profile. The SDK verifies the
|
|
67
|
+
instance and rereads the issued credential for each request. Client installation and
|
|
68
|
+
launch never install, start, restart or reconfigure server services.
|
|
69
|
+
|
|
70
|
+
Native ownership is keyed by Codex's actual thread ID. A normal live TUI exit forwards
|
|
71
|
+
that ID through the same `SessionEnd` input used by the hook. If Codex has not produced
|
|
72
|
+
a logical thread ID, there is no native owner for the launcher to release.
|
|
73
|
+
|
|
71
74
|
## Hooks and consent
|
|
72
75
|
|
|
73
76
|
The native plugin bundles `hooks/hooks.json` using Codex's default hook discovery.
|
|
@@ -77,9 +80,17 @@ lifecycle directly, while the hooks add standard-mode context and defensive stat
|
|
|
77
80
|
- `SessionStart` surfaces body-free unread metadata and an advisory `.ours-identity` pin.
|
|
78
81
|
- `UserPromptSubmit` can re-surface unresolved unread/pin context.
|
|
79
82
|
- `PostToolUse` records successful identity bindings and disarms on a switch.
|
|
80
|
-
- `SessionEnd
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
- `SessionEnd`, when delivered, reads the native `session_id`, durably captures the
|
|
84
|
+
exact owner, releases its lease, and waits for all owner-scoped temporary roles to
|
|
85
|
+
send best-effort removal notices and delete local state. Live mode also invokes
|
|
86
|
+
this cleanup directly when its TUI exits.
|
|
87
|
+
|
|
88
|
+
The ours MCP owner is allocated lazily from Codex's tool-call thread metadata, so
|
|
89
|
+
`SessionStart` is not required for ownership and may be disabled. Stdio/MCP recycle
|
|
90
|
+
does not end that owner; a later call in the same unended thread recovers it. If
|
|
91
|
+
`SessionEnd` is disabled or never delivered, ours does not infer death from EOF,
|
|
92
|
+
PID, or a timeout and does not claim automatic cleanup. The owner and protected
|
|
93
|
+
resources remain available for ordinary resume until a terminal hook is delivered.
|
|
83
94
|
|
|
84
95
|
Codex requires review and trust of the exact hook definitions before running them.
|
|
85
96
|
Installation does not bypass hook trust, and live monitoring remains available when the
|
|
@@ -33,7 +33,7 @@ export function planTomlInstall(text) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// Render the managed TOML block: the `ours` MCP server pointing at the globally-installed
|
|
36
|
-
//
|
|
36
|
+
// gateway client (`ours-mcp proxy`). Equivalent to `codex mcp add ours -- ours-mcp proxy`.
|
|
37
37
|
export function renderTomlBlock() {
|
|
38
38
|
return `${SENTINEL}
|
|
39
39
|
# Added by @ours.network/codex install.sh. Remove this whole block to uninstall.
|
|
@@ -41,9 +41,9 @@ export function renderTomlBlock() {
|
|
|
41
41
|
[mcp_servers.ours]
|
|
42
42
|
command = "ours-mcp"
|
|
43
43
|
args = ["proxy"]
|
|
44
|
-
# Optional
|
|
44
|
+
# Optional whole shared gateway profile override:
|
|
45
45
|
# [mcp_servers.ours.env]
|
|
46
|
-
#
|
|
46
|
+
# OURS_CONFIG = "/absolute/private/path/profile.json"
|
|
47
47
|
${SENTINEL_END}
|
|
48
48
|
`;
|
|
49
49
|
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { createReadStream, createWriteStream, mkdirSync } from 'node:fs';
|
|
3
|
+
import { basename, dirname, resolve } from 'node:path';
|
|
4
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
5
|
+
import { pipeline } from 'node:stream/promises';
|
|
6
|
+
|
|
7
|
+
function helperInvocation(container, command, args) {
|
|
8
|
+
const entrypoint = container.args.indexOf('/opt/ours/node_modules/@ours.network/mcp/dist/container.js');
|
|
9
|
+
return { ...container, args: [...container.args.slice(0, entrypoint + 2), command, ...args] };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function helperJson(container, command, args) {
|
|
13
|
+
const call = helperInvocation(container, command, args);
|
|
14
|
+
const child = spawnSync(call.command, call.args, { env: call.env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 5000, maxBuffer: 1024 * 1024 });
|
|
15
|
+
if (child.error) throw child.error;
|
|
16
|
+
if (child.status !== 0) throw new Error(child.stderr.trim() || `${command} exited ${child.status}`);
|
|
17
|
+
return JSON.parse(child.stdout);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function runHelper(container, command, args, input, output) {
|
|
21
|
+
const call = helperInvocation(container, command, args);
|
|
22
|
+
const child = spawn(call.command, call.args, { env: call.env, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
23
|
+
let stderr = '';
|
|
24
|
+
child.stderr.setEncoding('utf8');
|
|
25
|
+
child.stderr.on('data', (chunk) => { stderr += chunk; });
|
|
26
|
+
const exited = new Promise((resolveExit, reject) => {
|
|
27
|
+
child.once('error', reject);
|
|
28
|
+
child.once('close', (code, signal) => code === 0 ? resolveExit() : reject(new Error(stderr.trim() || `${command} exited ${signal ?? code}`)));
|
|
29
|
+
});
|
|
30
|
+
const streams = [];
|
|
31
|
+
if (input) streams.push(pipeline(input, child.stdin)); else child.stdin.end();
|
|
32
|
+
if (output) streams.push(pipeline(child.stdout, output)); else child.stdout.resume();
|
|
33
|
+
const results = await Promise.allSettled([...streams, exited]);
|
|
34
|
+
const failed = results.find((result) => result.status === 'rejected');
|
|
35
|
+
if (failed) throw failed.reason;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function stageFile(container, id, hostPath) {
|
|
39
|
+
const abs = resolve(hostPath);
|
|
40
|
+
const call = helperInvocation(container, 'file-stage', [id, basename(abs)]);
|
|
41
|
+
const child = spawn(call.command, call.args, { env: call.env, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
42
|
+
let stdout = '', stderr = '';
|
|
43
|
+
child.stdout.setEncoding('utf8'); child.stdout.on('data', (v) => { stdout += v; });
|
|
44
|
+
child.stderr.setEncoding('utf8'); child.stderr.on('data', (v) => { stderr += v; });
|
|
45
|
+
const exited = new Promise((resolveExit, reject) => {
|
|
46
|
+
child.once('error', reject);
|
|
47
|
+
child.once('close', (code, signal) => code === 0 ? resolveExit() : reject(new Error(stderr.trim() || `file-stage exited ${signal ?? code}`)));
|
|
48
|
+
});
|
|
49
|
+
const results = await Promise.allSettled([pipeline(createReadStream(abs), child.stdin), exited]);
|
|
50
|
+
const failed = results.find((result) => result.status === 'rejected');
|
|
51
|
+
if (failed) throw failed.reason;
|
|
52
|
+
return { path: JSON.parse(stdout).path, abs };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function toolError(id, error) {
|
|
56
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `host file transfer failed: ${error instanceof Error ? error.message : String(error)}` }], isError: true } };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function removeTransfer(container, id) {
|
|
60
|
+
try { await runHelper(container, 'file-remove', [id]); } catch { /* preserve the primary result */ }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function eachLine(stream, visit) {
|
|
64
|
+
let pending = Buffer.alloc(0);
|
|
65
|
+
for await (const chunk of stream) {
|
|
66
|
+
pending = Buffer.concat([pending, chunk]);
|
|
67
|
+
let newline;
|
|
68
|
+
while ((newline = pending.indexOf(10)) !== -1) {
|
|
69
|
+
const line = pending.subarray(0, newline + 1);
|
|
70
|
+
pending = pending.subarray(newline + 1);
|
|
71
|
+
await visit(line);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (pending.length) await visit(pending);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function runContainerProxy(container) {
|
|
78
|
+
const child = spawn(container.command, container.args, { stdio: ['pipe', 'pipe', 'inherit'], env: container.env });
|
|
79
|
+
const pending = new Map();
|
|
80
|
+
let stopping = false;
|
|
81
|
+
const stopInput = () => { stopping = true; process.stdin.destroy(); };
|
|
82
|
+
child.stdin.on('error', stopInput);
|
|
83
|
+
let outputWrites = Promise.resolve();
|
|
84
|
+
const writeOutput = (bytes) => { outputWrites = outputWrites.then(() => new Promise((done) => process.stdout.write(bytes, done))); return outputWrites; };
|
|
85
|
+
|
|
86
|
+
const inputDone = eachLine(process.stdin, async (raw) => {
|
|
87
|
+
let message;
|
|
88
|
+
try { message = JSON.parse(raw.toString('utf8')); } catch { child.stdin.write(raw); return; }
|
|
89
|
+
// Forward cancellation unchanged. The upstream handler owns cancellation;
|
|
90
|
+
// keep transfer tracking until its actual response so a completed save is
|
|
91
|
+
// copied to the host and its staging directory is removed.
|
|
92
|
+
if (message?.method === 'notifications/cancelled') { child.stdin.write(raw); return; }
|
|
93
|
+
if (message?.method !== 'tools/call' || !message.params?.arguments) { child.stdin.write(raw); return; }
|
|
94
|
+
const name = message.params.name;
|
|
95
|
+
if (name !== 'send_file' && name !== 'save_file') { child.stdin.write(raw); return; }
|
|
96
|
+
const args = message.params.arguments;
|
|
97
|
+
if (name === 'send_file' && !args.path) { child.stdin.write(raw); return; }
|
|
98
|
+
const id = randomUUID();
|
|
99
|
+
try {
|
|
100
|
+
if (name === 'send_file') {
|
|
101
|
+
const staged = await stageFile(container, id, args.path);
|
|
102
|
+
message.params.arguments = { ...args, path: staged.path, filename: args.filename ?? basename(staged.abs) };
|
|
103
|
+
pending.set(message.id, { id, kind: name });
|
|
104
|
+
} else {
|
|
105
|
+
const hostPath = resolve(args.dest_path);
|
|
106
|
+
const target = helperJson(container, 'file-target', [id]);
|
|
107
|
+
message.params.arguments = { ...args, dest_path: target.path };
|
|
108
|
+
pending.set(message.id, { id, kind: name, hostPath, containerPath: target.path });
|
|
109
|
+
}
|
|
110
|
+
if (!stopping) child.stdin.write(`${JSON.stringify(message)}\n`);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
await removeTransfer(container, id);
|
|
113
|
+
await writeOutput(`${JSON.stringify(toolError(message.id, error))}\n`);
|
|
114
|
+
}
|
|
115
|
+
}).catch((error) => {
|
|
116
|
+
if (!stopping) { process.stderr.write(`ours: container MCP input failed: ${error.message}\n`); process.exitCode = 1; child.kill(); }
|
|
117
|
+
}).finally(() => { if (!child.stdin.destroyed) child.stdin.end(); });
|
|
118
|
+
|
|
119
|
+
const outputDone = eachLine(child.stdout, async (raw) => {
|
|
120
|
+
let message;
|
|
121
|
+
try { message = JSON.parse(raw.toString('utf8')); } catch { await writeOutput(raw); return; }
|
|
122
|
+
const transfer = pending.get(message?.id);
|
|
123
|
+
if (!transfer) { await writeOutput(raw); return; }
|
|
124
|
+
pending.delete(message.id);
|
|
125
|
+
try {
|
|
126
|
+
if (transfer.kind === 'save_file' && !message.error && message.result?.isError !== true) {
|
|
127
|
+
mkdirSync(dirname(transfer.hostPath), { recursive: true });
|
|
128
|
+
await runHelper(container, 'file-read', [transfer.id], null, createWriteStream(transfer.hostPath));
|
|
129
|
+
for (const item of message.result?.content ?? []) {
|
|
130
|
+
if (item?.type === 'text' && typeof item.text === 'string') item.text = item.text.split(transfer.containerPath).join(transfer.hostPath);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
await writeOutput(`${JSON.stringify(message)}\n`);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
await writeOutput(`${JSON.stringify(toolError(message.id, error))}\n`);
|
|
136
|
+
} finally { await removeTransfer(container, transfer.id); }
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
child.on('error', (error) => { stopInput(); process.stderr.write(`ours: cannot launch container MCP: ${error.message}\n`); process.exitCode = 1; });
|
|
140
|
+
child.on('close', async (code, signal) => {
|
|
141
|
+
stopInput();
|
|
142
|
+
await Promise.allSettled([inputDone, outputDone, outputWrites]);
|
|
143
|
+
await Promise.allSettled([...pending.values()].map((v) => removeTransfer(container, v.id)));
|
|
144
|
+
if (signal) process.kill(process.pid, signal); else process.exitCode = process.exitCode || (code === null || code < 0 ? 1 : code);
|
|
145
|
+
});
|
|
146
|
+
return child;
|
|
147
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runNetworkWatch } from '../dist/network-watch.mjs';
|
|
3
|
+
|
|
4
|
+
const [identity, nativeSessionId] = process.argv.slice(2);
|
|
5
|
+
runNetworkWatch({ identity, nativeSessionId }).catch((error) => {
|
|
6
|
+
process.stderr.write(`ours network watch: ${error.message}\n`);
|
|
7
|
+
process.exitCode = 1;
|
|
8
|
+
});
|
|
@@ -5,14 +5,7 @@
|
|
|
5
5
|
// npm i -g @ours.network/codex
|
|
6
6
|
// ours-codex-install
|
|
7
7
|
//
|
|
8
|
-
//
|
|
9
|
-
// WHOLE stack (ours core + harness plugins + ours-fleet + Telegram). `ours-codex-install` is kept
|
|
10
|
-
// as a THIN ALIAS — by default it hands off to `ours-install` when that's available, so an old
|
|
11
|
-
// muscle-memory command still lands the user in the current, unified flow. Pass `--codex-only`
|
|
12
|
-
// (or set OURS_CODEX_LEGACY=1) to force the legacy Codex-only install below.
|
|
13
|
-
//
|
|
14
|
-
// Usage:
|
|
15
|
-
// ours-codex-install [--codex-only] [--codex-dir DIR] [--skills-dir DIR] [--skip-daemon]
|
|
8
|
+
// Installs client plugin wiring only after shared gateway profile validation.
|
|
16
9
|
import { spawnSync } from 'node:child_process';
|
|
17
10
|
import { fileURLToPath } from 'node:url';
|
|
18
11
|
import { dirname, join } from 'node:path';
|
|
@@ -33,38 +26,19 @@ for (let i = 0; i < argv.length; i++) {
|
|
|
33
26
|
else { console.error(`ours-codex-install: unknown argument "${a}"`); help(); process.exit(2); }
|
|
34
27
|
}
|
|
35
28
|
|
|
36
|
-
// Thin-alias hand-off: unless the caller forced the legacy Codex-only path, run the unified
|
|
37
|
-
// `ours-install` if we can find it — on PATH (published), or the monorepo sibling (dev/test).
|
|
38
|
-
if (!opts.codexOnly && !process.env.OURS_CODEX_LEGACY) {
|
|
39
|
-
const onPath = spawnSync('bash', ['-c', 'command -v ours-install'], { encoding: 'utf8' });
|
|
40
|
-
const unified = (onPath.status === 0 && (onPath.stdout || '').trim())
|
|
41
|
-
? { cmd: (onPath.stdout || '').trim(), args: argv }
|
|
42
|
-
: (() => {
|
|
43
|
-
const sib = join(PKG, '..', 'installer', 'install.mjs');
|
|
44
|
-
return existsSync(sib) ? { cmd: process.execPath, args: [sib, ...argv] } : null;
|
|
45
|
-
})();
|
|
46
|
-
if (unified) {
|
|
47
|
-
const r = spawnSync(unified.cmd, unified.args, { stdio: 'inherit', env: process.env });
|
|
48
|
-
if (r.error) { console.error(`ours-codex-install: ${r.error.message}`); process.exit(1); }
|
|
49
|
-
process.exit(r.status ?? 0);
|
|
50
|
-
}
|
|
51
|
-
// No unified installer found → fall through to the legacy Codex-only install (still never a dead end).
|
|
52
|
-
}
|
|
53
|
-
|
|
54
29
|
function help() {
|
|
55
30
|
console.log(`ours-codex-install — set up the ours.network plugin for the OpenAI Codex CLI.
|
|
56
31
|
|
|
57
32
|
ours-codex-install [options]
|
|
58
33
|
|
|
59
|
-
|
|
60
|
-
stack). Use --codex-only to run the legacy Codex-only install instead. Standard mode uses
|
|
34
|
+
Requires clients and a shared gateway profile provisioned by \`ours-install client\`. Standard mode uses
|
|
61
35
|
\`codex\`; live mode uses \`ours-codex\`. Live monitoring still requires explicit consent.
|
|
62
36
|
|
|
63
37
|
Options:
|
|
64
|
-
--codex-only
|
|
38
|
+
--codex-only compatibility flag; plugin setup is always client-only
|
|
65
39
|
--codex-dir <dir> Codex config+AGENTS.md root (default ~/.codex) [--codex-only]
|
|
66
40
|
--skills-dir <dir> skills root (default ~/.agents/skills — USER scope) [--codex-only]
|
|
67
|
-
--skip-daemon
|
|
41
|
+
--skip-daemon compatibility flag; server lifecycle is never changed [--codex-only]
|
|
68
42
|
-h, --help show this help
|
|
69
43
|
|
|
70
44
|
Idempotent: safe to re-run. Start a new Codex thread after installation and review the
|
|
@@ -81,7 +55,6 @@ if (!existsSync(INSTALL)) {
|
|
|
81
55
|
const env = { ...process.env };
|
|
82
56
|
if (opts.codexDir) env.CODEX_DIR = opts.codexDir;
|
|
83
57
|
if (opts.skillsDir) env.SKILLS_DIR = opts.skillsDir;
|
|
84
|
-
if (opts.skipDaemon) env.OURS_INSTALL_SKIP_DAEMON = '1';
|
|
85
58
|
|
|
86
59
|
const res = spawnSync('bash', [INSTALL], { stdio: 'inherit', env });
|
|
87
60
|
if (res.error) { console.error(`ours-codex-install: ${res.error.message}`); process.exit(1); }
|
package/bin/proxy.mjs
CHANGED
|
@@ -5,29 +5,34 @@ import { existsSync } from 'node:fs';
|
|
|
5
5
|
import { dirname, join } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
|
|
8
|
+
const sessionEnd = process.argv[2] === 'session-end';
|
|
9
|
+
const command = sessionEnd ? 'session-end' : 'proxy';
|
|
10
|
+
const forwarded = process.argv.slice(sessionEnd ? 3 : 2);
|
|
8
11
|
const require = createRequire(import.meta.url);
|
|
9
12
|
const spec = '@ours.network/mcp/dist/cli.js';
|
|
10
13
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
let
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
const env = { ...process.env };
|
|
15
|
+
|
|
16
|
+
async function main() {
|
|
17
|
+
let cliPath;
|
|
18
|
+
try { cliPath = require.resolve(spec); } catch { /* try plugin cache layouts */ }
|
|
19
|
+
if (!cliPath) {
|
|
20
|
+
let dir = here;
|
|
21
|
+
for (let i = 0; i < 12 && !cliPath; i += 1) {
|
|
22
|
+
for (const base of [dir, join(dir, 'npm-cache')]) {
|
|
23
|
+
if (!existsSync(join(base, 'node_modules'))) continue;
|
|
24
|
+
try { cliPath = require.resolve(spec, { paths: [base] }); } catch { /* next */ }
|
|
25
|
+
}
|
|
26
|
+
const parent = dirname(dir); if (parent === dir) break; dir = parent;
|
|
19
27
|
}
|
|
20
|
-
const parent = dirname(dir); if (parent === dir) break; dir = parent;
|
|
21
28
|
}
|
|
29
|
+
if (env.OURS_CODEX_LIVE === '1') env.OURS_AUTOSTART = '0';
|
|
30
|
+
if (!env.OURS_CLIENT_PID && process.ppid > 1) env.OURS_CLIENT_PID = String(process.ppid);
|
|
31
|
+
const child = cliPath
|
|
32
|
+
? spawn(process.execPath, [cliPath, command, ...forwarded], { stdio: 'inherit', env })
|
|
33
|
+
: spawn('ours-mcp', [command, ...forwarded], { stdio: 'inherit', env });
|
|
34
|
+
child.on('error', (error) => { process.stderr.write(`ours: cannot launch @ours.network/mcp proxy: ${error.message}\n`); process.exitCode = 1; });
|
|
35
|
+
child.on('exit', (code, signal) => { if (signal) process.kill(process.pid, signal); else process.exitCode = code ?? 0; });
|
|
22
36
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (!env.OURS_CLIENT_PID && process.ppid > 1) env.OURS_CLIENT_PID = String(process.ppid);
|
|
26
|
-
const sessionEnd = process.argv[2] === 'session-end';
|
|
27
|
-
const command = sessionEnd ? 'session-end' : 'proxy';
|
|
28
|
-
const forwarded = process.argv.slice(sessionEnd ? 3 : 2);
|
|
29
|
-
const child = cliPath
|
|
30
|
-
? spawn(process.execPath, [cliPath, command, ...forwarded], { stdio: 'inherit', env })
|
|
31
|
-
: spawn('ours-mcp', [command, ...forwarded], { stdio: 'inherit', env });
|
|
32
|
-
child.on('error', (error) => { process.stderr.write(`ours: cannot launch @ours.network/mcp proxy: ${error.message}\n`); process.exit(1); });
|
|
33
|
-
child.on('exit', (code, signal) => { if (signal) process.kill(process.pid, signal); else process.exit(code ?? 0); });
|
|
37
|
+
|
|
38
|
+
main().catch((error) => { process.stderr.write(`ours: ${error.message}\n`); process.exitCode = 1; });
|