@hediet/linkrpc-cli 0.0.1

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.
@@ -0,0 +1,381 @@
1
+ import { IRequestSender, LinkRpcJsonSchema, SigningCallCtx } from "@hediet/linkrpc";
2
+ //#region src/mcpForward.interface.d.ts
3
+ /**
4
+ * Transparent MCP tunnel.
5
+ *
6
+ * A single long-lived {@link mcpForwardInterface.connect} request opens one MCP
7
+ * session "leg". The MCP JSON-RPC byte stream rides the linkrpc `$stream` duplex
8
+ * correlated to that request — **no MCP method is modeled here**. Payloads are
9
+ * opaque JSON-RPC messages, exactly as MCP emits them, so the forwarder that
10
+ * serves this interface stays dumb: it shuttles frames between a child process'
11
+ * stdio and the stream without ever parsing them.
12
+ *
13
+ * Why one duplex stream instead of request/response per MCP message: MCP is
14
+ * bidirectional and asynchronous (server-initiated `notifications/.../
15
+ * list_changed`, sampling requests). `$stream` already provides an ordered,
16
+ * request-correlated, cancellable duplex that the runtime keeps alive with
17
+ * periodic pings, so one stream == one MCP session leg.
18
+ *
19
+ * Lifecycle:
20
+ * - The consumer (the aggregator) calls `connect()`, obtaining
21
+ * `{ result, send, cancel, onMessage }`.
22
+ * - `send({ frame })` carries a frame **to** the child (stdin);
23
+ * `onMessage(({ frame }) => …)` receives frames **from** the child (stdout).
24
+ * - Child exit → the forwarder resolves the request (`connect` returns) → the
25
+ * consumer drops the client.
26
+ * - Consumer dispose / service removed → `cancel()` → the forwarder kills the
27
+ * child.
28
+ *
29
+ * Lives in the CLI package because the producer (`hub mcp-forward`) is a CLI
30
+ * command; the in-extension aggregator imports this contract from here too.
31
+ */
32
+ declare const mcpForwardInterface: any;
33
+ //#endregion
34
+ //#region src/methodRef.d.ts
35
+ /**
36
+ * A method reference as accepted on the CLI: `[serviceId::][interfaceId::]name[@hash]`.
37
+ */
38
+ declare class MethodRefWithOptHash {
39
+ readonly serviceId: string | undefined;
40
+ readonly interfaceId: string | undefined;
41
+ readonly methodName: string;
42
+ readonly hash: string | undefined;
43
+ static parseMethodRef(input: string): MethodRefWithOptHash;
44
+ constructor(serviceId: string | undefined, interfaceId: string | undefined, methodName: string, hash: string | undefined);
45
+ /** Method name as it goes on the wire (no hash, no whitespace). */
46
+ getMethodOnWire(): string;
47
+ }
48
+ //#endregion
49
+ //#region src/paramParsing.d.ts
50
+ /**
51
+ * Parse `--param k=v` style overrides into a JSON object. Values that parse
52
+ * as JSON are used as-is (numbers, booleans, null, arrays, objects); the rest
53
+ * are treated as strings. Nested keys use `.` segments: `--param user.name=x`.
54
+ *
55
+ * Combined with `--params <json>` (the whole params blob), `--param k=v`
56
+ * entries layer on top (object-merge for `--params`, then per-key overrides).
57
+ */
58
+ interface ParseParamsOptions {
59
+ /** Optional base params object (from `--params <json>` or stdin). */
60
+ base?: unknown;
61
+ /** Raw `--param k=v` strings, in order. */
62
+ overrides?: readonly string[];
63
+ }
64
+ declare function parseParamOverride(raw: string): {
65
+ path: string[];
66
+ value: unknown;
67
+ };
68
+ declare function mergeParams(opts: ParseParamsOptions): unknown;
69
+ //#endregion
70
+ //#region src/validation.d.ts
71
+ /**
72
+ * Validate a concrete value against an `SvcJsonSchema`. Reuses linkrpc's
73
+ * structural assignability — the value is lowered to a closed, const-shaped
74
+ * schema and then asked "is this assignable to the target?". This avoids
75
+ * pulling in a separate JSON-Schema validator and stays consistent with how
76
+ * the connection layer reasons about interface compatibility.
77
+ *
78
+ * Returns `undefined` if the value is valid, or a short reason string.
79
+ */
80
+ declare function validateValueAgainstSchema(value: unknown, target: LinkRpcJsonSchema, components?: Record<string, LinkRpcJsonSchema>): string | undefined;
81
+ /**
82
+ * Lower a JSON value to the tightest `SvcJsonSchema` that matches only it.
83
+ * Primitives become `{ const }`; arrays become tuples with `items: false`
84
+ * (forbidding extras); objects become closed records with every property
85
+ * required.
86
+ *
87
+ * `undefined` becomes the empty closed object — that's the
88
+ * "no params supplied" case, which is only assignable to a target that has
89
+ * no required properties.
90
+ */
91
+ declare function valueToConstSchema(v: unknown): LinkRpcJsonSchema;
92
+ /**
93
+ * One thing wrong with `value` at a given JSON path. Path uses dot/bracket
94
+ * notation rooted at the validated value, e.g. `.query`, `.items[0].id`.
95
+ * The empty string is the root.
96
+ */
97
+ interface ValidationIssue {
98
+ readonly path: string;
99
+ readonly reason: string;
100
+ }
101
+ /**
102
+ * Walk `value` against `schema` and collect every mismatch we can pinpoint.
103
+ * Returns `[]` on success. Each issue has a JSON-style path plus a single
104
+ * line saying why that location is wrong — designed to be printed directly
105
+ * under a "Param validation failed:" header.
106
+ *
107
+ * Coverage is best-effort: scalar / object / array / tuple / const / enum /
108
+ * union / `$ref`. Unions report the branch with the fewest mismatches
109
+ * (heuristic) so the user gets one concrete trail to fix instead of a
110
+ * cascade of "no branch matched".
111
+ */
112
+ declare function explainValidation(value: unknown, schema: LinkRpcJsonSchema, components?: Record<string, LinkRpcJsonSchema>): ValidationIssue[];
113
+ /**
114
+ * Render an `SvcJsonSchema` as a short, copy-paste-friendly type expression.
115
+ * Used as a hint next to required-property errors (`"name required (string)"`)
116
+ * and as the "Expected" footer when full-on params reporting fires.
117
+ */
118
+ declare function describeSchema(schema: LinkRpcJsonSchema, components?: Record<string, LinkRpcJsonSchema>, depth?: number): string;
119
+ /**
120
+ * Multi-line table of an object schema's properties: name, required-marker,
121
+ * type, and description. Used as the "Expected params:" footer printed under
122
+ * a validation error. Returns `undefined` if `schema` is not an object —
123
+ * fall back to a single `describeSchema` line in that case.
124
+ */
125
+ declare function describeObjectParams(schema: LinkRpcJsonSchema, components?: Record<string, LinkRpcJsonSchema>): string | undefined;
126
+ //#endregion
127
+ //#region src/completions/directorySource.d.ts
128
+ interface DirectoryEntry {
129
+ readonly serviceId: string;
130
+ readonly interfaceId: string;
131
+ readonly hash: string;
132
+ }
133
+ interface DirectorySource {
134
+ entries(): Promise<readonly DirectoryEntry[]>;
135
+ methodsOnInterface(serviceId: string | undefined, interfaceId: string): Promise<readonly string[]>;
136
+ /**
137
+ * Property names of the method's params object schema, or `[]` if the
138
+ * method takes no params / has a non-object params descriptor / does
139
+ * not exist on the interface.
140
+ */
141
+ paramNamesForMethod(serviceId: string | undefined, interfaceId: string, methodName: string): Promise<readonly string[]>;
142
+ }
143
+ /**
144
+ * Live source: walks the hub once (memoized for the instance lifetime),
145
+ * then derives every entry / methods lookup from that single snapshot +
146
+ * lazy `fetchSchema` calls. Each (sid, iid) schema is fetched at most once
147
+ * per instance regardless of how many downstream queries reference it.
148
+ */
149
+ declare class ChannelDirectorySource implements DirectorySource {
150
+ private readonly _channel;
151
+ private _walkPromise;
152
+ private readonly _schemaCache;
153
+ constructor(_channel: IRequestSender<SigningCallCtx>);
154
+ entries(): Promise<readonly DirectoryEntry[]>;
155
+ methodsOnInterface(serviceId: string | undefined, interfaceId: string): Promise<readonly string[]>;
156
+ paramNamesForMethod(serviceId: string | undefined, interfaceId: string, methodName: string): Promise<readonly string[]>;
157
+ private _fetchSchema;
158
+ }
159
+ //#endregion
160
+ //#region src/completions/tree.d.ts
161
+ /**
162
+ * Static description of the CLI's command surface, used to drive
163
+ * completion. The runtime CLI is defined with `commander` in {@link ../cli};
164
+ * this table mirrors it. A drift test
165
+ * ({@link ./tree.test.ts}) verifies subcommand & flag names match.
166
+ *
167
+ * Keeping the tree static (rather than introspecting commander at
168
+ * completion time) lets the completer run with zero startup cost in the
169
+ * common static-only case and keeps the slot-resolution logic completely
170
+ * pure — straightforward to unit-test.
171
+ */
172
+ type SlotType =
173
+ /** A bare or qualified `[svc::][iface::]method[@hash]` reference. */
174
+ 'methodRef' |
175
+ /** A bare `interfaceId[@hash]` (e.g. `schema <interfaceRef>`). */
176
+ 'interfaceRef' |
177
+ /** A service id known to the hub. */
178
+ 'serviceId' |
179
+ /** A bare interface id, sans hash. */
180
+ 'interfaceId' |
181
+ /** One of the supported shell names (powershell, bash, zsh, fish). */
182
+ 'shell' |
183
+ /** Anything else — completer returns no dynamic suggestions. */
184
+ 'free';
185
+ interface FlagDef {
186
+ readonly name: string;
187
+ readonly takesValue: boolean;
188
+ readonly valueType?: SlotType;
189
+ readonly description?: string;
190
+ }
191
+ interface PositionalDef {
192
+ readonly name: string;
193
+ readonly type: SlotType;
194
+ }
195
+ interface SubcommandDef {
196
+ readonly name: string;
197
+ readonly description: string;
198
+ readonly options: readonly FlagDef[];
199
+ readonly positionals: readonly PositionalDef[];
200
+ readonly subcommands?: readonly SubcommandDef[];
201
+ /** When set, extra positionals beyond {@link positionals} are completed as this type. */
202
+ readonly variadic?: SlotType;
203
+ /** Hide from subcommand-name completion (used for `_complete`). */
204
+ readonly hidden?: boolean;
205
+ }
206
+ interface CommandTree {
207
+ readonly globalOptions: readonly FlagDef[];
208
+ readonly subcommands: readonly SubcommandDef[];
209
+ }
210
+ /** Hub-profile tree retained as the default for direct resolver consumers. */
211
+ declare const COMMAND_TREE: CommandTree;
212
+ //#endregion
213
+ //#region src/completions/complete.d.ts
214
+ interface Completion {
215
+ /** Text to replace the current word with. */
216
+ readonly text: string;
217
+ /** Tooltip / second-line detail (e.g. method signature). */
218
+ readonly tooltip?: string;
219
+ }
220
+ interface CompleteOptions {
221
+ readonly line: string;
222
+ readonly point: number;
223
+ readonly tree?: CommandTree;
224
+ /**
225
+ * Source of dynamic candidates. Omit to skip dynamic lookups entirely
226
+ * (only static completions returned) — useful when no hub is configured.
227
+ */
228
+ readonly directory?: DirectorySource;
229
+ }
230
+ /**
231
+ * Resolve completion candidates for the cursor at `point` in `line`.
232
+ *
233
+ * The slot kinds returned by {@link resolveSlot} map to:
234
+ * - `subcommand` / `flag-name` / static-typed `flag-value` / static-typed
235
+ * `positional` → filter against the {@link CommandTree}
236
+ * - `flag-value` / `positional` with a dynamic slot type → call into
237
+ * `directory` (skipped when `directory` is `undefined`)
238
+ * - `flag-name` under `call`/`notify` with a methodRef typed → adds
239
+ * `--p:<name>` shortcuts for the live params
240
+ * - `none` → empty
241
+ *
242
+ * Candidates are always prefix-filtered so the caller (PowerShell) can
243
+ * enumerate the result as-is. The output is sorted and de-duplicated.
244
+ */
245
+ declare function complete(opts: CompleteOptions): Promise<Completion[]>;
246
+ //#endregion
247
+ //#region src/completions/parse.d.ts
248
+ /**
249
+ * Tokenize a shell-style command line and locate the token at the cursor.
250
+ *
251
+ * Used by the `_complete` subcommand to figure out what the user is currently
252
+ * typing. Quoting rules are intentionally minimal (POSIX-ish, matching what
253
+ * users actually type at a PowerShell prompt): single/double quoted strings
254
+ * are single tokens; nothing else is special. Cross-shell quoting nuances
255
+ * don't matter for completion — we only need to know where token boundaries
256
+ * are well enough to pick the current word.
257
+ */
258
+ interface Token {
259
+ readonly text: string;
260
+ /** Byte offset where the token starts (including any opening quote). */
261
+ readonly start: number;
262
+ /** Byte offset one past the token end (including any closing quote). */
263
+ readonly end: number;
264
+ readonly quoted: boolean;
265
+ }
266
+ interface ParsedLine {
267
+ readonly tokens: readonly Token[];
268
+ /**
269
+ * Tokens fully to the left of the cursor (i.e. context — never includes the
270
+ * token the user is currently typing). The first token is the binary name.
271
+ */
272
+ readonly tokensBefore: readonly Token[];
273
+ /**
274
+ * The token the cursor is positioned in / at the right edge of, or
275
+ * `undefined` when the cursor sits in whitespace (a "new" word).
276
+ */
277
+ readonly currentToken: Token | undefined;
278
+ /**
279
+ * Text the user has typed for the current word so far (token start →
280
+ * cursor). Empty when {@link currentToken} is undefined. Used both for
281
+ * prefix-filtering candidates and as the value PowerShell replaces.
282
+ */
283
+ readonly currentWordPrefix: string;
284
+ }
285
+ /**
286
+ * Split `line` at `point` (0-indexed cursor position) into a previous-tokens
287
+ * list + a possibly in-progress current word. Cursor inside or at the end of
288
+ * a token = that token is the current word; cursor in whitespace = no current
289
+ * token, new empty word at this position.
290
+ */
291
+ declare function parseLine(line: string, point: number): ParsedLine;
292
+ //#endregion
293
+ //#region src/completions/resolve.d.ts
294
+ type Slot = {
295
+ readonly kind: 'subcommand';
296
+ readonly parent: SubcommandDef | undefined;
297
+ } | {
298
+ readonly kind: 'flag-name';
299
+ readonly subcommand: SubcommandDef | undefined;
300
+ } | {
301
+ readonly kind: 'flag-value';
302
+ readonly flag: FlagDef;
303
+ readonly subcommand: SubcommandDef | undefined;
304
+ } | {
305
+ readonly kind: 'positional';
306
+ readonly type: SlotType;
307
+ readonly subcommand: SubcommandDef;
308
+ readonly index: number;
309
+ } | {
310
+ readonly kind: 'none';
311
+ };
312
+ interface ResolvedContext {
313
+ readonly slot: Slot;
314
+ /** Subcommand parsed from the tokens (undefined if none seen yet). */
315
+ readonly subcommand: SubcommandDef | undefined;
316
+ /** Selected commands from the root command through the active leaf. */
317
+ readonly commandPath: readonly SubcommandDef[];
318
+ /**
319
+ * Flags already present on the line that take a value, paired with their
320
+ * value (or `undefined` if the value followed in the next token).
321
+ * Useful for extracting `--endpoint` etc. without re-parsing.
322
+ */
323
+ readonly seenFlagValues: ReadonlyMap<string, string | undefined>;
324
+ /**
325
+ * Positional values already typed (after the subcommand). Indexed
326
+ * positionally — `seenPositionals[0]` is the first positional, etc.
327
+ * Used to plumb e.g. the `methodRef` of `call <methodRef>` through to
328
+ * dynamic completion for `--p:<name>` flags.
329
+ */
330
+ readonly seenPositionals: readonly string[];
331
+ }
332
+ /**
333
+ * Walk the tokens before the cursor, tracking subcommand selection, current
334
+ * positional index, and whether the next token is the value for a flag.
335
+ * Returns the slot the cursor itself is in plus the surrounding context.
336
+ */
337
+ declare function resolveSlot(parsed: ParsedLine, tree: CommandTree): ResolvedContext;
338
+ //#endregion
339
+ //#region src/completions/runComplete.d.ts
340
+ interface CompleteForLineOptions {
341
+ readonly line: string;
342
+ readonly point: number;
343
+ /**
344
+ * Endpoint URI to connect to, overriding any `--endpoint` on the line and
345
+ * the env fallback. When omitted, the line's `--endpoint` (then `env`) is
346
+ * used.
347
+ */
348
+ readonly endpointOverride?: string;
349
+ /**
350
+ * Environment consulted for the `LINKRPC_ENDPOINT` fallback. Defaults to
351
+ * `process.env`. Pass `{}` to disable the env fallback entirely (the
352
+ * extension does this so completion never targets the extension host's
353
+ * environment).
354
+ */
355
+ readonly env?: NodeJS.ProcessEnv;
356
+ /** Override the directory source (tests). */
357
+ readonly directoryOverride?: DirectorySource;
358
+ /** Suppress the connect attempt entirely (tests). */
359
+ readonly skipConnect?: boolean;
360
+ }
361
+ interface CompleteForLineResult {
362
+ /** Completion candidates, already prefix-filtered, sorted and de-duped. */
363
+ readonly candidates: readonly Completion[];
364
+ /** The resolved slot the cursor sits in (subcommand / flag / positional). */
365
+ readonly slot: Slot;
366
+ /**
367
+ * Offset of the first character the candidate replaces (i.e. the start of
368
+ * the current word). Equal to `point - replacementLength`.
369
+ */
370
+ readonly replacementIndex: number;
371
+ /** Length of the current word prefix the candidate replaces. */
372
+ readonly replacementLength: number;
373
+ }
374
+ /**
375
+ * Resolve completion candidates for the cursor at `point` in `line`, opening a
376
+ * hub connection only when the slot needs dynamic data.
377
+ */
378
+ declare function completeForLine(opts: CompleteForLineOptions): Promise<CompleteForLineResult>;
379
+ //#endregion
380
+ export { COMMAND_TREE, ChannelDirectorySource, type CommandTree, type CompleteForLineOptions, type CompleteForLineResult, type CompleteOptions, type Completion, type DirectoryEntry, type DirectorySource, type FlagDef, MethodRefWithOptHash, ParseParamsOptions, type ParsedLine, type ResolvedContext, type Slot, type SlotType, type SubcommandDef, type Token, ValidationIssue, complete, completeForLine, describeObjectParams, describeSchema, explainValidation, mcpForwardInterface, mergeParams, parseLine, parseParamOverride, resolveSlot, validateValueAgainstSchema, valueToConstSchema };
381
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import { B as parseParamOverride, F as describeSchema, H as mcpForwardInterface, I as explainValidation, L as validateValueAgainstSchema, P as describeObjectParams, R as valueToConstSchema, V as MethodRefWithOptHash, _ as ChannelDirectorySource, f as complete, g as parseLine, h as COMMAND_TREE, m as resolveSlot, t as completeForLine, z as mergeParams } from "./chunks/runComplete-BkQwzPbF.js";
2
+ export { COMMAND_TREE, ChannelDirectorySource, MethodRefWithOptHash, complete, completeForLine, describeObjectParams, describeSchema, explainValidation, mcpForwardInterface, mergeParams, parseLine, parseParamOverride, resolveSlot, validateValueAgainstSchema, valueToConstSchema };
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import { t as runCli } from "./chunks/cli-D_-vlAa2.js";
3
+ //#region src/linkrpc.ts
4
+ runCli("linkrpc");
5
+ //#endregion
6
+ export {};
7
+
8
+ //# sourceMappingURL=linkrpc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linkrpc.js","names":[],"sources":["../src/linkrpc.ts"],"sourcesContent":["import { runCli } from './cli';\n\nrunCli('linkrpc');\n"],"mappings":";;;AAEA,OAAO,SAAS"}
package/dist/rpc.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {}
package/dist/rpc.js ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import { t as runCli } from "./chunks/cli-D_-vlAa2.js";
3
+ //#region src/rpc.ts
4
+ runCli("rpc");
5
+ //#endregion
6
+ export {};
7
+
8
+ //# sourceMappingURL=rpc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc.js","names":[],"sources":["../src/rpc.ts"],"sourcesContent":["import { runCli } from './cli';\n\nrunCli('rpc');\n"],"mappings":";;;AAEA,OAAO,KAAK"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@hediet/linkrpc-cli",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "bin": {
8
+ "linkrpc": "./dist/linkrpc.js",
9
+ "rpc": "./dist/rpc.js",
10
+ "hub": "./dist/hub.js"
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "dependencies": {
16
+ "commander": "^12.1.0",
17
+ "ink": "^5.1.0",
18
+ "ink-select-input": "^6.0.0",
19
+ "ink-text-input": "^6.0.0",
20
+ "react": "^18.3.1",
21
+ "zod": "^4.4.3",
22
+ "@hediet/linkrpc": "0.0.1",
23
+ "@hediet/linkrpc-hub": "0.0.1",
24
+ "@hediet/linkrpc-infra": "0.0.1"
25
+ },
26
+ "devDependencies": {
27
+ "@vscode/observables": "0.1.1-0",
28
+ "@types/node": "^20.0.0",
29
+ "@types/react": "^18.3.0",
30
+ "tsdown": "^0.22.3",
31
+ "tslib": "^2.8.1",
32
+ "tsx": "4.22.4",
33
+ "typescript": "^6.0.3",
34
+ "vitest": "^3.0.0",
35
+ "@hediet/linkrpc-client": "0.0.1"
36
+ },
37
+ "scripts": {
38
+ "cli": "tsx src/linkrpc.ts",
39
+ "build": "tsdown",
40
+ "dev": "tsdown --watch",
41
+ "generate:ahp-schema": "tsx generate-ahp-schema.ts",
42
+ "test": "vitest --run",
43
+ "test:watch": "vitest"
44
+ }
45
+ }