@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.
package/README.md ADDED
@@ -0,0 +1,370 @@
1
+ # @hediet/linkrpc-cli
2
+
3
+ Command-line and terminal-UI client for [`@hediet/linkrpc`](../linkrpc) endpoints.
4
+
5
+ The CLI speaks plain linkrpc over sockets, WebSockets, or a child process's
6
+ stdio: it connects to (or spawns) the server you point it at and drives it
7
+ through the JSON-RPC channel. Reflection
8
+ interfaces (`hubrpc.defaults`, `hubrpc.directory`, `hubrpc.schemas`) are
9
+ used to discover what the endpoint exposes; the CLI is otherwise generic — it
10
+ has no compiled-in knowledge of any particular interface.
11
+
12
+ ## Profiles
13
+
14
+ The same shared commands are available through two profiles:
15
+
16
+ - `linkrpc` and `rpc` use the generic RPC profile. Schema validation defaults to
17
+ `auto`, and endpoint environment variables are ignored unless `--use-env` is
18
+ present.
19
+ - `linkrpc hub`, `rpc hub`, and `hub` use the Hub profile. Schema validation
20
+ defaults to `required`, endpoint environment variables are enabled, and Hub
21
+ signing, capability negotiation, topology, traffic, identity, and approval
22
+ commands are available.
23
+
24
+ Inherited options may appear before the profile, between the profile and command,
25
+ or after the command:
26
+
27
+ ```sh
28
+ linkrpc --endpoint ws://localhost:7700 hub call app::api::get
29
+ linkrpc hub --endpoint ws://localhost:7700 call app::api::get
30
+ linkrpc hub call app::api::get --endpoint ws://localhost:7700
31
+ ```
32
+
33
+ Use `--validation auto|required|off` to override a profile default.
34
+
35
+ ## Contexts
36
+
37
+ Contexts persist command defaults in the global user configuration store. A
38
+ folder context is keyed by its canonical folder path; no file is written into
39
+ that folder. Without `--context`, the CLI searches from the current directory
40
+ through its parents, then uses `:root`, then the immutable `:empty` context.
41
+
42
+ Context selectors are:
43
+
44
+ - An absolute or relative folder path.
45
+ - `id:<name>` for a named context.
46
+ - `:root` for the system-wide root above platform filesystem roots. On Unix,
47
+ `/` is equivalent to `:root`.
48
+ - `:empty` for no stored defaults.
49
+
50
+ `LINKRPC_CONTEXT` selects a context when `--context` is absent;
51
+ `HUBRPC_CONTEXT` is its legacy alias. An explicit `--context` also suppresses
52
+ the Hub profile's normal environment endpoint overlay. Every context value can
53
+ still be overridden by a command-line option.
54
+
55
+ ```sh
56
+ # Set endpoint and principal on the nearest context, creating one for cwd if needed.
57
+ hub context set --endpoint 'wss://hub.example?token=%' \
58
+ --endpoint-token secret --principal user:work
59
+
60
+ # Create a named context from the effective values only after the call succeeds.
61
+ hub call app::api::get --new-context id:working-copy
62
+
63
+ # Apply this successful command's explicit overrides to the active context.
64
+ hub call app::api::get --validation off --context-set
65
+
66
+ hub context # same as `hub context show`
67
+ hub context resolve --context id:working-copy
68
+ hub context list
69
+ hub context remove --context .
70
+ hub call app::api::get --context :empty --endpoint ws://localhost:7700
71
+ ```
72
+
73
+ Context creation and mutation are explicit: use `context set`,
74
+ `--context-set`, or `--new-context <selector>`. Context values, including
75
+ endpoint tokens, are currently stored inline and unencrypted. Principals are
76
+ stored by selector (`managed`, `user:<id>`, or `file:<path>`); their private
77
+ keys remain in the existing identity store.
78
+
79
+ ## Endpoint syntax
80
+
81
+ An endpoint says **where the linkrpc server lives, and how to reach (or start)
82
+ it**. Pick one of:
83
+
84
+ | Flag / env | Meaning |
85
+ | -------------------------------- | ---------------------------------------------------------------------------------------------------------- |
86
+ | `--endpoint <uri>` | A strict endpoint URI (see below). |
87
+ | `--endpoint-cmd <command>` | Spawn a _server_, hand it a fresh socket + token via `LINKRPC_ENDPOINT`/`LINKRPC_TOKEN`, then connect to it. |
88
+ | `--endpoint-cmd-stdio <command>` | Spawn a child and talk linkrpc over its stdio. |
89
+ | `LINKRPC_ENDPOINT` (env) | A URI, or a legacy bare socket path / `ws://` URL. Token from `LINKRPC_TOKEN`; enabled by default in the Hub profile. |
90
+
91
+ Legacy `HUBRPC_ENDPOINT` and `HUBRPC_TOKEN` remain supported when the
92
+ corresponding `LINKRPC_*` variable is absent.
93
+
94
+ `--endpoint-token <token>` is accepted only when the selected explicit or
95
+ context endpoint contains exactly one `token=%` query parameter. Supplying it
96
+ without that placeholder, or with a literal `token=...`, is an error. This makes
97
+ token inheritance explicit: an endpoint override without `token=%` discards an
98
+ inherited context token. Environment endpoint/token pairs retain their legacy
99
+ fallback behavior. Put protocol-specific authentication directly in a
100
+ `ws-no-init:` query string. At most
101
+ one of `--endpoint`, `--endpoint-cmd`, `--endpoint-cmd-stdio` may be given;
102
+ they take precedence over the env var in that order.
103
+
104
+ ### Endpoint URIs
105
+
106
+ Every endpoint is a valid `new URL()` — safe in env vars and logs:
107
+
108
+ | URI | Transport |
109
+ | ----------------------------------------- | ------------------------------------------ |
110
+ | `unix:/run/hub/hub.sock?token=…` | unix-domain socket |
111
+ | `npipe://./pipe/linkrpc?token=…` | Windows named pipe |
112
+ | `ws://host:7700?token=…` / `wss://…` | LinkRPC WebSocket (token → `hubrpc::initialize`) |
113
+ | `ws-no-init://host:7700?…` | Plain JSON-RPC WebSocket; skip LinkRPC initialization/signing, preserve query params |
114
+ | `cmd:?command=…` / `cmd:?argv=…&argv=…` | spawn-as-server (`--endpoint-cmd`) |
115
+ | `cmd-stdio:?command=…` / `?argv=…&argv=…` | spawn over stdio (`--endpoint-cmd-stdio`) |
116
+
117
+ The command payload is either a single verbatim string (`?command=node%20server.js`,
118
+ split by the OS shell) or repeated, structure-preserving `argv` params
119
+ (`?argv=node&argv=server.js`).
120
+
121
+ ```sh
122
+ linkrpc ls --endpoint-cmd-stdio "node ./server.js"
123
+ linkrpc call acme.mailer::email::send --param to=a@b.c \
124
+ --endpoint 'wss://hub.example.com?token=%' --endpoint-token abc
125
+ LINKRPC_ENDPOINT=unix:/run/hub/hub.sock?token=abc linkrpc ls
126
+ ```
127
+
128
+ ## Commands
129
+
130
+ ### Reflection
131
+
132
+ | Command | Purpose |
133
+ | ----------------------------- | ---------------------------------------------------------------------------------------------------- |
134
+ | `ls` | Explore the directory referral graph and its services/interfaces. Supports exact and prefix filters, regexp `--search`, `--format pretty\|json\|jsonl`, `--watch`, `--dump <file>`, and `--dump-patches <file\|->`. |
135
+ | `defaults` | Print the preset service/interface (wraps `hubrpc.defaults::get`). |
136
+ | `schema show <interfaceId>[@hash]` | Print an interface schema (wraps `hubrpc.schemas::get`). `--method <name>`, `--json`. |
137
+ | `schema hash <schema.json>` | Compute `computeInterfaceHash` for a local schema without connecting. |
138
+ | `schema check-compat <interfaceId> <local.json>` | Compare a live interface schema with a local schema. |
139
+ | `codegen --input <bundle> --interface <id> --name <name> --output <file> [--preserve-wire-schema] [--check]` | Generate a TypeScript interface definition from a validated offline static bundle. |
140
+
141
+ `codegen` uses the same `{ interfaceSchemas, services, defaultInterface }`
142
+ bundle parser and interface-hash verification as static reflection. `--check`
143
+ compares the generated source with the output file and exits unsuccessfully if
144
+ the file is missing or stale; it never overwrites in check mode.
145
+
146
+ `hub ls --format json` waits for finite exploration and prints the final state.
147
+ `--format jsonl` emits progressive state while requests fan out: the first line
148
+ is `{ "type": "snapshot", "revision": n, "value": ... }` and each later line
149
+ is `{ "type": "patch", "revision": n, "patch": [...] }`, where `patch` is an
150
+ atomic RFC 6902 operation array. Applying the arrays in order reconstructs the
151
+ same state used by the terminal renderer. For compatibility, bare `--json`
152
+ still emits the original flat listing array and bare `--stream` still emits the
153
+ older timestamped patch-log format.
154
+
155
+ Add `--watch` to continue from that initial exploration into live directory
156
+ changes. A TTY repaints the human-readable referral forest; redirected watch
157
+ output defaults to JSONL:
158
+
159
+ ```sh
160
+ hub ls --watch
161
+ hub ls --watch > hub-directory.jsonl
162
+ ```
163
+
164
+ ### Topology inspection
165
+
166
+ | Command | Purpose |
167
+ | ------- | ------- |
168
+ | `topology` | Query or watch topology providers, merge their graphs by node/port identity, and show route claims without treating them as service ownership. Supports repeatable `--source`, node/service/kind filters, regexp `--search`, and the same `pretty`, `json`, and reconstructable `jsonl` formats as `ls`. |
169
+ | `topology participants` | Find participant descriptors and optionally start a traffic watch for a selected node. |
170
+
171
+ With no `--source`, topology providers are discovered through
172
+ `hubrpc.directory`. Repeating `--source` queries exactly those providers and
173
+ does not start directory discovery. On gated hubs the CLI requests the needed
174
+ permissions in one consent operation before querying: wildcard directory plus
175
+ wildcard topology access for discovery, or exact topology access for all
176
+ explicit sources.
177
+
178
+ Topology JSON links may include transport details reported by the accepting
179
+ server. Built-in WebSocket listeners report their local and remote socket
180
+ addresses, URL path, and separately labeled `Origin`/`X-Forwarded-For` metadata;
181
+ Unix-domain sockets and Windows named pipes report their endpoint path. Origin
182
+ and forwarded-address values are untrusted raw request headers for diagnostics,
183
+ not authoritative peer identity.
184
+
185
+ ```sh
186
+ hub topology --format json
187
+ hub topology --source app --source background-service --watch
188
+ hub topology --search 'telegram|workspace-42'
189
+ ```
190
+
191
+ `hub ls --dump <file>` writes the complete directory graph, native per-directory
192
+ responses, flattened reachable listings, inaccessible branches, and reflected
193
+ schemas. `hub ls --dump-patches <file>` writes one RFC 6902 operation per line
194
+ while that same document is being explored; use `-` for stdout and add
195
+ `--watch` for continuing changes. Apply those operations in order to this
196
+ initial document:
197
+
198
+ ```json
199
+ {
200
+ "format": "linkrpc-hub-dump",
201
+ "version": 2,
202
+ "revision": 0,
203
+ "complete": false,
204
+ "schemasComplete": false,
205
+ "root": {
206
+ "target": { "kind": "root" },
207
+ "state": "unexplored",
208
+ "effectiveScopes": [{ "prefix": "" }],
209
+ "depth": 0,
210
+ "parents": [],
211
+ "nativeListings": [],
212
+ "watching": false
213
+ },
214
+ "directories": {},
215
+ "listings": [],
216
+ "inaccessible": [],
217
+ "schemas": {},
218
+ "schemaErrors": {}
219
+ }
220
+ ```
221
+
222
+ ### Calls
223
+
224
+ | Command | Purpose |
225
+ | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
226
+ | `call <method> [--param k=v]... [--params <json>\|-]` | Invoke a request. Method is form 1/2/3 (`send`, `iface::send`, `svc::iface::send`). Validates params against the live schema by default; pass `--no-validate` to skip. Use `@hash` on the method ref (e.g. `iface::send@a3f2b1`) to look up a pinned schema version for local validation. Reads stdin for `--params -`. |
227
+ | `notify <method> ...` | Fire-and-forget notification. Same flags as `call`. |
228
+ | `batch --call <method> ... [--notify <method> ...]` | Run multiple calls and notifications sequentially on one connection. `--params`, repeatable `--param`, and `--no-validate` apply to the preceding operation. Fails fast unless `--continue-on-error` is set. Results are one JSON array; stream chunks are labeled on stderr. |
229
+ | `connection create [--timeout 30s] [--ttl 5min] [--schema source]` | Open the configured remote once in a detached broker and print its authenticated local `unix:` / `npipe:` endpoint. `--timeout` is transport inactivity; `--ttl` is an absolute lifetime. `--schema` overlays static LinkRPC reflection from a local JSON file or an HTTP(S) URL. |
230
+ | `connection status` | Read lifecycle and buffer status from the broker configured by the effective endpoint. |
231
+ | `connection notifications [--after n] [--wait 25s] [--follow]` | Read incoming remote notifications from the broker's bounded buffer. `--follow` emits one JSON object per line and can run alongside calls. |
232
+ | `connection destroy` | Stop the configured connection broker through its local overlay. |
233
+ | `json-rpc-stdio <serviceId> [--params <json>]` | In the Hub profile, expose `<serviceId>::jsonRpcConnection::connectRaw` as newline-delimited JSON-RPC on stdin/stdout. Diagnostics remain on stderr. |
234
+
235
+ The inherited `--schema <path-or-url>` option also overlays static
236
+ `hubrpc.defaults`, `hubrpc.directory`, and `hubrpc.schemas` reflection for
237
+ direct calls. Application requests still go to the selected endpoint. This is
238
+ especially useful with the RPC profile's `auto` validation mode and ordinary
239
+ JSON-RPC servers.
240
+
241
+ The former `connect`, `connection-status`, `notifications`, `disconnect`,
242
+ `hash`, `check-compat`, and `schema <interface>` spellings are accepted as
243
+ compatibility aliases.
244
+
245
+ `json-rpc-stdio` lets JSON-RPC tools consume a remote service without knowing
246
+ about LinkRPC. Each non-empty input line must contain one complete JSON-RPC
247
+ frame, and each remote frame is written as one JSON line:
248
+
249
+ ```sh
250
+ hub --endpoint unix:/run/hub.sock json-rpc-stdio language-server
251
+ ```
252
+
253
+ ### Identity and approvals
254
+
255
+ These commands resolve the same persistent identity selected by the global
256
+ `--principal` option and endpoint configuration. Approval commands discover and
257
+ validate Hub access manifests through the shared Hub approver client. The
258
+ resolved identity acts as the capability issuer/root: commands only show or
259
+ decide requests whose `acceptableRootIds` allow that root, while minted
260
+ capabilities retain the request consumer as their audience.
261
+
262
+ | Command | Purpose |
263
+ | ---------------------------------------- | ------- |
264
+ | `identity show [--json]` | Print the resolved persistent principal. JSON output has a stable versioned shape. |
265
+ | `approval requests [--json]` | List pending requests and copyable opaque request IDs. |
266
+ | `approval approve <request-id> [--json]` | Approve one pending request and mint its capability. |
267
+ | `approval deny <request-id> [--reason text] [--json]` | Deny one pending request. |
268
+ | `approval ui` | Open a live, interactive approval dashboard with request details, explicit confirmation, and denial reasons. |
269
+
270
+ Approval commands install and reuse a narrowly scoped self-issued capability for
271
+ recursive directory discovery and `hubAccessManifest` inspection/decisions.
272
+ They never call `hubAccess::requestAccess`; the Hub still admits that capability
273
+ only when the selected identity is a configured trusted root.
274
+ Discovery diagnostics are written to stderr, including every manifest source
275
+ and its pending-request count; `--json` stdout remains machine-readable.
276
+
277
+ ```sh
278
+ hub --endpoint unix:/run/hub.sock?token=abc identity show --json
279
+ hub --endpoint unix:/run/hub.sock?token=abc approval requests --json
280
+ hub --endpoint unix:/run/hub.sock?token=abc approval approve ar1_c2VydmljZQByZXF1ZXN0
281
+ hub --endpoint unix:/run/hub.sock?token=abc approval ui
282
+ ```
283
+
284
+ The approval dashboard watches all discovered manifests live. Use `Up`/`Down`
285
+ or `j`/`k` to select a request, `a` to review and confirm an approval, `d` to
286
+ deny with an optional reason, `PageUp`/`PageDown` for long authority lists,
287
+ `r` to refresh, and `?` for in-app help. The layout adapts to narrow and short
288
+ terminals, preserves the selected request as the queue changes, and disables
289
+ decisions while its authoritative manifest snapshot is stale. Request details
290
+ also identify the source `hubAccessManifest` service. If a successful decision
291
+ is followed by an equivalent pending request, the dashboard keeps it visible
292
+ and reports that the consumer may have retried instead of treating the write as
293
+ failed.
294
+
295
+ ```sh
296
+ hub batch \
297
+ --call acme.mailer::prepare --param draftId=42 \
298
+ --notify acme.audit::record --params '{"event":"prepared"}' \
299
+ --call acme.mailer::send --param draftId=42
300
+ ```
301
+
302
+ ### Persistent connections
303
+
304
+ `connection create` keeps one remote transport open in a detached process. It prints the
305
+ actual local socket endpoint; store that endpoint in `LINKRPC_ENDPOINT` for later
306
+ commands:
307
+
308
+ ```sh
309
+ export LINKRPC_ENDPOINT="$(
310
+ hub --endpoint 'ws-no-init://localhost:4123?tkn=…' \
311
+ connection create --timeout 30s --ttl 5min --schema ./ahp-schema.json
312
+ )"
313
+
314
+ hub defaults
315
+ hub schema show ahp --method initialize
316
+ hub call initialize --params '{"channel":"ahp-root://","protocolVersions":["0.6.0"],"clientId":"hub"}'
317
+ hub call listSessions --params '{"channel":"ahp-root://","limit":50}'
318
+ hub connection notifications --follow
319
+ hub connection status
320
+ hub connection destroy
321
+ ```
322
+
323
+ The endpoint contains a local authentication token and should be treated as a
324
+ secret. The broker handles `hubrpc.connectionBroker::status`,
325
+ `hubrpc.connectionBroker::readNotifications`, and
326
+ `hubrpc.connectionBroker::disconnect` locally. With `--schema`, it also handles
327
+ `hubrpc.directory`, `hubrpc.schemas`, and `hubrpc.defaults` from the static
328
+ document. Every other request or notification is forwarded to the persistent
329
+ remote connection. The schema file is validated, including its interface
330
+ hashes and references, before the broker detaches. `ws-no-init:` remotes remain raw
331
+ JSON-RPC: the local endpoint carries `broker=raw`, telling subsequent CLI
332
+ processes not to perform signing or remote LinkRPC initialization.
333
+
334
+ `defaultInterface` is independent of `services`: it describes the schema for
335
+ bare methods on an ordinary RPC server and does not implicitly add a directory
336
+ service. The terminal UI presents it as a synthetic `default` entry and keeps
337
+ its application methods bare on the wire.
338
+
339
+ The inactivity timer resets for every message sent or received on either side.
340
+ The TTL is measured from broker startup and never resets. Incoming remote
341
+ notifications are kept in a sequence-numbered bounded buffer (1,000 entries by
342
+ default; configure with `--notification-limit`).
343
+
344
+ ### Offline
345
+
346
+ | Command | Purpose |
347
+ | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
348
+ | `schema hash <schema.json>` | Compute `computeInterfaceHash` of a local schema. No connection. |
349
+ | `schema check-compat <interfaceId> <local.json>` | Fetch live schema, run `isAssignable` against the local one. Verdict: identical / compatible-subset / incompatible. |
350
+ | `ping` | One `defaults::get` round-trip with latency. |
351
+
352
+ ### Interactive
353
+
354
+ | Command | Purpose |
355
+ | ------- | ------------------------------------------------------------------------------------------------------------ |
356
+ | `ui` | Launch a terminal UI: tree of services on the left, schema-derived form on the right, results at the bottom. |
357
+
358
+ ### Transport diagnostics
359
+
360
+ Pass `--log-transport` to print every raw JSON-RPC message sent and received,
361
+ including `hubrpc::initialize`. Output goes to stderr. This is lower-level than
362
+ `--log-messages`, which coalesces request flows. Raw transport logs can contain
363
+ tokens and sensitive payloads.
364
+
365
+ ## Development
366
+
367
+ ```sh
368
+ pnpm --filter @hediet/linkrpc-cli cli -- ls --cmd -- node ./your-server.js
369
+ pnpm --filter @hediet/linkrpc-cli test
370
+ ```