@ours.network/claude-code 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/.claude-plugin/plugin.json +1 -1
- package/README.md +16 -0
- package/bin/container-file-transfer.mjs +147 -0
- package/bin/container-launch.d.mts +2 -0
- package/bin/container-launch.mjs +40 -0
- package/bin/proxy.mjs +73 -66
- package/dist/hooks/runner.js +46 -4
- package/dist/network-client.mjs +16924 -0
- package/package.json +7 -4
- package/skills/ours/SKILL.md +6 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "ours.network",
|
|
4
4
|
"displayName": "ours.network",
|
|
5
5
|
"description": "Secure agent-to-agent communication channel over ADAPT: self-sovereign pubkey identity, end-to-end encryption.",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.2.0-nightly.2",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Adapt Toolkit"
|
|
9
9
|
},
|
package/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# OURS Claude Code plugin
|
|
2
|
+
|
|
3
|
+
The plugin connects Claude's existing native session to the selected OURS server.
|
|
4
|
+
Its host bridge and hooks remain part of the Claude integration; the main MCP
|
|
5
|
+
server runs with the server installation.
|
|
6
|
+
|
|
7
|
+
New plugin invocations select explicit `OURS_CONFIG` first, otherwise the private
|
|
8
|
+
`~/.ours-client/profile.json` when present. With the managed profile, ordinary
|
|
9
|
+
`claude` launches need no environment override. The file contains the existing
|
|
10
|
+
`endpoint`, `expectedInstanceId` and absolute `credentialPath` tuple. Invalid or
|
|
11
|
+
unreadable managed profiles fail without selecting Docker or a local MCP server.
|
|
12
|
+
When neither selection exists, existing unmanaged behavior remains available.
|
|
13
|
+
|
|
14
|
+
An explicit override applies to a new invocation. It does not rebind existing
|
|
15
|
+
sessions or change `OURS_MCP_CONFIG`, application state or native ownership.
|
|
16
|
+
The existing SessionEnd hook remains responsible for native session completion.
|
|
@@ -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
|
+
}
|
package/bin/proxy.mjs
CHANGED
|
@@ -1,90 +1,97 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Resolves @ours.network/mcp (the MCP server dependency) and runs its
|
|
3
|
-
// daemon CLI in proxy mode, forwarding the stdio MCP channel unchanged.
|
|
4
|
-
//
|
|
5
|
-
// Claude Code installs a plugin's npm dependency in one of two layouts,
|
|
6
|
-
// depending on its version. Either inside this plugin's own per-version
|
|
7
|
-
// node_modules (reachable by Node's default walk-up from this file), or in a
|
|
8
|
-
// SHARED `~/.claude/plugins/npm-cache/node_modules` tree that sits as a SIBLING
|
|
9
|
-
// of the plugin cache root — which a plain walk-up can never reach. We try the
|
|
10
|
-
// default resolution first, then fall back to candidate node_modules roots (any
|
|
11
|
-
// `npm-cache` found by walking up from this file, plus the documented
|
|
12
|
-
// CLAUDE_PLUGIN_DATA dir) so the proxy launches under either layout.
|
|
13
2
|
import { createRequire } from 'node:module';
|
|
14
3
|
import { spawn } from 'node:child_process';
|
|
15
|
-
import {
|
|
4
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
16
6
|
import { dirname, join } from 'node:path';
|
|
17
|
-
import {
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
import { containerInvocation } from './container-launch.mjs';
|
|
10
|
+
import { runContainerProxy } from './container-file-transfer.mjs';
|
|
18
11
|
|
|
12
|
+
const sessionEnd = process.argv[2] === 'session-end';
|
|
13
|
+
const watch = process.argv[2] === 'watch';
|
|
14
|
+
const command = sessionEnd ? 'session-end' : 'proxy';
|
|
15
|
+
const forwarded = process.argv.slice(sessionEnd ? 3 : 2);
|
|
16
|
+
const env = { ...process.env };
|
|
19
17
|
const require = createRequire(import.meta.url);
|
|
20
|
-
const SPEC = '@ours.network/mcp/dist/cli.js';
|
|
21
18
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
function fallbackBases() {
|
|
20
|
+
function resolveLocalServer() {
|
|
21
|
+
const spec = '@ours.network/mcp/dist/cli.js';
|
|
22
|
+
try { return require.resolve(spec); } catch { /* try plugin cache layouts */ }
|
|
27
23
|
const bases = [];
|
|
28
|
-
// Walk up from this file looking for a sibling `npm-cache` (the shared
|
|
29
|
-
// plugin-dependency tree Claude Code populates on some versions).
|
|
30
24
|
let dir = here;
|
|
31
|
-
for (let
|
|
25
|
+
for (let index = 0; index < 12; index += 1) {
|
|
32
26
|
const shared = join(dir, 'npm-cache');
|
|
33
27
|
if (existsSync(join(shared, 'node_modules'))) bases.push(shared);
|
|
34
28
|
const parent = dirname(dir);
|
|
35
29
|
if (parent === dir) break;
|
|
36
30
|
dir = parent;
|
|
37
31
|
}
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
32
|
+
if (env.CLAUDE_PLUGIN_DATA) bases.push(env.CLAUDE_PLUGIN_DATA);
|
|
33
|
+
for (const base of bases) {
|
|
34
|
+
try { return require.resolve(spec, { paths: [base] }); } catch { /* next */ }
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
42
37
|
}
|
|
43
38
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
39
|
+
function relayChild(child, label) {
|
|
40
|
+
child.on('error', (error) => {
|
|
41
|
+
process.stderr.write(`ours: cannot launch ${label}: ${error.message}\n`);
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
});
|
|
44
|
+
child.on('exit', (code, signal) => {
|
|
45
|
+
if (signal) process.kill(process.pid, signal);
|
|
46
|
+
else process.exitCode = code ?? 0;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function main() {
|
|
51
|
+
const network = await import('../dist/network-client.mjs');
|
|
52
|
+
const profile = network.hostProfileFromEnv(env);
|
|
53
|
+
if (profile) {
|
|
54
|
+
const configPath = env.OURS_MCP_CONFIG || join(env.HOME || homedir(), '.ours-mcp', 'config.json');
|
|
55
|
+
const hostRecordRoot = dirname(configPath);
|
|
56
|
+
if (sessionEnd) {
|
|
57
|
+
const payload = JSON.parse(readFileSync(0, 'utf8') || '{}');
|
|
58
|
+
if (typeof payload.session_id !== 'string' || !payload.session_id) throw new Error('SessionEnd requires session_id.');
|
|
59
|
+
await network.endNetworkNativeSession({ profile, nativeSessionId: payload.session_id, hostRecordRoot });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const nativeSessionId = env.CLAUDE_CODE_SESSION_ID;
|
|
63
|
+
if (watch) {
|
|
64
|
+
await network.runNetworkWatch({ identity: process.argv[3], nativeSessionId, profile, hostRecordRoot, env });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
await network.runNetworkProxy({ nativeSessionId, profile, hostRecordRoot, env });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const container = containerInvocation(watch ? 'watch' : command, watch ? process.argv.slice(3) : forwarded, env);
|
|
72
|
+
if (container) {
|
|
73
|
+
const child = watch || sessionEnd
|
|
74
|
+
? spawn(container.command, container.args, { stdio: watch ? ['pipe', 'inherit', 'inherit'] : 'inherit', env: container.env })
|
|
75
|
+
: runContainerProxy(container);
|
|
76
|
+
if (watch) {
|
|
77
|
+
const stop = () => { child.stdin?.end(); if (!child.killed) child.kill('SIGTERM'); };
|
|
78
|
+
process.on('SIGINT', stop);
|
|
79
|
+
process.on('SIGTERM', stop);
|
|
55
80
|
}
|
|
81
|
+
if (watch || sessionEnd) relayChild(child, 'container MCP');
|
|
82
|
+
return;
|
|
56
83
|
}
|
|
57
|
-
}
|
|
58
84
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
'
|
|
62
|
-
|
|
63
|
-
);
|
|
64
|
-
process.
|
|
85
|
+
const cliPath = resolveLocalServer();
|
|
86
|
+
if (!cliPath) {
|
|
87
|
+
throw new Error('cannot resolve @ours.network/mcp. Install the main MCP server explicitly or select an ours host network profile.');
|
|
88
|
+
}
|
|
89
|
+
if (!env.OURS_CLIENT_PID && process.ppid > 1) env.OURS_CLIENT_PID = String(process.ppid);
|
|
90
|
+
const child = spawn(process.execPath, [cliPath, command, ...forwarded], { stdio: 'inherit', env });
|
|
91
|
+
relayChild(child, 'the proxy');
|
|
65
92
|
}
|
|
66
93
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
// Only forward the pid when it is > 1; on macOS, orphaned processes are reparented
|
|
71
|
-
// to launchd (pid 1) and pidAlive(1) is always true — skip it in that case.
|
|
72
|
-
const env = { ...process.env };
|
|
73
|
-
if (!env.OURS_CLIENT_PID && process.ppid > 1) env.OURS_CLIENT_PID = String(process.ppid);
|
|
74
|
-
const sessionEnd = process.argv[2] === 'session-end';
|
|
75
|
-
const command = sessionEnd ? 'session-end' : 'proxy';
|
|
76
|
-
const forwarded = process.argv.slice(sessionEnd ? 3 : 2);
|
|
77
|
-
const child = spawn(
|
|
78
|
-
process.execPath,
|
|
79
|
-
[cliPath, command, ...forwarded],
|
|
80
|
-
{ stdio: 'inherit', env },
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
child.on('error', (err) => {
|
|
84
|
-
process.stderr.write(`ours: failed to launch the proxy: ${String(err)}\n`);
|
|
85
|
-
process.exit(1);
|
|
86
|
-
});
|
|
87
|
-
child.on('exit', (code, signal) => {
|
|
88
|
-
if (signal) process.kill(process.pid, signal);
|
|
89
|
-
else process.exit(code ?? 0);
|
|
94
|
+
main().catch((error) => {
|
|
95
|
+
process.stderr.write(`ours: ${error.message}\n`);
|
|
96
|
+
process.exitCode = 1;
|
|
90
97
|
});
|