@hediet/linkrpc-cli 0.0.3-next.20261023.2 → 0.0.3-next.20261023.4

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 CHANGED
@@ -793,6 +793,7 @@ initial document:
793
793
  | `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. |
794
794
  | `connection destroy` | Stop the configured connection broker through its local overlay. |
795
795
  | `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. |
796
+ | `json-rpc-export --target-endpoint <uri> --serviceId <id> --path <path> ...` | In the Hub profile, export multiple JSON-RPC factories through one authenticated raw WebSocket listener. |
796
797
 
797
798
  The inherited `--schema <path-or-url>` option also overlays static
798
799
  `hubrpc.defaults`, `hubrpc.directory`, and `hubrpc.schemas` reflection for
@@ -812,6 +813,27 @@ frame, and each remote frame is written as one JSON line:
812
813
  hub --endpoint unix:/run/hub.sock json-rpc-stdio language-server
813
814
  ```
814
815
 
816
+ `json-rpc-export` maps multiple Hub JSON-RPC services to distinct paths on one listener:
817
+
818
+ ```sh
819
+ linkrpc hub json-rpc-export \
820
+ --target-endpoint "listen:ws://127.0.0.1:8123" \
821
+ --target-token "$EXPORT_TOKEN" \
822
+ --target-token-query-param tkn \
823
+ --serviceId foo --path /foo \
824
+ --serviceId bar --path /bar
825
+ ```
826
+
827
+ Each `--serviceId` must be followed by its `--path`; a leading slash is optional. Missing pairs, duplicate paths, and unsupported endpoints fail before connecting to the Hub. Each WebSocket client opens an independent `<serviceId>::jsonRpcConnection::connectRaw` session, including clients on the same route. Requests, responses, notifications, and server-initiated requests pass unchanged, without a `hubrpc::initialize` handshake.
828
+
829
+ The nonempty export token is separate from the source Hub credentials. Clients authenticate during the WebSocket upgrade with `Authorization: Bearer <token>` or a configured query parameter. Without `--target-token-query-param`, the accepted query names are `tkn` (VS Code sends its `connectionToken` under this name) and `token`. Supplying the repeatable option replaces those defaults: for example `--target-token-query-param connectionToken --target-token-query-param api_key` accepts either custom name. Bearer headers remain accepted. Exactly one credential is required: duplicate query parameters, multiple Authorization headers, or a header combined with a recognized query parameter are rejected, even if their values match. Unrecognized parameters are not credentials.
830
+
831
+ Alternatively, configure the listener token in the target URI as `listen:ws://127.0.0.1:8123?token=...`; this configuration syntax is independent of the accepted client query names. Readiness output never includes the token. Unknown routes return 404 and missing/invalid/ambiguous credentials return 401 without opening an upstream session. Anyone holding this token can access all configured routes using the export process's Hub identity; use a narrowly scoped identity and protect the token. Client credentials are consumed by the listener and never forwarded to the upstream factory.
832
+
833
+ Only `listen:ws://` TCP listeners are currently supported, not named pipes or TLS listeners. Bind loopback for local use. For remote access, terminate TLS in a trusted reverse proxy rather than sending tokens over unencrypted networks. The gateway limits connections to 100, frames to 16 MiB, and pending input per client to 32 MiB / 1024 frames; slow consumers and failed upstreams close only their own connection. Factories must open within 30 seconds.
834
+
835
+ The command stays in the foreground until SIGINT/SIGTERM, then closes clients and the listener. Use the existing Hub daemon/service lifecycle to supervise it; this command neither installs a service nor starts remote AHP hosts. Diagnostics and the bound address (including an assigned port when using port 0) go to stderr.
836
+
815
837
  ### Participant proxy
816
838
 
817
839
  `proxy` stands in for a participant on the hub: the participant is either this
@@ -26,6 +26,7 @@ import { stdin, stdout } from "node:process";
26
26
  import { TrafficFlowAggregator, createTerminalConsentPrompt, formatFlowSummary } from "@hediet/linkrpc-hub";
27
27
  import { loadHubConfig, printHubConfigSchema } from "@hediet/linkrpc-hub/config-file";
28
28
  import { connectJsonRpcTransports, connectRawJsonRpcTransport, createNdjsonJsonRpcTransport, jsonRpcConnectionInterface } from "@hediet/linkrpc-infra/json-rpc";
29
+ import { startJsonRpcWebSocketExport, validateJsonRpcExport } from "@hediet/linkrpc-infra/json-rpc/node";
29
30
  import { GRAPH_INTERFACE_TAG, IMMUTABLE_GRAPH_INTERFACE_ID, ROOT_WATCH_INTERFACE_ID, evaluateGraphPresentation, graphPresentationInterface, graphPresentationSchema, isGraphRef, standardGraphRuntimeOptions, validateGraphInterfaceSchema } from "@hediet/linkrpc-infra/graph";
30
31
  import React from "react";
31
32
  import { Box, Text, measureElement, render } from "ink";
@@ -3510,6 +3511,36 @@ async function jsonRpcStdioCommand(options) {
3510
3511
  stdio.close("closed");
3511
3512
  }
3512
3513
  //#endregion
