@hediet/linkrpc-cli 0.0.3-next.20261008.2 → 0.0.3-next.20261021.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.
@@ -1,7 +1,7 @@
1
- import { A as fetchSchema$1, B as resolveTargetEndpoint, D as requestTopologyAccess, E as requestReflectionAccess, F as formatPrincipalSource, G as validateValueAgainstSchema, H as describeObjectParams, I as parsePrincipalSpec, L as connect, M as walkHub, O as setupSigning, P as logoutCliIdentity, R as connectViaRootOverlay, U as describeSchema, V as resolvedEndpointToConfig, W as explainValidation, X as mcpForwardInterface, Y as MethodRefWithOptHash, _ as parseConnectionLimit, a as resolveInvocationContext, c as ContextStore, d as getBrokerStatus, f as readBrokerNotifications, g as withStaticHubReflection, h as tunnelCommand, i as mutationReference, j as findMethodInSchema, k as fetchDefaults, l as formatContextReference, m as ConnectionBroker, n as loadStaticHubSchema, o as resolveInvocationEndpoint, p as stopConnectionBroker, q as mergeParams, r as resolveStaticHubSchemaSource, s as validationDefault, t as completeForLine, u as disconnectBroker, v as parseConnectionLimitMilliseconds, x as resolveCliInvocation, y as parseDuration, z as connectViaTransport } from "./runComplete-DvUneaDU.js";
2
- import { a as derived, i as autorun, r as observableValue } from "./dist-BOcSU4-O.js";
3
- import { ErrorCode, INTERFACE_TEMPLATES_EXTENSION, LinkRpcConnection, RpcError, computeInterfaceHash, connectTransports, createSchemaToZod, exportStaticHubSchema, generateTsContract, generateTsInterface, interfaceTemplateArgumentsEquivalent, interfaceTemplateMemberName, isAssignable, jcsCanonicalize } from "@hediet/linkrpc";
4
- import { WebSocketTransport, connectNdjson, formatEndpointUri, isHubEndpoint, openWebSocket, parseEndpointUri, runInitializeHandshake, traceMessageTransport as traceMessageTransport$1 } from "@hediet/linkrpc/node";
1
+ import { $ as setupSigning, B as tunnelCommand, F as disconnectBroker, H as parseConnectionLimit, I as getBrokerStatus, K as resolveCliInvocation, L as readBrokerNotifications, M as readEndpointDiscovery, N as ContextStore, P as formatContextReference, Q as requestTopologyAccess, R as stopConnectionBroker, U as parseConnectionLimitMilliseconds, V as withStaticHubReflection, W as parseDuration, Z as requestReflectionAccess, a as resolveInvocationEndpoint, at as logoutCliIdentity, bt as MethodRefWithOptHash, c as resolveLocalEndpoint, ct as connect, dt as resolveTargetEndpoint, et as fetchDefaults, ft as resolvedEndpointToConfig, gt as validateValueAgainstSchema, ht as explainValidation, i as resolveInvocationContext, lt as connectViaRootOverlay, m as managedEndpointsInterface, mt as describeSchema, n as loadStaticHubSchema, nt as findMethodInSchema, o as resolveSigning, ot as formatPrincipalSource, pt as describeObjectParams, r as resolveStaticHubSchemaSource, rt as walkHub, s as validationDefault, st as parsePrincipalSpec, t as completeForLine, tt as fetchSchema$1, ut as connectViaTransport, vt as mergeParams, xt as mcpForwardInterface, y as HubHome, yt as parseParamOverride, z as ConnectionBroker } from "./runComplete-B3b-UhnA.js";
2
+ import { ErrorCode, INTERFACE_TEMPLATES_EXTENSION, LinkRpcConnection, RpcError, computeInterfaceHash, connectTransports, createSchemaToZod, directoryInterface, exportStaticHubSchema, generateTsContract, generateTsInterface, interfaceTemplateArgumentsEquivalent, interfaceTemplateMemberName, interfaceTemplatesEqual, isAssignable, jcsCanonicalize, matchParams, schemaToZod, validateInterfaceTemplates } from "@hediet/linkrpc";
3
+ import { z } from "zod";
4
+ import { WebSocketTransport, connectNdjson, connectToHub, formatEndpointUri, isHubEndpoint, openWebSocket, parseEndpointUri, runInitializeHandshake, traceMessageTransport as traceMessageTransport$1 } from "@hediet/linkrpc/node";
5
5
  import { SocketServer } from "@hediet/linkrpc-hub/hub/server/node";
6
6
  import { randomBytes, randomUUID } from "node:crypto";
7
7
  import * as fs from "node:fs";
@@ -11,241 +11,1222 @@ import { spawnCommand } from "@hediet/linkrpc-hub/spawn";
11
11
  import { tapTransport } from "@hediet/linkrpc-hub/hub/server/transit";
12
12
  import * as net from "node:net";
13
13
  import { mkdir, open, readFile, writeFile } from "node:fs/promises";
14
- import { DEFAULT_WALK_DEPTH, HubDirectoryExplorer, fetchSchema, hubGrantedServiceIdInterface } from "@hediet/linkrpc/hub/common";
15
- import { Command, InvalidArgumentError } from "commander";
14
+ import { DEFAULT_WALK_DEPTH, HubDirectoryExplorer, fetchSchema, findCoveringCapabilities, hubGrantedServiceIdInterface } from "@hediet/linkrpc/hub/common";
15
+ import { CONSOLE_INTERFACE_TAG, ConsoleTemplate, consoleAttachResultSchema, consoleServerMessageSchema, decodeConsoleData, encodeConsoleData } from "@hediet/linkrpc-infra/console";
16
+ import { Command, InvalidArgumentError, Option } from "commander";
17
+ import * as readline from "node:readline";
18
+ import { createInterface, emitKeypressEvents } from "node:readline";
19
+ import { parse, unknown } from "zod/mini";
16
20
  import { bootstrapApprovalCapability, createHubApprovalClient } from "@hediet/linkrpc-infra/approval";
17
21
  import { connectionTokenBinderInterface } from "@hediet/linkrpc-hub/hub/server/connection-token-binder";
18
22
  import { spawn } from "node:child_process";
19
23
  import { TopologyClient, TopologyNetworkClient, TrafficClient } from "@hediet/linkrpc-infra/inspection";
20
- import { createInterface } from "node:readline/promises";
24
+ import { createInterface as createInterface$1 } from "node:readline/promises";
21
25
  import { stdin, stdout } from "node:process";
22
26
  import { TrafficFlowAggregator, createTerminalConsentPrompt, formatFlowSummary } from "@hediet/linkrpc-hub";
23
27
  import { loadHubConfig, printHubConfigSchema } from "@hediet/linkrpc-hub/config-file";
24
- import * as readline from "node:readline";
25
- import { emitKeypressEvents } from "node:readline";
26
28
  import { connectJsonRpcTransports, connectRawJsonRpcTransport, createNdjsonJsonRpcTransport, jsonRpcConnectionInterface } from "@hediet/linkrpc-infra/json-rpc";
27
29
  import { GRAPH_INTERFACE_TAG, IMMUTABLE_GRAPH_INTERFACE_ID, ROOT_WATCH_INTERFACE_ID, evaluateGraphPresentation, graphPresentationInterface, graphPresentationSchema, isGraphRef, standardGraphRuntimeOptions, validateGraphInterfaceSchema } from "@hediet/linkrpc-infra/graph";
28
30
  import React from "react";
29
31
  import { Box, Text, measureElement, render } from "ink";
30
32
  import { jsx, jsxs } from "react/jsx-runtime";
31
- import { parse } from "zod/mini";
32
33
  import { applyJsonDocumentEdits, logDocumentSchema, logStreamEventSchema, loggingInterface } from "@hediet/linkrpc-infra";
33
34
  import { topologyInterface, zTopologyGraph } from "@hediet/linkrpc/inspection";
34
35
  import { renderMermaidASCII } from "beautiful-mermaid";
