@ours.network/claude-code 1.2.0-nightly.1 → 1.2.0-nightly.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/claude-code",
3
- "version": "1.2.0-nightly.1",
3
+ "version": "1.2.0-nightly.11",
4
4
  "description": "Claude Code plugin for ours \u2014 secure agent-to-agent messaging over ADAPT with native host network transport.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",
@@ -45,9 +45,10 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@modelcontextprotocol/sdk": "^1.29.0",
48
- "@ours.network/sdk": "3.8.1-nightly.3",
48
+ "@ours.network/sdk": "3.8.1-nightly.16",
49
49
  "ws": "^8.21.0",
50
- "zod": "^3.25.76"
50
+ "zod": "^3.25.76",
51
+ "@ours.network/mcp": "1.2.0-nightly.11"
51
52
  },
52
53
  "devDependencies": {
53
54
  "@types/node": "^20.14.0",
@@ -93,12 +93,12 @@ existing file.
93
93
  **control-plane monitoring proxy is not available in this release** — there is no tool
94
94
  to call. See "Control plane" below.
95
95
 
96
- - **Configuration.** Port, state dir, broker, and GC interval are configurable
97
- (env > `~/.ours/config.json` > default; port default 3050). Daemon config is
98
- **host-wide and shared** — changing it restarts the daemon and drops every
99
- session's binding. Never self-configure on your own initiative: surface the
100
- need, explain the impact, and act only on the user's explicit yes. Details:
101
- `references/configuration.md`.
96
+ - **Configuration.** Every client selects the HTTP gateway through
97
+ `~/.ours-client/profile.json` (or the whole-profile `OURS_CONFIG` override).
98
+ `serverUrl` is the base URL; daemon and Cowork use `/daemon` and `/cowork`
99
+ beneath it. Import the issued credential with `ours-install client`.
100
+ Local daemon ports, state directories, and Unix sockets are not client routes.
101
+ Missing or invalid configuration fails closed. See `references/configuration.md`.
102
102
 
103
103
  ## ours-fleet — persistent harness roles
104
104
 
@@ -404,9 +404,8 @@ release, and do not improvise a substitute. Per-identity wake-on-mail is a **dif
404
404
  feature and still works; it is described above.
405
405
  ## Notes
406
406
 
407
- - Identities and their state (contacts, history, keys) persist under the daemon's state dir
408
- (`OURS_STATE_DIR`, default `~/.ours`) and survive restarts. The daemon is a singleton
409
- shared by all your Claude Code sessions.
407
+ - Identity state is stored on the selected server and survives client restarts.
408
+ Clients access it through the gateway; no server state directory is required locally.
410
409
  - Inbound messages from unknown (non-contact) senders are rejected — only peers added via an
411
410
  invite handshake, same-host agents under the same Human identity, or registrar-verified
412
411
  local-contact-book introductions can reach you.
@@ -414,4 +413,4 @@ feature and still works; it is described above.
414
413
  as immutable content-addressed blobs. The wake path stays body-free:
415
414
  `notifications.log` and `unread.json` contain metadata only, and SessionStart reads no bodies.
416
415
  - This is a **Claude-Code-specific seam** (Monitor + SessionStart hook + the `watch`
417
- command). Other clients wire the same `notifications.log` signal to their own wake mechanism.
416
+ command). Clients consume gateway notification APIs for their own wake mechanisms.
@@ -1,26 +1,24 @@
1
- # ours daemon configuration
1
+ # Shared gateway client configuration
2
2
 
3
- ours-mcp is a client of one already-running shared daemon. The operator CLI owns
4
- configuration and lifecycle:
3
+ Fleet, ours CLI, MCP and harness hooks select one server through
4
+ `~/.ours-client/profile.json`. `serverUrl` names the HTTP(S) gateway, with
5
+ optional base path. Daemon operations use `/daemon`; Cowork uses `/cowork`.
6
+ The profile also pins `expectedInstanceId` and an absolute `credentialPath`.
7
+ An optional `endpoint` must equal `serverUrl + "/daemon"`.
5
8
 
6
- ```sh
7
- ours config show --json
8
- ours config setup --port 3050 --state-dir "$HOME/.ours"
9
- ours daemon start
10
- ours daemon status --json
11
- ```
9
+ Import a prepared private profile with `ours-install client --config /absolute/profile.json`.
10
+ Use `ours config show --json` to inspect connection metadata without reading the token.
11
+ Keep the profile directory mode 0700 and files mode 0600. `OURS_CONFIG` may
12
+ select the same whole profile for all clients. Remove legacy `OURS_PORT`,
13
+ `OURS_STATE_DIR`, `OURS_API_TOKEN` and `OURS_DAEMON_ID` overrides.
12
14
 
13
- The MCP adapter uses the published SDK's coherent selection. The wholly default
14
- selection is port 3050 with state directory `~/.ours`. For another daemon, set
15
- `OURS_CONFIG`, or set matching `OURS_PORT` and `OURS_STATE_DIR`. A token or
16
- endpoint selection must be paired with its state directory. The daemon's
17
- `/state-dir` response is verified before credentials are sent.
15
+ Missing profiles, rejected credentials and unavailable gateways fail closed.
16
+ Clients never read daemon state, start a daemon, or try a local port/socket.
17
+ Hooks use the same gateway and return a harmless no-op on failure. Their
18
+ application-local identity/session files do not select another server.
18
19
 
19
- The adapter never starts a daemon and never falls back to an embedded one.
20
- `OURS_INSTANCE`, `--application`, and old ours-mcp daemon variables are errors.
21
- Do not add a duplicate MCP registration; the managed command is simply
22
- `ours-mcp proxy`.
23
-
24
- Changing daemon configuration or restarting the shared daemon affects every
25
- connected application. Explain that blast radius and obtain the user's consent
26
- before making operator-level changes.
20
+ For full-stack setup, a custom public port (such as 4050), systemd propagation
21
+ and migration, see the [gateway setup guide](https://github.com/adapt-toolkit/ours-network/blob/prerelease/packages/installer/GATEWAY_SETUP.md).
22
+ The coordinated gateway changes require approved matching package releases;
23
+ current npm nightly is not automatically evidence that these changes are installed.
24
+ Server administration uses `ours-install server` on the server host.
@@ -1,147 +0,0 @@
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
- }
@@ -1,2 +0,0 @@
1
- export function containerInvocation(command: string, args?: string[], env?: NodeJS.ProcessEnv): { command: string; args: string[]; env: NodeJS.ProcessEnv } | null;
2
- export function readContainerJson(command: string, env?: NodeJS.ProcessEnv): unknown;
@@ -1,40 +0,0 @@
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
- }