3514
+ //#region src/commands/jsonRpcExport.ts
3515
+ /** Preserve option order so a missing path cannot shift subsequent service mappings. */
3516
+ var JsonRpcExportRoutes = class {
3517
+ _routes = [];
3518
+ _pendingService;
3519
+ serviceId(value) {
3520
+ if (this._pendingService !== void 0) throw new Error("Each --serviceId must be followed by --path.");
3521
+ this._pendingService = value;
3522
+ }
3523
+ path(value) {
3524
+ if (this._pendingService === void 0) throw new Error("--path must follow --serviceId.");
3525
+ this._routes.push({
3526
+ serviceId: this._pendingService,
3527
+ path: value.startsWith("/") ? value : `/${value}`
3528
+ });
3529
+ this._pendingService = void 0;
3530
+ }
3531
+ getRoutes() {
3532
+ if (this._pendingService !== void 0) throw new Error("Each --serviceId must be followed by --path.");
3533
+ return this._routes;
3534
+ }
3535
+ };
3536
+ async function jsonRpcExportCommand(options) {
3537
+ const connection = new LinkRpcConnection(options.local.rpcChannel);
3538
+ return startJsonRpcWebSocketExport({
3539
+ ...options,
3540
+ openTransport: (serviceId, signal) => connectRawJsonRpcTransport(connection.service(serviceId).get(jsonRpcConnectionInterface), void 0, signal)
3541
+ });
3542
+ }
3543
+ //#endregion
3513
3544
  //#region ../../node_modules/.pnpm/@vscode+observables@0.1.1-0/node_modules/@vscode/observables/dist/observableInternal/logging/logging.js
3514
3545
  let globalObservableLogger;
3515
3546
  function addLogger(logger) {
@@ -8953,6 +8984,40 @@ Batch options:
8953
8984
  });
8954
8985
  });
8955
8986
  if (profile === "hub") {
8987
+ const exportRoutes = new JsonRpcExportRoutes();
8988
+ program.command("json-rpc-export").description("Export Hub JSON-RPC services through one authenticated raw WebSocket listener.").requiredOption("--target-endpoint <uri>", "Listener URI, e.g. listen:ws://127.0.0.1:8123").option("--target-token <token>", "Local client authentication token (or put token in target URI)").option("--target-token-query-param <name>", "Accepted token query parameter (repeatable; replaces defaults tkn and token)", collectOption).option("--serviceId <id>", "Begin a route; follow each occurrence with --path", (value) => {
8989
+ exportRoutes.serviceId(value);
8990
+ return value;
8991
+ }).option("--path <path>", "Path for the preceding service ID; repeat pairs for multiple routes", (value) => {
8992
+ exportRoutes.path(value);
8993
+ return value;
8994
+ }).action(async (opts) => {
8995
+ const options = {
8996
+ targetEndpoint: opts.targetEndpoint,
8997
+ token: opts.targetToken,
8998
+ tokenQueryParameters: opts.targetTokenQueryParam,
8999
+ routes: exportRoutes.getRoutes()
9000
+ };
9001
+ validateJsonRpcExport(options);
9002
+ const localEp = needEndpoint(endpoint);
9003
+ const local = await connect(localEp, wireLog("hub"));
9004
+ try {
9005
+ reportIdentity(await setupSigning(local.channel, local.signing, getPrincipalSpec(), { negotiateHubCaps: isHubEndpoint(localEp) }));
9006
+ const server = await jsonRpcExportCommand({
9007
+ ...options,
9008
+ local,
9009
+ onError: (error) => process.stderr.write(`linkrpc: ${error.message}\n`)
9010
+ });
9011
+ try {
9012
+ process.stderr.write(`linkrpc: JSON-RPC export listening at ${server.endpoint}\n`);
9013
+ await _untilSignalled();
9014
+ } finally {
9015
+ await server.close();
9016
+ }
9017
+ } finally {
9018
+ local.close();
9019
+ }
9020
+ });
8956
9021
  program.command("json-rpc-stdio <serviceId>").description("Expose <serviceId>::jsonRpcConnection::connectRaw as a newline-delimited JSON-RPC endpoint on this process's stdin/stdout.").option("--params <json>", "JSON value passed to the remote transport factory").action(async (serviceId, opts) => {
8957
9022
  const localEp = needEndpoint(endpoint);
8958
9023
  let params;
@@ -9186,7 +9251,7 @@ Batch options:
9186
9251
  process.once("SIGINT", onSignal);
9187
9252
  process.once("SIGTERM", onSignal);
9188
9253
  try {
9189
- const { runApprovalUi } = await import("./runApprovalUi-CNp2DhKG.js");
9254
+ const { runApprovalUi } = await import("./runApprovalUi-CVymyDHh.js");
9190
9255
  await runApprovalUi({
9191
9256
  client,
9192
9257
  signal: stop.signal
@@ -9201,7 +9266,7 @@ Batch options:
9201
9266
  registerViewCommands(program, (action) => withChannel(endpoint, getPrincipalSpec(), action));
9202
9267
  program.command("ui").description("Launch the terminal UI (form-based browser of services + methods).").action(async () => {
9203
9268
  const acquired = await acquireConnection(endpoint);
9204
- const { runUi } = await import("./runUi-B4rV3ZfF.js");
9269
+ const { runUi } = await import("./runUi-ByL1VdAP.js");
9205
9270
  try {
9206
9271
  await runUi({
9207
9272
  endpoint: acquired.ep,
@@ -9737,4 +9802,4 @@ function safeJson(value) {
9737
9802
  //#endregion
9738
9803
  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 };
9739
9804
 
9740
- //# sourceMappingURL=cli-B93twHKF.js.map
9805
+ //# sourceMappingURL=cli-CYL6JUxF.js.map