35
- //#region src/output.ts
36
- /**
37
- * Read params either from a `--params <json>` argument (treated as inline
38
- * JSON) or `--params -` (read from stdin). `undefined` if neither was given.
39
- */
40
- function readParamsArg(value) {
41
- if (value === void 0) return void 0;
42
- const trimmed = (value === "-" ? readFileSync(0, "utf8") : value).trim();
43
- if (trimmed.length === 0) return void 0;
44
- try {
45
- return JSON.parse(trimmed);
46
- } catch (e) {
47
- throw new Error(`--params: invalid JSON (${e.message})`);
36
+ //#region src/commands/serviceConsoleInput.ts
37
+ var ServiceConsoleInput = class {
38
+ _input;
39
+ _send;
40
+ _interrupt;
41
+ _close;
42
+ _readline;
43
+ _wasRaw;
44
+ constructor(_input, output, _send, _interrupt, _close) {
45
+ this._input = _input;
46
+ this._send = _send;
47
+ this._interrupt = _interrupt;
48
+ this._close = _close;
49
+ this._wasRaw = _input.isRaw ?? false;
50
+ if (_input.isTTY) {
51
+ this._readline = createInterface({
52
+ input: _input,
53
+ output,
54
+ terminal: true,
55
+ prompt: "",
56
+ historySize: 0
57
+ });
58
+ this._readline.on("line", this._onLine);
59
+ this._readline.on("SIGINT", _interrupt);
60
+ this._readline.on("close", _close);
61
+ _input.on("keypress", this._onKeypress);
62
+ } else {
63
+ _input.on("data", this._onData);
64
+ _input.resume();
65
+ }
48
66
  }
49
- }
50
- function formatJson(v) {
51
- if (v === void 0) return "undefined";
52
- return JSON.stringify(v, null, 2);
53
- }
67
+ dispose() {
68
+ if (this._readline) {
69
+ this._readline.removeListener("line", this._onLine);
70
+ this._readline.removeListener("SIGINT", this._interrupt);
71
+ this._readline.removeListener("close", this._close);
72
+ this._readline.close();
73
+ this._input.removeListener("keypress", this._onKeypress);
74
+ this._input.setRawMode?.(this._wasRaw);
75
+ } else this._input.removeListener("data", this._onData);
76
+ this._input.pause();
77
+ this._input.unref?.();
78
+ }
79
+ _onLine = (line) => {
80
+ this._send(line + "\n");
81
+ };
82
+ _onKeypress = (_text, key) => {
83
+ if (key.sequence === "") this._close();
84
+ };
85
+ _onData = (data) => {
86
+ if (data.includes(29)) this._close();
87
+ else this._send(data.toString("utf8"));
88
+ };
89
+ };
54
90
  //#endregion
55
- //#region src/suggest.ts
56
- const _SYSTEM_PREFIX = "hubrpc.";
57
- function _isSystemInterface(id) {
58
- return id.startsWith(_SYSTEM_PREFIX);
91
+ //#region src/commands/consoleSession.ts
92
+ /**
93
+ * Drive a `linkrpc.console` attachment (or a stream embedding its protocol):
94
+ * render output, and configure local input from the console's mode. TTY
95
+ * consoles get raw keystrokes, so detaching uses a key sequence (see
96
+ * {@link DetachKeys}); pipe consoles get local line editing, and Ctrl+D on an
97
+ * empty line detaches.
98
+ */
99
+ async function runConsoleSession(start, options, onExtra) {
100
+ return new ConsoleSession(options, onExtra).run(start);
59
101
  }
60
- function _isUserInterface(id) {
61
- return !_isSystemInterface(id);
102
+ /** Local terminal size, sent so that TTY consoles match this terminal. */
103
+ function localTerminalSize() {
104
+ const { columns, rows } = process.stdout;
105
+ return process.stdout.isTTY && columns > 0 && rows > 0 ? {
106
+ cols: columns,
107
+ rows
108
+ } : void 0;
62
109
  }
63
110
  /**
64
- * Indexed view of the bus's directory + per-interface methods. Constructed
65
- * directly from a list of entries (tests) or by walking a live channel
66
- * (`HubSnapshot.load`). Read-only after construction.
111
+ * A detach key sequence in Docker's `--detach-keys` syntax: comma-separated
112
+ * keys, each a single character or `ctrl-<key>` with `<key>` in `a`-`z`, `@`,
113
+ * `[`, `\`, `]`, `^`, or `_`. For example `ctrl-b,d` or `ctrl-p,ctrl-q`.
67
114
  */
68
- var HubSnapshot = class HubSnapshot {
69
- entries;
70
- _byService = /* @__PURE__ */ new Map();
71
- _byInterface = /* @__PURE__ */ new Map();
72
- _byMethodName = /* @__PURE__ */ new Map();
73
- constructor(entries) {
74
- this.entries = entries;
75
- for (const e of entries) {
76
- let byIface = this._byService.get(e.serviceId);
77
- if (!byIface) {
78
- byIface = /* @__PURE__ */ new Map();
79
- this._byService.set(e.serviceId, byIface);
80
- }
81
- byIface.set(e.interfaceId, e);
82
- const hosts = this._byInterface.get(e.interfaceId) ?? [];
83
- if (!hosts.includes(e.serviceId)) hosts.push(e.serviceId);
84
- this._byInterface.set(e.interfaceId, hosts);
85
- for (const m of e.methods) {
86
- const locs = this._byMethodName.get(m) ?? [];
87
- locs.push({
88
- serviceId: e.serviceId,
89
- interfaceId: e.interfaceId
90
- });
91
- this._byMethodName.set(m, locs);
92
- }
93
- }
94
- }
95
- /** All serviceIds present on the bus, in encounter order ("" = root). */
96
- services() {
97
- return [...this._byService.keys()];
98
- }
99
- /** All distinct interface ids (system + user), in encounter order. */
100
- interfaces() {
101
- return [...this._byInterface.keys()];
102
- }
103
- interfacesOnService(serviceId) {
104
- const m = this._byService.get(serviceId);
105
- return m ? [...m.keys()] : [];
106
- }
107
- servicesHostingInterface(interfaceId) {
108
- return this._byInterface.get(interfaceId) ?? [];
109
- }
110
- methodsOn(serviceId, interfaceId) {
111
- return this._byService.get(serviceId)?.get(interfaceId)?.methods ?? [];
115
+ var DetachKeys = class DetachKeys {
116
+ spec;
117
+ bytes;
118
+ constructor(spec, bytes) {
119
+ this.spec = spec;
120
+ this.bytes = bytes;
121
+ }
122
+ static parse(spec) {
123
+ const bytes = spec.split(",").map((part) => {
124
+ const key = part.trim();
125
+ const ctrl = /^ctrl-(.)$/i.exec(key);
126
+ if (ctrl) {
127
+ const index = "@abcdefghijklmnopqrstuvwxyz[\\]^_".indexOf(ctrl[1].toLowerCase());
128
+ if (index >= 0) return index;
129
+ } else if (key.length === 1 && key.charCodeAt(0) >= 32 && key.charCodeAt(0) < 127) return key.charCodeAt(0);
130
+ throw new Error(`Invalid detach key "${key}" in "${spec}": use a character or ctrl-<a-z, @, [, \\, ], ^, _>, separated by commas`);
131
+ });
132
+ if (bytes.length === 0) throw new Error("The detach key sequence is empty");
133
+ return new DetachKeys(spec, Uint8Array.from(bytes));
112
134
  }
113
- locationsOfMethod(name) {
114
- return this._byMethodName.get(name) ?? [];
135
+ /** Human-readable form, e.g. `Ctrl+B, then d`. */
136
+ get label() {
137
+ return [...this.bytes].map((byte) => byte < 32 ? `Ctrl+${String.fromCharCode(byte + 64)}` : String.fromCharCode(byte)).join(", then ");
115
138
  }
116
- /**
117
- * Walk the live bus once and fetch the schema for every (service, interface)
118
- * pair in parallel. Failures on individual schema fetches are absorbed —
119
- * the affected entry just ends up with `methods: []`, so it still
120
- * participates in service/interface-level suggestions.
121
- */
122
- static async load(channel) {
123
- const listings = await walkHub(channel);
124
- const entries = await Promise.all(listings.map(async (l) => {
125
- try {
126
- const schema = await fetchSchema$1(channel, l.interfaceId, l.hash, l.serviceId || void 0);
127
- return {
128
- serviceId: l.serviceId,
129
- interfaceId: l.interfaceId,
130
- hash: l.hash,
131
- methods: Object.keys(schema.methods)
132
- };
133
- } catch {
139
+ };
140
+ /** Ctrl+B, then d (tmux's binding): unlike Docker's Ctrl+P, Ctrl+Q, it reaches programs in VS Code's terminal. */
141
+ const DEFAULT_DETACH_KEYS = DetachKeys.parse("ctrl-b,d");
142
+ /**
143
+ * Recognizes a detach sequence in raw keyboard input. Bytes of a partial match
144
+ * are held back, and forwarded unchanged when the sequence does not complete.
145
+ */
146
+ var DetachKeyFilter = class {
147
+ _keys;
148
+ _matched = 0;
149
+ constructor(_keys = DEFAULT_DETACH_KEYS) {
150
+ this._keys = _keys;
151
+ }
152
+ push(chunk) {
153
+ const sequence = this._keys.bytes;
154
+ const forward = [];
155
+ for (const byte of chunk) {
156
+ const pending = [...sequence.subarray(0, this._matched), byte];
157
+ let start = 0;
158
+ while (start < pending.length && !pending.slice(start).every((value, index) => value === sequence[index])) start++;
159
+ forward.push(...pending.slice(0, start));
160
+ this._matched = pending.length - start;
161
+ if (this._matched === sequence.length) {
162
+ this._matched = 0;
134
163
  return {
135
- serviceId: l.serviceId,
136
- interfaceId: l.interfaceId,
137
- hash: l.hash,
138
- methods: []
164
+ forward: Uint8Array.from(forward),
165
+ detach: true
139
166
  };
140
167
  }
141
- }));
142
- return new HubSnapshot(entries);
168
+ }
169
+ return {
170
+ forward: Uint8Array.from(forward),
171
+ detach: false
172
+ };
143
173
  }
144
174
  };
145
- /**
146
- * Compose a multi-line "Did you mean…" message tailored to the failed ref.
147
- * The returned string has no trailing newline and no `linkrpc:` prefix (the
148
- * outer error path adds those).
149
- */
150
- function formatSuggestion(input) {
151
- const ctx = {
152
- ref: input.ref,
153
- snapshot: input.snapshot,
154
- verb: input.verb,
155
- tool: input.tool ?? "hub"
156
- };
157
- if (ctx.ref.serviceId !== void 0 && ctx.ref.interfaceId !== void 0) return _suggestForm3(ctx);
158
- if (ctx.ref.interfaceId !== void 0) return _suggestForm2(ctx);
159
- return _suggestForm1(ctx);
160
- }
161
- function _qualify(ctx, s, i, m) {
162
- return `${ctx.tool} ${ctx.verb} ${s}::${i}::${m}`;
163
- }
164
- function _suggestForm1(ctx) {
165
- const { ref, snapshot } = ctx;
166
- const token = ref.methodName;
167
- if (token !== "" && snapshot.services().includes(token)) {
168
- const samples = _sampleCallsForService(ctx, token, 3);
169
- const lines = [`"${token}" is a serviceId, not a method.`];
170
- lines.push(" See its interfaces:");
171
- lines.push(` ${ctx.tool} ls --service ${token}`);
172
- if (samples.length > 0) {
173
- lines.push(" Or call one:");
174
- for (const s of samples) lines.push(` ${s}`);
175
+ const ALTERNATE_SCREEN = /\x1b\[\?(?:1049|1047|47)([hl])/g;
176
+ /** Undo terminal modes a program may have enabled: mouse, bracketed paste, focus, win32-input, hidden cursor. */
177
+ const TERMINAL_RESET = "\x1B[0m\x1B[?25h\x1B[?1000l\x1B[?1002l\x1B[?1003l\x1B[?1006l\x1B[?2004l\x1B[?1004l\x1B[?9001l";
178
+ var ConsoleSession = class {
179
+ _options;
180
+ _onExtra;
181
+ _call;
182
+ _detachKeys;
183
+ _mode;
184
+ _signals = [];
185
+ _input;
186
+ _raw = false;
187
+ _alternateScreen = false;
188
+ _closedLocally = false;
189
+ constructor(_options, _onExtra) {
190
+ this._options = _options;
191
+ this._onExtra = _onExtra;
192
+ this._detachKeys = _options.detachKeys ?? DEFAULT_DETACH_KEYS;
193
+ }
194
+ async run(start) {
195
+ const onSignal = () => this._interrupt();
196
+ const onTerminate = () => this.close();
197
+ process.on("SIGINT", onSignal);
198
+ process.on("SIGTERM", onTerminate);
199
+ this._options.signal?.addEventListener("abort", onTerminate);
200
+ try {
201
+ this._call = start((message) => this._onMessage(message));
202
+ if (this._closedLocally || this._options.signal?.aborted) this.close();
203
+ let exit = null;
204
+ try {
205
+ exit = (await this._call).exit;
206
+ } catch (error) {
207
+ if (!this._closedLocally) throw error;
208
+ }
209
+ return {
210
+ exit,
211
+ closedLocally: this._closedLocally
212
+ };
213
+ } finally {
214
+ process.removeListener("SIGINT", onSignal);
215
+ process.removeListener("SIGTERM", onTerminate);
216
+ this._options.signal?.removeEventListener("abort", onTerminate);
217
+ this._input?.dispose();
218
+ if (this._raw) process.stdout.write(TERMINAL_RESET + (this._alternateScreen ? "\x1B[?1049l" : "") + "\r\n");
219
+ if (this._closedLocally && this._options.interactive && !this._options.owner && this._options.reattachCommand) process.stderr.write(`Detached. Reattach: ${this._options.reattachCommand}\n`);
175
220
  }
176
- return lines.join("\n");
177
221
  }
178
- if (snapshot.interfaces().includes(token) && _isUserInterface(token)) {
179
- const hosts = snapshot.servicesHostingInterface(token).filter((s) => s !== "");
180
- const samples = _sampleCallsForInterface(ctx, token, 3);
181
- const lines = [`"${token}" is an interface, not a method. It's hosted on services: ${hosts.join(", ")}.`];
182
- if (samples.length > 0) {
183
- lines.push(" Try a call:");
184
- for (const s of samples) lines.push(` ${s}`);
222
+ close() {
223
+ this._closedLocally = true;
224
+ try {
225
+ Promise.resolve(this._call?.cancel()).catch(() => {});
226
+ } catch {}
227
+ }
228
+ _onMessage(message) {
229
+ const console = message;
230
+ switch (console.t) {
231
+ case "console":
232
+ this._mode = console.mode;
233
+ this._signals = console.signals;
234
+ this._startInput();
235
+ return;
236
+ case "screen": {
237
+ const screen = decodeConsoleData(console.data);
238
+ if (screen.length > 0 && process.stdout.isTTY) process.stdout.write("\x1B[H\x1B[2J");
239
+ this._write(process.stdout, screen);
240
+ return;
241
+ }
242
+ case "output":
243
+ this._write(console.stream === "stderr" ? process.stderr : process.stdout, decodeConsoleData(console.data));
244
+ return;
245
+ case "notice":
246
+ process.stderr.write(this._mode === "tty" ? `\r\n[console] ${console.text}\r\n` : `${console.text}\n`);
247
+ return;
248
+ case "live":
249
+ if (this._options.untilLive) this.close();
250
+ return;
251
+ case "started":
252
+ case "exit":
253
+ case "resized": return;
185
254
  }
186
- return lines.join("\n");
255
+ this._onExtra?.(message);
256
+ }
257
+ _write(stream, data) {
258
+ if (this._mode === "tty") for (const match of new TextDecoder().decode(data).matchAll(ALTERNATE_SCREEN)) this._alternateScreen = match[1] === "h";
259
+ stream.write(data);
260
+ }
261
+ _startInput() {
262
+ if (!this._options.interactive || this._input) return;
263
+ const owner = this._options.owner;
264
+ const reattach = this._options.reattachCommand;
265
+ if (this._mode === "tty") process.stderr.write(owner ? `Foreground (TTY). Keys go to the program; ${this._detachKeys.label} stops and removes the service.\n` : `Attached (TTY). Keys go to the program; ${this._detachKeys.label} detaches.\n`);
266
+ else {
267
+ const interrupt = owner ? "Ctrl+C or " : this._signals.includes("SIGINT") ? "Ctrl+C interrupts; " : "";
268
+ process.stderr.write(owner ? `Foreground (pipe console). ${interrupt}Ctrl+D on an empty line stops and removes the service.\n` : `Attached (pipe console). ${interrupt}Ctrl+D on an empty line detaches.\n`);
269
+ }
270
+ if (reattach) process.stderr.write(`${owner ? "Attach from another terminal while running" : "Reattach"}: ${reattach}\n`);
271
+ this._input = this._mode === "tty" ? this._startTerminalInput() : this._startLineInput();
272
+ }
273
+ _startTerminalInput() {
274
+ const stdin = process.stdin;
275
+ const filter = stdin.isTTY ? new DetachKeyFilter(this._detachKeys) : void 0;
276
+ const wasRaw = stdin.isRaw;
277
+ if (stdin.isTTY) {
278
+ stdin.setRawMode(true);
279
+ this._raw = true;
280
+ }
281
+ const onData = (chunk) => {
282
+ const { forward, detach } = filter ? filter.push(chunk) : {
283
+ forward: chunk,
284
+ detach: false
285
+ };
286
+ if (forward.length > 0) this._send({
287
+ t: "input",
288
+ data: encodeConsoleData(forward)
289
+ });
290
+ if (detach) this.close();
291
+ };
292
+ const onResize = () => {
293
+ const size = localTerminalSize();
294
+ if (size) this._send({
295
+ t: "resize",
296
+ ...size
297
+ });
298
+ };
299
+ stdin.on("data", onData);
300
+ stdin.resume();
301
+ process.stdout.on("resize", onResize);
302
+ return { dispose: () => {
303
+ stdin.removeListener("data", onData);
304
+ process.stdout.removeListener("resize", onResize);
305
+ if (stdin.isTTY) stdin.setRawMode(wasRaw);
306
+ stdin.pause();
307
+ stdin.unref?.();
308
+ } };
309
+ }
310
+ _startLineInput() {
311
+ return new ServiceConsoleInput(process.stdin, process.stderr, (data) => this._send({
312
+ t: "input",
313
+ data: encodeConsoleData(new TextEncoder().encode(data))
314
+ }), () => this._interrupt(), () => this.close());
315
+ }
316
+ _interrupt() {
317
+ if (this._options.owner || !this._options.interactive) this.close();
318
+ else if (this._mode === "tty") this._send({
319
+ t: "input",
320
+ data: encodeConsoleData(Uint8Array.of(3))
321
+ });
322
+ else if (this._signals.includes("SIGINT")) this._send({
323
+ t: "signal",
324
+ signal: "SIGINT"
325
+ });
326
+ else process.stderr.write("Interrupting is not supported for pipe processes on this host; run with -t for a terminal. Ctrl+D on an empty line detaches.\n");
187
327
  }
188
- const locs = snapshot.locationsOfMethod(token).filter((l) => _isUserInterface(l.interfaceId));
189
- if (locs.length > 0) {
190
- const lines = [`"${token}" is not callable without an interface (no preset is set).`];
191
- lines.push(" Did you mean:");
192
- for (const l of locs.slice(0, 5)) lines.push(` ${_qualify(ctx, l.serviceId, l.interfaceId, token)}`);
193
- return lines.join("\n");
328
+ _send(message) {
329
+ try {
330
+ Promise.resolve(this._call?.send(message)).catch(() => {});
331
+ } catch {}
194
332
  }
195
- return _suggestForm1Fuzzy(ctx);
333
+ };
334
+ //#endregion
335
+ //#region src/commands/services.ts
336
+ function withDetachKeys(host) {
337
+ const detachKeys = host.detachKeys();
338
+ return detachKeys ? { detachKeys } : {};
196
339
  }
197
- function _suggestForm1Fuzzy(ctx) {
198
- const { snapshot } = ctx;
199
- const token = ctx.ref.methodName;
200
- const methodHits = _rankAllMethods(snapshot, token, 3);
201
- const ifaceHits = _rankAllInterfaces(snapshot, token, 3);
202
- const svcHits = _rankServices(snapshot, token, 3);
203
- const lines = [`"${token}" matches no known service, interface, or method.`];
204
- if (methodHits.length > 0) {
205
- lines.push(" Closest methods:");
206
- for (const h of methodHits) lines.push(` ${_qualify(ctx, h.serviceId, h.interfaceId, h.name)}`);
207
- }
208
- if (ifaceHits.length > 0) {
209
- lines.push(" Closest interfaces:");
210
- for (const id of ifaceHits) {
211
- const sampleSvc = snapshot.servicesHostingInterface(id).find((s) => s !== "") ?? snapshot.servicesHostingInterface(id)[0];
212
- lines.push(` ${id} (try ${ctx.tool} ${ctx.verb} ${sampleSvc ?? ""}::${id}::<method>)`);
340
+ function serviceAttachCommand(options, platform = process.platform) {
341
+ const args = [
342
+ "hub",
343
+ "services",
344
+ "attach",
345
+ "--id",
346
+ options.id,
347
+ "--context",
348
+ options.context,
349
+ "--namespace",
350
+ options.namespace,
351
+ options.useEnv ? "--use-env" : "--no-use-env"
352
+ ];
353
+ for (const key of Object.keys(options.overrides)) {
354
+ if (key === "namespace") continue;
355
+ const value = options.overrides[key];
356
+ if (value === void 0) continue;
357
+ const flag = "--" + key.replace(/[A-Z]/g, (letter) => "-" + letter.toLowerCase());
358
+ if (key === "endpointCmdEnv" && typeof value === "object") for (const name of Object.keys(value)) args.push(flag, `${name}=<same value>`);
359
+ else if (typeof value === "boolean") {
360
+ if (value) args.push(flag);
361
+ else if (key === "sign") args.push("--no-sign");
362
+ } else if (typeof value === "string" || typeof value === "number") args.push(flag, key === "endpointToken" ? "<same endpoint token>" : String(value).replace(/([?&]token=)[^&]+/g, "$1<same token>"));
363
+ }
364
+ return args.map((arg) => /^[A-Za-z0-9_./:-]+$/.test(arg) ? arg : "'" + arg.replace(/'/g, platform === "win32" ? "''" : "'\\''") + "'").join(" ");
365
+ }
366
+ const requiredInterfaces = [{
367
+ id: managedEndpointsInterface.info.id,
368
+ hash: managedEndpointsInterface.schemaHash
369
+ }];
370
+ async function checkDaemonInterfaces(connection, serviceId) {
371
+ const recovery = serviceId === void 0 ? "Run `hub services restart` to stop the local daemon via RPC and start this CLI's bundled daemon." : "Update/restart the daemon on its execution host. To replace the default local daemon, run `hub --context :empty --no-use-env services restart`.";
372
+ for (const required of requiredInterfaces) {
373
+ let cursor;
374
+ let supported = false;
375
+ const hashes = /* @__PURE__ */ new Set();
376
+ try {
377
+ do {
378
+ const page = await connection.get(directoryInterface, { serviceId }).list({
379
+ interfaceId: required.id,
380
+ serviceId: serviceId ?? "",
381
+ cursor
382
+ });
383
+ for (const item of page.items) {
384
+ if (item.interfaceId !== required.id || item.serviceId !== (serviceId ?? "")) continue;
385
+ hashes.add(item.interfaceHash);
386
+ supported ||= item.interfaceHash === required.hash;
387
+ }
388
+ if (page.truncated) throw new Error("Daemon interface discovery was truncated; compatibility could not be checked.");
389
+ cursor = page.nextCursor;
390
+ } while (!supported && cursor !== void 0);
391
+ } catch (error) {
392
+ if (!(error instanceof RpcError) || error.code !== ErrorCode.methodNotFound) throw error;
393
+ throw new Error(`The selected daemon cannot report its interfaces. ${recovery}`, { cause: error });
213
394
  }
395
+ if (!supported) throw new Error(`The selected daemon does not support required interface ${required.id}@${required.hash} (reported: ${[...hashes].join(", ") || "missing"}). ${recovery}`);
396
+ }
397
+ }
398
+ function contextNamespace(reference) {
399
+ switch (reference.kind) {
400
+ case "path": return process.platform === "win32" ? reference.path.toLowerCase() : reference.path;
401
+ case "id": return `id:${reference.id}`;
402
+ case "root": return ":root";
403
+ case "empty": throw new Error("The :empty context has no namespace. Pass --namespace <name>, or select another context with --context <selector>.");
404
+ }
405
+ }
406
+ async function useLocalManagement(start, action) {
407
+ const home = HubHome.resolve();
408
+ if (start) {
409
+ const { startDaemon, nodeDaemonLaunch } = await import("./src-ConeU7vr.js");
410
+ if (await startDaemon({
411
+ home,
412
+ launch: nodeDaemonLaunch(home)
413
+ }) === "started") process.stderr.write("linkrpc: local daemon started.\n");
414
+ }
415
+ const discovery = readEndpointDiscovery(home.managementFile);
416
+ if (!discovery) throw new Error("No endpoint configured through flags, environment, or context, and the local Hub daemon is not running.");
417
+ const handle = await connectToHub({
418
+ endpoint: discovery.endpoint,
419
+ token: discovery.token ?? ""
420
+ });
421
+ try {
422
+ await checkDaemonInterfaces(handle.connection);
423
+ return await action(handle.connection.get(managedEndpointsInterface), { local: true });
424
+ } finally {
425
+ handle.close();
214
426
  }
215
- if (svcHits.length > 0) {
216
- lines.push(" Closest services:");
217
- for (const s of svcHits) lines.push(` ${s} (try ${ctx.tool} ls --service ${s})`);
218
- }
219
- if (methodHits.length === 0 && ifaceHits.length === 0 && svcHits.length === 0) lines.push(` Tip: run \`${ctx.tool} ls\` to see what's available.`);
220
- return lines.join("\n");
221
427
  }
222
- function _suggestForm2(ctx) {
223
- const { ref, snapshot } = ctx;
224
- const iface = ref.interfaceId;
225
- const method = ref.methodName;
226
- const hostsOfIface = snapshot.servicesHostingInterface(iface);
227
- if (hostsOfIface.length === 0) {
228
- if (iface !== "" && snapshot.services().includes(iface)) {
229
- const locs = snapshot.locationsOfMethod(method).filter((l) => l.serviceId === iface && _isUserInterface(l.interfaceId));
230
- if (locs.length > 0) {
231
- const lines = [`Interface "${method}" not found on service "${iface}", but "${method}" is a method name there.`];
232
- lines.push(" Did you mean:");
233
- for (const l of locs.slice(0, 3)) lines.push(` ${_qualify(ctx, l.serviceId, l.interfaceId, method)}`);
234
- return lines.join("\n");
235
- }
236
- return _formatInterfaceListOnService(ctx, iface, method);
428
+ async function restartLocalDaemon() {
429
+ const { startDaemon, stopDaemon, nodeDaemonLaunch } = await import("./src-ConeU7vr.js");
430
+ const home = HubHome.resolve();
431
+ process.stderr.write("Restarting the local Hub daemon: clients will disconnect and managed workloads will stop. OS autostart settings are unchanged.\n");
432
+ await stopDaemon(home);
433
+ await startDaemon({
434
+ home,
435
+ launch: nodeDaemonLaunch(home)
436
+ });
437
+ await useLocalManagement(false, async () => {});
438
+ process.stderr.write("The bundled daemon is running and supports the required interfaces.\n");
439
+ }
440
+ function registerServices(program, host) {
441
+ const createEndpoint = async (id, action) => {
442
+ try {
443
+ await host.use(true, action);
444
+ } catch (error) {
445
+ if (error instanceof RpcError && error.message === `Endpoint "${id}" already exists in ${host.namespace()}`) throw new RpcError(`${error.message}\nAttach to it: ${host.attachCommand(id)}\nOr replace it: rerun this command with --force`, error.code, error.data, error.origin, error.hasData);
446
+ throw error;
237
447
  }
238
- const candidates = _rankInterfacesWithMethodBoost(snapshot, iface, method, 3);
239
- const lines = [`Interface "${iface}" not found.`];
240
- if (candidates.length > 0) {
241
- lines.push(" Did you mean:");
242
- for (const c of candidates) lines.push(` ${_qualify(ctx, c.serviceId, c.interfaceId, c.methodToTry)}`);
243
- } else lines.push(` Tip: run \`${ctx.tool} ls\` to see what's available.`);
244
- return lines.join("\n");
245
- }
246
- const fuzzy = _rankMethodsOnInterface(snapshot, iface, method, 3);
247
- if (fuzzy.length > 0) {
248
- const lines = [`Method "${method}" not found on interface "${iface}"${hostsOfIface.length === 1 ? ` (service: ${hostsOfIface[0]})` : ""}.`];
448
+ };
449
+ const services = program.command("services").description("Manage context-scoped endpoints through the selected daemon.").option("--namespace <name>", "managed endpoint namespace (defaults to selected context)");
450
+ services.command("restart").description("Stop the local daemon via RPC and start the bundled daemon; does not change OS autostart.").action(() => host.restart());
451
+ const addressed = (command) => command.option("--id <id>", "endpoint name in this context", "main");
452
+ const policy = (command) => command.option("--persistent", "retain definition across stop and daemon restart").addOption(new Option("--restart <policy>", "restart process or reconnect after failure").choices([
453
+ "never",
454
+ "on-failure",
455
+ "always"
456
+ ]).default("never")).option("--force", "stop and replace an existing endpoint with the same ID, even one owned by another terminal");
457
+ policy(addressed(services.command("run <command...>").description("Start a process on the daemon; arguments follow --."))).option("--detach", "return after readiness").addOption(new Option("--foreground", "stop and remove the service when this CLI disconnects").conflicts(["detach", "persistent"])).option("-t, --tty", "run in a pseudoterminal; keys go to the program and Ctrl+P, Ctrl+Q detaches").option("-x, --no-rpc", "run a plain stdio process without an RPC connection; ready on spawn").option("--cwd <directory>", "working directory on the execution host").option("--env <key=value...>", "explicit child environment").addOption(new Option("--readiness <mode>", "wait for Hub connection or process spawn (default: connected, or spawned with --no-rpc)").choices(["connected", "spawned"])).action(async (command, options) => {
458
+ if (!options.rpc && options.readiness === "connected") throw new Error("--no-rpc cannot use --readiness connected; plain processes are ready on spawn.");
459
+ const namespace = host.namespace();
460
+ const env = {};
461
+ for (const value of options.env ?? []) {
462
+ const index = value.indexOf("=");
463
+ if (index <= 0) throw new Error("--env requires KEY=value");
464
+ env[value.slice(0, index)] = value.slice(index + 1);
465
+ }
466
+ await createEndpoint(options.id, async (client, context) => {
467
+ const info = await client.info({});
468
+ const cwd = options.cwd ?? (context.local ? process.cwd() : void 0);
469
+ if (!cwd) throw new Error("Remote execution requires --cwd with a path on the execution host.");
470
+ process.stderr.write(`Execution host: ${info.hostname}\nNamespace: ${namespace}\nEndpoint: ${options.id}\nCommand: ${JSON.stringify(command)}\nWorking directory: ${cwd}\n`);
471
+ const tty = options.tty === true;
472
+ const size = tty ? localTerminalSize() : void 0;
473
+ const detach = new AbortController();
474
+ let status;
475
+ let timedOut = false;
476
+ const timeout = options.detach ? setTimeout(() => {
477
+ timedOut = true;
478
+ detach.abort();
479
+ }, 3e4) : void 0;
480
+ try {
481
+ const result = await runConsoleSession((onMessage) => client.run({
482
+ definition: {
483
+ namespace,
484
+ id: options.id,
485
+ persistent: options.persistent ?? false,
486
+ restart: options.restart,
487
+ source: {
488
+ kind: "process",
489
+ command,
490
+ cwd,
491
+ env,
492
+ rpc: options.rpc,
493
+ tty,
494
+ readiness: options.readiness ?? (options.rpc ? "connected" : "spawned")
495
+ }
496
+ },
497
+ owner: options.foreground === true,
498
+ replace: options.force === true,
499
+ console: {
500
+ interactive: !options.detach,
501
+ ...size ? { size } : {}
502
+ }
503
+ }, { onMessage }), {
504
+ interactive: !options.detach,
505
+ owner: options.foreground === true,
506
+ reattachCommand: host.attachCommand(options.id),
507
+ ...withDetachKeys(host),
508
+ signal: detach.signal
509
+ }, (event) => {
510
+ status = event.status;
511
+ if (options.detach && status.runtime.state === "running") detach.abort();
512
+ });
513
+ if (options.detach) {
514
+ if (timedOut) throw new Error("Endpoint did not become ready within 30 seconds. It remains daemon-owned; use services list or logs to inspect it.");
515
+ if (status?.runtime.state !== "running") throw new Error(status?.runtime.error ?? "Endpoint exited before readiness");
516
+ print(status);
517
+ } else if (!result.closedLocally && result.exit?.code) process.exitCode = result.exit.code;
518
+ else if (!result.closedLocally && status?.runtime.state === "failed" && !result.exit) throw new Error(status.runtime.error ?? "Endpoint failed");
519
+ } finally {
520
+ clearTimeout(timeout);
521
+ }
522
+ });
523
+ });
524
+ policy(addressed(services.command("connect").description("Connect an existing LinkRPC endpoint; never owns its external process."))).requiredOption("--target-endpoint <uri>", "endpoint resolved on the daemon machine").action(async (options) => {
525
+ const namespace = host.namespace();
526
+ await createEndpoint(options.id, async (client) => {
527
+ const info = await client.info({});
528
+ process.stderr.write(`Execution host: ${info.hostname}\nNamespace: ${namespace}\nEndpoint: ${options.id}\n`);
529
+ print(await waitUntilReady(client, await client.create({
530
+ namespace,
531
+ id: options.id,
532
+ persistent: options.persistent ?? false,
533
+ restart: options.restart,
534
+ replace: options.force === true,
535
+ source: {
536
+ kind: "connection",
537
+ endpoint: options.targetEndpoint
538
+ }
539
+ })));
540
+ });
541
+ });
542
+ services.command("list").option("--all-contexts", "include every namespace visible to this caller").action(async (options) => {
543
+ const namespace = options.allContexts ? void 0 : host.namespace();
544
+ await host.use(false, async (client) => print(await client.list({ namespace })));
545
+ });
546
+ for (const operation of [
547
+ "start",
548
+ "stop",
549
+ "remove"
550
+ ]) addressed(services.command(operation)).action(async (options) => {
551
+ const namespace = host.namespace();
552
+ await host.use(operation === "start", async (client) => {
553
+ if (operation === "start") print(await waitUntilReady(client, await client.start({
554
+ namespace,
555
+ id: options.id
556
+ })));
557
+ else print(await client[operation]({
558
+ namespace,
559
+ id: options.id
560
+ }));
561
+ });
562
+ });
563
+ addressed(services.command("configure")).option("--persistent", "retain this endpoint").option("--no-persistent", "remove it when stopped").addOption(new Option("--restart <policy>").choices([
564
+ "never",
565
+ "on-failure",
566
+ "always"
567
+ ])).action(async (options, command) => {
568
+ const namespace = host.namespace();
569
+ await host.use(false, async (client) => print(await client.configure({
570
+ namespace,
571
+ id: options.id,
572
+ restart: options.restart,
573
+ persistent: command.getOptionValueSource("persistent") === "cli" ? options.persistent : void 0
574
+ })));
575
+ });
576
+ for (const operation of ["attach", "logs"]) {
577
+ const subcommand = addressed(services.command(operation));
578
+ if (operation === "attach") subcommand.option("--read-only", "observe without sending input");
579
+ else subcommand.option("--follow", "follow new output");
580
+ subcommand.action(async (options) => {
581
+ const namespace = host.namespace();
582
+ await host.use(false, async (client) => {
583
+ const status = (await client.list({ namespace })).find((value) => value.id === options.id);
584
+ if (!status) throw new Error(`Endpoint "${options.id}" not found in ${namespace}`);
585
+ if (!status.console) throw new Error(`Endpoint "${options.id}" is a connection and has no console`);
586
+ const interactive = operation === "attach" && options.readOnly !== true;
587
+ const size = interactive ? localTerminalSize() : void 0;
588
+ const result = await runConsoleSession((onMessage) => client.consoles.attach({
589
+ params: {
590
+ namespace,
591
+ id: options.id
592
+ },
593
+ interactive,
594
+ ...size ? { size } : {}
595
+ }, { onMessage }), {
596
+ interactive,
597
+ owner: false,
598
+ untilLive: operation === "logs" && !options.follow,
599
+ reattachCommand: host.attachCommand(options.id),
600
+ ...withDetachKeys(host)
601
+ });
602
+ if (operation === "attach" && !result.closedLocally && result.exit?.code) process.exitCode = result.exit.code;
603
+ });
604
+ });
605
+ }
606
+ }
607
+ /** Wait until an endpoint runs; fail if it stops, fails, or is removed first. */
608
+ async function waitUntilReady(client, initial) {
609
+ if (initial.runtime.state === "running") return initial;
610
+ let current = initial;
611
+ let outcome;
612
+ const call = client.watch({
613
+ namespace: initial.namespace,
614
+ id: initial.id
615
+ }, { onMessage: (event) => {
616
+ if (event.t === "removed") outcome = "removed";
617
+ else {
618
+ current = event.status;
619
+ if (current.runtime.state === "running") outcome = "ready";
620
+ else if ([
621
+ "failed",
622
+ "stopped",
623
+ "disabled"
624
+ ].includes(current.runtime.state)) outcome = "finished";
625
+ }
626
+ if (outcome) call.cancel().catch(() => {});
627
+ } });
628
+ const timeout = setTimeout(() => {
629
+ outcome ??= "timeout";
630
+ call.cancel().catch(() => {});
631
+ }, 3e4);
632
+ try {
633
+ await call;
634
+ } catch (error) {
635
+ if (!outcome) throw error;
636
+ } finally {
637
+ clearTimeout(timeout);
638
+ }
639
+ switch (outcome) {
640
+ case "ready": return current;
641
+ case "timeout": throw new Error("Endpoint did not become ready within 30 seconds. It remains daemon-owned; use services list or logs to inspect it.");
642
+ default: throw new Error(current.runtime.error ?? "Endpoint exited before readiness");
643
+ }
644
+ }
645
+ function print(value) {
646
+ process.stdout.write(JSON.stringify(value, null, 2) + "\n");
647
+ }
648
+ //#endregion
649
+ //#region src/views/types.ts
650
+ function formatCondition(condition) {
651
+ const match = condition.kind === "interface" ? condition.interface : condition.implements;
652
+ const predicate = "tag" in match ? `tag(${match.tag})` : `id(${match.interfaceId})`;
653
+ return condition.kind === "interface" ? `interface:${predicate}` : `service:implements(${predicate})`;
654
+ }
655
+ function matchesInterface(value, condition) {
656
+ return "tag" in condition ? value.tags?.includes(condition.tag) === true : value.interfaceId === condition.interfaceId;
657
+ }
658
+ function matchesTarget(target, view) {
659
+ return view.conditions.some((condition) => condition.kind === target.kind && target.interfaces.some((value) => matchesInterface(value, condition.kind === "interface" ? condition.interface : condition.implements)));
660
+ }
661
+ function targetId(kind, listing) {
662
+ return `${kind}:${encodeURIComponent(listing.serviceId)}:${encodeURIComponent(listing.discoveredFrom)}` + (kind === "interface" ? `:${encodeURIComponent(listing.interfaceId)}:${listing.isDefault ? "default" : "qualified"}` : "");
663
+ }
664
+ function interfaceTarget(listing) {
665
+ return {
666
+ id: targetId("interface", listing),
667
+ kind: "interface",
668
+ serviceId: listing.serviceId,
669
+ interfaceId: listing.interfaceId,
670
+ discoveredFrom: listing.discoveredFrom,
671
+ interfaces: [listing]
672
+ };
673
+ }
674
+ //#endregion
675
+ //#region src/views/discovery.ts
676
+ async function discoverViewTargets(channel, view, options = {}) {
677
+ const explorer = new HubDirectoryExplorer(channel, options);
678
+ const warnings = [];
679
+ try {
680
+ await explorer.explore();
681
+ const snapshot = explorer.graphSnapshot.result;
682
+ warnings.push(...snapshot.inaccessible.map((item) => `${item.serviceId || "<root>"}: ${item.reason}`));
683
+ const listings = [...snapshot.listings];
684
+ try {
685
+ const defaults = await deadline(fetchDefaults(channel), options.timeoutMs ?? 5e3);
686
+ if (defaults.interfaceId !== void 0) {
687
+ const existing = listings.find((item) => item.serviceId === "" && item.interfaceId === defaults.interfaceId);
688
+ listings.unshift({
689
+ serviceId: "",
690
+ interfaceId: defaults.interfaceId,
691
+ hash: defaults.hash ?? existing?.hash,
692
+ discoveredFrom: "",
693
+ isDefault: true,
694
+ tags: existing?.tags
695
+ });
696
+ }
697
+ } catch (error) {
698
+ warnings.push(`Default service: ${message(error)}`);
699
+ }
700
+ const interfacesById = /* @__PURE__ */ new Map();
701
+ for (const listing of listings) {
702
+ const target = interfaceTarget(listing);
703
+ const previous = interfacesById.get(target.id);
704
+ interfacesById.set(target.id, previous === void 0 ? target : {
705
+ ...previous,
706
+ interfaces: [...previous.interfaces, listing]
707
+ });
708
+ }
709
+ const targets = [...interfacesById.values()];
710
+ const services = /* @__PURE__ */ new Map();
711
+ for (const listing of listings) {
712
+ const id = targetId("service", listing);
713
+ const values = services.get(id) ?? [];
714
+ if (!values.some((value) => value.interfaceId === listing.interfaceId && value.hash === listing.hash)) values.push(listing);
715
+ services.set(id, values);
716
+ }
717
+ for (const [id, interfaces] of services) {
718
+ const first = interfaces[0];
719
+ targets.push({
720
+ id,
721
+ kind: "service",
722
+ serviceId: first.serviceId,
723
+ discoveredFrom: first.discoveredFrom,
724
+ interfaces
725
+ });
726
+ }
727
+ return {
728
+ targets: targets.filter((target) => matchesTarget(target, view)).map((target) => ({
729
+ ...target,
730
+ serviceInterfaces: listings.filter((listing) => listing.serviceId === target.serviceId && listing.discoveredFrom === target.discoveredFrom)
731
+ })).sort((a, b) => a.id.localeCompare(b.id)),
732
+ warnings
733
+ };
734
+ } finally {
735
+ explorer.dispose();
736
+ }
737
+ }
738
+ async function resolveViewTarget(channel, view, id, options = {}) {
739
+ const { targets } = await discoverViewTargets(channel, view, options);
740
+ const target = targets.find((value) => value.id === id);
741
+ if (!target) throw new Error(`Unknown or unavailable ${view.id} target "${id}". Run view targets ${view.id} again.`);
742
+ return loadViewTarget(channel, target, options.timeoutMs, view);
743
+ }
744
+ /** Selection and opening share a connection, including spawned ephemeral endpoints. */
745
+ async function resolveViewSelection(channel, view, options) {
746
+ if (options.target !== void 0) {
747
+ const context = await resolveViewTarget(channel, view, options.target, options);
748
+ if (options.service !== void 0 && context.target.serviceId !== options.service) throw new Error("--target does not match --service");
749
+ if (options.interface !== void 0 && !context.target.interfaces.some((value) => value.interfaceId === options.interface)) throw new Error("--target does not match --interface");
750
+ return context;
751
+ }
752
+ if (options.interface === void 0 && options.service === void 0) throw new Error("Specify --target, or an exact --interface / --service selector");
753
+ const candidates = (await discoverViewTargets(channel, view, options)).targets.filter((target) => target.kind === (options.interface === void 0 ? "service" : "interface") && (options.interface === void 0 || target.interfaceId === options.interface) && (options.service === void 0 || target.serviceId === options.service));
754
+ const matches = candidates.filter((target) => target.kind !== "interface" || target.interfaces.some((value) => value.isDefault) || !candidates.some((other) => other.serviceId === target.serviceId && other.discoveredFrom === target.discoveredFrom && other.interfaceId === target.interfaceId && other.interfaces.every((value) => target.interfaces.some((candidate) => candidate.hash === value.hash)) && target.interfaces.every((value) => other.interfaces.some((candidate) => candidate.hash === value.hash)) && other.interfaces.some((value) => value.isDefault)));
755
+ if (matches.length !== 1) {
756
+ const detail = matches.length === 0 ? "No matching target" : `Target selection is ambiguous (${matches.length} targets)`;
757
+ throw new Error(`${detail}; use --service to narrow the selection or --target from view targets ${view.id}`);
758
+ }
759
+ return loadViewTarget(channel, matches[0], options.timeoutMs, view);
760
+ }
761
+ async function loadViewTarget(channel, target, timeoutMs = 5e3, view) {
762
+ const candidates = view === void 0 || target.kind === "interface" ? target.interfaces : target.interfaces.filter((listing) => view.conditions.some((condition) => condition.kind === "service" && matchesInterface(listing, condition.implements)));
763
+ for (const listing of candidates) if (candidates.some((other) => other.interfaceId === listing.interfaceId && other.hash !== listing.hash)) throw new Error(`Target "${target.id}" is ambiguous: interface "${listing.interfaceId}" advertises multiple schema hashes`);
764
+ return {
765
+ channel,
766
+ target,
767
+ interfaces: await Promise.all(candidates.map(async (listing) => {
768
+ const schema = await deadline(fetchSchema$1(channel, listing.interfaceId, listing.hash, listing.discoveredFrom || void 0), timeoutMs);
769
+ if (schema.id !== listing.interfaceId || listing.hash !== void 0 && schema.hash !== listing.hash) throw new Error(`Schema for "${listing.interfaceId}" does not match its current directory advertisement; rediscover the target`);
770
+ return {
771
+ ...listing,
772
+ hash: schema.hash,
773
+ schema
774
+ };
775
+ }))
776
+ };
777
+ }
778
+ async function deadline(promise, timeoutMs) {
779
+ let timer;
780
+ try {
781
+ return await Promise.race([promise, new Promise((_, reject) => {
782
+ timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Reflection timed out after ${timeoutMs}ms`)), timeoutMs);
783
+ })]);
784
+ } finally {
785
+ clearTimeout(timer);
786
+ }
787
+ }
788
+ function message(error) {
789
+ return error instanceof Error ? error.message : String(error);
790
+ }
791
+ //#endregion
792
+ //#region src/commands/consoleAttach.ts
793
+ const consoleTemplate = ConsoleTemplate({ params: unknown() }).template;
794
+ /** Discovery-only view: the generic `attach` command reuses directory and reflection resolution. */
795
+ const consoleDiscovery = {
796
+ id: "console",
797
+ title: "Console",
798
+ modes: ["cli"],
799
+ conditions: [{
800
+ kind: "interface",
801
+ interface: { tag: CONSOLE_INTERFACE_TAG }
802
+ }, {
803
+ kind: "service",
804
+ implements: { tag: CONSOLE_INTERFACE_TAG }
805
+ }],
806
+ configureCommand() {},
807
+ open() {
808
+ throw new Error("Use the attach command");
809
+ },
810
+ createSession() {
811
+ throw new Error("Consoles have no TUI view");
812
+ }
813
+ };
814
+ /** Console template instances in reflected schemas; tags are only discovery hints. */
815
+ function consoleTargets(interfaces) {
816
+ return interfaces.flatMap((listing) => {
817
+ const metadata = listing.schema[INTERFACE_TEMPLATES_EXTENSION];
818
+ if (metadata === void 0) return [];
819
+ validateInterfaceTemplates(listing.schema);
820
+ return metadata.instances.flatMap((instance) => {
821
+ const template = metadata.templates[instance.template];
822
+ if (template?.id !== consoleTemplate.id) return [];
823
+ if (!interfaceTemplatesEqual(template, consoleTemplate)) throw new Error(`${listing.interfaceId} uses an unsupported revision of the ${consoleTemplate.id} template`);
824
+ const method = (member) => {
825
+ const name = interfaceTemplateMemberName(instance, member);
826
+ if (listing.isDefault) return name;
827
+ return listing.serviceId === "" ? `${listing.interfaceId}::${name}` : `${listing.serviceId}::${listing.interfaceId}::${name}`;
828
+ };
829
+ const params = instance.arguments.params;
830
+ if (params === void 0) throw new Error(`Console instance "${instance.name}" is missing its params argument`);
831
+ return [{
832
+ serviceId: listing.serviceId,
833
+ interfaceId: listing.interfaceId,
834
+ instance: instance.name,
835
+ infoMethod: method("info"),
836
+ attachMethod: method("attach"),
837
+ params
838
+ }];
839
+ });
840
+ });
841
+ }
842
+ /** List console targets without attaching: discovery uses directory tags, then reflection resolves instances. */
843
+ async function listConsoleTargets(channel) {
844
+ const discovery = await discoverViewTargets(channel, consoleDiscovery);
845
+ const rows = [];
846
+ for (const target of discovery.targets.filter((value) => value.kind === "service")) {
847
+ const context = await loadViewTarget(channel, target, void 0, consoleDiscovery);
848
+ for (const console of consoleTargets(context.interfaces)) rows.push(`${console.serviceId || "<root>"} ${console.interfaceId} ${console.instance} ${JSON.stringify(console.params.schema)}`);
849
+ }
850
+ return [
851
+ rows.length === 0 ? "No services offer linkrpc.console." : "SERVICE INTERFACE INSTANCE PARAMS",
852
+ ...rows,
853
+ ...discovery.warnings.map((warning) => `warning: ${warning}`)
854
+ ].join("\n");
855
+ }
856
+ /** Attach to any service implementing the console template, selected by service, interface, and instance. */
857
+ async function attachConsole(channel, options) {
858
+ const context = await resolveViewSelection(channel, consoleDiscovery, {
859
+ service: options.serviceId,
860
+ ...options.interfaceId === void 0 ? {} : { interface: options.interfaceId }
861
+ });
862
+ const candidates = consoleTargets(context.interfaces).filter((target) => options.instance === void 0 || target.instance === options.instance);
863
+ if (candidates.length !== 1) {
864
+ const names = consoleTargets(context.interfaces).map((target) => `${target.interfaceId}#${target.instance}`).join(", ");
865
+ throw new Error(candidates.length === 0 ? `No console instance matches on "${options.serviceId}"${names ? ` (available: ${names})` : ""}` : `Several consoles are available (${names}); select one with --instance or --interface`);
866
+ }
867
+ const target = candidates[0];
868
+ let params;
869
+ try {
870
+ params = JSON.parse(options.params ?? "{}");
871
+ } catch {
872
+ throw new Error("--params must be valid JSON");
873
+ }
874
+ const validation = createSchemaToZod(target.params.components?.schemas ?? {}).toZod(target.params.schema).safeParse(params);
875
+ if (!validation.success) throw new Error(`--params does not match the params of console "${target.instance}" (${JSON.stringify(target.params.schema)}): ${validation.error.message}`);
876
+ const interactive = options.readOnly !== true;
877
+ const size = interactive ? localTerminalSize() : void 0;
878
+ const result = await runConsoleSession((onMessage) => rawConsoleCall(channel, target.attachMethod, {
879
+ params,
880
+ interactive,
881
+ ...size ? { size } : {}
882
+ }, onMessage), {
883
+ interactive,
884
+ owner: false,
885
+ reattachCommand: options.reattachCommand,
886
+ ...options.detachKeys ? { detachKeys: options.detachKeys } : {}
887
+ });
888
+ if (!result.closedLocally && result.exit?.code) process.exitCode = result.exit.code;
889
+ }
890
+ /** Call a reflected attach method on the raw channel, validating every stream message against the protocol. */
891
+ function rawConsoleCall(channel, method, params, onMessage) {
892
+ const call = channel.sendRequestWithStream(method, params, { onStreamMessage: (payload) => {
893
+ const message = consoleServerMessageSchema.safeParse(payload);
894
+ onMessage(message.success ? message.data : {
895
+ t: "notice",
896
+ text: `Ignored an invalid console message: ${message.error.message}`
897
+ });
898
+ } });
899
+ const result = call.result.then((value) => consoleAttachResultSchema.parse(value));
900
+ result.finally(() => call.dispose?.()).catch(() => {});
901
+ return {
902
+ then: (onFulfilled, onRejected) => result.then(onFulfilled, onRejected),
903
+ send: (message) => call.send(message),
904
+ cancel: () => call.cancel("detached")
905
+ };
906
+ }
907
+ //#endregion
908
+ //#region src/output.ts
909
+ /**
910
+ * Read params either from a `--params <json>` argument (treated as inline
911
+ * JSON) or `--params -` (read from stdin). `undefined` if neither was given.
912
+ */
913
+ function readParamsArg(value) {
914
+ if (value === void 0) return void 0;
915
+ const trimmed = (value === "-" ? readFileSync(0, "utf8") : value).trim();
916
+ if (trimmed.length === 0) return void 0;
917
+ try {
918
+ return JSON.parse(trimmed);
919
+ } catch (e) {
920
+ throw new Error(`--params: invalid JSON (${e.message})`);
921
+ }
922
+ }
923
+ function formatJson(v) {
924
+ if (v === void 0) return "undefined";
925
+ return JSON.stringify(v, null, 2);
926
+ }
927
+ //#endregion
928
+ //#region src/commands/caps.ts
929
+ const matcherSchema = z.union([
930
+ z.strictObject({ exact: z.json() }),
931
+ z.strictObject({ enum: z.array(z.json()).min(1) }),
932
+ z.strictObject({ prefix: z.string() }),
933
+ z.strictObject({ subsetOf: z.array(z.string()) }),
934
+ z.strictObject({ any: z.literal(true) })
935
+ ]);
936
+ async function requestCallCapability(channel, session, ref, params, schema, opts) {
937
+ const method = findMethodInSchema(schema, ref.methodName);
938
+ if (method === void 0) throw new Error(`Unknown method ${ref.getMethodOnWire()}.`);
939
+ const matchers = createParamMatchers(params, opts.allowParams ?? [], method.params, schema.components?.schemas);
940
+ const defaults = ref.interfaceId === void 0 ? await fetchDefaults(channel) : void 0;
941
+ const permission = {
942
+ target: {
943
+ serviceId: { exact: ref.serviceId ?? defaults?.serviceId ?? "" },
944
+ interfaceId: { exact: ref.interfaceId ?? schema.id },
945
+ members: [{ exact: ref.methodName }],
946
+ ...ref.hash !== void 0 ? { interfaceHash: ref.hash } : {}
947
+ },
948
+ canInvoke: true,
949
+ params: matchers
950
+ };
951
+ const coverageOptions = {
952
+ audience: session.principal.id,
953
+ freshnessMarginMs: 2e3
954
+ };
955
+ const cached = findCoveringCapabilities(session.listGrants(), [permission], coverageOptions);
956
+ if (cached !== void 0) return formatJson({
957
+ status: "granted",
958
+ reused: true,
959
+ capabilities: cached
960
+ });
961
+ const result = await session.requestAccess({
962
+ consumer: {
963
+ name: "linkrpc-cli",
964
+ purpose: `Invoke ${ref.getMethodOnWire()}.`
965
+ },
966
+ permissions: [permission],
967
+ duration: opts.duration ?? "persistent"
968
+ });
969
+ if (result.status !== "granted") {
970
+ const reason = "reason" in result ? result.reason : void 0;
971
+ throw new Error(`Capability request ${result.status}${reason ? `: ${reason}` : "."}`);
972
+ }
973
+ if (findCoveringCapabilities(session.listGrants(), [permission], coverageOptions) === void 0) throw new Error("The granted capabilities do not cover the requested durable scope.");
974
+ return formatJson({
975
+ ...result,
976
+ reused: false
977
+ });
978
+ }
979
+ function createParamMatchers(params, allowParams, schema, components = {}) {
980
+ if (params !== void 0 && (params === null || typeof params !== "object" || Array.isArray(params))) throw new Error("Capability requests require object parameters.");
981
+ const actual = params === void 0 ? {} : Object.fromEntries(Object.entries(params));
982
+ const matchers = Object.fromEntries(Object.entries(actual).map(([key, value]) => [key, { exact: value }]));
983
+ const seen = /* @__PURE__ */ new Set();
984
+ for (const raw of allowParams) {
985
+ const { path, value } = parseParamOverride(raw);
986
+ if (path.length !== 1) throw new Error("--cap:allow-param only supports top-level parameter names.");
987
+ const key = path[0];
988
+ if (seen.has(key)) throw new Error(`Duplicate --cap:allow-param for ${key}.`);
989
+ seen.add(key);
990
+ const parsed = matcherSchema.safeParse(value);
991
+ if (!parsed.success) throw new Error(`Invalid --cap:allow-param ${key}: ${parsed.error.message}`);
992
+ matchers[key] = parsed.data;
993
+ }
994
+ const properties = Object.fromEntries(Object.entries(matchers).map(([key, matcher]) => [key, matcherToSchema(matcher)]));
995
+ if (!isAssignable({
996
+ type: "object",
997
+ properties,
998
+ required: Object.keys(properties),
999
+ additionalProperties: false
1000
+ }, schema, { schemas: components })) throw new Error("--cap:allow-param scope does not match the method parameter schema.");
1001
+ const validator = schemaToZod(schema, components);
1002
+ const samples = [actual];
1003
+ for (const [key, matcher] of Object.entries(matchers)) {
1004
+ const values = "exact" in matcher ? [matcher.exact] : "enum" in matcher ? matcher.enum : "subsetOf" in matcher ? [[], ...matcher.subsetOf.map((value) => [value])] : [];
1005
+ for (const value of values) samples.push({
1006
+ ...actual,
1007
+ [key]: value
1008
+ });
1009
+ }
1010
+ for (const sample of samples) {
1011
+ const validated = validator.safeParse(sample);
1012
+ if (!validated.success) throw new Error(`Capability parameter schema validation failed: ${validated.error.message}`);
1013
+ }
1014
+ const matched = matchParams(matchers, actual);
1015
+ if (!matched.ok) throw new Error(`Parameters do not match --cap:allow-param: ${matched.reason}`);
1016
+ return matchers;
1017
+ }
1018
+ function matcherToSchema(matcher) {
1019
+ if ("any" in matcher) return true;
1020
+ if ("prefix" in matcher) return { type: "string" };
1021
+ if ("subsetOf" in matcher) return {
1022
+ type: "array",
1023
+ items: { enum: matcher.subsetOf }
1024
+ };
1025
+ if ("enum" in matcher) return { enum: z.array(z.json()).parse(matcher.enum) };
1026
+ return { const: z.json().parse(matcher.exact) };
1027
+ }
1028
+ function listCaps(session, json) {
1029
+ const capabilities = session.listGrants();
1030
+ return json ? formatJson({
1031
+ principal: session.principal.id,
1032
+ capabilities
1033
+ }) : `principal: ${session.principal.id}\n${capabilities.length === 0 ? "No stored capabilities." : formatJson(capabilities)}`;
1034
+ }
1035
+ //#endregion
1036
+ //#region src/suggest.ts
1037
+ const _SYSTEM_PREFIX = "hubrpc.";
1038
+ function _isSystemInterface(id) {
1039
+ return id.startsWith(_SYSTEM_PREFIX);
1040
+ }
1041
+ function _isUserInterface(id) {
1042
+ return !_isSystemInterface(id);
1043
+ }
1044
+ /**
1045
+ * Indexed view of the bus's directory + per-interface methods. Constructed
1046
+ * directly from a list of entries (tests) or by walking a live channel
1047
+ * (`HubSnapshot.load`). Read-only after construction.
1048
+ */
1049
+ var HubSnapshot = class HubSnapshot {
1050
+ entries;
1051
+ _byService = /* @__PURE__ */ new Map();
1052
+ _byInterface = /* @__PURE__ */ new Map();
1053
+ _byMethodName = /* @__PURE__ */ new Map();
1054
+ constructor(entries) {
1055
+ this.entries = entries;
1056
+ for (const e of entries) {
1057
+ let byIface = this._byService.get(e.serviceId);
1058
+ if (!byIface) {
1059
+ byIface = /* @__PURE__ */ new Map();
1060
+ this._byService.set(e.serviceId, byIface);
1061
+ }
1062
+ byIface.set(e.interfaceId, e);
1063
+ const hosts = this._byInterface.get(e.interfaceId) ?? [];
1064
+ if (!hosts.includes(e.serviceId)) hosts.push(e.serviceId);
1065
+ this._byInterface.set(e.interfaceId, hosts);
1066
+ for (const m of e.methods) {
1067
+ const locs = this._byMethodName.get(m) ?? [];
1068
+ locs.push({
1069
+ serviceId: e.serviceId,
1070
+ interfaceId: e.interfaceId
1071
+ });
1072
+ this._byMethodName.set(m, locs);
1073
+ }
1074
+ }
1075
+ }
1076
+ /** All serviceIds present on the bus, in encounter order ("" = root). */
1077
+ services() {
1078
+ return [...this._byService.keys()];
1079
+ }
1080
+ /** All distinct interface ids (system + user), in encounter order. */
1081
+ interfaces() {
1082
+ return [...this._byInterface.keys()];
1083
+ }
1084
+ interfacesOnService(serviceId) {
1085
+ const m = this._byService.get(serviceId);
1086
+ return m ? [...m.keys()] : [];
1087
+ }
1088
+ servicesHostingInterface(interfaceId) {
1089
+ return this._byInterface.get(interfaceId) ?? [];
1090
+ }
1091
+ methodsOn(serviceId, interfaceId) {
1092
+ return this._byService.get(serviceId)?.get(interfaceId)?.methods ?? [];
1093
+ }
1094
+ locationsOfMethod(name) {
1095
+ return this._byMethodName.get(name) ?? [];
1096
+ }
1097
+ /**
1098
+ * Walk the live bus once and fetch the schema for every (service, interface)
1099
+ * pair in parallel. Failures on individual schema fetches are absorbed —
1100
+ * the affected entry just ends up with `methods: []`, so it still
1101
+ * participates in service/interface-level suggestions.
1102
+ */
1103
+ static async load(channel) {
1104
+ const listings = await walkHub(channel);
1105
+ const entries = await Promise.all(listings.map(async (l) => {
1106
+ try {
1107
+ const schema = await fetchSchema$1(channel, l.interfaceId, l.hash, l.serviceId || void 0);
1108
+ return {
1109
+ serviceId: l.serviceId,
1110
+ interfaceId: l.interfaceId,
1111
+ hash: l.hash,
1112
+ methods: Object.keys(schema.methods)
1113
+ };
1114
+ } catch {
1115
+ return {
1116
+ serviceId: l.serviceId,
1117
+ interfaceId: l.interfaceId,
1118
+ hash: l.hash,
1119
+ methods: []
1120
+ };
1121
+ }
1122
+ }));
1123
+ return new HubSnapshot(entries);
1124
+ }
1125
+ };
1126
+ /**
1127
+ * Compose a multi-line "Did you mean…" message tailored to the failed ref.
1128
+ * The returned string has no trailing newline and no `linkrpc:` prefix (the
1129
+ * outer error path adds those).
1130
+ */
1131
+ function formatSuggestion(input) {
1132
+ const ctx = {
1133
+ ref: input.ref,
1134
+ snapshot: input.snapshot,
1135
+ verb: input.verb,
1136
+ tool: input.tool ?? "hub"
1137
+ };
1138
+ if (ctx.ref.serviceId !== void 0 && ctx.ref.interfaceId !== void 0) return _suggestForm3(ctx);
1139
+ if (ctx.ref.interfaceId !== void 0) return _suggestForm2(ctx);
1140
+ return _suggestForm1(ctx);
1141
+ }
1142
+ function _qualify(ctx, s, i, m) {
1143
+ return `${ctx.tool} ${ctx.verb} ${s}::${i}::${m}`;
1144
+ }
1145
+ function _suggestForm1(ctx) {
1146
+ const { ref, snapshot } = ctx;
1147
+ const token = ref.methodName;
1148
+ if (token !== "" && snapshot.services().includes(token)) {
1149
+ const samples = _sampleCallsForService(ctx, token, 3);
1150
+ const lines = [`"${token}" is a serviceId, not a method.`];
1151
+ lines.push(" See its interfaces:");
1152
+ lines.push(` ${ctx.tool} ls --service ${token}`);
1153
+ if (samples.length > 0) {
1154
+ lines.push(" Or call one:");
1155
+ for (const s of samples) lines.push(` ${s}`);
1156
+ }
1157
+ return lines.join("\n");
1158
+ }
1159
+ if (snapshot.interfaces().includes(token) && _isUserInterface(token)) {
1160
+ const hosts = snapshot.servicesHostingInterface(token).filter((s) => s !== "");
1161
+ const samples = _sampleCallsForInterface(ctx, token, 3);
1162
+ const lines = [`"${token}" is an interface, not a method. It's hosted on services: ${hosts.join(", ")}.`];
1163
+ if (samples.length > 0) {
1164
+ lines.push(" Try a call:");
1165
+ for (const s of samples) lines.push(` ${s}`);
1166
+ }
1167
+ return lines.join("\n");
1168
+ }
1169
+ const locs = snapshot.locationsOfMethod(token).filter((l) => _isUserInterface(l.interfaceId));
1170
+ if (locs.length > 0) {
1171
+ const lines = [`"${token}" is not callable without an interface (no preset is set).`];
1172
+ lines.push(" Did you mean:");
1173
+ for (const l of locs.slice(0, 5)) lines.push(` ${_qualify(ctx, l.serviceId, l.interfaceId, token)}`);
1174
+ return lines.join("\n");
1175
+ }
1176
+ return _suggestForm1Fuzzy(ctx);
1177
+ }
1178
+ function _suggestForm1Fuzzy(ctx) {
1179
+ const { snapshot } = ctx;
1180
+ const token = ctx.ref.methodName;
1181
+ const methodHits = _rankAllMethods(snapshot, token, 3);
1182
+ const ifaceHits = _rankAllInterfaces(snapshot, token, 3);
1183
+ const svcHits = _rankServices(snapshot, token, 3);
1184
+ const lines = [`"${token}" matches no known service, interface, or method.`];
1185
+ if (methodHits.length > 0) {
1186
+ lines.push(" Closest methods:");
1187
+ for (const h of methodHits) lines.push(` ${_qualify(ctx, h.serviceId, h.interfaceId, h.name)}`);
1188
+ }
1189
+ if (ifaceHits.length > 0) {
1190
+ lines.push(" Closest interfaces:");
1191
+ for (const id of ifaceHits) {
1192
+ const sampleSvc = snapshot.servicesHostingInterface(id).find((s) => s !== "") ?? snapshot.servicesHostingInterface(id)[0];
1193
+ lines.push(` ${id} (try ${ctx.tool} ${ctx.verb} ${sampleSvc ?? ""}::${id}::<method>)`);
1194
+ }
1195
+ }
1196
+ if (svcHits.length > 0) {
1197
+ lines.push(" Closest services:");
1198
+ for (const s of svcHits) lines.push(` ${s} (try ${ctx.tool} ls --service ${s})`);
1199
+ }
1200
+ if (methodHits.length === 0 && ifaceHits.length === 0 && svcHits.length === 0) lines.push(` Tip: run \`${ctx.tool} ls\` to see what's available.`);
1201
+ return lines.join("\n");
1202
+ }
1203
+ function _suggestForm2(ctx) {
1204
+ const { ref, snapshot } = ctx;
1205
+ const iface = ref.interfaceId;
1206
+ const method = ref.methodName;
1207
+ const hostsOfIface = snapshot.servicesHostingInterface(iface);
1208
+ if (hostsOfIface.length === 0) {
1209
+ if (iface !== "" && snapshot.services().includes(iface)) {
1210
+ const locs = snapshot.locationsOfMethod(method).filter((l) => l.serviceId === iface && _isUserInterface(l.interfaceId));
1211
+ if (locs.length > 0) {
1212
+ const lines = [`Interface "${method}" not found on service "${iface}", but "${method}" is a method name there.`];
1213
+ lines.push(" Did you mean:");
1214
+ for (const l of locs.slice(0, 3)) lines.push(` ${_qualify(ctx, l.serviceId, l.interfaceId, method)}`);
1215
+ return lines.join("\n");
1216
+ }
1217
+ return _formatInterfaceListOnService(ctx, iface, method);
1218
+ }
1219
+ const candidates = _rankInterfacesWithMethodBoost(snapshot, iface, method, 3);
1220
+ const lines = [`Interface "${iface}" not found.`];
1221
+ if (candidates.length > 0) {
1222
+ lines.push(" Did you mean:");
1223
+ for (const c of candidates) lines.push(` ${_qualify(ctx, c.serviceId, c.interfaceId, c.methodToTry)}`);
1224
+ } else lines.push(` Tip: run \`${ctx.tool} ls\` to see what's available.`);
1225
+ return lines.join("\n");
1226
+ }
1227
+ const fuzzy = _rankMethodsOnInterface(snapshot, iface, method, 3);
1228
+ if (fuzzy.length > 0) {
1229
+ const lines = [`Method "${method}" not found on interface "${iface}"${hostsOfIface.length === 1 ? ` (service: ${hostsOfIface[0]})` : ""}.`];
249
1230
  lines.push(" Did you mean:");
250
1231
  for (const f of fuzzy) lines.push(` ${_qualify(ctx, f.serviceId, iface, f.methodName)}`);
251
1232
  return lines.join("\n");
@@ -493,11 +1474,23 @@ async function callCommand(channel, opts) {
493
1474
  base: baseParams,
494
1475
  overrides: opts.paramOverrides
495
1476
  });
496
- const validation = opts.noValidate ? "off" : opts.validation ?? "required";
497
- if (validation !== "off") await validateParams(channel, ref, params, "call", validation);
1477
+ const requestCap = opts.requestCap === true || opts.capabilityOnly === true;
1478
+ if (!requestCap && opts.allowParams !== void 0) throw new Error("--cap:allow-param requires --cap:request.");
1479
+ if (requestCap && (opts.noValidate || opts.validation === "off")) throw new Error("Capability requests require schema validation; remove --no-validate/--validation off.");
1480
+ if (requestCap && opts.signingSession === void 0) throw new Error("Capability requests require a signed LinkRPC connection.");
498
1481
  const onStreamChunk = opts.onStreamChunk ?? writeChunkToStderr;
499
1482
  try {
500
- const result = await channel.sendRequestWithStream(ref.getMethodOnWire(), params, { onStreamMessage: onStreamChunk }).result;
1483
+ const validation = requestCap ? "required" : opts.noValidate ? "off" : opts.validation ?? "required";
1484
+ const schema = validation === "off" ? void 0 : await validateParams(channel, ref, params, "call", validation);
1485
+ if (requestCap) {
1486
+ if (schema === void 0 || opts.signingSession === void 0) throw new Error("Capability requests require a method schema and signing session.");
1487
+ const result = await requestCallCapability(channel, opts.signingSession, ref, params, schema, opts);
1488
+ if (opts.capabilityOnly) return result;
1489
+ }
1490
+ const result = await channel.sendRequestWithStream(ref.getMethodOnWire(), params, {
1491
+ onStreamMessage: onStreamChunk,
1492
+ ...opts.signingSession !== void 0 && ref.hash !== void 0 ? { interfaceHash: ref.hash } : {}
1493
+ }).result;
501
1494
  return opts.json !== false ? formatJson(result) : String(result);
502
1495
  } catch (e) {
503
1496
  if (e instanceof RpcError && e.code === ErrorCode.methodNotFound) await _failWithSuggestion(channel, ref, "call");
@@ -539,15 +1532,16 @@ async function validateParams(channel, ref, params, verb, validation) {
539
1532
  }
540
1533
  if (schema === void 0) {
541
1534
  if (validation === "auto") return;
542
- await _failWithSuggestion(channel, ref, verb);
1535
+ return _failWithSuggestion(channel, ref, verb);
543
1536
  }
544
1537
  const method = findMethodInSchema(schema, ref.methodName);
545
- if (!method) await _failWithSuggestion(channel, ref, verb);
1538
+ if (!method) return _failWithSuggestion(channel, ref, verb);
546
1539
  if (verb === "call" && method.result === void 0) throw new Error(`${ref.getMethodOnWire()} is a notification member; use \`notify\` instead of \`call\`.`);
547
1540
  if (verb === "notify" && method.result !== void 0) throw new Error(`${ref.getMethodOnWire()} is a request member; use \`call\` instead of \`notify\`.`);
548
1541
  const paramsSchema = method.params;
549
1542
  const reason = validateValueAgainstSchema(params, paramsSchema, schema.components?.schemas ?? {});
550
1543
  if (reason !== void 0) throw new Error(_formatParamValidationError(ref, schema, method, params, reason));
1544
+ return schema;
551
1545
  }
552
1546
  function _formatParamValidationError(ref, schema, method, params, fallback) {
553
1547
  const components = schema.components?.schemas ?? {};
@@ -593,20 +1587,13 @@ function formatRpcError(e) {
593
1587
  const dataStr = e.data === void 0 ? "" : ` ${JSON.stringify(e.data)}`;
594
1588
  return /* @__PURE__ */ new Error(`RPC error ${e.code}: ${e.message}${dataStr}`);
595
1589
  }
596
- /**
597
- * Friendly explanation for `permissionRequired` (-32401). Typical when the
598
- * CLI couldn't acquire a capability via `hubAccess::requestAccess` and the
599
- * target service gates on caps. The earlier `capability negotiation
600
- * skipped` warning explains *why* we couldn't get one; this footer tells
601
- * the user what to do.
602
- */
603
1590
  function _formatPermissionRequired(e) {
604
1591
  return [
605
1592
  `Permission required: ${e.message}`,
606
- " The hub gates this call on a capability, and none was acquired.",
607
- " If your hub uses an access handler, accept the consent prompt and retry.",
608
- " Otherwise, the hub admin has to issue a capability to this CLI identity",
609
- ` (\`hub logout\` then retry to mint a fresh identity).`
1593
+ " No usable capability authorizes this call.",
1594
+ " Retry with --cap:request to request access before calling.",
1595
+ " Use --cap:allow-param with --cap:request to request a broader parameter scope,",
1596
+ " or use `linkrpc hub caps request <methodRef>` to acquire and store access separately."
610
1597
  ].join("\n");
611
1598
  }
612
1599
  //#endregion
@@ -2003,7 +2990,7 @@ function selectParticipant(participants, nodeId) {
2003
2990
  return participants.length === 1 ? participants[0] : void 0;
2004
2991
  }
2005
2992
  async function promptForParticipant(participants, search) {
2006
- const readline = createInterface({
2993
+ const readline = createInterface$1({
2007
2994
  input: stdin,
2008
2995
  output: stdout
2009
2996
  });
@@ -2070,409 +3057,1906 @@ async function schemaCommand(channel, opts) {
2070
3057
  if (opts.json) return formatJson(schema);
2071
3058
  return renderSchema(schema);
2072
3059
  }
2073
- function renderSchema(schema) {
2074
- const lines = [];
2075
- lines.push(`${schema.id}@${schema.hash}`);
2076
- if (schema.description) lines.push(` ${schema.description}`);
2077
- lines.push("");
2078
- for (const [name, method] of Object.entries(schema.methods)) {
2079
- lines.push(renderMethod(name, method));
2080
- lines.push("");
3060
+ function renderSchema(schema) {
3061
+ const lines = [];
3062
+ lines.push(`${schema.id}@${schema.hash}`);
3063
+ if (schema.description) lines.push(` ${schema.description}`);
3064
+ lines.push("");
3065
+ for (const [name, method] of Object.entries(schema.methods)) {
3066
+ lines.push(renderMethod(name, method));
3067
+ lines.push("");
3068
+ }
3069
+ return lines.join("\n").trimEnd();
3070
+ }
3071
+ function renderMethod(name, m) {
3072
+ const kind = m.result === void 0 ? "notify" : "request";
3073
+ const lines = [];
3074
+ lines.push(` ${kind} ${name}`);
3075
+ if (m.summary) lines.push(` ${m.summary}`);
3076
+ if (m.description) lines.push(` ${m.description}`);
3077
+ lines.push(` params: ${shortSchema(m.params)}`);
3078
+ if (m.result) lines.push(` result: ${shortSchema(m.result)}`);
3079
+ return lines.join("\n");
3080
+ }
3081
+ function shortSchema(s) {
3082
+ const str = JSON.stringify(s);
3083
+ if (str.length <= 120) return str;
3084
+ return str.slice(0, 117) + "...";
3085
+ }
3086
+ //#endregion
3087
+ //#region src/commands/serve.ts
3088
+ /**
3089
+ * `linkrpc serve <config>` — run a hub from a declarative `HubConfig` file and
3090
+ * block until interrupted. Thin CLI face over the shared `@hediet/linkrpc-hub`
3091
+ * engine, so one config format drives the CLI hub, the server hub, and the
3092
+ * VS Code extension.
3093
+ */
3094
+ /** Run a hub from `configPath` and resolve only when interrupted (SIGINT/SIGTERM). */
3095
+ async function serveCommand(opts) {
3096
+ const { serveHub } = await import("@hediet/linkrpc-hub/serve");
3097
+ await serveHub({
3098
+ configPath: opts.configPath,
3099
+ cmdInteractive: opts.cmdInteractive,
3100
+ logMessages: opts.logMessages,
3101
+ log: (line) => process.stderr.write(`linkrpc: ${line}\n`)
3102
+ });
3103
+ }
3104
+ //#endregion
3105
+ //#region src/commands/connectAs.ts
3106
+ /**
3107
+ * Run a **connect-as** proxy until either side closes (or {@link
3108
+ * ConnectAsDeps.stop} resolves).
3109
+ *
3110
+ * Mints a bound token for a slot, opens the target-facing transport and the
3111
+ * hub-facing transport (which redeems the token → this connection *is* the slot
3112
+ * on the hub), and splices them message-for-message. The target thereby joins
3113
+ * the hub as a first-class participant with the slot's managed identity +
3114
+ * granted namespace, without running a local hub and without the target needing
3115
+ * to perform the hub handshake itself.
3116
+ *
3117
+ * Because all traffic crosses the CLI's splice point, {@link
3118
+ * ConnectAsDeps.tapTarget} yields complete `--log-messages` observability.
3119
+ */
3120
+ async function connectAs(deps) {
3121
+ const token = await deps.mintToken();
3122
+ const target = await deps.openTargetTransport();
3123
+ let hub;
3124
+ try {
3125
+ hub = await deps.openHubTransport(token);
3126
+ } catch (e) {
3127
+ target.dispose();
3128
+ throw e;
3129
+ }
3130
+ const targetTransport = deps.tapTarget ? deps.tapTarget(target.transport) : target.transport;
3131
+ const pipe = connectTransports(targetTransport, hub.transport);
3132
+ deps.log?.("connect-as: bridged target ⟷ hub");
3133
+ await new Promise((resolve) => {
3134
+ let done = false;
3135
+ const teardown = () => {
3136
+ if (done) return;
3137
+ done = true;
3138
+ pipe.dispose();
3139
+ try {
3140
+ target.dispose();
3141
+ } catch {}
3142
+ try {
3143
+ hub.dispose();
3144
+ } catch {}
3145
+ resolve();
3146
+ };
3147
+ target.onClose(teardown);
3148
+ hub.onClose(teardown);
3149
+ deps.stop?.then(teardown);
3150
+ });
3151
+ deps.log?.("connect-as: closed");
3152
+ }
3153
+ //#endregion
3154
+ //#region src/commands/connectAsTransports.ts
3155
+ /**
3156
+ * Dial a ws / socket endpoint as a client, presenting `token` in the
3157
+ * `hubrpc::initialize` handshake. Used for the hub-facing side (with the minted
3158
+ * bound token) and for ws / socket / uri targets (with the target's own token).
3159
+ */
3160
+ async function openDialTransport(spec, token) {
3161
+ if (spec.kind === "ws") {
3162
+ const ws = await openWebSocket(spec.url);
3163
+ const closeWs = () => {
3164
+ try {
3165
+ ws.close();
3166
+ } catch {}
3167
+ };
3168
+ const transport = new WebSocketTransport(ws, closeWs);
3169
+ try {
3170
+ await runInitializeHandshake(transport, {
3171
+ kind: "client",
3172
+ token
3173
+ });
3174
+ } catch (err) {
3175
+ transport.dispose();
3176
+ closeWs();
3177
+ throw err;
3178
+ }
3179
+ return {
3180
+ transport,
3181
+ onClose: (cb) => ws.addEventListener("close", () => cb()),
3182
+ dispose: () => {
3183
+ transport.dispose();
3184
+ closeWs();
3185
+ }
3186
+ };
3187
+ }
3188
+ const socket = net.createConnection(spec.path);
3189
+ const { transport } = await connectNdjson({
3190
+ input: socket,
3191
+ output: socket,
3192
+ onClose: () => socket.destroy(),
3193
+ initialize: {
3194
+ kind: "client",
3195
+ token
3196
+ }
3197
+ });
3198
+ return {
3199
+ transport,
3200
+ onClose: (cb) => socket.on("close", cb),
3201
+ dispose: () => socket.destroy()
3202
+ };
3203
+ }
3204
+ /** Forward a child stream's lines to `log`, prefixed with `label`. */
3205
+ function _pipeLines(stream, label, log) {
3206
+ if (!stream) return;
3207
+ stream.setEncoding("utf8");
3208
+ stream.on("data", (chunk) => {
3209
+ for (const line of chunk.split(/\r?\n/)) if (line.length > 0) log(`[${label}] ${line}`);
3210
+ });
3211
+ }
3212
+ /** Spawn a `cmd-stdio` target and talk linkrpc over its stdin/stdout. */
3213
+ async function openStdioTransport(command, env, cwd, log) {
3214
+ const child = spawnCommand(command, {
3215
+ stdio: [
3216
+ "pipe",
3217
+ "pipe",
3218
+ "pipe"
3219
+ ],
3220
+ ...env !== void 0 ? { env: {
3221
+ ...process.env,
3222
+ ...env
3223
+ } } : {},
3224
+ ...cwd !== void 0 ? { cwd } : {}
3225
+ });
3226
+ if (!child.stdin || !child.stdout) throw new Error("connect-as: cmd-stdio child exposes no stdio");
3227
+ if (log) _pipeLines(child.stderr, "target", log);
3228
+ const { transport } = await connectNdjson({
3229
+ input: child.stdout,
3230
+ output: child.stdin,
3231
+ onClose: () => {
3232
+ if (!child.killed) child.kill();
3233
+ }
3234
+ });
3235
+ return {
3236
+ transport,
3237
+ onClose: (cb) => child.once("exit", cb),
3238
+ dispose: () => {
3239
+ transport.dispose();
3240
+ if (!child.killed) child.kill();
3241
+ }
3242
+ };
3243
+ }
3244
+ /**
3245
+ * Spawn a `cmd-env` target against a private socket the CLI listens on, and
3246
+ * take the single connection it dials back. This is the sole role-inverted
3247
+ * case: the target dials the CLI, so we accept rather than dial. No hub /
3248
+ * acceptor is needed — just the raw post-handshake transport to splice.
3249
+ */
3250
+ async function openAcceptOneTransport(command, env, cwd, log) {
3251
+ const socketPath = SocketServer.allocSocketPath();
3252
+ const token = randomBytes(16).toString("hex");
3253
+ const server = await SocketServer.start({ endpoint: socketPath });
3254
+ const accepted = new Promise((resolve) => {
3255
+ server.setConnectionHandler((t) => resolve(t));
3256
+ });
3257
+ const child = spawnCommand(command, {
3258
+ stdio: [
3259
+ "ignore",
3260
+ "pipe",
3261
+ "pipe"
3262
+ ],
3263
+ env: {
3264
+ ...process.env,
3265
+ ...env,
3266
+ LINKRPC_ENDPOINT: socketPath,
3267
+ LINKRPC_TOKEN: token
3268
+ },
3269
+ ...cwd !== void 0 ? { cwd } : {}
3270
+ });
3271
+ if (log) {
3272
+ _pipeLines(child.stdout, "target", log);
3273
+ _pipeLines(child.stderr, "target", log);
3274
+ }
3275
+ let exited = false;
3276
+ child.once("exit", () => {
3277
+ exited = true;
3278
+ });
3279
+ const transport = await Promise.race([accepted, new Promise((_resolve, reject) => {
3280
+ child.once("exit", (code) => reject(/* @__PURE__ */ new Error(`connect-as: cmd-env child exited (code ${code ?? "?"}) before connecting`)));
3281
+ })]).catch((err) => {
3282
+ server.dispose();
3283
+ _unlink(socketPath);
3284
+ if (!child.killed) child.kill();
3285
+ throw err;
3286
+ });
3287
+ const dispose = () => {
3288
+ transport.dispose();
3289
+ server.dispose();
3290
+ _unlink(socketPath);
3291
+ if (!exited && !child.killed) child.kill();
3292
+ };
3293
+ return {
3294
+ transport,
3295
+ onClose: (cb) => {
3296
+ transport.onDidClose(cb);
3297
+ child.once("exit", cb);
3298
+ },
3299
+ dispose
3300
+ };
3301
+ }
3302
+ /** Open the target-facing transport for any resolved endpoint kind. */
3303
+ function openTargetTransport(endpoint, log) {
3304
+ switch (endpoint.kind) {
3305
+ case "cmd-env": return openAcceptOneTransport(endpoint.command, endpoint.env, endpoint.cwd, log);
3306
+ case "cmd-stdio": return openStdioTransport(endpoint.command, endpoint.env, endpoint.cwd, log);
3307
+ case "ws":
3308
+ case "socket": return openDialTransport(endpoint, endpoint.token ?? "");
2081
3309
  }
2082
- return lines.join("\n").trimEnd();
2083
- }
2084
- function renderMethod(name, m) {
2085
- const kind = m.result === void 0 ? "notify" : "request";
2086
- const lines = [];
2087
- lines.push(` ${kind} ${name}`);
2088
- if (m.summary) lines.push(` ${m.summary}`);
2089
- if (m.description) lines.push(` ${m.description}`);
2090
- lines.push(` params: ${shortSchema(m.params)}`);
2091
- if (m.result) lines.push(` result: ${shortSchema(m.result)}`);
2092
- return lines.join("\n");
2093
3310
  }
2094
- function shortSchema(s) {
2095
- const str = JSON.stringify(s);
2096
- if (str.length <= 120) return str;
2097
- return str.slice(0, 117) + "...";
3311
+ function _unlink(socketPath) {
3312
+ if (process.platform === "win32") return;
3313
+ try {
3314
+ fs.unlinkSync(socketPath);
3315
+ } catch {}
2098
3316
  }
2099
3317
  //#endregion
2100
- //#region src/commands/serve.ts
3318
+ //#region src/commands/mcpForward.ts
2101
3319
  /**
2102
- * `linkrpc serve <config>` — run a hub from a declarative `HubConfig` file and
2103
- * block until interrupted. Thin CLI face over the shared `@hediet/linkrpc-hub`
2104
- * engine, so one config format drives the CLI hub, the server hub, and the
2105
- * VS Code extension.
3320
+ * Front door for claiming a serviceId within the connection's granted
3321
+ * namespace — interface-form `hubGrantedServiceId::register`. Same claim the
3322
+ * `tunnel` command uses.
2106
3323
  */
2107
- /** Run a hub from `configPath` and resolve only when interrupted (SIGINT/SIGTERM). */
2108
- async function serveCommand(opts) {
2109
- const { serveHub } = await import("@hediet/linkrpc-hub/serve");
2110
- await serveHub({
2111
- configPath: opts.configPath,
2112
- cmdInteractive: opts.cmdInteractive,
2113
- logMessages: opts.logMessages,
2114
- log: (line) => process.stderr.write(`linkrpc: ${line}\n`)
2115
- });
2116
- }
2117
- //#endregion
2118
- //#region src/commands/connectAs.ts
3324
+ const CLAIM_METHOD = `${hubGrantedServiceIdInterface.info.id}::register`;
3325
+ /** Read-only connection facts — `hubGrantedServiceId::get`. */
3326
+ const INFO_METHOD = `${hubGrantedServiceIdInterface.info.id}::get`;
2119
3327
  /**
2120
- * Run a **connect-as** proxy until either side closes (or {@link
2121
- * ConnectAsDeps.stop} resolves).
3328
+ * Run a transparent MCP forwarder until stopped.
2122
3329
  *
2123
- * Mints a bound token for a slot, opens the target-facing transport and the
2124
- * hub-facing transport (which redeems the token → this connection *is* the slot
2125
- * on the hub), and splices them message-for-message. The target thereby joins
2126
- * the hub as a first-class participant with the slot's managed identity +
2127
- * granted namespace, without running a local hub and without the target needing
2128
- * to perform the hub handshake itself.
3330
+ * Serves {@link mcpForwardInterface} under `serviceId` on the hub: each inbound
3331
+ * `connect` request spawns a fresh MCP server child and shuttles raw JSON-RPC
3332
+ * frames between the child's stdio (newline-delimited JSON, the MCP stdio
3333
+ * transport framing) and the request's duplex stream. The forwarder never
3334
+ * parses MCP semantics — it only splits/joins NDJSON lines so each line becomes
3335
+ * one opaque `frame`. {@link LinkRpcConnection.enableReflection} is enabled so
3336
+ * the hub's directory can discover the service.
2129
3337
  *
2130
- * Because all traffic crosses the CLI's splice point, {@link
2131
- * ConnectAsDeps.tapTarget} yields complete `--log-messages` observability.
3338
+ * Resolves when {@link McpForwardOptions.stop} resolves (or on SIGINT/SIGTERM).
2132
3339
  */
2133
- async function connectAs(deps) {
2134
- const token = await deps.mintToken();
2135
- const target = await deps.openTargetTransport();
2136
- let hub;
2137
- try {
2138
- hub = await deps.openHubTransport(token);
2139
- } catch (e) {
2140
- target.dispose();
2141
- throw e;
2142
- }
2143
- const targetTransport = deps.tapTarget ? deps.tapTarget(target.transport) : target.transport;
2144
- const pipe = connectTransports(targetTransport, hub.transport);
2145
- deps.log?.("connect-as: bridged target ⟷ hub");
2146
- await new Promise((resolve) => {
2147
- let done = false;
2148
- const teardown = () => {
2149
- if (done) return;
2150
- done = true;
2151
- pipe.dispose();
2152
- try {
2153
- target.dispose();
2154
- } catch {}
3340
+ async function mcpForwardCommand(opts) {
3341
+ const log = opts.log ?? ((line) => process.stderr.write(line + "\n"));
3342
+ const serviceId = opts.serviceId ?? await _resolveGrantedServiceId(opts.local);
3343
+ const conn = new LinkRpcConnection(opts.local.rpcChannel);
3344
+ conn.register(mcpForwardInterface, { connect: (_params, _ctx, stream) => _runLeg(opts.command, opts.env, stream, log) }, { serviceId });
3345
+ conn.enableReflection({ serviceId });
3346
+ await opts.local.channel.sendRequest(CLAIM_METHOD, { serviceId });
3347
+ log(`mcp-forward: claimed '${serviceId}' — forwarding MCP stdio to ${_describe(opts.command)}`);
3348
+ await (opts.stop ?? _untilSignalled$1());
3349
+ log("mcp-forward: stopping");
3350
+ }
3351
+ /**
3352
+ * Resolve the connection's granted serviceId namespace
3353
+ * (`hubGrantedServiceId::get`) to use as the default forward prefix. Throws if
3354
+ * the namespace is empty ("claim nothing freely") — the caller must then pass an
3355
+ * explicit `--serviceId`.
3356
+ */
3357
+ async function _resolveGrantedServiceId(local) {
3358
+ const granted = (await local.channel.sendRequest(INFO_METHOD, {}))?.grantedServiceIdNamespace ?? "";
3359
+ if (granted.length === 0) throw new Error("mcp-forward: this connection has no granted serviceId namespace to claim; pass an explicit --serviceId.");
3360
+ return granted;
3361
+ }
3362
+ /**
3363
+ * One MCP session leg: spawn the child, bridge stdio<->stream, and resolve when
3364
+ * the child exits or the caller cancels (killing the child).
3365
+ */
3366
+ function _runLeg(command, env, stream, log) {
3367
+ return new Promise((resolve) => {
3368
+ const child = spawnCommand(command, {
3369
+ stdio: [
3370
+ "pipe",
3371
+ "pipe",
3372
+ "inherit"
3373
+ ],
3374
+ ...env !== void 0 ? { env: {
3375
+ ...process.env,
3376
+ ...env
3377
+ } } : {}
3378
+ });
3379
+ if (!child.stdin || !child.stdout) {
3380
+ log("mcp-forward: child exposes no stdio");
3381
+ if (!child.killed) child.kill();
3382
+ resolve({});
3383
+ return;
3384
+ }
3385
+ let settled = false;
3386
+ const rl = readline.createInterface({ input: child.stdout });
3387
+ const finish = () => {
3388
+ if (settled) return;
3389
+ settled = true;
3390
+ rl.close();
3391
+ if (!child.killed) child.kill();
3392
+ resolve({});
3393
+ };
3394
+ rl.on("line", (line) => {
3395
+ const trimmed = line.trim();
3396
+ if (trimmed.length === 0) return;
3397
+ let frame;
2155
3398
  try {
2156
- hub.dispose();
2157
- } catch {}
3399
+ frame = JSON.parse(trimmed);
3400
+ } catch {
3401
+ return;
3402
+ }
3403
+ if (stream.signal.aborted) return;
3404
+ stream.send({ frame });
3405
+ });
3406
+ stream.onMessage(({ frame }) => {
3407
+ if (!child.stdin || child.stdin.destroyed) return;
3408
+ child.stdin.write(JSON.stringify(frame) + "\n");
3409
+ });
3410
+ if (stream.signal.aborted) {
3411
+ finish();
3412
+ return;
3413
+ }
3414
+ stream.signal.addEventListener("abort", finish, { once: true });
3415
+ child.on("exit", finish);
3416
+ child.on("error", (err) => {
3417
+ log(`mcp-forward: child error: ${String(err)}`);
3418
+ finish();
3419
+ });
3420
+ });
3421
+ }
3422
+ /** Resolve on the next `SIGINT` / `SIGTERM`. */
3423
+ function _untilSignalled$1() {
3424
+ return new Promise((resolve) => {
3425
+ const done = () => {
3426
+ process.removeListener("SIGINT", done);
3427
+ process.removeListener("SIGTERM", done);
2158
3428
  resolve();
2159
3429
  };
2160
- target.onClose(teardown);
2161
- hub.onClose(teardown);
2162
- deps.stop?.then(teardown);
3430
+ process.once("SIGINT", done);
3431
+ process.once("SIGTERM", done);
2163
3432
  });
2164
- deps.log?.("connect-as: closed");
3433
+ }
3434
+ /** One-line description of the spawned command for logs. */
3435
+ function _describe(command) {
3436
+ return "argv" in command ? command.argv.join(" ") : command.command;
3437
+ }
3438
+ //#endregion
3439
+ //#region src/commands/jsonRpcStdio.ts
3440
+ /** Expose a remote `jsonRpcConnection` service as an NDJSON stdio endpoint. */
3441
+ async function jsonRpcStdioCommand(options) {
3442
+ const client = new LinkRpcConnection(options.local.rpcChannel).service(options.serviceId).get(jsonRpcConnectionInterface);
3443
+ const remote = await connectRawJsonRpcTransport(client, options.params);
3444
+ const stdio = createNdjsonJsonRpcTransport({
3445
+ input: options.input ?? process.stdin,
3446
+ output: options.output ?? process.stdout
3447
+ });
3448
+ const bridge = connectJsonRpcTransports(remote, stdio);
3449
+ await new Promise((resolve) => {
3450
+ let settled = false;
3451
+ const finish = () => {
3452
+ if (settled) return;
3453
+ settled = true;
3454
+ resolve();
3455
+ };
3456
+ remote.onClose(finish);
3457
+ stdio.onClose(finish);
3458
+ });
3459
+ bridge.dispose();
3460
+ remote.close("closed");
3461
+ stdio.close("closed");
3462
+ }
3463
+ //#endregion
3464
+ //#region ../../node_modules/.pnpm/@vscode+observables@0.1.1-0/node_modules/@vscode/observables/dist/observableInternal/logging/logging.js
3465
+ let globalObservableLogger;
3466
+ function addLogger(logger) {
3467
+ if (!globalObservableLogger) globalObservableLogger = logger;
3468
+ else if (globalObservableLogger instanceof ComposedLogger) globalObservableLogger.loggers.push(logger);
3469
+ else globalObservableLogger = new ComposedLogger([globalObservableLogger, logger]);
3470
+ }
3471
+ function getLogger() {
3472
+ return globalObservableLogger;
3473
+ }
3474
+ let globalObservableLoggerFn = void 0;
3475
+ function setLogObservableFn(fn) {
3476
+ globalObservableLoggerFn = fn;
3477
+ }
3478
+ function logObservable(obs) {
3479
+ if (globalObservableLoggerFn) globalObservableLoggerFn(obs);
3480
+ }
3481
+ var ComposedLogger = class {
3482
+ constructor(loggers) {
3483
+ this.loggers = loggers;
3484
+ }
3485
+ handleObservableCreated(observable, location) {
3486
+ for (const logger of this.loggers) logger.handleObservableCreated(observable, location);
3487
+ }
3488
+ handleOnListenerCountChanged(observable, newCount) {
3489
+ for (const logger of this.loggers) logger.handleOnListenerCountChanged(observable, newCount);
3490
+ }
3491
+ handleObservableUpdated(observable, info) {
3492
+ for (const logger of this.loggers) logger.handleObservableUpdated(observable, info);
3493
+ }
3494
+ handleAutorunCreated(autorun, location) {
3495
+ for (const logger of this.loggers) logger.handleAutorunCreated(autorun, location);
3496
+ }
3497
+ handleAutorunDisposed(autorun) {
3498
+ for (const logger of this.loggers) logger.handleAutorunDisposed(autorun);
3499
+ }
3500
+ handleAutorunDependencyChanged(autorun, observable, change) {
3501
+ for (const logger of this.loggers) logger.handleAutorunDependencyChanged(autorun, observable, change);
3502
+ }
3503
+ handleAutorunStarted(autorun) {
3504
+ for (const logger of this.loggers) logger.handleAutorunStarted(autorun);
3505
+ }
3506
+ handleAutorunFinished(autorun) {
3507
+ for (const logger of this.loggers) logger.handleAutorunFinished(autorun);
3508
+ }
3509
+ handleDerivedDependencyChanged(derived, observable, change) {
3510
+ for (const logger of this.loggers) logger.handleDerivedDependencyChanged(derived, observable, change);
3511
+ }
3512
+ handleDerivedCleared(observable) {
3513
+ for (const logger of this.loggers) logger.handleDerivedCleared(observable);
3514
+ }
3515
+ handleBeginTransaction(transaction) {
3516
+ for (const logger of this.loggers) logger.handleBeginTransaction(transaction);
3517
+ }
3518
+ handleEndTransaction(transaction) {
3519
+ for (const logger of this.loggers) logger.handleEndTransaction(transaction);
3520
+ }
3521
+ };
3522
+ //#endregion
3523
+ //#region ../../node_modules/.pnpm/@vscode+observables@0.1.1-0/node_modules/@vscode/observables/dist/observableInternal/debugLocation.js
3524
+ var DebugLocation;
3525
+ (function(DebugLocation) {
3526
+ let enabled = false;
3527
+ function enable() {
3528
+ enabled = true;
3529
+ }
3530
+ DebugLocation.enable = enable;
3531
+ function ofCaller() {
3532
+ return ofNthCaller(1);
3533
+ }
3534
+ DebugLocation.ofCaller = ofCaller;
3535
+ function ofNthCaller(n) {
3536
+ if (!enabled) return;
3537
+ const Err = Error;
3538
+ const l = Err.stackTraceLimit;
3539
+ Err.stackTraceLimit = 3;
3540
+ const stack = (/* @__PURE__ */ new Error()).stack;
3541
+ Err.stackTraceLimit = l;
3542
+ return DebugLocationImpl.fromStack(stack, n + 1);
3543
+ }
3544
+ DebugLocation.ofNthCaller = ofNthCaller;
3545
+ })(DebugLocation || (DebugLocation = {}));
3546
+ var DebugLocationImpl = class DebugLocationImpl {
3547
+ static fromStack(stack, parentIdx) {
3548
+ const location = parseLine(stack.split("\n")[parentIdx + 1]);
3549
+ if (location) return new DebugLocationImpl(location.fileName, location.line, location.column, location.id);
3550
+ else return;
3551
+ }
3552
+ constructor(fileName, line, column, id) {
3553
+ this.fileName = fileName;
3554
+ this.line = line;
3555
+ this.column = column;
3556
+ this.id = id;
3557
+ }
3558
+ };
3559
+ function parseLine(stackLine) {
3560
+ if (!stackLine) return;
3561
+ const match = stackLine.match(/\((.*):(\d+):(\d+)\)/);
3562
+ if (match) return {
3563
+ fileName: match[1],
3564
+ line: parseInt(match[2]),
3565
+ column: parseInt(match[3]),
3566
+ id: stackLine
3567
+ };
3568
+ const match2 = stackLine.match(/at ([^\(\)]*):(\d+):(\d+)/);
3569
+ if (match2) return {
3570
+ fileName: match2[1],
3571
+ line: parseInt(match2[2]),
3572
+ column: parseInt(match2[3]),
3573
+ id: stackLine
3574
+ };
2165
3575
  }
2166
3576
  //#endregion
2167
- //#region src/commands/connectAsTransports.ts
3577
+ //#region ../../node_modules/.pnpm/@vscode+observables@0.1.1-0/node_modules/@vscode/observables/dist/observableInternal/debugName.js
3578
+ var DebugNameData = class {
3579
+ constructor(owner, debugNameSource, referenceFn) {
3580
+ this.owner = owner;
3581
+ this.debugNameSource = debugNameSource;
3582
+ this.referenceFn = referenceFn;
3583
+ }
3584
+ getDebugName(target) {
3585
+ return getDebugName(target, this);
3586
+ }
3587
+ };
3588
+ const countPerName = /* @__PURE__ */ new Map();
3589
+ const cachedDebugName = /* @__PURE__ */ new WeakMap();
3590
+ function getDebugName(target, data) {
3591
+ const cached = cachedDebugName.get(target);
3592
+ if (cached) return cached;
3593
+ const dbgName = computeDebugName(target, data);
3594
+ if (dbgName) {
3595
+ let count = countPerName.get(dbgName) ?? 0;
3596
+ count++;
3597
+ countPerName.set(dbgName, count);
3598
+ const result = count === 1 ? dbgName : `${dbgName}#${count}`;
3599
+ cachedDebugName.set(target, result);
3600
+ return result;
3601
+ }
3602
+ }
3603
+ function computeDebugName(self, data) {
3604
+ const cached = cachedDebugName.get(self);
3605
+ if (cached) return cached;
3606
+ const ownerStr = data.owner ? formatOwner(data.owner) + `.` : "";
3607
+ let result;
3608
+ const debugNameSource = data.debugNameSource;
3609
+ if (debugNameSource !== void 0) {
3610
+ if (typeof debugNameSource === "function") {
3611
+ result = debugNameSource();
3612
+ if (result !== void 0) return ownerStr + result;
3613
+ } else return ownerStr + debugNameSource;
3614
+ }
3615
+ const referenceFn = data.referenceFn;
3616
+ if (referenceFn !== void 0) {
3617
+ result = getFunctionName(referenceFn);
3618
+ if (result !== void 0) return ownerStr + result;
3619
+ }
3620
+ if (data.owner !== void 0) {
3621
+ const key = findKey(data.owner, self);
3622
+ if (key !== void 0) return ownerStr + key;
3623
+ }
3624
+ }
3625
+ function findKey(obj, value) {
3626
+ for (const key in obj) if (obj[key] === value) return key;
3627
+ }
3628
+ const countPerClassName = /* @__PURE__ */ new Map();
3629
+ const ownerId = /* @__PURE__ */ new WeakMap();
3630
+ function formatOwner(owner) {
3631
+ const id = ownerId.get(owner);
3632
+ if (id) return id;
3633
+ const className = getClassName(owner) ?? "Object";
3634
+ let count = countPerClassName.get(className) ?? 0;
3635
+ count++;
3636
+ countPerClassName.set(className, count);
3637
+ const result = count === 1 ? className : `${className}#${count}`;
3638
+ ownerId.set(owner, result);
3639
+ return result;
3640
+ }
3641
+ function getClassName(obj) {
3642
+ const ctor = obj.constructor;
3643
+ if (ctor) {
3644
+ if (ctor.name === "Object") return;
3645
+ return ctor.name;
3646
+ }
3647
+ }
3648
+ function getFunctionName(fn) {
3649
+ const fnSrc = fn.toString();
3650
+ const match = /\/\*\*\s*@description\s*([^*]*)\*\//.exec(fnSrc);
3651
+ return (match ? match[1] : void 0)?.trim();
3652
+ }
3653
+ //#endregion
3654
+ //#region ../../node_modules/.pnpm/@vscode+observables@0.1.1-0/node_modules/@vscode/observables/dist/observableInternal/observables/baseObservable.js
3655
+ let _derived;
2168
3656
  /**
2169
- * Dial a ws / socket endpoint as a client, presenting `token` in the
2170
- * `hubrpc::initialize` handshake. Used for the hub-facing side (with the minted
2171
- * bound token) and for ws / socket / uri targets (with the target's own token).
3657
+ * @internal
3658
+ * This is to allow splitting files.
2172
3659
  */
2173
- async function openDialTransport(spec, token) {
2174
- if (spec.kind === "ws") {
2175
- const ws = await openWebSocket(spec.url);
2176
- const closeWs = () => {
3660
+ function _setDerivedOpts(derived) {
3661
+ _derived = derived;
3662
+ }
3663
+ let _recomputeInitiallyAndOnChange;
3664
+ function _setRecomputeInitiallyAndOnChange(recomputeInitiallyAndOnChange) {
3665
+ _recomputeInitiallyAndOnChange = recomputeInitiallyAndOnChange;
3666
+ }
3667
+ let _keepObserved;
3668
+ function _setKeepObserved(keepObserved) {
3669
+ _keepObserved = keepObserved;
3670
+ }
3671
+ let _debugGetDependencyGraph;
3672
+ function _setDebugGetDependencyGraph(debugGetDependencyGraph) {
3673
+ _debugGetDependencyGraph = debugGetDependencyGraph;
3674
+ }
3675
+ var ConvenientObservable = class {
3676
+ get TChange() {
3677
+ return null;
3678
+ }
3679
+ reportChanges() {
3680
+ this.get();
3681
+ }
3682
+ /** @sealed */
3683
+ read(reader) {
3684
+ if (reader) return reader.readObservable(this);
3685
+ else return this.get();
3686
+ }
3687
+ map(fnOrOwner, fnOrUndefined, debugLocation = DebugLocation.ofCaller()) {
3688
+ const owner = fnOrUndefined === void 0 ? void 0 : fnOrOwner;
3689
+ const fn = fnOrUndefined === void 0 ? fnOrOwner : fnOrUndefined;
3690
+ return _derived({
3691
+ owner,
3692
+ debugName: () => {
3693
+ const name = getFunctionName(fn);
3694
+ if (name !== void 0) return name;
3695
+ const match = /^\s*\(?\s*([a-zA-Z_$][a-zA-Z_$0-9]*)\s*\)?\s*=>\s*\1(?:\??)\.([a-zA-Z_$][a-zA-Z_$0-9]*)\s*$/.exec(fn.toString());
3696
+ if (match) return `${this.debugName}.${match[2]}`;
3697
+ if (!owner) return `${this.debugName} (mapped)`;
3698
+ },
3699
+ debugReferenceFn: fn
3700
+ }, (reader) => fn(this.read(reader), reader), debugLocation);
3701
+ }
3702
+ /**
3703
+ * @sealed
3704
+ * Converts an observable of an observable value into a direct observable of the value.
3705
+ */
3706
+ flatten() {
3707
+ return _derived({
3708
+ owner: void 0,
3709
+ debugName: () => `${this.debugName} (flattened)`
3710
+ }, (reader) => this.read(reader).read(reader));
3711
+ }
3712
+ recomputeInitiallyAndOnChange(store, handleValue) {
3713
+ store.add(_recomputeInitiallyAndOnChange(this, handleValue));
3714
+ return this;
3715
+ }
3716
+ /**
3717
+ * Ensures that this observable is observed. This keeps the cache alive.
3718
+ * However, in case of deriveds, it does not force eager evaluation (only when the value is read/get).
3719
+ * Use `recomputeInitiallyAndOnChange` for eager evaluation.
3720
+ */
3721
+ keepObserved(store) {
3722
+ store.add(_keepObserved(this));
3723
+ return this;
3724
+ }
3725
+ get debugValue() {
3726
+ return this.get();
3727
+ }
3728
+ debugGetDependencyGraph() {
3729
+ return _debugGetDependencyGraph(this);
3730
+ }
3731
+ };
3732
+ var BaseObservable = class extends ConvenientObservable {
3733
+ constructor(debugLocation) {
3734
+ super();
3735
+ this._observers = /* @__PURE__ */ new Set();
3736
+ getLogger()?.handleObservableCreated(this, debugLocation);
3737
+ }
3738
+ addObserver(observer) {
3739
+ const len = this._observers.size;
3740
+ this._observers.add(observer);
3741
+ if (len === 0) this.onFirstObserverAdded();
3742
+ if (len !== this._observers.size) getLogger()?.handleOnListenerCountChanged(this, this._observers.size);
3743
+ }
3744
+ removeObserver(observer) {
3745
+ const deleted = this._observers.delete(observer);
3746
+ if (deleted && this._observers.size === 0) this.onLastObserverRemoved();
3747
+ if (deleted) getLogger()?.handleOnListenerCountChanged(this, this._observers.size);
3748
+ }
3749
+ onFirstObserverAdded() {}
3750
+ onLastObserverRemoved() {}
3751
+ log() {
3752
+ const hadLogger = !!getLogger();
3753
+ logObservable(this);
3754
+ if (!hadLogger) getLogger()?.handleObservableCreated(this, DebugLocation.ofCaller());
3755
+ return this;
3756
+ }
3757
+ debugGetObservers() {
3758
+ return this._observers;
3759
+ }
3760
+ };
3761
+ //#endregion
3762
+ //#region ../../node_modules/.pnpm/@vscode+observables@0.1.1-0/node_modules/@vscode/observables/dist/observableInternal/commonFacade/deps.js
3763
+ function toDisposable(fn) {
3764
+ return { dispose: fn };
3765
+ }
3766
+ var BugIndicatingError = class BugIndicatingError extends Error {
3767
+ constructor(message) {
3768
+ super(message || "An unexpected bug occurred.");
3769
+ Object.setPrototypeOf(this, BugIndicatingError.prototype);
3770
+ }
3771
+ };
3772
+ function onBugIndicatingError(error) {
3773
+ throw error;
3774
+ }
3775
+ function onUnexpectedError(error) {
3776
+ console.error("Unexpected error:", error);
3777
+ }
3778
+ /**
3779
+ * Compares two items for equality using strict equality.
3780
+ */
3781
+ const strictEquals = (a, b) => a === b;
3782
+ //#endregion
3783
+ //#region ../../node_modules/.pnpm/@vscode+observables@0.1.1-0/node_modules/@vscode/observables/dist/disposables.js
3784
+ var DisposableStore = class {
3785
+ constructor() {
3786
+ this.disposables = [];
3787
+ this._isDisposed = false;
3788
+ }
3789
+ get isDisposed() {
3790
+ return this._isDisposed;
3791
+ }
3792
+ dispose() {
3793
+ this.clear();
3794
+ this._isDisposed = true;
3795
+ }
3796
+ add(disposable) {
3797
+ if (disposable) this.disposables.push(disposable);
3798
+ return disposable;
3799
+ }
3800
+ remove(disposable) {
3801
+ if (!disposable) return disposable;
3802
+ const index = this.disposables.indexOf(disposable);
3803
+ if (index !== -1) this.disposables.splice(index, 1);
3804
+ return disposable;
3805
+ }
3806
+ clear() {
3807
+ for (const disposable of this.disposables) disposable.dispose();
3808
+ this.disposables = [];
3809
+ }
3810
+ leakItems() {
3811
+ this.disposables = [];
3812
+ }
3813
+ };
3814
+ var Disposable = class {
3815
+ constructor() {
3816
+ this._store = new DisposableStore();
3817
+ }
3818
+ dispose() {
3819
+ this._store.dispose();
3820
+ }
3821
+ _register(t) {
3822
+ this._store.add(t);
3823
+ return t;
3824
+ }
3825
+ _registerOrDispose(t) {
3826
+ if (t) {
3827
+ if (this._store.isDisposed) t.dispose();
3828
+ else this._store.add(t);
3829
+ }
3830
+ return t;
3831
+ }
3832
+ };
3833
+ //#endregion
3834
+ //#region ../../node_modules/.pnpm/@vscode+observables@0.1.1-0/node_modules/@vscode/observables/dist/observableInternal/observables/derivedImpl.js
3835
+ function derivedStateToString(state) {
3836
+ switch (state) {
3837
+ case 0: return "initial";
3838
+ case 1: return "dependenciesMightHaveChanged";
3839
+ case 2: return "stale";
3840
+ case 3: return "upToDate";
3841
+ default: return "<unknown>";
3842
+ }
3843
+ }
3844
+ var Derived = class extends BaseObservable {
3845
+ get debugName() {
3846
+ return this._debugNameData.getDebugName(this) ?? "(anonymous)";
3847
+ }
3848
+ constructor(_debugNameData, _computeFn, _changeTracker, _handleLastObserverRemoved = void 0, _equalityComparator, debugLocation) {
3849
+ super(debugLocation);
3850
+ this._debugNameData = _debugNameData;
3851
+ this._computeFn = _computeFn;
3852
+ this._changeTracker = _changeTracker;
3853
+ this._handleLastObserverRemoved = _handleLastObserverRemoved;
3854
+ this._equalityComparator = _equalityComparator;
3855
+ this._state = 0;
3856
+ this._value = void 0;
3857
+ this._updateCount = 0;
3858
+ this._dependencies = /* @__PURE__ */ new Set();
3859
+ this._dependenciesToBeRemoved = /* @__PURE__ */ new Set();
3860
+ this._changeSummary = void 0;
3861
+ this._isUpdating = false;
3862
+ this._isComputing = false;
3863
+ this._didReportChange = false;
3864
+ this._isInBeforeUpdate = false;
3865
+ this._isReaderValid = false;
3866
+ this._store = void 0;
3867
+ this._delayedStore = void 0;
3868
+ this._removedObserverToCallEndUpdateOn = null;
3869
+ this._changeSummary = this._changeTracker?.createChangeSummary(void 0);
3870
+ }
3871
+ onLastObserverRemoved() {
3872
+ /**
3873
+ * We are not tracking changes anymore, thus we have to assume
3874
+ * that our cache is invalid.
3875
+ */
3876
+ this._state = 0;
3877
+ this._value = void 0;
3878
+ getLogger()?.handleDerivedCleared(this);
3879
+ for (const d of this._dependencies) d.removeObserver(this);
3880
+ this._dependencies.clear();
3881
+ if (this._store !== void 0) {
3882
+ this._store.dispose();
3883
+ this._store = void 0;
3884
+ }
3885
+ if (this._delayedStore !== void 0) {
3886
+ this._delayedStore.dispose();
3887
+ this._delayedStore = void 0;
3888
+ }
3889
+ this._handleLastObserverRemoved?.();
3890
+ }
3891
+ get() {
3892
+ if (this._isComputing && false);
3893
+ if (this._observers.size === 0) {
3894
+ let result;
2177
3895
  try {
2178
- ws.close();
2179
- } catch {}
2180
- };
2181
- const transport = new WebSocketTransport(ws, closeWs);
3896
+ this._isReaderValid = true;
3897
+ let changeSummary = void 0;
3898
+ if (this._changeTracker) {
3899
+ changeSummary = this._changeTracker.createChangeSummary(void 0);
3900
+ this._changeTracker.beforeUpdate?.(this, changeSummary);
3901
+ }
3902
+ result = this._computeFn(this, changeSummary);
3903
+ } finally {
3904
+ this._isReaderValid = false;
3905
+ }
3906
+ this.onLastObserverRemoved();
3907
+ return result;
3908
+ } else {
3909
+ do {
3910
+ if (this._state === 1) for (const d of this._dependencies) {
3911
+ /** might call {@link handleChange} indirectly, which could make us stale */
3912
+ d.reportChanges();
3913
+ if (this._state === 2) break;
3914
+ }
3915
+ if (this._state === 1) this._state = 3;
3916
+ if (this._state !== 3) this._recompute();
3917
+ } while (this._state !== 3);
3918
+ return this._value;
3919
+ }
3920
+ }
3921
+ _recompute() {
3922
+ let didChange = false;
3923
+ this._isComputing = true;
3924
+ this._didReportChange = false;
3925
+ const emptySet = this._dependenciesToBeRemoved;
3926
+ this._dependenciesToBeRemoved = this._dependencies;
3927
+ this._dependencies = emptySet;
2182
3928
  try {
2183
- await runInitializeHandshake(transport, {
2184
- kind: "client",
2185
- token
3929
+ const changeSummary = this._changeSummary;
3930
+ this._isReaderValid = true;
3931
+ if (this._changeTracker) {
3932
+ this._isInBeforeUpdate = true;
3933
+ this._changeTracker.beforeUpdate?.(this, changeSummary);
3934
+ this._isInBeforeUpdate = false;
3935
+ this._changeSummary = this._changeTracker?.createChangeSummary(changeSummary);
3936
+ }
3937
+ const hadValue = this._state !== 0;
3938
+ const oldValue = this._value;
3939
+ this._state = 3;
3940
+ const delayedStore = this._delayedStore;
3941
+ if (delayedStore !== void 0) this._delayedStore = void 0;
3942
+ try {
3943
+ if (this._store !== void 0) {
3944
+ this._store.dispose();
3945
+ this._store = void 0;
3946
+ }
3947
+ /** might call {@link handleChange} indirectly, which could invalidate us */
3948
+ this._value = this._computeFn(this, changeSummary);
3949
+ } finally {
3950
+ this._isReaderValid = false;
3951
+ for (const o of this._dependenciesToBeRemoved) o.removeObserver(this);
3952
+ this._dependenciesToBeRemoved.clear();
3953
+ if (delayedStore !== void 0) delayedStore.dispose();
3954
+ }
3955
+ didChange = this._didReportChange || hadValue && !this._equalityComparator(oldValue, this._value);
3956
+ getLogger()?.handleObservableUpdated(this, {
3957
+ oldValue,
3958
+ newValue: this._value,
3959
+ change: void 0,
3960
+ didChange,
3961
+ hadValue
2186
3962
  });
2187
- } catch (err) {
2188
- transport.dispose();
2189
- closeWs();
2190
- throw err;
3963
+ } catch (e) {
3964
+ onBugIndicatingError(e);
2191
3965
  }
2192
- return {
2193
- transport,
2194
- onClose: (cb) => ws.addEventListener("close", () => cb()),
2195
- dispose: () => {
2196
- transport.dispose();
2197
- closeWs();
3966
+ this._isComputing = false;
3967
+ if (!this._didReportChange && didChange) for (const r of this._observers) r.handleChange(this, void 0);
3968
+ else this._didReportChange = false;
3969
+ }
3970
+ toString() {
3971
+ return `LazyDerived<${this.debugName}>`;
3972
+ }
3973
+ beginUpdate(_observable) {
3974
+ if (this._isUpdating) throw new BugIndicatingError("Cyclic deriveds are not supported yet!");
3975
+ this._updateCount++;
3976
+ this._isUpdating = true;
3977
+ try {
3978
+ const propagateBeginUpdate = this._updateCount === 1;
3979
+ if (this._state === 3) {
3980
+ this._state = 1;
3981
+ if (!propagateBeginUpdate) for (const r of this._observers) r.handlePossibleChange(this);
2198
3982
  }
2199
- };
3983
+ if (propagateBeginUpdate) for (const r of this._observers) r.beginUpdate(this);
3984
+ } finally {
3985
+ this._isUpdating = false;
3986
+ }
2200
3987
  }
2201
- const socket = net.createConnection(spec.path);
2202
- const { transport } = await connectNdjson({
2203
- input: socket,
2204
- output: socket,
2205
- onClose: () => socket.destroy(),
2206
- initialize: {
2207
- kind: "client",
2208
- token
3988
+ endUpdate(_observable) {
3989
+ this._updateCount--;
3990
+ if (this._updateCount === 0) {
3991
+ const observers = [...this._observers];
3992
+ for (const r of observers) r.endUpdate(this);
3993
+ if (this._removedObserverToCallEndUpdateOn) {
3994
+ const observers = [...this._removedObserverToCallEndUpdateOn];
3995
+ this._removedObserverToCallEndUpdateOn = null;
3996
+ for (const r of observers) r.endUpdate(this);
3997
+ }
2209
3998
  }
2210
- });
2211
- return {
2212
- transport,
2213
- onClose: (cb) => socket.on("close", cb),
2214
- dispose: () => socket.destroy()
2215
- };
3999
+ }
4000
+ handlePossibleChange(observable) {
4001
+ if (this._state === 3 && this._dependencies.has(observable) && !this._dependenciesToBeRemoved.has(observable)) {
4002
+ this._state = 1;
4003
+ for (const r of this._observers) r.handlePossibleChange(this);
4004
+ }
4005
+ }
4006
+ handleChange(observable, change) {
4007
+ if (this._dependencies.has(observable) && !this._dependenciesToBeRemoved.has(observable) || this._isInBeforeUpdate) {
4008
+ getLogger()?.handleDerivedDependencyChanged(this, observable, change);
4009
+ let shouldReact = false;
4010
+ try {
4011
+ shouldReact = this._changeTracker ? this._changeTracker.handleChange({
4012
+ changedObservable: observable,
4013
+ change,
4014
+ didChange: (o) => o === observable
4015
+ }, this._changeSummary) : true;
4016
+ } catch (e) {
4017
+ onBugIndicatingError(e);
4018
+ }
4019
+ const wasUpToDate = this._state === 3;
4020
+ if (shouldReact && (this._state === 1 || wasUpToDate)) {
4021
+ this._state = 2;
4022
+ if (wasUpToDate) for (const r of this._observers) r.handlePossibleChange(this);
4023
+ }
4024
+ }
4025
+ }
4026
+ _ensureReaderValid() {
4027
+ if (!this._isReaderValid) throw new BugIndicatingError("The reader object cannot be used outside its compute function!");
4028
+ }
4029
+ readObservable(observable) {
4030
+ this._ensureReaderValid();
4031
+ observable.addObserver(this);
4032
+ /** This might call {@link handleChange} indirectly, which could invalidate us */
4033
+ const value = observable.get();
4034
+ this._dependencies.add(observable);
4035
+ this._dependenciesToBeRemoved.delete(observable);
4036
+ return value;
4037
+ }
4038
+ reportChange(change) {
4039
+ this._ensureReaderValid();
4040
+ this._didReportChange = true;
4041
+ for (const r of this._observers) r.handleChange(this, change);
4042
+ }
4043
+ get store() {
4044
+ this._ensureReaderValid();
4045
+ if (this._store === void 0) this._store = new DisposableStore();
4046
+ return this._store;
4047
+ }
4048
+ get delayedStore() {
4049
+ this._ensureReaderValid();
4050
+ if (this._delayedStore === void 0) this._delayedStore = new DisposableStore();
4051
+ return this._delayedStore;
4052
+ }
4053
+ addObserver(observer) {
4054
+ const shouldCallBeginUpdate = !this._observers.has(observer) && this._updateCount > 0;
4055
+ super.addObserver(observer);
4056
+ if (shouldCallBeginUpdate) {
4057
+ if (this._removedObserverToCallEndUpdateOn && this._removedObserverToCallEndUpdateOn.has(observer)) this._removedObserverToCallEndUpdateOn.delete(observer);
4058
+ else observer.beginUpdate(this);
4059
+ }
4060
+ }
4061
+ removeObserver(observer) {
4062
+ if (this._observers.has(observer) && this._updateCount > 0) {
4063
+ if (!this._removedObserverToCallEndUpdateOn) this._removedObserverToCallEndUpdateOn = /* @__PURE__ */ new Set();
4064
+ this._removedObserverToCallEndUpdateOn.add(observer);
4065
+ }
4066
+ super.removeObserver(observer);
4067
+ }
4068
+ debugGetState() {
4069
+ return {
4070
+ state: this._state,
4071
+ stateStr: derivedStateToString(this._state),
4072
+ updateCount: this._updateCount,
4073
+ isComputing: this._isComputing,
4074
+ dependencies: this._dependencies,
4075
+ value: this._value
4076
+ };
4077
+ }
4078
+ debugSetValue(newValue) {
4079
+ this._value = newValue;
4080
+ }
4081
+ debugRecompute() {
4082
+ if (!this._isComputing) this._recompute();
4083
+ else this._state = 2;
4084
+ }
4085
+ setValue(newValue, tx, change) {
4086
+ this._value = newValue;
4087
+ const observers = this._observers;
4088
+ tx.updateObserver(this, this);
4089
+ for (const d of observers) d.handleChange(this, change);
4090
+ }
4091
+ };
4092
+ //#endregion
4093
+ //#region ../../node_modules/.pnpm/@vscode+observables@0.1.1-0/node_modules/@vscode/observables/dist/observableInternal/observables/derived.js
4094
+ function derived(computeFnOrOwner, computeFn, debugLocation = DebugLocation.ofCaller()) {
4095
+ if (computeFn !== void 0) return new Derived(new DebugNameData(computeFnOrOwner, void 0, computeFn), computeFn, void 0, void 0, strictEquals, debugLocation);
4096
+ return new Derived(new DebugNameData(void 0, void 0, computeFnOrOwner), computeFnOrOwner, void 0, void 0, strictEquals, debugLocation);
2216
4097
  }
2217
- /** Forward a child stream's lines to `log`, prefixed with `label`. */
2218
- function _pipeLines(stream, label, log) {
2219
- if (!stream) return;
2220
- stream.setEncoding("utf8");
2221
- stream.on("data", (chunk) => {
2222
- for (const line of chunk.split(/\r?\n/)) if (line.length > 0) log(`[${label}] ${line}`);
2223
- });
4098
+ function derivedOpts(options, computeFn, debugLocation = DebugLocation.ofCaller()) {
4099
+ return new Derived(new DebugNameData(options.owner, options.debugName, options.debugReferenceFn), computeFn, void 0, options.onLastObserverRemoved, options.equalsFn ?? strictEquals, debugLocation);
2224
4100
  }
2225
- /** Spawn a `cmd-stdio` target and talk linkrpc over its stdin/stdout. */
2226
- async function openStdioTransport(command, env, cwd, log) {
2227
- const child = spawnCommand(command, {
2228
- stdio: [
2229
- "pipe",
2230
- "pipe",
2231
- "pipe"
2232
- ],
2233
- ...env !== void 0 ? { env: {
2234
- ...process.env,
2235
- ...env
2236
- } } : {},
2237
- ...cwd !== void 0 ? { cwd } : {}
2238
- });
2239
- if (!child.stdin || !child.stdout) throw new Error("connect-as: cmd-stdio child exposes no stdio");
2240
- if (log) _pipeLines(child.stderr, "target", log);
2241
- const { transport } = await connectNdjson({
2242
- input: child.stdout,
2243
- output: child.stdin,
2244
- onClose: () => {
2245
- if (!child.killed) child.kill();
4101
+ _setDerivedOpts(derivedOpts);
4102
+ //#endregion
4103
+ //#region ../../node_modules/.pnpm/@vscode+observables@0.1.1-0/node_modules/@vscode/observables/dist/observableInternal/reactions/createEffectImpl.js
4104
+ function autorunStateToString(state) {
4105
+ switch (state) {
4106
+ case 1: return "dependenciesMightHaveChanged";
4107
+ case 2: return "stale";
4108
+ case 3: return "upToDate";
4109
+ default: return "<unknown>";
4110
+ }
4111
+ }
4112
+ var AutorunObserver = class {
4113
+ get debugName() {
4114
+ return this._debugNameData.getDebugName(this) ?? "(anonymous)";
4115
+ }
4116
+ constructor(_debugNameData, _runFn, _changeTracker, debugLocation) {
4117
+ this._debugNameData = _debugNameData;
4118
+ this._runFn = _runFn;
4119
+ this._changeTracker = _changeTracker;
4120
+ this._state = 2;
4121
+ this._updateCount = 0;
4122
+ this._disposed = false;
4123
+ this._dependencies = /* @__PURE__ */ new Set();
4124
+ this._dependenciesToBeRemoved = /* @__PURE__ */ new Set();
4125
+ this._isRunning = false;
4126
+ this._store = void 0;
4127
+ this._delayedStore = void 0;
4128
+ this._changeSummary = this._changeTracker?.createChangeSummary(void 0);
4129
+ getLogger()?.handleAutorunCreated(this, debugLocation);
4130
+ this._run();
4131
+ }
4132
+ dispose() {
4133
+ if (this._disposed) return;
4134
+ this._disposed = true;
4135
+ for (const o of this._dependencies) o.removeObserver(this);
4136
+ this._dependencies.clear();
4137
+ if (this._store !== void 0) this._store.dispose();
4138
+ if (this._delayedStore !== void 0) this._delayedStore.dispose();
4139
+ getLogger()?.handleAutorunDisposed(this);
4140
+ }
4141
+ _run() {
4142
+ const emptySet = this._dependenciesToBeRemoved;
4143
+ this._dependenciesToBeRemoved = this._dependencies;
4144
+ this._dependencies = emptySet;
4145
+ this._state = 3;
4146
+ try {
4147
+ if (!this._disposed) {
4148
+ getLogger()?.handleAutorunStarted(this);
4149
+ const changeSummary = this._changeSummary;
4150
+ const delayedStore = this._delayedStore;
4151
+ if (delayedStore !== void 0) this._delayedStore = void 0;
4152
+ try {
4153
+ this._isRunning = true;
4154
+ if (this._changeTracker) {
4155
+ this._changeTracker.beforeUpdate?.(this, changeSummary);
4156
+ this._changeSummary = this._changeTracker.createChangeSummary(changeSummary);
4157
+ }
4158
+ if (this._store !== void 0) {
4159
+ this._store.dispose();
4160
+ this._store = void 0;
4161
+ }
4162
+ this._runFn(this, changeSummary);
4163
+ } catch (e) {
4164
+ onBugIndicatingError(e);
4165
+ } finally {
4166
+ this._isRunning = false;
4167
+ if (delayedStore !== void 0) delayedStore.dispose();
4168
+ }
4169
+ }
4170
+ } finally {
4171
+ if (!this._disposed) getLogger()?.handleAutorunFinished(this);
4172
+ for (const o of this._dependenciesToBeRemoved) o.removeObserver(this);
4173
+ this._dependenciesToBeRemoved.clear();
2246
4174
  }
2247
- });
2248
- return {
2249
- transport,
2250
- onClose: (cb) => child.once("exit", cb),
2251
- dispose: () => {
2252
- transport.dispose();
2253
- if (!child.killed) child.kill();
4175
+ }
4176
+ toString() {
4177
+ return `Autorun<${this.debugName}>`;
4178
+ }
4179
+ beginUpdate(_observable) {
4180
+ if (this._state === 3) this._state = 1;
4181
+ this._updateCount++;
4182
+ }
4183
+ endUpdate(_observable) {
4184
+ try {
4185
+ if (this._updateCount === 1) do {
4186
+ if (this._state === 1) {
4187
+ this._state = 3;
4188
+ for (const d of this._dependencies) {
4189
+ d.reportChanges();
4190
+ if (this._state === 2) break;
4191
+ }
4192
+ }
4193
+ if (this._state !== 3) this._run();
4194
+ } while (this._state !== 3);
4195
+ } finally {
4196
+ this._updateCount--;
2254
4197
  }
2255
- };
2256
- }
2257
- /**
2258
- * Spawn a `cmd-env` target against a private socket the CLI listens on, and
2259
- * take the single connection it dials back. This is the sole role-inverted
2260
- * case: the target dials the CLI, so we accept rather than dial. No hub /
2261
- * acceptor is needed — just the raw post-handshake transport to splice.
2262
- */
2263
- async function openAcceptOneTransport(command, env, cwd, log) {
2264
- const socketPath = SocketServer.allocSocketPath();
2265
- const token = randomBytes(16).toString("hex");
2266
- const server = await SocketServer.start({ endpoint: socketPath });
2267
- const accepted = new Promise((resolve) => {
2268
- server.setConnectionHandler((t) => resolve(t));
2269
- });
2270
- const child = spawnCommand(command, {
2271
- stdio: [
2272
- "ignore",
2273
- "pipe",
2274
- "pipe"
2275
- ],
2276
- env: {
2277
- ...process.env,
2278
- ...env,
2279
- LINKRPC_ENDPOINT: socketPath,
2280
- LINKRPC_TOKEN: token
2281
- },
2282
- ...cwd !== void 0 ? { cwd } : {}
2283
- });
2284
- if (log) {
2285
- _pipeLines(child.stdout, "target", log);
2286
- _pipeLines(child.stderr, "target", log);
2287
4198
  }
2288
- let exited = false;
2289
- child.once("exit", () => {
2290
- exited = true;
2291
- });
2292
- const transport = await Promise.race([accepted, new Promise((_resolve, reject) => {
2293
- child.once("exit", (code) => reject(/* @__PURE__ */ new Error(`connect-as: cmd-env child exited (code ${code ?? "?"}) before connecting`)));
2294
- })]).catch((err) => {
2295
- server.dispose();
2296
- _unlink(socketPath);
2297
- if (!child.killed) child.kill();
2298
- throw err;
2299
- });
2300
- const dispose = () => {
2301
- transport.dispose();
2302
- server.dispose();
2303
- _unlink(socketPath);
2304
- if (!exited && !child.killed) child.kill();
2305
- };
2306
- return {
2307
- transport,
2308
- onClose: (cb) => {
2309
- transport.onDidClose(cb);
2310
- child.once("exit", cb);
2311
- },
2312
- dispose
2313
- };
2314
- }
2315
- /** Open the target-facing transport for any resolved endpoint kind. */
2316
- function openTargetTransport(endpoint, log) {
2317
- switch (endpoint.kind) {
2318
- case "cmd-env": return openAcceptOneTransport(endpoint.command, endpoint.env, endpoint.cwd, log);
2319
- case "cmd-stdio": return openStdioTransport(endpoint.command, endpoint.env, endpoint.cwd, log);
2320
- case "ws":
2321
- case "socket": return openDialTransport(endpoint, endpoint.token ?? "");
4199
+ handlePossibleChange(observable) {
4200
+ if (this._state === 3 && this._isDependency(observable)) this._state = 1;
4201
+ }
4202
+ handleChange(observable, change) {
4203
+ if (this._isDependency(observable)) {
4204
+ getLogger()?.handleAutorunDependencyChanged(this, observable, change);
4205
+ try {
4206
+ if (this._changeTracker ? this._changeTracker.handleChange({
4207
+ changedObservable: observable,
4208
+ change,
4209
+ didChange: (o) => o === observable
4210
+ }, this._changeSummary) : true) this._state = 2;
4211
+ } catch (e) {
4212
+ onBugIndicatingError(e);
4213
+ }
4214
+ }
2322
4215
  }
2323
- }
2324
- function _unlink(socketPath) {
2325
- if (process.platform === "win32") return;
2326
- try {
2327
- fs.unlinkSync(socketPath);
2328
- } catch {}
2329
- }
4216
+ _isDependency(observable) {
4217
+ return this._dependencies.has(observable) && !this._dependenciesToBeRemoved.has(observable);
4218
+ }
4219
+ _ensureNoRunning() {
4220
+ if (!this._isRunning) throw new BugIndicatingError("The reader object cannot be used outside its compute function!");
4221
+ }
4222
+ readObservable(observable) {
4223
+ this._ensureNoRunning();
4224
+ if (this._disposed) return observable.get();
4225
+ observable.addObserver(this);
4226
+ const value = observable.get();
4227
+ this._dependencies.add(observable);
4228
+ this._dependenciesToBeRemoved.delete(observable);
4229
+ return value;
4230
+ }
4231
+ get store() {
4232
+ this._ensureNoRunning();
4233
+ if (this._disposed) throw new BugIndicatingError("Cannot access store after dispose");
4234
+ if (this._store === void 0) this._store = new DisposableStore();
4235
+ return this._store;
4236
+ }
4237
+ get delayedStore() {
4238
+ this._ensureNoRunning();
4239
+ if (this._disposed) throw new BugIndicatingError("Cannot access store after dispose");
4240
+ if (this._delayedStore === void 0) this._delayedStore = new DisposableStore();
4241
+ return this._delayedStore;
4242
+ }
4243
+ debugGetState() {
4244
+ return {
4245
+ isRunning: this._isRunning,
4246
+ updateCount: this._updateCount,
4247
+ dependencies: this._dependencies,
4248
+ state: this._state,
4249
+ stateStr: autorunStateToString(this._state)
4250
+ };
4251
+ }
4252
+ debugRerun() {
4253
+ if (!this._isRunning) this._run();
4254
+ else this._state = 2;
4255
+ }
4256
+ };
2330
4257
  //#endregion
2331
- //#region src/commands/mcpForward.ts
4258
+ //#region ../../node_modules/.pnpm/@vscode+observables@0.1.1-0/node_modules/@vscode/observables/dist/observableInternal/reactions/createEffect.js
2332
4259
  /**
2333
- * Front door for claiming a serviceId within the connection's granted
2334
- * namespace — interface-form `hubGrantedServiceId::register`. Same claim the
2335
- * `tunnel` command uses.
4260
+ * Runs immediately and whenever a transaction ends and an observed observable changed.
4261
+ * {@link fn} should start with a JS Doc using `@description` to name the effect.
2336
4262
  */
2337
- const CLAIM_METHOD = `${hubGrantedServiceIdInterface.info.id}::register`;
2338
- /** Read-only connection facts — `hubGrantedServiceId::get`. */
2339
- const INFO_METHOD = `${hubGrantedServiceIdInterface.info.id}::get`;
4263
+ function createEffect(fn, debugLocation = DebugLocation.ofCaller()) {
4264
+ return new AutorunObserver(new DebugNameData(void 0, void 0, fn), fn, void 0, debugLocation);
4265
+ }
2340
4266
  /**
2341
- * Run a transparent MCP forwarder until stopped.
2342
- *
2343
- * Serves {@link mcpForwardInterface} under `serviceId` on the hub: each inbound
2344
- * `connect` request spawns a fresh MCP server child and shuttles raw JSON-RPC
2345
- * frames between the child's stdio (newline-delimited JSON, the MCP stdio
2346
- * transport framing) and the request's duplex stream. The forwarder never
2347
- * parses MCP semantics — it only splits/joins NDJSON lines so each line becomes
2348
- * one opaque `frame`. {@link LinkRpcConnection.enableReflection} is enabled so
2349
- * the hub's directory can discover the service.
2350
- *
2351
- * Resolves when {@link McpForwardOptions.stop} resolves (or on SIGINT/SIGTERM).
4267
+ * @deprecated Use {@link createEffect} instead.
2352
4268
  */
2353
- async function mcpForwardCommand(opts) {
2354
- const log = opts.log ?? ((line) => process.stderr.write(line + "\n"));
2355
- const serviceId = opts.serviceId ?? await _resolveGrantedServiceId(opts.local);
2356
- const conn = new LinkRpcConnection(opts.local.rpcChannel);
2357
- conn.register(mcpForwardInterface, { connect: (_params, _ctx, stream) => _runLeg(opts.command, opts.env, stream, log) }, { serviceId });
2358
- conn.enableReflection({ serviceId });
2359
- await opts.local.channel.sendRequest(CLAIM_METHOD, { serviceId });
2360
- log(`mcp-forward: claimed '${serviceId}' — forwarding MCP stdio to ${_describe(opts.command)}`);
2361
- await (opts.stop ?? _untilSignalled$1());
2362
- log("mcp-forward: stopping");
4269
+ const autorun = createEffect;
4270
+ //#endregion
4271
+ //#region ../../node_modules/.pnpm/@vscode+observables@0.1.1-0/node_modules/@vscode/observables/dist/observableInternal/base.js
4272
+ /**
4273
+ * This function is used to indicate that the caller recovered from an error that indicates a bug.
4274
+ */
4275
+ function handleBugIndicatingErrorRecovery(message) {
4276
+ const err = /* @__PURE__ */ new Error("BugIndicatingErrorRecovery: " + message);
4277
+ onUnexpectedError(err);
4278
+ console.error("recovered from an error that indicates a bug", err);
2363
4279
  }
4280
+ //#endregion
4281
+ //#region ../../node_modules/.pnpm/@vscode+observables@0.1.1-0/node_modules/@vscode/observables/dist/observableInternal/transaction.js
2364
4282
  /**
2365
- * Resolve the connection's granted serviceId namespace
2366
- * (`hubGrantedServiceId::get`) to use as the default forward prefix. Throws if
2367
- * the namespace is empty ("claim nothing freely") — the caller must then pass an
2368
- * explicit `--serviceId`.
4283
+ * Starts a transaction in which many observables can be changed at once.
4284
+ * {@link fn} should start with a JS Doc using `@description` to give the transaction a debug name.
4285
+ * Reaction run on demand or when the transaction ends.
2369
4286
  */
2370
- async function _resolveGrantedServiceId(local) {
2371
- const granted = (await local.channel.sendRequest(INFO_METHOD, {}))?.grantedServiceIdNamespace ?? "";
2372
- if (granted.length === 0) throw new Error("mcp-forward: this connection has no granted serviceId namespace to claim; pass an explicit --serviceId.");
2373
- return granted;
4287
+ function transaction(fn, getDebugName) {
4288
+ const tx = new TransactionImpl(fn, getDebugName);
4289
+ try {
4290
+ fn(tx);
4291
+ } finally {
4292
+ tx.finish();
4293
+ }
2374
4294
  }
2375
4295
  /**
2376
- * One MCP session leg: spawn the child, bridge stdio<->stream, and resolve when
2377
- * the child exits or the caller cancels (killing the child).
4296
+ * Allows to chain transactions.
2378
4297
  */
2379
- function _runLeg(command, env, stream, log) {
2380
- return new Promise((resolve) => {
2381
- const child = spawnCommand(command, {
2382
- stdio: [
2383
- "pipe",
2384
- "pipe",
2385
- "inherit"
2386
- ],
2387
- ...env !== void 0 ? { env: {
2388
- ...process.env,
2389
- ...env
2390
- } } : {}
2391
- });
2392
- if (!child.stdin || !child.stdout) {
2393
- log("mcp-forward: child exposes no stdio");
2394
- if (!child.killed) child.kill();
2395
- resolve({});
4298
+ function subtransaction(tx, fn, getDebugName) {
4299
+ if (!tx) transaction(fn, getDebugName);
4300
+ else fn(tx);
4301
+ }
4302
+ var TransactionImpl = class {
4303
+ constructor(_fn, _getDebugName) {
4304
+ this._fn = _fn;
4305
+ this._getDebugName = _getDebugName;
4306
+ this._updatingObservers = [];
4307
+ getLogger()?.handleBeginTransaction(this);
4308
+ }
4309
+ getDebugName() {
4310
+ if (this._getDebugName) return this._getDebugName();
4311
+ return getFunctionName(this._fn);
4312
+ }
4313
+ updateObserver(observer, observable) {
4314
+ if (!this._updatingObservers) {
4315
+ handleBugIndicatingErrorRecovery("Transaction already finished!");
4316
+ transaction((tx) => {
4317
+ tx.updateObserver(observer, observable);
4318
+ });
2396
4319
  return;
2397
4320
  }
2398
- let settled = false;
2399
- const rl = readline.createInterface({ input: child.stdout });
2400
- const finish = () => {
2401
- if (settled) return;
2402
- settled = true;
2403
- rl.close();
2404
- if (!child.killed) child.kill();
2405
- resolve({});
2406
- };
2407
- rl.on("line", (line) => {
2408
- const trimmed = line.trim();
2409
- if (trimmed.length === 0) return;
2410
- let frame;
2411
- try {
2412
- frame = JSON.parse(trimmed);
2413
- } catch {
2414
- return;
2415
- }
2416
- if (stream.signal.aborted) return;
2417
- stream.send({ frame });
2418
- });
2419
- stream.onMessage(({ frame }) => {
2420
- if (!child.stdin || child.stdin.destroyed) return;
2421
- child.stdin.write(JSON.stringify(frame) + "\n");
4321
+ this._updatingObservers.push({
4322
+ observer,
4323
+ observable
2422
4324
  });
2423
- if (stream.signal.aborted) {
2424
- finish();
4325
+ observer.beginUpdate(observable);
4326
+ }
4327
+ finish() {
4328
+ const updatingObservers = this._updatingObservers;
4329
+ if (!updatingObservers) {
4330
+ handleBugIndicatingErrorRecovery("transaction.finish() has already been called!");
2425
4331
  return;
2426
4332
  }
2427
- stream.signal.addEventListener("abort", finish, { once: true });
2428
- child.on("exit", finish);
2429
- child.on("error", (err) => {
2430
- log(`mcp-forward: child error: ${String(err)}`);
2431
- finish();
4333
+ for (let i = 0; i < updatingObservers.length; i++) {
4334
+ const { observer, observable } = updatingObservers[i];
4335
+ observer.endUpdate(observable);
4336
+ }
4337
+ this._updatingObservers = null;
4338
+ getLogger()?.handleEndTransaction(this);
4339
+ }
4340
+ debugGetUpdatingObservers() {
4341
+ return this._updatingObservers;
4342
+ }
4343
+ };
4344
+ //#endregion
4345
+ //#region ../../node_modules/.pnpm/@vscode+observables@0.1.1-0/node_modules/@vscode/observables/dist/observableInternal/observables/observableValue.js
4346
+ function observableValue(nameOrOwner, initialValue, debugLocation = DebugLocation.ofCaller()) {
4347
+ let debugNameData;
4348
+ if (typeof nameOrOwner === "string") debugNameData = new DebugNameData(void 0, nameOrOwner, void 0);
4349
+ else debugNameData = new DebugNameData(nameOrOwner, void 0, void 0);
4350
+ return new ObservableValue(debugNameData, initialValue, strictEquals, debugLocation);
4351
+ }
4352
+ var ObservableValue = class extends BaseObservable {
4353
+ get debugName() {
4354
+ return this._debugNameData.getDebugName(this) ?? "ObservableValue";
4355
+ }
4356
+ constructor(_debugNameData, initialValue, _equalityComparator, debugLocation) {
4357
+ super(debugLocation);
4358
+ this._debugNameData = _debugNameData;
4359
+ this._equalityComparator = _equalityComparator;
4360
+ this._value = initialValue;
4361
+ getLogger()?.handleObservableUpdated(this, {
4362
+ hadValue: false,
4363
+ newValue: initialValue,
4364
+ change: void 0,
4365
+ didChange: true,
4366
+ oldValue: void 0
2432
4367
  });
2433
- });
2434
- }
2435
- /** Resolve on the next `SIGINT` / `SIGTERM`. */
2436
- function _untilSignalled$1() {
2437
- return new Promise((resolve) => {
2438
- const done = () => {
2439
- process.removeListener("SIGINT", done);
2440
- process.removeListener("SIGTERM", done);
2441
- resolve();
4368
+ }
4369
+ get() {
4370
+ return this._value;
4371
+ }
4372
+ set(value, tx, change) {
4373
+ if (change === void 0 && this._equalityComparator(this._value, value)) return;
4374
+ let _tx;
4375
+ if (!tx) tx = _tx = new TransactionImpl(() => {}, () => `Setting ${this.debugName}`);
4376
+ try {
4377
+ const oldValue = this._value;
4378
+ this._setValue(value);
4379
+ getLogger()?.handleObservableUpdated(this, {
4380
+ oldValue,
4381
+ newValue: value,
4382
+ change,
4383
+ didChange: true,
4384
+ hadValue: true
4385
+ });
4386
+ for (const observer of this._observers) {
4387
+ tx.updateObserver(observer, this);
4388
+ observer.handleChange(this, change);
4389
+ }
4390
+ } finally {
4391
+ if (_tx) _tx.finish();
4392
+ }
4393
+ }
4394
+ toString() {
4395
+ return `${this.debugName}: ${this._value}`;
4396
+ }
4397
+ _setValue(newValue) {
4398
+ this._value = newValue;
4399
+ }
4400
+ debugGetState() {
4401
+ return { value: this._value };
4402
+ }
4403
+ debugSetValue(value) {
4404
+ this._value = value;
4405
+ }
4406
+ };
4407
+ //#endregion
4408
+ //#region ../../node_modules/.pnpm/@vscode+observables@0.1.1-0/node_modules/@vscode/observables/dist/observableInternal/observables/observableFromEvent.js
4409
+ function observableFromEvent(...args) {
4410
+ let owner;
4411
+ let event;
4412
+ let getValue;
4413
+ let debugLocation;
4414
+ if (args.length === 2) [event, getValue] = args;
4415
+ else [owner, event, getValue, debugLocation] = args;
4416
+ return new FromEventObservable(new DebugNameData(owner, void 0, getValue), event, getValue, () => FromEventObservable.globalTransaction, strictEquals, debugLocation ?? DebugLocation.ofCaller());
4417
+ }
4418
+ var FromEventObservable = class extends BaseObservable {
4419
+ constructor(_debugNameData, event, _getValue, _getTransaction, _equalityComparator, debugLocation) {
4420
+ super(debugLocation);
4421
+ this._debugNameData = _debugNameData;
4422
+ this.event = event;
4423
+ this._getValue = _getValue;
4424
+ this._getTransaction = _getTransaction;
4425
+ this._equalityComparator = _equalityComparator;
4426
+ this._hasValue = false;
4427
+ this.handleEvent = (args) => {
4428
+ const newValue = this._getValue(args);
4429
+ const oldValue = this._value;
4430
+ const didChange = !this._hasValue || !this._equalityComparator(oldValue, newValue);
4431
+ let didRunTransaction = false;
4432
+ if (didChange) {
4433
+ this._value = newValue;
4434
+ if (this._hasValue) {
4435
+ didRunTransaction = true;
4436
+ subtransaction(this._getTransaction(), (tx) => {
4437
+ getLogger()?.handleObservableUpdated(this, {
4438
+ oldValue,
4439
+ newValue,
4440
+ change: void 0,
4441
+ didChange,
4442
+ hadValue: this._hasValue
4443
+ });
4444
+ for (const o of this._observers) {
4445
+ tx.updateObserver(o, this);
4446
+ o.handleChange(this, void 0);
4447
+ }
4448
+ }, () => {
4449
+ const name = this.getDebugName();
4450
+ return "Event fired" + (name ? `: ${name}` : "");
4451
+ });
4452
+ }
4453
+ this._hasValue = true;
4454
+ }
4455
+ if (!didRunTransaction) getLogger()?.handleObservableUpdated(this, {
4456
+ oldValue,
4457
+ newValue,
4458
+ change: void 0,
4459
+ didChange,
4460
+ hadValue: this._hasValue
4461
+ });
2442
4462
  };
2443
- process.once("SIGINT", done);
2444
- process.once("SIGTERM", done);
4463
+ }
4464
+ getDebugName() {
4465
+ return this._debugNameData.getDebugName(this);
4466
+ }
4467
+ get debugName() {
4468
+ const name = this.getDebugName();
4469
+ return "From Event" + (name ? `: ${name}` : "");
4470
+ }
4471
+ onFirstObserverAdded() {
4472
+ this._subscription = this.event(this.handleEvent);
4473
+ }
4474
+ onLastObserverRemoved() {
4475
+ this._subscription.dispose();
4476
+ this._subscription = void 0;
4477
+ this._hasValue = false;
4478
+ this._value = void 0;
4479
+ }
4480
+ get() {
4481
+ if (this._subscription) {
4482
+ if (!this._hasValue) this.handleEvent(void 0);
4483
+ return this._value;
4484
+ } else return this._getValue(void 0);
4485
+ }
4486
+ debugSetValue(value) {
4487
+ this._value = value;
4488
+ }
4489
+ debugGetState() {
4490
+ return {
4491
+ value: this._value,
4492
+ hasValue: this._hasValue
4493
+ };
4494
+ }
4495
+ };
4496
+ (function(observableFromEvent) {
4497
+ observableFromEvent.Observer = FromEventObservable;
4498
+ function batchEventsGlobally(tx, fn) {
4499
+ let didSet = false;
4500
+ if (FromEventObservable.globalTransaction === void 0) {
4501
+ FromEventObservable.globalTransaction = tx;
4502
+ didSet = true;
4503
+ }
4504
+ try {
4505
+ fn();
4506
+ } finally {
4507
+ if (didSet) FromEventObservable.globalTransaction = void 0;
4508
+ }
4509
+ }
4510
+ observableFromEvent.batchEventsGlobally = batchEventsGlobally;
4511
+ })(observableFromEvent || (observableFromEvent = {}));
4512
+ //#endregion
4513
+ //#region ../../node_modules/.pnpm/@vscode+observables@0.1.1-0/node_modules/@vscode/observables/dist/observableInternal/utils/utils.js
4514
+ /**
4515
+ * This makes sure the observable is being observed and keeps its cache alive.
4516
+ */
4517
+ function keepObserved(observable) {
4518
+ const o = new KeepAliveObserver(false, void 0);
4519
+ observable.addObserver(o);
4520
+ return toDisposable(() => {
4521
+ observable.removeObserver(o);
2445
4522
  });
2446
4523
  }
2447
- /** One-line description of the spawned command for logs. */
2448
- function _describe(command) {
2449
- return "argv" in command ? command.argv.join(" ") : command.command;
4524
+ _setKeepObserved(keepObserved);
4525
+ /**
4526
+ * This converts the given observable into an autorun.
4527
+ */
4528
+ function recomputeInitiallyAndOnChange(observable, handleValue) {
4529
+ const o = new KeepAliveObserver(true, handleValue);
4530
+ observable.addObserver(o);
4531
+ try {
4532
+ o.beginUpdate(observable);
4533
+ } finally {
4534
+ o.endUpdate(observable);
4535
+ }
4536
+ return toDisposable(() => {
4537
+ observable.removeObserver(o);
4538
+ });
2450
4539
  }
4540
+ _setRecomputeInitiallyAndOnChange(recomputeInitiallyAndOnChange);
4541
+ var KeepAliveObserver = class {
4542
+ constructor(_forceRecompute, _handleValue) {
4543
+ this._forceRecompute = _forceRecompute;
4544
+ this._handleValue = _handleValue;
4545
+ this._counter = 0;
4546
+ }
4547
+ beginUpdate(observable) {
4548
+ this._counter++;
4549
+ }
4550
+ endUpdate(observable) {
4551
+ if (this._counter === 1 && this._forceRecompute) {
4552
+ if (this._handleValue) this._handleValue(observable.get());
4553
+ else observable.reportChanges();
4554
+ }
4555
+ this._counter--;
4556
+ }
4557
+ handlePossibleChange(observable) {}
4558
+ handleChange(observable, change) {}
4559
+ };
2451
4560
  //#endregion
2452
- //#region src/commands/jsonRpcStdio.ts
2453
- /** Expose a remote `jsonRpcConnection` service as an NDJSON stdio endpoint. */
2454
- async function jsonRpcStdioCommand(options) {
2455
- const client = new LinkRpcConnection(options.local.rpcChannel).service(options.serviceId).get(jsonRpcConnectionInterface);
2456
- const remote = await connectRawJsonRpcTransport(client, options.params);
2457
- const stdio = createNdjsonJsonRpcTransport({
2458
- input: options.input ?? process.stdin,
2459
- output: options.output ?? process.stdout
2460
- });
2461
- const bridge = connectJsonRpcTransports(remote, stdio);
2462
- await new Promise((resolve) => {
2463
- let settled = false;
2464
- const finish = () => {
2465
- if (settled) return;
2466
- settled = true;
2467
- resolve();
2468
- };
2469
- remote.onClose(finish);
2470
- stdio.onClose(finish);
4561
+ //#region ../../node_modules/.pnpm/@vscode+observables@0.1.1-0/node_modules/@vscode/observables/dist/observableInternal/logging/consoleObservableLogger.js
4562
+ let consoleObservableLogger;
4563
+ function logObservableToConsole(obs) {
4564
+ if (!consoleObservableLogger) {
4565
+ consoleObservableLogger = new ConsoleObservableLogger();
4566
+ addLogger(consoleObservableLogger);
4567
+ }
4568
+ consoleObservableLogger.addFilteredObj(obs);
4569
+ }
4570
+ var ConsoleObservableLogger = class {
4571
+ constructor() {
4572
+ this.indentation = 0;
4573
+ this.changedObservablesSets = /* @__PURE__ */ new WeakMap();
4574
+ }
4575
+ addFilteredObj(obj) {
4576
+ if (!this._filteredObjects) this._filteredObjects = /* @__PURE__ */ new Set();
4577
+ this._filteredObjects.add(obj);
4578
+ }
4579
+ _isIncluded(obj) {
4580
+ return this._filteredObjects?.has(obj) ?? true;
4581
+ }
4582
+ textToConsoleArgs(text) {
4583
+ return consoleTextToArgs([normalText(repeat("| ", this.indentation)), text]);
4584
+ }
4585
+ formatInfo(info) {
4586
+ if (!info.hadValue) return [
4587
+ normalText(` `),
4588
+ styled(formatValue(info.newValue, 60), { color: "green" }),
4589
+ normalText(` (initial)`)
4590
+ ];
4591
+ return info.didChange ? [
4592
+ normalText(` `),
4593
+ styled(formatValue(info.oldValue, 70), {
4594
+ color: "red",
4595
+ strikeThrough: true
4596
+ }),
4597
+ normalText(` `),
4598
+ styled(formatValue(info.newValue, 60), { color: "green" })
4599
+ ] : [normalText(` (unchanged)`)];
4600
+ }
4601
+ handleObservableCreated(observable) {
4602
+ if (observable instanceof Derived) {
4603
+ const derived = observable;
4604
+ this.changedObservablesSets.set(derived, /* @__PURE__ */ new Set());
4605
+ }
4606
+ }
4607
+ handleOnListenerCountChanged(observable, newCount) {}
4608
+ handleObservableUpdated(observable, info) {
4609
+ if (!this._isIncluded(observable)) return;
4610
+ if (observable instanceof Derived) {
4611
+ this._handleDerivedRecomputed(observable, info);
4612
+ return;
4613
+ }
4614
+ console.log(...this.textToConsoleArgs([
4615
+ formatKind("observable value changed"),
4616
+ styled(observable.debugName, { color: "BlueViolet" }),
4617
+ ...this.formatInfo(info)
4618
+ ]));
4619
+ }
4620
+ formatChanges(changes) {
4621
+ if (changes.size === 0) return;
4622
+ return styled(" (changed deps: " + [...changes].map((o) => o.debugName).join(", ") + ")", { color: "gray" });
4623
+ }
4624
+ handleDerivedDependencyChanged(derived, observable, change) {
4625
+ if (!this._isIncluded(derived)) return;
4626
+ this.changedObservablesSets.get(derived)?.add(observable);
4627
+ }
4628
+ _handleDerivedRecomputed(derived, info) {
4629
+ if (!this._isIncluded(derived)) return;
4630
+ const changedObservables = this.changedObservablesSets.get(derived);
4631
+ if (!changedObservables) return;
4632
+ console.log(...this.textToConsoleArgs([
4633
+ formatKind("derived recomputed"),
4634
+ styled(derived.debugName, { color: "BlueViolet" }),
4635
+ ...this.formatInfo(info),
4636
+ this.formatChanges(changedObservables),
4637
+ { data: [{ fn: derived._debugNameData.referenceFn ?? derived._computeFn }] }
4638
+ ]));
4639
+ changedObservables.clear();
4640
+ }
4641
+ handleDerivedCleared(derived) {
4642
+ if (!this._isIncluded(derived)) return;
4643
+ console.log(...this.textToConsoleArgs([formatKind("derived cleared"), styled(derived.debugName, { color: "BlueViolet" })]));
4644
+ }
4645
+ handleFromEventObservableTriggered(observable, info) {
4646
+ if (!this._isIncluded(observable)) return;
4647
+ console.log(...this.textToConsoleArgs([
4648
+ formatKind("observable from event triggered"),
4649
+ styled(observable.debugName, { color: "BlueViolet" }),
4650
+ ...this.formatInfo(info),
4651
+ { data: [{ fn: observable._getValue }] }
4652
+ ]));
4653
+ }
4654
+ handleAutorunCreated(autorun) {
4655
+ if (!this._isIncluded(autorun)) return;
4656
+ this.changedObservablesSets.set(autorun, /* @__PURE__ */ new Set());
4657
+ }
4658
+ handleAutorunDisposed(autorun) {}
4659
+ handleAutorunDependencyChanged(autorun, observable, change) {
4660
+ if (!this._isIncluded(autorun)) return;
4661
+ this.changedObservablesSets.get(autorun).add(observable);
4662
+ }
4663
+ handleAutorunStarted(autorun) {
4664
+ const changedObservables = this.changedObservablesSets.get(autorun);
4665
+ if (!changedObservables) return;
4666
+ if (this._isIncluded(autorun)) console.log(...this.textToConsoleArgs([
4667
+ formatKind("autorun"),
4668
+ styled(autorun.debugName, { color: "BlueViolet" }),
4669
+ this.formatChanges(changedObservables),
4670
+ { data: [{ fn: autorun._debugNameData.referenceFn ?? autorun._runFn }] }
4671
+ ]));
4672
+ changedObservables.clear();
4673
+ this.indentation++;
4674
+ }
4675
+ handleAutorunFinished(autorun) {
4676
+ this.indentation--;
4677
+ }
4678
+ handleBeginTransaction(transaction) {
4679
+ let transactionName = transaction.getDebugName();
4680
+ if (transactionName === void 0) transactionName = "";
4681
+ if (this._isIncluded(transaction)) console.log(...this.textToConsoleArgs([
4682
+ formatKind("transaction"),
4683
+ styled(transactionName, { color: "BlueViolet" }),
4684
+ { data: [{ fn: transaction._fn }] }
4685
+ ]));
4686
+ this.indentation++;
4687
+ }
4688
+ handleEndTransaction() {
4689
+ this.indentation--;
4690
+ }
4691
+ };
4692
+ function consoleTextToArgs(text) {
4693
+ const styles = new Array();
4694
+ const data = [];
4695
+ let firstArg = "";
4696
+ function process(t) {
4697
+ if ("length" in t) {
4698
+ for (const item of t) if (item) process(item);
4699
+ } else if ("text" in t) {
4700
+ firstArg += `%c${t.text}`;
4701
+ styles.push(t.style);
4702
+ if (t.data) data.push(...t.data);
4703
+ } else if ("data" in t) data.push(...t.data);
4704
+ }
4705
+ process(text);
4706
+ const result = [firstArg, ...styles];
4707
+ result.push(...data);
4708
+ return result;
4709
+ }
4710
+ function normalText(text) {
4711
+ return styled(text, { color: "black" });
4712
+ }
4713
+ function formatKind(kind) {
4714
+ return styled(padStr(`${kind}: `, 10), {
4715
+ color: "black",
4716
+ bold: true
2471
4717
  });
2472
- bridge.dispose();
2473
- remote.close("closed");
2474
- stdio.close("closed");
2475
4718
  }
4719
+ function styled(text, options = { color: "black" }) {
4720
+ function objToCss(styleObj) {
4721
+ return Object.entries(styleObj).reduce((styleString, [propName, propValue]) => {
4722
+ return `${styleString}${propName}:${propValue};`;
4723
+ }, "");
4724
+ }
4725
+ const style = { color: options.color };
4726
+ if (options.strikeThrough) style["text-decoration"] = "line-through";
4727
+ if (options.bold) style["font-weight"] = "bold";
4728
+ return {
4729
+ text,
4730
+ style: objToCss(style)
4731
+ };
4732
+ }
4733
+ function formatValue(value, availableLen) {
4734
+ try {
4735
+ return formatValueUnsafe(value, availableLen);
4736
+ } catch {
4737
+ return "[[???]]";
4738
+ }
4739
+ }
4740
+ function formatValueUnsafe(value, availableLen) {
4741
+ switch (typeof value) {
4742
+ case "number": return "" + value;
4743
+ case "string":
4744
+ if (value.length + 2 <= availableLen) return `"${value}"`;
4745
+ return `"${value.substr(0, availableLen - 7)}"+...`;
4746
+ case "boolean": return value ? "true" : "false";
4747
+ case "undefined": return "undefined";
4748
+ case "object":
4749
+ if (value === null) return "null";
4750
+ if (Array.isArray(value)) return formatArray(value, availableLen);
4751
+ return formatObject(value, availableLen);
4752
+ case "symbol": return value.toString();
4753
+ case "function": return `[[Function${value.name ? " " + value.name : ""}]]`;
4754
+ default: return "" + value;
4755
+ }
4756
+ }
4757
+ function formatArray(value, availableLen) {
4758
+ let result = "[ ";
4759
+ let first = true;
4760
+ for (const val of value) {
4761
+ if (!first) result += ", ";
4762
+ if (result.length - 5 > availableLen) {
4763
+ result += "...";
4764
+ break;
4765
+ }
4766
+ first = false;
4767
+ result += `${formatValue(val, availableLen - result.length)}`;
4768
+ }
4769
+ result += " ]";
4770
+ return result;
4771
+ }
4772
+ function formatObject(value, availableLen) {
4773
+ try {
4774
+ if (typeof value.toString === "function" && value.toString !== Object.prototype.toString) {
4775
+ const val = value.toString();
4776
+ if (val.length <= availableLen) return val;
4777
+ return val.substring(0, availableLen - 3) + "...";
4778
+ }
4779
+ } catch {
4780
+ return "[[Object]]";
4781
+ }
4782
+ const className = getClassName(value);
4783
+ let result = className ? className + "(" : "{ ";
4784
+ let first = true;
4785
+ let entries;
4786
+ try {
4787
+ entries = Object.entries(value);
4788
+ } catch {
4789
+ return className ? `${className}(...)` : "[[Object]]";
4790
+ }
4791
+ for (const [key, val] of entries) {
4792
+ if (!first) result += ", ";
4793
+ if (result.length - 5 > availableLen) {
4794
+ result += "...";
4795
+ break;
4796
+ }
4797
+ first = false;
4798
+ result += `${key}: ${formatValue(val, availableLen - result.length)}`;
4799
+ }
4800
+ result += className ? ")" : " }";
4801
+ return result;
4802
+ }
4803
+ function repeat(str, count) {
4804
+ let result = "";
4805
+ for (let i = 1; i <= count; i++) result += str;
4806
+ return result;
4807
+ }
4808
+ function padStr(str, length) {
4809
+ while (str.length < length) str += " ";
4810
+ return str;
4811
+ }
4812
+ //#endregion
4813
+ //#region ../../node_modules/.pnpm/@vscode+observables@0.1.1-0/node_modules/@vscode/observables/dist/observableInternal/logging/debugGetDependencyGraph.js
4814
+ function debugGetDependencyGraph(obs, options) {
4815
+ const debugNamePostProcessor = options?.debugNamePostProcessor ?? ((str) => str);
4816
+ const info = Info.from(obs, debugNamePostProcessor);
4817
+ if (!info) return "";
4818
+ return formatObservableInfo(info, 0, /* @__PURE__ */ new Set()).trim();
4819
+ }
4820
+ function formatObservableInfo(info, indentLevel, alreadyListed) {
4821
+ const indent = " ".repeat(indentLevel);
4822
+ const lines = [];
4823
+ if (alreadyListed.has(info.sourceObj)) {
4824
+ lines.push(`${indent}* ${info.type} ${info.name} (already listed)`);
4825
+ return lines.join("\n");
4826
+ }
4827
+ alreadyListed.add(info.sourceObj);
4828
+ lines.push(`${indent}* ${info.type} ${info.name}:`);
4829
+ lines.push(`${indent} value: ${formatValue(info.value, 50)}`);
4830
+ lines.push(`${indent} state: ${info.state}`);
4831
+ if (info.dependencies.length > 0) {
4832
+ lines.push(`${indent} dependencies:`);
4833
+ for (const dep of info.dependencies) lines.push(formatObservableInfo(dep, indentLevel + 1, alreadyListed));
4834
+ }
4835
+ return lines.join("\n");
4836
+ }
4837
+ var Info = class Info {
4838
+ static from(obs, debugNamePostProcessor) {
4839
+ if (obs instanceof AutorunObserver) {
4840
+ const state = obs.debugGetState();
4841
+ return new Info(obs, debugNamePostProcessor(obs.debugName), "autorun", void 0, state.stateStr, Array.from(state.dependencies).map((dep) => Info.from(dep, debugNamePostProcessor) || Info.unknown(dep)));
4842
+ } else if (obs instanceof Derived) {
4843
+ const state = obs.debugGetState();
4844
+ return new Info(obs, debugNamePostProcessor(obs.debugName), "derived", state.value, state.stateStr, Array.from(state.dependencies).map((dep) => Info.from(dep, debugNamePostProcessor) || Info.unknown(dep)));
4845
+ } else if (obs instanceof ObservableValue) {
4846
+ const state = obs.debugGetState();
4847
+ return new Info(obs, debugNamePostProcessor(obs.debugName), "observableValue", state.value, "upToDate", []);
4848
+ } else if (obs instanceof FromEventObservable) {
4849
+ const state = obs.debugGetState();
4850
+ return new Info(obs, debugNamePostProcessor(obs.debugName), "fromEvent", state.value, state.hasValue ? "upToDate" : "initial", []);
4851
+ }
4852
+ }
4853
+ static unknown(obs) {
4854
+ return new Info(obs, "(unknown)", "unknown", void 0, "unknown", []);
4855
+ }
4856
+ constructor(sourceObj, name, type, value, state, dependencies) {
4857
+ this.sourceObj = sourceObj;
4858
+ this.name = name;
4859
+ this.type = type;
4860
+ this.value = value;
4861
+ this.state = state;
4862
+ this.dependencies = dependencies;
4863
+ }
4864
+ };
4865
+ //#endregion
4866
+ //#region ../../node_modules/.pnpm/@vscode+observables@0.1.1-0/node_modules/@vscode/observables/dist/observableInternal/index.js
4867
+ _setDebugGetDependencyGraph(debugGetDependencyGraph);
4868
+ setLogObservableFn(logObservableToConsole);
4869
+ //#endregion
4870
+ //#region ../../node_modules/.pnpm/@vscode+observables@0.1.1-0/node_modules/@vscode/observables/dist/observableInternal/utils/promise.js
4871
+ var ObservableLazy = class {
4872
+ /**
4873
+ * The cached value.
4874
+ * Does not force a computation of the value.
4875
+ */
4876
+ get cachedValue() {
4877
+ return this._value;
4878
+ }
4879
+ constructor(_computeValue) {
4880
+ this._computeValue = _computeValue;
4881
+ this._value = observableValue(this, void 0);
4882
+ }
4883
+ /**
4884
+ * Returns the cached value.
4885
+ * Computes the value if the value has not been cached yet.
4886
+ */
4887
+ getValue() {
4888
+ let v = this._value.get();
4889
+ if (!v) {
4890
+ v = this._computeValue();
4891
+ this._value.set(v, void 0);
4892
+ }
4893
+ return v;
4894
+ }
4895
+ };
4896
+ /**
4897
+ * A promise whose state is observable.
4898
+ */
4899
+ var ObservablePromise = class ObservablePromise {
4900
+ static fromFn(fn) {
4901
+ return new ObservablePromise(fn());
4902
+ }
4903
+ constructor(promise) {
4904
+ this._value = observableValue(this, void 0);
4905
+ /**
4906
+ * The current state of the promise.
4907
+ * Is `undefined` if the promise didn't resolve yet.
4908
+ */
4909
+ this.promiseResult = this._value;
4910
+ this.resolvedValue = derived(this, (reader) => {
4911
+ const result = this.promiseResult.read(reader);
4912
+ if (!result) return;
4913
+ return result.getDataOrThrow();
4914
+ });
4915
+ this.promise = promise.then((value) => {
4916
+ transaction((tx) => {
4917
+ /** @description onPromiseResolved */
4918
+ this._value.set(new PromiseResult(value, void 0), tx);
4919
+ });
4920
+ return value;
4921
+ }, (error) => {
4922
+ transaction((tx) => {
4923
+ /** @description onPromiseRejected */
4924
+ this._value.set(new PromiseResult(void 0, error), tx);
4925
+ });
4926
+ throw error;
4927
+ });
4928
+ }
4929
+ };
4930
+ var PromiseResult = class {
4931
+ constructor(data, error) {
4932
+ this.data = data;
4933
+ this.error = error;
4934
+ }
4935
+ /**
4936
+ * Returns the value if the promise resolved, otherwise throws the error.
4937
+ */
4938
+ getDataOrThrow() {
4939
+ if (this.error) throw this.error;
4940
+ return this.data;
4941
+ }
4942
+ };
4943
+ /**
4944
+ * A lazy promise whose state is observable.
4945
+ */
4946
+ var ObservableLazyPromise = class {
4947
+ constructor(_computePromise) {
4948
+ this._computePromise = _computePromise;
4949
+ this._lazyValue = new ObservableLazy(() => new ObservablePromise(this._computePromise()));
4950
+ /**
4951
+ * Does not enforce evaluation of the promise compute function.
4952
+ * Is undefined if the promise has not been computed yet.
4953
+ */
4954
+ this.cachedPromiseResult = derived(this, (reader) => this._lazyValue.cachedValue.read(reader)?.promiseResult.read(reader));
4955
+ }
4956
+ getPromise() {
4957
+ return this._lazyValue.getValue().promise;
4958
+ }
4959
+ };
2476
4960
  //#endregion
2477
4961
  //#region src/approvalPresentation.ts
2478
4962
  function createApprovalPresentation(item, prepared) {
@@ -2780,6 +5264,9 @@ const FLAG_PREFIX = "--p:";
2780
5264
  * mutates the input.
2781
5265
  */
2782
5266
  function rewriteParamShortcuts(argv) {
5267
+ return rewriteCapabilityShortcuts(expandParamShortcuts(argv));
5268
+ }
5269
+ function expandParamShortcuts(argv) {
2783
5270
  const out = [];
2784
5271
  let stopExpanding = false;
2785
5272
  for (let i = 0; i < argv.length; i++) {
@@ -2820,148 +5307,81 @@ function rewriteParamShortcuts(argv) {
2820
5307
  }
2821
5308
  return out;
2822
5309
  }
2823
- //#endregion
2824
- //#region src/views/types.ts
2825
- function formatCondition(condition) {
2826
- const match = condition.kind === "interface" ? condition.interface : condition.implements;
2827
- const predicate = "tag" in match ? `tag(${match.tag})` : `id(${match.interfaceId})`;
2828
- return condition.kind === "interface" ? `interface:${predicate}` : `service:implements(${predicate})`;
2829
- }
2830
- function matchesInterface(value, condition) {
2831
- return "tag" in condition ? value.tags?.includes(condition.tag) === true : value.interfaceId === condition.interfaceId;
2832
- }
2833
- function matchesTarget(target, view) {
2834
- return view.conditions.some((condition) => condition.kind === target.kind && target.interfaces.some((value) => matchesInterface(value, condition.kind === "interface" ? condition.interface : condition.implements)));
2835
- }
2836
- function targetId(kind, listing) {
2837
- return `${kind}:${encodeURIComponent(listing.serviceId)}:${encodeURIComponent(listing.discoveredFrom)}` + (kind === "interface" ? `:${encodeURIComponent(listing.interfaceId)}:${listing.isDefault ? "default" : "qualified"}` : "");
2838
- }
2839
- function interfaceTarget(listing) {
2840
- return {
2841
- id: targetId("interface", listing),
2842
- kind: "interface",
2843
- serviceId: listing.serviceId,
2844
- interfaceId: listing.interfaceId,
2845
- discoveredFrom: listing.discoveredFrom,
2846
- interfaces: [listing]
2847
- };
2848
- }
2849
- //#endregion
2850
- //#region src/views/discovery.ts
2851
- async function discoverViewTargets(channel, view, options = {}) {
2852
- const explorer = new HubDirectoryExplorer(channel, options);
2853
- const warnings = [];
2854
- try {
2855
- await explorer.explore();
2856
- const snapshot = explorer.graphSnapshot.result;
2857
- warnings.push(...snapshot.inaccessible.map((item) => `${item.serviceId || "<root>"}: ${item.reason}`));
2858
- const listings = [...snapshot.listings];
2859
- try {
2860
- const defaults = await deadline(fetchDefaults(channel), options.timeoutMs ?? 5e3);
2861
- if (defaults.interfaceId !== void 0) {
2862
- const existing = listings.find((item) => item.serviceId === "" && item.interfaceId === defaults.interfaceId);
2863
- listings.unshift({
2864
- serviceId: "",
2865
- interfaceId: defaults.interfaceId,
2866
- hash: defaults.hash ?? existing?.hash,
2867
- discoveredFrom: "",
2868
- isDefault: true,
2869
- tags: existing?.tags
2870
- });
2871
- }
2872
- } catch (error) {
2873
- warnings.push(`Default service: ${message(error)}`);
2874
- }
2875
- const interfacesById = /* @__PURE__ */ new Map();
2876
- for (const listing of listings) {
2877
- const target = interfaceTarget(listing);
2878
- const previous = interfacesById.get(target.id);
2879
- interfacesById.set(target.id, previous === void 0 ? target : {
2880
- ...previous,
2881
- interfaces: [...previous.interfaces, listing]
2882
- });
5310
+ const CAPABILITY_SHORTCUTS = /* @__PURE__ */ new Map([
5311
+ ["--cap:allow-prefix", "prefix"],
5312
+ ["--cap:allow-any", "any"],
5313
+ ["--cap:allow-enum", "enum"],
5314
+ ["--cap:allow-subset", "subsetOf"]
5315
+ ]);
5316
+ function rewriteCapabilityShortcuts(argv) {
5317
+ const out = [];
5318
+ const constrained = /* @__PURE__ */ new Set();
5319
+ const assigned = /* @__PURE__ */ new Set();
5320
+ let precedingParam;
5321
+ for (let i = 0; i < argv.length; i++) {
5322
+ const token = argv[i];
5323
+ if (token === "--") {
5324
+ out.push(...argv.slice(i));
5325
+ break;
2883
5326
  }
2884
- const targets = [...interfacesById.values()];
2885
- const services = /* @__PURE__ */ new Map();
2886
- for (const listing of listings) {
2887
- const id = targetId("service", listing);
2888
- const values = services.get(id) ?? [];
2889
- if (!values.some((value) => value.interfaceId === listing.interfaceId && value.hash === listing.hash)) values.push(listing);
2890
- services.set(id, values);
5327
+ const eq = token.indexOf("=");
5328
+ const flag = eq < 0 ? token : token.slice(0, eq);
5329
+ const matcher = CAPABILITY_SHORTCUTS.get(flag);
5330
+ if (matcher !== void 0) {
5331
+ if (precedingParam === void 0) throw new Error(`${flag} must immediately follow a --param or --p:<name> assignment.`);
5332
+ if (precedingParam.includes(".")) throw new Error(`${flag} only supports top-level parameter names.`);
5333
+ if (constrained.has(precedingParam)) throw new Error(`Duplicate --cap:allow-param for ${precedingParam}.`);
5334
+ let value = true;
5335
+ if (matcher === "any") {
5336
+ if (eq >= 0) throw new Error("--cap:allow-any takes no value.");
5337
+ } else {
5338
+ const raw = eq >= 0 ? token.slice(eq + 1) : argv[++i];
5339
+ if (raw === void 0 || eq < 0 && raw.startsWith("--")) throw new Error(`${flag} requires a value.`);
5340
+ if (matcher === "prefix") value = raw;
5341
+ else {
5342
+ try {
5343
+ value = JSON.parse(raw);
5344
+ } catch {
5345
+ throw new Error(`${flag} requires a JSON array.`);
5346
+ }
5347
+ if (!Array.isArray(value) || matcher === "enum" && value.length === 0 || matcher === "subsetOf" && !value.every((item) => typeof item === "string")) throw new Error(`${flag} requires ${matcher === "enum" ? "a non-empty JSON array" : "a JSON string array"}.`);
5348
+ }
5349
+ }
5350
+ constrained.add(precedingParam);
5351
+ out.push("--cap:allow-param", `${precedingParam}=${JSON.stringify({ [matcher]: value })}`);
5352
+ continue;
2891
5353
  }
2892
- for (const [id, interfaces] of services) {
2893
- const first = interfaces[0];
2894
- targets.push({
2895
- id,
2896
- kind: "service",
2897
- serviceId: first.serviceId,
2898
- discoveredFrom: first.discoveredFrom,
2899
- interfaces
2900
- });
5354
+ if (flag === "--param" || flag === "--cap:allow-param") {
5355
+ const values = [];
5356
+ if (eq >= 0) {
5357
+ values.push(token.slice(eq + 1));
5358
+ out.push(token);
5359
+ } else {
5360
+ out.push(token);
5361
+ while (argv[i + 1] !== void 0 && !argv[i + 1].startsWith("-")) {
5362
+ const value = argv[++i];
5363
+ values.push(value);
5364
+ out.push(value);
5365
+ }
5366
+ }
5367
+ precedingParam = void 0;
5368
+ for (const value of values) {
5369
+ const assignment = value.indexOf("=");
5370
+ if (assignment <= 0) continue;
5371
+ const key = value.slice(0, assignment);
5372
+ if (constrained.has(key) && (flag === "--cap:allow-param" || assigned.has(key))) throw new Error(flag === "--param" ? `Cannot reassign parameter ${key} after an explicit capability constraint.` : `Duplicate --cap:allow-param for ${key}.`);
5373
+ if (flag === "--cap:allow-param") constrained.add(key);
5374
+ else {
5375
+ assigned.add(key);
5376
+ precedingParam = key;
5377
+ }
5378
+ }
5379
+ continue;
2901
5380
  }
2902
- return {
2903
- targets: targets.filter((target) => matchesTarget(target, view)).map((target) => ({
2904
- ...target,
2905
- serviceInterfaces: listings.filter((listing) => listing.serviceId === target.serviceId && listing.discoveredFrom === target.discoveredFrom)
2906
- })).sort((a, b) => a.id.localeCompare(b.id)),
2907
- warnings
2908
- };
2909
- } finally {
2910
- explorer.dispose();
2911
- }
2912
- }
2913
- async function resolveViewTarget(channel, view, id, options = {}) {
2914
- const { targets } = await discoverViewTargets(channel, view, options);
2915
- const target = targets.find((value) => value.id === id);
2916
- if (!target) throw new Error(`Unknown or unavailable ${view.id} target "${id}". Run view targets ${view.id} again.`);
2917
- return loadViewTarget(channel, target, options.timeoutMs, view);
2918
- }
2919
- /** Selection and opening share a connection, including spawned ephemeral endpoints. */
2920
- async function resolveViewSelection(channel, view, options) {
2921
- if (options.target !== void 0) {
2922
- const context = await resolveViewTarget(channel, view, options.target, options);
2923
- if (options.service !== void 0 && context.target.serviceId !== options.service) throw new Error("--target does not match --service");
2924
- if (options.interface !== void 0 && !context.target.interfaces.some((value) => value.interfaceId === options.interface)) throw new Error("--target does not match --interface");
2925
- return context;
2926
- }
2927
- if (options.interface === void 0 && options.service === void 0) throw new Error("Specify --target, or an exact --interface / --service selector");
2928
- const candidates = (await discoverViewTargets(channel, view, options)).targets.filter((target) => target.kind === (options.interface === void 0 ? "service" : "interface") && (options.interface === void 0 || target.interfaceId === options.interface) && (options.service === void 0 || target.serviceId === options.service));
2929
- const matches = candidates.filter((target) => target.kind !== "interface" || target.interfaces.some((value) => value.isDefault) || !candidates.some((other) => other.serviceId === target.serviceId && other.discoveredFrom === target.discoveredFrom && other.interfaceId === target.interfaceId && other.interfaces.every((value) => target.interfaces.some((candidate) => candidate.hash === value.hash)) && target.interfaces.every((value) => other.interfaces.some((candidate) => candidate.hash === value.hash)) && other.interfaces.some((value) => value.isDefault)));
2930
- if (matches.length !== 1) {
2931
- const detail = matches.length === 0 ? "No matching target" : `Target selection is ambiguous (${matches.length} targets)`;
2932
- throw new Error(`${detail}; use --service to narrow the selection or --target from view targets ${view.id}`);
2933
- }
2934
- return loadViewTarget(channel, matches[0], options.timeoutMs, view);
2935
- }
2936
- async function loadViewTarget(channel, target, timeoutMs = 5e3, view) {
2937
- const candidates = view === void 0 || target.kind === "interface" ? target.interfaces : target.interfaces.filter((listing) => view.conditions.some((condition) => condition.kind === "service" && matchesInterface(listing, condition.implements)));
2938
- for (const listing of candidates) if (candidates.some((other) => other.interfaceId === listing.interfaceId && other.hash !== listing.hash)) throw new Error(`Target "${target.id}" is ambiguous: interface "${listing.interfaceId}" advertises multiple schema hashes`);
2939
- return {
2940
- channel,
2941
- target,
2942
- interfaces: await Promise.all(candidates.map(async (listing) => {
2943
- const schema = await deadline(fetchSchema$1(channel, listing.interfaceId, listing.hash, listing.discoveredFrom || void 0), timeoutMs);
2944
- if (schema.id !== listing.interfaceId || listing.hash !== void 0 && schema.hash !== listing.hash) throw new Error(`Schema for "${listing.interfaceId}" does not match its current directory advertisement; rediscover the target`);
2945
- return {
2946
- ...listing,
2947
- hash: schema.hash,
2948
- schema
2949
- };
2950
- }))
2951
- };
2952
- }
2953
- async function deadline(promise, timeoutMs) {
2954
- let timer;
2955
- try {
2956
- return await Promise.race([promise, new Promise((_, reject) => {
2957
- timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Reflection timed out after ${timeoutMs}ms`)), timeoutMs);
2958
- })]);
2959
- } finally {
2960
- clearTimeout(timer);
5381
+ precedingParam = void 0;
5382
+ out.push(token);
2961
5383
  }
2962
- }
2963
- function message(error) {
2964
- return error instanceof Error ? error.message : String(error);
5384
+ return out;
2965
5385
  }
2966
5386
  //#endregion
2967
5387
  //#region src/ui/terminalText.ts
@@ -5899,13 +8319,13 @@ async function main(rawArgv, executableName = "linkrpc") {
5899
8319
  const profile = cliInvocation.profile;
5900
8320
  const program = new Command();
5901
8321
  program.configureHelp({ showGlobalOptions: true });
5902
- program.name(cliInvocation.programName).description(profile === "hub" ? "CLI / TUI for LinkRPC hubs. Shared RPC commands use hub initialization, signing, capability negotiation, and required schema validation by default." : "CLI / TUI for JSON-RPC and LinkRPC endpoints. Shared commands use the RPC profile with automatic schema validation and no endpoint environment override by default.").option("--endpoint <uri>", "endpoint URI (unix:/path?token=…, npipe://./pipe/…, ws[s]://host?token=…, ws-no-init://host?…, cmd:?command=… or cmd-stdio:?command=…)").option("--endpoint-cmd <command>", "spawn a server, connect via an injected env socket").option("--endpoint-cmd-stdio <command>", "spawn a child and talk over its stdio").option("--endpoint-cmd-env <key=value>", "env var for the --endpoint-cmd / --endpoint-cmd-stdio child (repeatable)", (val, acc) => {
8322
+ program.name(cliInvocation.programName).description(profile === "hub" ? "CLI / TUI for LinkRPC hubs. Shared RPC commands use hub initialization, signing, capability negotiation, and required schema validation by default." : "CLI / TUI for JSON-RPC and LinkRPC endpoints. Shared commands use the RPC profile with automatic schema validation and no endpoint environment override by default.").option("--endpoint <uri>", "endpoint URI (linkrpc:local, unix:/path?token=…, npipe://./pipe/…, ws[s]://host?token=…, ws-no-init://host?…, cmd:?command=… or cmd-stdio:?command=…)").option("--endpoint-cmd <command>", "spawn a server, connect via an injected env socket").option("--endpoint-cmd-stdio <command>", "spawn a child and talk over its stdio").option("--endpoint-cmd-env <key=value>", "env var for the --endpoint-cmd / --endpoint-cmd-stdio child (repeatable)", (val, acc) => {
5903
8323
  const eq = val.indexOf("=");
5904
8324
  if (eq === -1) throw new InvalidArgumentError(`expected key=value, got '${val}'`);
5905
8325
  const next = acc ?? {};
5906
8326
  next[val.slice(0, eq)] = val.slice(eq + 1);
5907
8327
  return next;
5908
- }).option("--endpoint-token <token>", "fill the exact token=% placeholder in --endpoint (or a context endpoint)").option("--endpoint-cmd-cwd <dir>", "working directory for the --endpoint-cmd / --endpoint-cmd-stdio child").option("--context <selector>", "use an exact folder, id:<name>, :root, or :empty context; otherwise look up from cwd").option("--context-set", "persist context-capable overrides supplied on this command to the active context").option("--new-context <selector>", "create a new context from the effective command values; fails if it already exists").option("--schema <path-or-url>", "static schema catalog used for discovery and validation").option("--validation <mode>", "schema validation mode: auto, required, or off", parseValidationMode).option("--use-env", "allow LINKRPC_* / legacy HUBRPC_* endpoint variables to override context").option("--no-use-env", "ignore LINKRPC_* / legacy HUBRPC_* endpoint variables").option("--provision-identity", "with --endpoint-cmd: provision & reuse a persistent identity (slot derived from cwd + command)").option("--provision-identity-slot <slot>", "with --endpoint-cmd: provision & reuse a persistent identity under this explicit slot id").option("--principal <spec>", "identity to sign calls with: \"managed\" (default, falls back to user:hubrpc-cli), \"user:<id>\" (per-user data dir), or \"file:<path>\"", (v) => {
8328
+ }).option("--endpoint-token <token>", "fill the exact token=% placeholder in --endpoint (or a context endpoint)").option("--endpoint-cmd-cwd <dir>", "working directory for the --endpoint-cmd / --endpoint-cmd-stdio child").option("--context <selector>", "use an exact folder, id:<name>, :root, or :empty context; otherwise look up from cwd").option("--context-set", "persist context-capable overrides supplied on this command to the active context").option("--new-context <selector>", "create a new context from the effective command values; fails if it already exists").option("--schema <path-or-url>", "static schema catalog used for discovery and validation").option("--daemon-service <id>", "daemon management service ID on the selected Hub").option("--detach-keys <keys>", "TTY console detach sequence in Docker syntax, e.g. 'ctrl-b,d' (default) or 'ctrl-p,ctrl-q'").option("--validation <mode>", "schema validation mode: auto, required, or off", parseValidationMode).option("--use-env", "allow LINKRPC_* / legacy HUBRPC_* endpoint variables to override context").option("--no-use-env", "ignore LINKRPC_* / legacy HUBRPC_* endpoint variables").option("--sign", "enable Hub signing and stored capabilities (Hub profile default)").option("--no-sign", "disable Hub signing and stored capabilities (RPC profile default)").option("--provision-identity", "with --endpoint-cmd: provision & reuse a persistent identity (slot derived from cwd + command)").option("--provision-identity-slot <slot>", "with --endpoint-cmd: provision & reuse a persistent identity under this explicit slot id").option("--principal <spec>", "identity to sign calls with: \"managed\" (default, falls back to user:hubrpc-cli), \"user:<id>\" (per-user data dir), or \"file:<path>\"", (v) => {
5909
8329
  try {
5910
8330
  parsePrincipalSpec(v);
5911
8331
  } catch (e) {
@@ -5920,9 +8340,21 @@ async function main(rawArgv, executableName = "linkrpc") {
5920
8340
  let pendingContextSet;
5921
8341
  let pendingNewContext;
5922
8342
  let preparedNewContext;
8343
+ /** Create a context from the effective values, then make it the selected context of this invocation. */
8344
+ const createAndSelectContext = async (reference) => {
8345
+ if (g_contextStore === void 0 || g_invocation === void 0) throw new Error("context resolver is unavailable");
8346
+ await createContextFromInvocation(g_contextStore, reference, g_invocation.values);
8347
+ const useEnvironment = explicitOption(program, "useEnv");
8348
+ g_invocation = await resolveInvocationContext({
8349
+ profile,
8350
+ store: g_contextStore,
8351
+ selector: formatContextReference(reference),
8352
+ cliOverrides: g_invocation.cliOverrides,
8353
+ ...useEnvironment !== void 0 ? { useEnvironment } : {}
8354
+ });
8355
+ };
5923
8356
  program.hook("preAction", async (_thisCommand, actionCommand) => {
5924
8357
  const commandPath = getCommandPath(actionCommand);
5925
- const allowMissingContext = commandPath === "context set";
5926
8358
  const store = new ContextStore();
5927
8359
  const cliOverrides = collectContextOverrides(program, actionCommand);
5928
8360
  const selector = explicitOption(program, "context");
@@ -5933,16 +8365,17 @@ async function main(rawArgv, executableName = "linkrpc") {
5933
8365
  store,
5934
8366
  ...selector !== void 0 ? { selector } : {},
5935
8367
  cliOverrides,
5936
- ...useEnvironment !== void 0 ? { useEnvironment } : {},
5937
- allowMissingContext
8368
+ ...useEnvironment !== void 0 ? { useEnvironment } : {}
5938
8369
  });
5939
8370
  if (profile === "rpc" && g_invocation.values.config !== void 0) throw new Error("--config is only available in the hub profile");
5940
8371
  if (program.opts().contextSet === true) {
5941
8372
  if (program.opts().newContext !== void 0) throw new Error("--context-set cannot be combined with --new-context");
5942
8373
  if (Object.keys(cliOverrides).length === 0) throw new Error("--context-set requires at least one context-capable override");
8374
+ assertExplicitRootMutation(g_invocation.selected);
8375
+ if (g_invocation.selected.context === void 0) throw new Error("No context exists to update. Use --new-context . to create one.");
5943
8376
  pendingContextSet = {
5944
8377
  store,
5945
- reference: await mutationReference(store, g_invocation.selected),
8378
+ reference: mutableReference(g_invocation.selected.reference),
5946
8379
  values: cliOverrides
5947
8380
  };
5948
8381
  }
@@ -5959,6 +8392,14 @@ async function main(rawArgv, executableName = "linkrpc") {
5959
8392
  reference: preparedNewContext.reference,
5960
8393
  values: g_invocation.values
5961
8394
  };
8395
+ if (commandPath.startsWith("services ") && preparedNewContext !== void 0) {
8396
+ await createContextFromInvocation(preparedNewContext.store, preparedNewContext.reference, g_invocation.values);
8397
+ pendingNewContext = void 0;
8398
+ }
8399
+ if ((commandPath === "context" || commandPath === "context show") && preparedNewContext !== void 0) {
8400
+ await createAndSelectContext(preparedNewContext.reference);
8401
+ pendingNewContext = void 0;
8402
+ }
5962
8403
  endpoint = commandUsesEndpoint(commandPath) || hasEndpointOverrides(cliOverrides) ? resolveInvocationEndpoint(g_invocation, actionCommand.name() === "tunnel") : void 0;
5963
8404
  g_hubConfigPath = g_invocation.values.config;
5964
8405
  g_logMessages = program.opts().logMessages === true && actionCommand.name() !== "ui";
@@ -5975,6 +8416,65 @@ async function main(rawArgv, executableName = "linkrpc") {
5975
8416
  pendingNewContext = void 0;
5976
8417
  }
5977
8418
  });
8419
+ if (profile === "hub") registerServices(program, {
8420
+ detachKeys,
8421
+ attachCommand: (id) => {
8422
+ if (!g_invocation) throw new Error("Context resolver is unavailable");
8423
+ const reference = preparedNewContext?.reference ?? g_invocation.selected.reference;
8424
+ return serviceAttachCommand({
8425
+ context: formatContextReference(reference),
8426
+ namespace: g_invocation.values.namespace ?? contextNamespace(reference),
8427
+ id,
8428
+ overrides: g_invocation.cliOverrides,
8429
+ useEnv: g_invocation.environmentApplied
8430
+ });
8431
+ },
8432
+ restart: async () => {
8433
+ if (endpoint !== void 0 || g_hubConfigPath !== void 0 || g_invocation?.values.daemonService !== void 0) throw new Error("services restart only replaces the default local daemon, not a selected Hub endpoint or routed daemon. Run `hub --context :empty --no-use-env services restart` to explicitly choose the local daemon.");
8434
+ await restartLocalDaemon();
8435
+ },
8436
+ namespace: () => {
8437
+ if (!g_invocation) throw new Error("Context resolver is unavailable");
8438
+ return g_invocation.values.namespace ?? contextNamespace(preparedNewContext?.reference ?? g_invocation.selected.reference);
8439
+ },
8440
+ use: async (startLocal, action) => {
8441
+ if (endpoint === void 0 && g_hubConfigPath === void 0) return useLocalManagement(startLocal, action);
8442
+ let result;
8443
+ await withChannel(endpoint, getPrincipalSpec(), async (channel) => {
8444
+ const connection = new LinkRpcConnection(channel);
8445
+ const serviceId = g_invocation?.values.daemonService ?? `${(await connection.get(hubGrantedServiceIdInterface).getHubServiceId({})).hubServiceId}/daemon`;
8446
+ await checkDaemonInterfaces(connection, serviceId);
8447
+ result = await action(connection.get(managedEndpointsInterface, { serviceId }), { local: false });
8448
+ }, {
8449
+ requestReflectionAccess: false,
8450
+ autoNegotiatePerCall: true
8451
+ });
8452
+ return result;
8453
+ }
8454
+ });
8455
+ program.command("attach [serviceId]").description("Attach to a console of any service implementing the `linkrpc.console.v1` interface template (found through the `linkrpc.console` directory tag). Without a service, list available consoles. Pipe consoles: Ctrl+D on an empty line detaches. TTY consoles: keys go to the program; the detach keys (default Ctrl+B, then d) detach.").option("--params <json>", "params of the console template instance, e.g. '{\"namespace\":\"…\",\"id\":\"main\"}'", "{}").option("--interface <id>", "interface containing the console instance").option("--instance <name>", "console template instance name, when an interface has several").option("--read-only", "observe output without sending input").action(async (serviceId, opts) => {
8456
+ await withChannel(endpoint, getPrincipalSpec(), async (channel) => {
8457
+ if (serviceId === void 0) {
8458
+ process.stdout.write(`${await listConsoleTargets(channel)}\n`);
8459
+ return;
8460
+ }
8461
+ await attachConsole(channel, {
8462
+ serviceId,
8463
+ params: opts.params,
8464
+ readOnly: opts.readOnly,
8465
+ ...opts.interface === void 0 ? {} : { interfaceId: opts.interface },
8466
+ ...opts.instance === void 0 ? {} : { instance: opts.instance },
8467
+ detachKeys: detachKeys(),
8468
+ reattachCommand: [
8469
+ cliInvocation.programName,
8470
+ "attach",
8471
+ serviceId,
8472
+ ...opts.params === "{}" ? [] : ["--params", `'${opts.params}'`],
8473
+ ...opts.instance === void 0 ? [] : ["--instance", opts.instance]
8474
+ ].join(" ")
8475
+ });
8476
+ }, { autoNegotiatePerCall: true });
8477
+ });
5978
8478
  program.command("ls").description("List services and interfaces. Walks the bus recursively, following `hubrpc.directory` references.").option("--interface <id>", "filter by interfaceId").option("--interface-prefix <prefix>", "filter by interfaceId prefix").option("--service <id>", "list a single service (no recursion)").option("--service-prefix <prefix>", "filter by serviceId prefix").option("--search <regexp>", "case-insensitive regexp over listings").option("--depth <n>", "max recursion depth (default 5)", (v) => Number.parseInt(v, 10)).option("--with-members", "include request and notification members from interface schemas").option("--json", "legacy flat listing JSON; use --format json for graph state").option("--format <format>", "pretty, json, or jsonl").option("--dump <file>", "write topology and all schemas to one JSON fixture file").option("--dump-patches <file>", "write progressive RFC 6902 operations as JSON Lines; use - for stdout").option("--stream", "legacy patch-log JSONL; use --format jsonl for reconstructable graph state").option("--watch", "continue observing directory changes until interrupted").action(async (opts) => {
5979
8479
  await withChannel(endpoint, getPrincipalSpec(), async (channel) => {
5980
8480
  const format = resolveGraphFormat(opts.format, {
@@ -6166,32 +8666,69 @@ async function main(rawArgv, executableName = "linkrpc") {
6166
8666
  }
6167
8667
  const rendered = renderMethodParamHelp(method, schema);
6168
8668
  if (rendered.text.length > 0) process.stdout.write("\n" + rendered.text);
8669
+ }, {
8670
+ requestReflectionAccess: false,
8671
+ autoNegotiatePerCall: false
6169
8672
  });
6170
8673
  } catch (e) {
6171
8674
  process.stdout.write(`\n(Could not fetch method schema: ${e.message})\n`);
6172
8675
  }
6173
8676
  };
6174
- program.command("call [methodRef]").description("Invoke a request. `<methodRef>` is `[svc::][iface::]method[@hash]`. Use `--p:<name> <value>` (or `--p:<name>=<value>`) as a shortcut for `--param <name>=<value>`. Run `hub call <methodRef> --help` to see the method-specific `--p:` flags derived from the live schema.").option("--params <json>", "inline params JSON, or \"-\" to read stdin").option("--param <kv...>", "param override, key=value (JSON or string)").option("--no-validate", "skip schema-based param validation").helpOption(false).option("-h, --help", "show help (includes method params when reachable)").action(async (methodRef, opts, cmd) => {
6175
- if (opts.help) {
6176
- await _printCallHelp(cmd, methodRef ?? "");
6177
- return;
6178
- }
6179
- if (methodRef === void 0) {
6180
- process.stderr.write("linkrpc: missing required argument 'methodRef'.\n");
6181
- process.stderr.write(cmd.helpInformation());
6182
- process.exit(2);
6183
- }
6184
- await withChannel(endpoint, getPrincipalSpec(), async (channel) => {
8677
+ const call = program.command("call [methodRef]");
8678
+ const caps = profile === "hub" ? program.command("caps").description("List stored capabilities or request access for a call.") : void 0;
8679
+ caps?.command("list").description("List capabilities stored for the selected principal (including expired grants).").option("--json", "machine-readable JSON output").action(async (opts) => {
8680
+ await withResolvedSigning(endpoint, getPrincipalSpec(), async (_conn, session) => {
8681
+ process.stdout.write(listCaps(session, opts.json === true) + "\n");
8682
+ });
8683
+ });
8684
+ const capsRequest = caps?.command("request [methodRef]").description("Request and store a capability without invoking the method.");
8685
+ const addCapabilityOptions = (cmd) => cmd.option("--cap:allow-param <kv...>", "top-level key=matcher JSON (exact, enum, prefix, subsetOf, any)").option("--cap:allow-prefix <prefix>", "allow a string prefix for the immediately preceding parameter").option("--cap:allow-any", "allow any JSON value for the immediately preceding parameter").option("--cap:allow-enum <json>", "allow one value from a JSON array for the preceding parameter").option("--cap:allow-subset <json>", "allow an array of items from a JSON string array for the preceding parameter").addOption(new Option("--cap:duration <duration>", "requested grant lifetime").choices([
8686
+ "shortLived",
8687
+ "longLived",
8688
+ "persistent"
8689
+ ]).default("persistent"));
8690
+ const configureCall = (cmd, capabilityOnly) => {
8691
+ cmd.description((capabilityOnly ? "Request a capability without executing a call. " : "Invoke a request. ") + "`<methodRef>` is `[svc::][iface::]method[@hash]`. Use `--p:<name> <value>` (or `--p:<name>=<value>`) as a shortcut for `--param <name>=<value>`. Run `hub call <methodRef> --help` to see the method-specific `--p:` flags derived from the live schema.").option("--params <json>", "inline params JSON, or \"-\" to read stdin").option("--param <kv...>", "param override, key=value (JSON or string)").option("--no-validate", "skip schema-based param validation").helpOption(false).option("-h, --help", "show help (includes method params when reachable)").action(async (methodRef, opts, cmd) => {
8692
+ if (opts.help) {
8693
+ await _printCallHelp(cmd, methodRef ?? "");
8694
+ return;
8695
+ }
8696
+ if (methodRef === void 0) {
8697
+ process.stderr.write("linkrpc: missing required argument 'methodRef'.\n");
8698
+ process.stderr.write(cmd.helpInformation());
8699
+ process.exit(2);
8700
+ }
8701
+ const requestCap = opts["cap:request"] === true;
8702
+ const allowParams = opts["cap:allowParam"];
8703
+ if (!capabilityOnly && allowParams !== void 0 && !requestCap) throw new Error("--cap:allow-param requires --cap:request.");
8704
+ if (!capabilityOnly && cmd.getOptionValueSource("cap:duration") === "cli" && !requestCap) throw new Error("--cap:duration requires --cap:request.");
8705
+ resolveSigning(profile, g_invocation?.values ?? {}, capabilityOnly || requestCap);
6185
8706
  const validation = opts.validate === false ? "off" : effectiveValidation(profile);
6186
- const out = await callCommand(channel, {
6187
- methodRef,
6188
- paramsArg: opts.params,
6189
- paramOverrides: opts.param,
6190
- validation
8707
+ if ((capabilityOnly || requestCap) && validation === "off") throw new Error("Capability requests require schema validation; remove --no-validate/--validation off.");
8708
+ await withChannel(endpoint, getPrincipalSpec(), async (channel, session) => {
8709
+ const out = await callCommand(channel, {
8710
+ methodRef,
8711
+ paramsArg: opts.params,
8712
+ paramOverrides: opts.param,
8713
+ validation,
8714
+ allowParams,
8715
+ requestCap,
8716
+ capabilityOnly,
8717
+ signingSession: session,
8718
+ duration: opts["cap:duration"]
8719
+ });
8720
+ process.stdout.write(out + "\n");
8721
+ }, {
8722
+ requestReflectionAccess: (capabilityOnly || requestCap) && validation !== "off",
8723
+ requireSigning: capabilityOnly || requestCap,
8724
+ autoNegotiatePerCall: false
6191
8725
  });
6192
- process.stdout.write(out + "\n");
6193
- }, { requestReflectionAccess: effectiveValidation(profile) !== "off" });
6194
- });
8726
+ });
8727
+ addCapabilityOptions(cmd);
8728
+ };
8729
+ configureCall(call, false);
8730
+ call.option("--cap:request", "ensure a stored capability covers the requested scope before calling");
8731
+ if (capsRequest !== void 0) configureCall(capsRequest, true);
6195
8732
  program.command("notify [methodRef]").description("Fire a notification (no response). Same `--p:<name>` shortcuts as `call`.").option("--params <json>", "inline params JSON, or \"-\" to read stdin").option("--param <kv...>", "param override, key=value").option("--no-validate", "skip schema-based param validation").helpOption(false).option("-h, --help", "show help (includes method params when reachable)").action(async (methodRef, opts, cmd) => {
6196
8733
  if (opts.help) {
6197
8734
  await _printCallHelp(cmd, methodRef ?? "");
@@ -6299,13 +8836,29 @@ Batch options:
6299
8836
  context.command("show").description("Show only the selected context and its stored defaults.").action(() => {
6300
8837
  printSelectedContext(false);
6301
8838
  });
6302
- context.command("list").description("List every path, named, and root context in the global store.").action(async () => {
8839
+ context.command("create <selector>").description("Create a context (a folder, id:<name>) from the effective values, including flags on this command; same as hub context --new-context <selector>.").action(async (selector) => {
6303
8840
  if (g_contextStore === void 0) throw new Error("context store is unavailable");
6304
- for (const item of await g_contextStore.list()) process.stdout.write(`${formatContextReference(item.reference)}\n`);
8841
+ if (preparedNewContext !== void 0) throw new Error("Use either context create <selector> or --new-context, not both");
8842
+ const reference = mutableReference(await g_contextStore.resolveReference(selector));
8843
+ await g_contextStore.assertCanCreate(reference);
8844
+ await createAndSelectContext(reference);
8845
+ printSelectedContext(false);
8846
+ });
8847
+ context.command("list").description("List every path and named context in the global store, and the root context, which always exists.").action(async () => {
8848
+ if (g_contextStore === void 0) throw new Error("context store is unavailable");
8849
+ const references = (await g_contextStore.list()).map((item) => formatContextReference(item.reference));
8850
+ if (!references.includes(":root")) references.push(":root");
8851
+ for (const reference of references.sort()) process.stdout.write(`${reference}\n`);
6305
8852
  });
6306
- context.command("set").description("Set explicit context-capable flags on the active context; creates a cwd context when none exists.").option("--unset <key...>", "remove one or more stored context values").action(async (opts) => {
8853
+ context.command("set").description("Set explicit context-capable flags on an existing context, or create one with --new-context.").option("--unset <key...>", "remove one or more stored context values").action(async (opts) => {
6307
8854
  if (g_contextStore === void 0 || g_invocation === void 0) throw new Error("context resolver is unavailable");
6308
- const target = explicitOption(program, "context") !== void 0 ? mutableReference(g_invocation.selected.reference) : await mutationReference(g_contextStore, g_invocation.selected);
8855
+ if (preparedNewContext !== void 0) {
8856
+ if ((opts.unset?.length ?? 0) > 0) throw new Error("--unset cannot be used with --new-context");
8857
+ return;
8858
+ }
8859
+ assertExplicitRootMutation(g_invocation.selected);
8860
+ if (g_invocation.selected.context === void 0) throw new Error("No context exists to update. Use hub context set --new-context . with your desired flags.");
8861
+ const target = mutableReference(g_invocation.selected.reference);
6309
8862
  const overrides = g_invocation.cliOverrides;
6310
8863
  const unset = (opts.unset ?? []).map(parseContextValueKey);
6311
8864
  if (Object.keys(overrides).length === 0 && unset.length === 0) throw new Error("context set requires at least one context-capable flag or --unset");
@@ -6314,12 +8867,12 @@ Batch options:
6314
8867
  process.stdout.write(`Updated context ${formatContextReference(target)}.\n`);
6315
8868
  if (result !== void 0) printContextValues(result.values, false);
6316
8869
  });
6317
- context.command("remove").description("Stop any owned connection and remove the selected context from the global store.").action(async () => {
8870
+ context.command("remove").description("Stop any owned connection and remove the selected context; removing :root resets it to no defaults.").action(async () => {
6318
8871
  if (g_contextStore === void 0 || g_invocation === void 0) throw new Error("context resolver is unavailable");
6319
8872
  const reference = g_invocation.selected.reference;
6320
8873
  if (reference.kind === "empty" || reference.kind === "root" && explicitOption(program, "context") === void 0) throw new Error("select the context to remove explicitly with --context");
6321
8874
  if (!await g_contextStore.remove(reference)) throw new Error(`context ${formatContextReference(reference)} does not exist`);
6322
- process.stdout.write(`Removed context ${formatContextReference(reference)}.\n`);
8875
+ process.stdout.write(reference.kind === "root" ? "Reset context :root to no stored defaults (the root context always exists).\n" : `Removed context ${formatContextReference(reference)}.\n`);
6323
8876
  });
6324
8877
  program.command("_connection-broker").description("(internal) Run a persistent connection broker.").requiredOption("--remote-endpoint <uri>").requiredOption("--timeout-ms <milliseconds-or-inf>", "", parseConnectionLimitMilliseconds).requiredOption("--ttl-ms <milliseconds-or-inf>", "", parseConnectionLimitMilliseconds).option("--notification-limit <count>", "", (value) => parsePositiveInteger(value, "--notification-limit"), 1e3).option("--schema <path-or-url>").action(async (opts) => {
6325
8878
  const running = await runConnectionBroker({
@@ -6574,7 +9127,7 @@ Batch options:
6574
9127
  process.once("SIGINT", onSignal);
6575
9128
  process.once("SIGTERM", onSignal);
6576
9129
  try {
6577
- const { runApprovalUi } = await import("./runApprovalUi-BFI84eck.js");
9130
+ const { runApprovalUi } = await import("./runApprovalUi-BVAPyMfz.js");
6578
9131
  await runApprovalUi({
6579
9132
  client,
6580
9133
  signal: stop.signal
@@ -6589,7 +9142,7 @@ Batch options:
6589
9142
  registerViewCommands(program, (action) => withChannel(endpoint, getPrincipalSpec(), action));
6590
9143
  program.command("ui").description("Launch the terminal UI (form-based browser of services + methods).").action(async () => {
6591
9144
  const acquired = await acquireConnection(endpoint);
6592
- const { runUi } = await import("./runUi-BNCT8zwR.js");
9145
+ const { runUi } = await import("./runUi-BrFeYazs.js");
6593
9146
  try {
6594
9147
  await runUi({
6595
9148
  endpoint: acquired.ep,
@@ -6650,8 +9203,12 @@ function collectContextOverrides(program, actionCommand) {
6650
9203
  copyExplicit(program, opts, result, "provisionIdentity");
6651
9204
  copyExplicit(program, opts, result, "provisionIdentitySlot");
6652
9205
  copyExplicit(program, opts, result, "principal");
9206
+ copyExplicit(program, opts, result, "sign");
6653
9207
  copyExplicit(program, opts, result, "schema");
6654
9208
  copyExplicit(program, opts, result, "validation");
9209
+ copyExplicit(program, opts, result, "daemonService");
9210
+ copyExplicit(program, opts, result, "detachKeys");
9211
+ for (let command = actionCommand; command; command = command.parent) if (command.name() === "services") copyExplicit(command, command.opts(), result, "namespace");
6655
9212
  if (getCommandPath(actionCommand) === "connection create") {
6656
9213
  const actionOpts = actionCommand.opts();
6657
9214
  copyExplicit(actionCommand, actionOpts, result, "timeout", "connectionTimeout");
@@ -6695,6 +9252,10 @@ function mutableReference(reference) {
6695
9252
  if (reference.kind === "empty") throw new Error(":empty is immutable and cannot be modified");
6696
9253
  return reference;
6697
9254
  }
9255
+ /** The root context applies to every folder without a context: change it only when selected explicitly. */
9256
+ function assertExplicitRootMutation(selected) {
9257
+ if (selected.selectedBy === "root") throw new Error("No folder or named context applies here, so the root context is in effect. To change defaults for every such folder, select it explicitly with --context :root; to create a context for this folder, use --new-context .");
9258
+ }
6698
9259
  function printSelectedContext(effective) {
6699
9260
  if (g_invocation === void 0) throw new Error("context resolver is unavailable");
6700
9261
  const selected = g_invocation.selected;
@@ -6721,7 +9282,7 @@ function contextSelectionDescription(selectedBy) {
6721
9282
  case "argument": return "--context";
6722
9283
  case "environment": return "LINKRPC_CONTEXT (HUBRPC_CONTEXT fallback)";
6723
9284
  case "cwd": return "current-directory lookup";
6724
- case "root": return ":root fallback";
9285
+ case "root": return ":root fallback (no folder context applies)";
6725
9286
  case "empty": return "no matching context (:empty)";
6726
9287
  }
6727
9288
  }
@@ -6746,6 +9307,9 @@ function printContextValues(values, revealToken, source) {
6746
9307
  }
6747
9308
  function contextValueFlag(key) {
6748
9309
  return {
9310
+ daemonService: "daemon-service",
9311
+ detachKeys: "detach-keys",
9312
+ namespace: "namespace",
6749
9313
  endpoint: "endpoint",
6750
9314
  endpointCmd: "endpoint-cmd",
6751
9315
  endpointCmdStdio: "endpoint-cmd-stdio",
@@ -6756,6 +9320,7 @@ function contextValueFlag(key) {
6756
9320
  provisionIdentity: "provision-identity",
6757
9321
  provisionIdentitySlot: "provision-identity-slot",
6758
9322
  principal: "principal",
9323
+ sign: "sign",
6759
9324
  schema: "schema",
6760
9325
  validation: "validation",
6761
9326
  connectionTimeout: "connection-timeout",
@@ -6765,6 +9330,9 @@ function contextValueFlag(key) {
6765
9330
  }
6766
9331
  function parseContextValueKey(value) {
6767
9332
  const entry = Object.keys({
9333
+ daemonService: true,
9334
+ detachKeys: true,
9335
+ namespace: true,
6768
9336
  endpoint: true,
6769
9337
  endpointCmd: true,
6770
9338
  endpointCmdStdio: true,
@@ -6775,6 +9343,7 @@ function parseContextValueKey(value) {
6775
9343
  provisionIdentity: true,
6776
9344
  provisionIdentitySlot: true,
6777
9345
  principal: true,
9346
+ sign: true,
6778
9347
  schema: true,
6779
9348
  validation: true,
6780
9349
  connectionTimeout: true,
@@ -6787,6 +9356,11 @@ function parseContextValueKey(value) {
6787
9356
  function effectiveValidation(profile) {
6788
9357
  return g_invocation?.values.validation ?? validationDefault(profile);
6789
9358
  }
9359
+ /** The configured TTY detach sequence; validated when used, so invalid stored values fail with a clear message. */
9360
+ function detachKeys() {
9361
+ const spec = g_invocation?.values.detachKeys;
9362
+ return spec === void 0 ? void 0 : DetachKeys.parse(spec);
9363
+ }
6790
9364
  function parseValidationMode(value) {
6791
9365
  if (value === "auto" || value === "required" || value === "off") return value;
6792
9366
  throw new InvalidArgumentError("--validation must be 'auto', 'required', or 'off'");
@@ -6803,20 +9377,25 @@ function hasEndpointOverrides(values) {
6803
9377
  return values.endpoint !== void 0 || values.endpointCmd !== void 0 || values.endpointCmdStdio !== void 0 || values.endpointCmdEnv !== void 0 || values.endpointToken !== void 0 || values.endpointCmdCwd !== void 0 || values.provisionIdentity !== void 0 || values.provisionIdentitySlot !== void 0;
6804
9378
  }
6805
9379
  async function withChannel(endpoint, principalSpec, fn, opts = {}) {
9380
+ const sign = resolveSigning(g_invocation?.profile ?? "rpc", g_invocation?.values ?? {}, opts.requireSigning);
6806
9381
  const acquired = await acquireConnection(endpoint);
6807
9382
  const conn = await connect(acquired.ep, acquired.running === void 0 ? wireLog("hub") : void 0);
6808
9383
  try {
6809
9384
  const schemaSource = g_invocation?.values.schema;
6810
9385
  const channel = schemaSource === void 0 ? conn.channel : withStaticHubReflection(conn.channel, await loadStaticHubSchema(schemaSource));
6811
9386
  if (acquired.ep.kind === "ws-no-init" || acquired.ep.kind === "socket" && acquired.ep.brokerMode === "raw") {
9387
+ if (opts.requireSigning || g_invocation?.values.sign === true) throw new Error("Signing requires an initialized LinkRPC endpoint.");
6812
9388
  await fn(channel);
6813
9389
  return;
6814
9390
  }
6815
- if (g_invocation?.profile === "rpc") {
9391
+ if (!sign) {
6816
9392
  await fn(channel);
6817
9393
  return;
6818
9394
  }
6819
- const session = await setupSigning(conn.channel, conn.signing, principalSpec, { negotiateHubCaps: acquired.running !== void 0 || isHubEndpoint(acquired.ep) });
9395
+ const session = await setupSigning(conn.channel, conn.signing, principalSpec, {
9396
+ negotiateHubCaps: acquired.running !== void 0 || isHubEndpoint(acquired.ep),
9397
+ autoNegotiatePerCall: opts.autoNegotiatePerCall
9398
+ });
6820
9399
  reportIdentity(session);
6821
9400
  if (opts.requestReflectionAccess !== false && schemaSource === void 0 && (acquired.running !== void 0 || isHubEndpoint(acquired.ep))) {
6822
9401
  if (await requestReflectionAccess(session) === "denied") process.stderr.write("linkrpc: reflection access denied; falling back to per-call negotiation.\n");
@@ -6824,13 +9403,14 @@ async function withChannel(endpoint, principalSpec, fn, opts = {}) {
6824
9403
  if (opts.requestTopologyAccess !== void 0 && (acquired.running !== void 0 || isHubEndpoint(acquired.ep))) {
6825
9404
  if (await requestTopologyAccess(session, opts.requestTopologyAccess) === "denied") process.stderr.write("linkrpc: topology access denied; topology sources may be inaccessible.\n");
6826
9405
  }
6827
- await fn(channel);
9406
+ await fn(channel, session);
6828
9407
  } finally {
6829
9408
  conn.close();
6830
9409
  acquired.running?.dispose();
6831
9410
  }
6832
9411
  }
6833
9412
  async function withResolvedSigning(endpoint, principalSpec, fn) {
9413
+ resolveSigning(g_invocation?.profile ?? "rpc", g_invocation?.values ?? {}, true);
6834
9414
  const acquired = await acquireConnection(endpoint);
6835
9415
  if (acquired.ep.kind === "ws-no-init" || acquired.ep.kind === "socket" && acquired.ep.brokerMode === "raw") {
6836
9416
  acquired.running?.dispose();
@@ -6927,6 +9507,11 @@ async function acquireConnection(endpoint) {
6927
9507
  running
6928
9508
  };
6929
9509
  }
9510
+ if (endpoint === void 0 && g_invocation?.profile === "hub") try {
9511
+ return { ep: resolveLocalEndpoint() };
9512
+ } catch {
9513
+ throw new Error("No endpoint configured through flags, environment, or context, and the local Hub daemon is not running. Pass --endpoint, or start the daemon (any `hub services run` starts it).");
9514
+ }
6930
9515
  return { ep: needEndpoint(endpoint) };
6931
9516
  }
6932
9517
  /** Print the identity used to sign calls to stderr (keeps stdout clean for JSON output). */
@@ -7022,6 +9607,6 @@ function safeJson(value) {
7022
9607
  }
7023
9608
  }
7024
9609
  //#endregion
7025
- export { formatCommandLegend as a, safeTerminalText as c, interfaceTarget as d, matchesTarget as f, dispatchHostKey as i, resolveViewTarget as l, approvalDetailLines as m, views as n, hostCommands as o, targetId as p, ViewFrame as r, matchesKey as s, runCli as t, formatCondition as u };
9610
+ export { formatCondition as _, formatCommandLegend as a, targetId as b, safeTerminalText as c, ObservablePromise as d, observableValue as f, resolveViewTarget as g, Disposable as h, dispatchHostKey as i, approvalDetailLines as l, derived as m, views as n, hostCommands as o, autorun as p, ViewFrame as r, matchesKey as s, runCli as t, ObservableLazyPromise as u, interfaceTarget as v, matchesTarget as y };
7026
9611
 
7027
- //# sourceMappingURL=cli-JE4URhh3.js.map
9612
+ //# sourceMappingURL=cli-CO0iSiY_.js.map