@ours.network/codex 1.1.1 → 1.2.0-nightly.2
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 +33 -5
- package/bin/container-file-transfer.mjs +147 -0
- package/bin/container-launch.mjs +40 -0
- package/bin/network-watch.mjs +8 -0
- package/bin/proxy.mjs +42 -21
- package/dist/hooks-runner.mjs +16649 -0
- package/dist/monitor-mcp.mjs +154 -24
- package/dist/network-proxy.mjs +16782 -0
- package/dist/network-watch.mjs +391 -0
- package/dist/profile.mjs +198 -0
- package/hooks/hooks.json +3 -3
- package/package.json +3 -4
- package/skills/ours/SKILL.md +2 -1
- package/src/hooks/runner.mjs +54 -7
- package/src/launcher.mjs +49 -15
- package/src/monitor-mcp.mjs +19 -6
- package/src/network-proxy.mjs +308 -0
- package/src/network-watch.mjs +45 -0
- package/src/profile.mjs +17 -2
- 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
|
@@ -54,7 +54,11 @@ selected daemon is absent or incompatible, it exits with an error and leaves sta
|
|
|
54
54
|
## Selecting a daemon
|
|
55
55
|
|
|
56
56
|
Multiple daemons may run on one host when each uses a distinct port and state directory.
|
|
57
|
-
|
|
57
|
+
For network clients, explicit `OURS_CONFIG` takes precedence over the managed
|
|
58
|
+
`~/.ours-client/profile.json`. When the managed file exists, ordinary `ours-codex`
|
|
59
|
+
launches need no environment override. Invalid or unreadable managed profiles
|
|
60
|
+
fail before selecting another daemon. With neither selection, the existing
|
|
61
|
+
local-daemon precedence remains:
|
|
58
62
|
|
|
59
63
|
1. `ours-codex --ours-port <port>`
|
|
60
64
|
2. `OURS_PORT`
|
|
@@ -62,12 +66,28 @@ Selection precedence is:
|
|
|
62
66
|
4. `~/.ours/config.json`
|
|
63
67
|
5. port `3050`
|
|
64
68
|
|
|
65
|
-
All MCP, hooks, unread, and watcher calls inherit the same selected profile.
|
|
69
|
+
All MCP, hooks, unread, and watcher calls inherit the same selected profile. Host-profile
|
|
70
|
+
traffic goes directly to the selected daemon's authenticated `/mcp` endpoint through the
|
|
71
|
+
Codex package; it does not require Docker or an installed `@ours.network/mcp` package.
|
|
72
|
+
Example:
|
|
66
73
|
|
|
67
74
|
```sh
|
|
68
75
|
OURS_CONFIG="$HOME/.ours/testing.json" ours-codex --ours-port 4050
|
|
69
76
|
```
|
|
70
77
|
|
|
78
|
+
An operator-provisioned host profile uses the complete
|
|
79
|
+
`endpoint`/`expectedInstanceId`/`credentialPath` tuple in the selected file. In this mode,
|
|
80
|
+
do not combine the profile with `--ours-port`, `OURS_PORT`, `OURS_API_TOKEN`, or
|
|
81
|
+
`OURS_STATE_DIR`. Codex forwards the actual selected path as `OURS_CONFIG` and the optional `OURS_MCP_CONFIG` host
|
|
82
|
+
record location; the SDK verifies the expected
|
|
83
|
+
daemon instance and rereads `credentialPath` for every request, so an operator token
|
|
84
|
+
update takes effect in the running hook and live monitor without a restart or token
|
|
85
|
+
snapshot in child process environments.
|
|
86
|
+
|
|
87
|
+
Native ownership is keyed by Codex's actual thread ID. A normal live TUI exit forwards
|
|
88
|
+
that ID through the same `SessionEnd` input used by the hook. If Codex has not produced
|
|
89
|
+
a logical thread ID, there is no native owner for the launcher to release.
|
|
90
|
+
|
|
71
91
|
## Hooks and consent
|
|
72
92
|
|
|
73
93
|
The native plugin bundles `hooks/hooks.json` using Codex's default hook discovery.
|
|
@@ -77,9 +97,17 @@ lifecycle directly, while the hooks add standard-mode context and defensive stat
|
|
|
77
97
|
- `SessionStart` surfaces body-free unread metadata and an advisory `.ours-identity` pin.
|
|
78
98
|
- `UserPromptSubmit` can re-surface unresolved unread/pin context.
|
|
79
99
|
- `PostToolUse` records successful identity bindings and disarms on a switch.
|
|
80
|
-
- `SessionEnd
|
|
81
|
-
|
|
82
|
-
|
|
100
|
+
- `SessionEnd`, when delivered, reads the native `session_id`, durably captures the
|
|
101
|
+
exact owner, releases its lease, and waits for all owner-scoped temporary roles to
|
|
102
|
+
send best-effort removal notices and delete local state. Live mode also invokes
|
|
103
|
+
this cleanup directly when its TUI exits.
|
|
104
|
+
|
|
105
|
+
The ours MCP owner is allocated lazily from Codex's tool-call thread metadata, so
|
|
106
|
+
`SessionStart` is not required for ownership and may be disabled. Stdio/MCP recycle
|
|
107
|
+
does not end that owner; a later call in the same unended thread recovers it. If
|
|
108
|
+
`SessionEnd` is disabled or never delivered, ours does not infer death from EOF,
|
|
109
|
+
PID, or a timeout and does not claim automatic cleanup. The owner and protected
|
|
110
|
+
resources remain available for ordinary resume until a terminal hook is delivered.
|
|
83
111
|
|
|
84
112
|
Codex requires review and trust of the exact hook definitions before running them.
|
|
85
113
|
Installation does not bypass hook trust, and live monitoring remains available when the
|
|
@@ -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,40 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { isAbsolute, join } from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
|
|
6
|
+
// An explicit container selection is authoritative: failures never fall back
|
|
7
|
+
// to a host MCP or host application-identity configuration.
|
|
8
|
+
export function containerInvocation(command, args = [], env = process.env) {
|
|
9
|
+
const explicitPath = (env.OURS_CONFIG ?? '').trim();
|
|
10
|
+
const path = explicitPath || join(homedir(), '.ours', 'config.json');
|
|
11
|
+
let profile;
|
|
12
|
+
try { profile = JSON.parse(readFileSync(path, 'utf8')); }
|
|
13
|
+
catch (error) {
|
|
14
|
+
if (!explicitPath && error.code === 'ENOENT') return null;
|
|
15
|
+
throw new Error(`cannot read OURS_CONFIG profile ${path}: ${error.message}`);
|
|
16
|
+
}
|
|
17
|
+
if (!profile || !Object.hasOwn(profile, 'composeFile')) return null;
|
|
18
|
+
if (typeof profile.composeFile !== 'string' || !isAbsolute(profile.composeFile)) throw new Error('composeFile must be an absolute path');
|
|
19
|
+
if (typeof profile.expectedInstanceId !== 'string' || !profile.expectedInstanceId.trim()) throw new Error('container profile requires expectedInstanceId');
|
|
20
|
+
if (profile.composeProject !== undefined && (typeof profile.composeProject !== 'string' || !profile.composeProject.trim())) throw new Error('composeProject must be a nonempty string');
|
|
21
|
+
const dockerArgs = ['compose', '-f', profile.composeFile];
|
|
22
|
+
if (profile.composeProject !== undefined) dockerArgs.push('-p', profile.composeProject);
|
|
23
|
+
dockerArgs.push('exec', '-T');
|
|
24
|
+
for (const name of ['CLAUDE_CODE_SESSION_ID', 'OURS_BIND_IDENTITY']) {
|
|
25
|
+
if (env[name] !== undefined) dockerArgs.push('-e', `${name}=${env[name]}`);
|
|
26
|
+
}
|
|
27
|
+
dockerArgs.push('daemon', 'node', '/opt/ours/node_modules/@ours.network/mcp/dist/container.js', profile.expectedInstanceId, command, ...args);
|
|
28
|
+
return { command: 'docker', args: dockerArgs, env: { ...env, OURS_DAEMON_ID: profile.expectedInstanceId } };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function readContainerJson(command, env = process.env) {
|
|
32
|
+
const invocation = containerInvocation(command, [], env);
|
|
33
|
+
if (!invocation) return null;
|
|
34
|
+
const child = spawnSync(invocation.command, invocation.args, { env: invocation.env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 5000, maxBuffer: 1024 * 1024 });
|
|
35
|
+
if (child.error) throw child.error;
|
|
36
|
+
if (child.status !== 0) throw new Error(`ours container ${command} failed: ${child.stderr.trim() || child.status}`);
|
|
37
|
+
const value = JSON.parse(child.stdout);
|
|
38
|
+
if (value === null) throw new Error(`ours container ${command} returned null`);
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
@@ -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
|
+
});
|
package/bin/proxy.mjs
CHANGED
|
@@ -1,33 +1,54 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
|
-
import { existsSync } from 'node:fs';
|
|
4
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
5
6
|
import { dirname, join } from 'node:path';
|
|
6
7
|
import { fileURLToPath } from 'node:url';
|
|
7
8
|
|
|
9
|
+
const sessionEnd = process.argv[2] === 'session-end';
|
|
10
|
+
const command = sessionEnd ? 'session-end' : 'proxy';
|
|
11
|
+
const forwarded = process.argv.slice(sessionEnd ? 3 : 2);
|
|
8
12
|
const require = createRequire(import.meta.url);
|
|
9
13
|
const spec = '@ours.network/mcp/dist/cli.js';
|
|
10
14
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
const env = { ...process.env };
|
|
16
|
+
|
|
17
|
+
async function main() {
|
|
18
|
+
const network = await import('../dist/network-proxy.mjs');
|
|
19
|
+
const profile = network.hostProfileFromEnv(env);
|
|
20
|
+
if (profile) {
|
|
21
|
+
const configPath = env.OURS_MCP_CONFIG || join(env.HOME || homedir(), '.ours-mcp', 'config.json');
|
|
22
|
+
const hostRecordRoot = dirname(configPath);
|
|
23
|
+
if (sessionEnd) {
|
|
24
|
+
const payload = JSON.parse(readFileSync(0, 'utf8') || '{}');
|
|
25
|
+
if (typeof payload.session_id !== 'string' || !payload.session_id) throw new Error('SessionEnd requires session_id.');
|
|
26
|
+
await network.endNetworkNativeSession({ profile, nativeSessionId: payload.session_id, hostRecordRoot });
|
|
27
|
+
return;
|
|
19
28
|
}
|
|
20
|
-
|
|
29
|
+
await network.runNetworkProxy({ profile, hostRecordRoot, env });
|
|
30
|
+
return;
|
|
21
31
|
}
|
|
32
|
+
|
|
33
|
+
let cliPath;
|
|
34
|
+
try { cliPath = require.resolve(spec); } catch { /* try plugin cache layouts */ }
|
|
35
|
+
if (!cliPath) {
|
|
36
|
+
let dir = here;
|
|
37
|
+
for (let i = 0; i < 12 && !cliPath; i += 1) {
|
|
38
|
+
for (const base of [dir, join(dir, 'npm-cache')]) {
|
|
39
|
+
if (!existsSync(join(base, 'node_modules'))) continue;
|
|
40
|
+
try { cliPath = require.resolve(spec, { paths: [base] }); } catch { /* next */ }
|
|
41
|
+
}
|
|
42
|
+
const parent = dirname(dir); if (parent === dir) break; dir = parent;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (env.OURS_CODEX_LIVE === '1') env.OURS_AUTOSTART = '0';
|
|
46
|
+
if (!env.OURS_CLIENT_PID && process.ppid > 1) env.OURS_CLIENT_PID = String(process.ppid);
|
|
47
|
+
const child = cliPath
|
|
48
|
+
? spawn(process.execPath, [cliPath, command, ...forwarded], { stdio: 'inherit', env })
|
|
49
|
+
: spawn('ours-mcp', [command, ...forwarded], { stdio: 'inherit', env });
|
|
50
|
+
child.on('error', (error) => { process.stderr.write(`ours: cannot launch @ours.network/mcp proxy: ${error.message}\n`); process.exitCode = 1; });
|
|
51
|
+
child.on('exit', (code, signal) => { if (signal) process.kill(process.pid, signal); else process.exitCode = code ?? 0; });
|
|
22
52
|
}
|
|
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); });
|
|
53
|
+
|
|
54
|
+
main().catch((error) => { process.stderr.write(`ours: ${error.message}\n`); process.exitCode = 1; });
|