@hediet/linkrpc-cli 0.0.3-next.20261021.2 → 0.0.3-next.20261022.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 CHANGED
@@ -793,6 +793,35 @@ frame, and each remote frame is written as one JSON line:
793
793
  hub --endpoint unix:/run/hub.sock json-rpc-stdio language-server
794
794
  ```
795
795
 
796
+ ### Participant proxy
797
+
798
+ `proxy` stands in for a participant on the hub: the participant is either this
799
+ process's stdin/stdout (`--stdio`) or a `--target-endpoint*`. It keeps full
800
+ LinkRPC semantics (service ids, streaming, cancellation) and adds what the
801
+ participant cannot do itself:
802
+
803
+ - `--outbound signed` (default) re-signs every call the participant emits as
804
+ the CLI's `--principal` and negotiates capabilities, so a participant without
805
+ signing support can call services behind `forwardChecking`.
806
+ `--outbound verbatim` forwards calls unsigned (keeping any `$hubrpc` envelope
807
+ the participant attached); `--outbound none` drops them.
808
+ - `--claim <serviceId>` claims the service id and forwards requests the hub
809
+ routes to it to the participant, verbatim (callers' signatures are kept).
810
+
811
+ ```sh
812
+ # A Rust client that cannot sign yet reaches a gated remote service.
813
+ # Point it at this cmd-stdio endpoint and address calls to the remote service id:
814
+ # cmd-stdio:?argv=linkrpc&argv=hub&argv=proxy&argv=--stdio
815
+ hub proxy --stdio
816
+
817
+ # Serve a stdio program under a service id (equivalent to `tunnel`).
818
+ hub proxy --claim my/service --outbound none --target-endpoint-cmd-stdio "my-server --stdio"
819
+ ```
820
+
821
+ With `--stdio`, the proxy exits when stdin closes. `tunnel <serviceId>` is
822
+ `proxy --claim <serviceId>` with `--bidi` selecting `--outbound verbatim`
823
+ instead of `none`.
824
+
796
825
  ### Capabilities
797
826
 
798
827
  `linkrpc hub caps list [--json]` lists the selected principal's stored capabilities,
@@ -1,4 +1,4 @@
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";
1
+ import { $ as requestTopologyAccess, B as proxyCommand, F as disconnectBroker, G as parseDuration, H as withStaticHubReflection, I as getBrokerStatus, L as readBrokerNotifications, M as readEndpointDiscovery, N as ContextStore, P as formatContextReference, Q as requestReflectionAccess, R as stopConnectionBroker, St as mcpForwardInterface, U as parseConnectionLimit, V as tunnelCommand, W as parseConnectionLimitMilliseconds, _t as validateValueAgainstSchema, a as resolveInvocationEndpoint, bt as parseParamOverride, c as resolveLocalEndpoint, ct as parsePrincipalSpec, dt as connectViaTransport, et as setupSigning, ft as resolveTargetEndpoint, gt as explainValidation, ht as describeSchema, i as resolveInvocationContext, it as walkHub, lt as connect, m as managedEndpointsInterface, mt as describeObjectParams, n as loadStaticHubSchema, nt as fetchSchema$1, o as resolveSigning, ot as logoutCliIdentity, pt as resolvedEndpointToConfig, q as resolveCliInvocation, r as resolveStaticHubSchemaSource, rt as findMethodInSchema, s as validationDefault, st as formatPrincipalSource, t as completeForLine, tt as fetchDefaults, ut as connectViaRootOverlay, xt as MethodRefWithOptHash, y as HubHome, yt as mergeParams, z as ConnectionBroker } from "./runComplete-HH0NuvsL.js";
2
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
3
  import { z } from "zod";
4
4
  import { WebSocketTransport, connectNdjson, connectToHub, formatEndpointUri, isHubEndpoint, openWebSocket, parseEndpointUri, runInitializeHandshake, traceMessageTransport as traceMessageTransport$1 } from "@hediet/linkrpc/node";
@@ -406,7 +406,7 @@ function contextNamespace(reference) {
406
406
  async function useLocalManagement(start, action) {
407
407
  const home = HubHome.resolve();
408
408
  if (start) {
409
- const { startDaemon, nodeDaemonLaunch } = await import("./src-ConeU7vr.js");
409
+ const { startDaemon, nodeDaemonLaunch } = await import("./src-CCHXMIFR.js");
410
410
  if (await startDaemon({
411
411
  home,
412
412
  launch: nodeDaemonLaunch(home)
@@ -426,7 +426,7 @@ async function useLocalManagement(start, action) {
426
426
  }
427
427
  }
428
428
  async function restartLocalDaemon() {
429
- const { startDaemon, stopDaemon, nodeDaemonLaunch } = await import("./src-ConeU7vr.js");
429
+ const { startDaemon, stopDaemon, nodeDaemonLaunch } = await import("./src-CCHXMIFR.js");
430
430
  const home = HubHome.resolve();
431
431
  process.stderr.write("Restarting the local Hub daemon: clients will disconnect and managed workloads will stop. OS autostart settings are unchanged.\n");
432
432
  await stopDaemon(home);
@@ -8927,13 +8927,7 @@ Batch options:
8927
8927
  logMessages: g_logMessages
8928
8928
  });
8929
8929
  });
8930
- program.command("tunnel <serviceId>").description("Claim <serviceId> on the source hub (--endpoint*) and forward every request it routes here to a target endpoint over a second connection. The target endpoint is set via --target-endpoint / --target-endpoint-cmd / --target-endpoint-cmd-stdio; when it is --target-endpoint-cmd, the spawned child sees `hubGrantedServiceId::get().grantedServiceIdNamespace` = <serviceId> from its local hub, and global --provision-identity[-slot] applies to it. With `-c, --config`, the source hub is instead appended to the config as an endpoint claiming <serviceId>, and the in-process hub serves it (the config provides the target). Runs until Ctrl-C.").option("--target-endpoint <uri>", "target endpoint URI (unix:/path?token=…, npipe://./pipe/…, ws[s]://host?token=…, cmd:?command=… or cmd-stdio:?command=…)").option("--target-endpoint-cmd <command>", "spawn the target as a server, connect via an injected env socket; the child sees grantedServiceIdNamespace = <serviceId> via hubGrantedServiceId::get").option("--target-endpoint-cmd-stdio <command>", "spawn the target and talk linkrpc over its stdio").option("--target-endpoint-cmd-env <key=value>", "env var for the --target-endpoint-cmd / --target-endpoint-cmd-stdio child (repeatable)", (val, acc) => {
8931
- const eq = val.indexOf("=");
8932
- if (eq === -1) throw new InvalidArgumentError(`expected key=value, got '${val}'`);
8933
- const next = acc ?? {};
8934
- next[val.slice(0, eq)] = val.slice(eq + 1);
8935
- return next;
8936
- }).option("--target-endpoint-token <token>", "token overriding the target URI token (no env fallback for the target)").option("--target-endpoint-cmd-cwd <dir>", "working directory for the --target-endpoint-cmd / --target-endpoint-cmd-stdio child").option("--bidi", "also forward every request/notification the target endpoint emits back to the source hub, exposing the hub to the endpoint (forwards everything, unsigned)").action(async (serviceId, cmdOpts) => {
8930
+ addTargetEndpointOptions(program.command("tunnel <serviceId>").description("Claim <serviceId> on the source hub (--endpoint*) and forward every request it routes here to a target endpoint over a second connection. The target endpoint is set via --target-endpoint / --target-endpoint-cmd / --target-endpoint-cmd-stdio; when it is --target-endpoint-cmd, the spawned child sees `hubGrantedServiceId::get().grantedServiceIdNamespace` = <serviceId> from its local hub, and global --provision-identity[-slot] applies to it. With `-c, --config`, the source hub is instead appended to the config as an endpoint claiming <serviceId>, and the in-process hub serves it (the config provides the target). Runs until Ctrl-C.")).option("--bidi", "also forward every request/notification the target endpoint emits back to the source hub, exposing the hub to the endpoint (forwards everything, unsigned)").action(async (serviceId, cmdOpts) => {
8937
8931
  const localEp = needEndpoint(endpoint);
8938
8932
  if (g_hubConfigPath !== void 0) {
8939
8933
  const baseConfig = loadHubConfig(g_hubConfigPath);
@@ -8957,42 +8951,9 @@ Batch options:
8957
8951
  });
8958
8952
  return;
8959
8953
  }
8960
- const globalOpts = program.opts();
8961
- const targetRes = resolveTargetEndpoint({
8962
- targetEndpoint: cmdOpts.targetEndpoint,
8963
- targetEndpointCmd: cmdOpts.targetEndpointCmd,
8964
- targetEndpointCmdStdio: cmdOpts.targetEndpointCmdStdio,
8965
- targetEndpointCmdEnv: cmdOpts.targetEndpointCmdEnv,
8966
- targetEndpointToken: cmdOpts.targetEndpointToken,
8967
- targetEndpointCmdCwd: cmdOpts.targetEndpointCmdCwd,
8968
- provisionIdentity: globalOpts.provisionIdentity,
8969
- provisionIdentitySlot: globalOpts.provisionIdentitySlot
8970
- });
8971
- if (targetRes.error) {
8972
- process.stderr.write(`linkrpc: ${targetRes.error}\n`);
8973
- process.exit(2);
8974
- }
8975
- if (!targetRes.endpoint) {
8976
- process.stderr.write("linkrpc: tunnel requires --target-endpoint / --target-endpoint-cmd / --target-endpoint-cmd-stdio.\n");
8977
- process.exit(2);
8978
- }
8979
- const targetEp = targetRes.endpoint;
8980
- const remoteTok = process.env.LINKRPC_REMOTE_ENDPOINT_TOKEN;
8981
- const targetEpWithToken = remoteTok !== void 0 && (targetEp.kind === "socket" || targetEp.kind === "ws") && targetEp.token === void 0 ? {
8982
- ...targetEp,
8983
- token: remoteTok
8984
- } : targetEp;
8954
+ const targetEp = resolveTargetEndpointOrExit(cmdOpts, program.opts(), "tunnel");
8985
8955
  const local = await connect(localEp, wireLog("source"));
8986
- let remote;
8987
- if (targetEpWithToken.kind === "cmd-env") remote = await connectViaRootOverlay({
8988
- command: targetEpWithToken.command,
8989
- provisionSlot: targetEpWithToken.provisionSlot,
8990
- env: targetEpWithToken.env,
8991
- ...targetEpWithToken.cwd !== void 0 ? { cwd: targetEpWithToken.cwd } : {},
8992
- grantedNamespace: serviceId,
8993
- log: wireLog("target")
8994
- });
8995
- else remote = await connect(targetEpWithToken, wireLog("target"));
8956
+ const remote = await connectTargetEndpoint(targetEp, serviceId);
8996
8957
  try {
8997
8958
  reportIdentity(await setupSigning(local.channel, local.signing, getPrincipalSpec(), { negotiateHubCaps: isHubEndpoint(localEp) }));
8998
8959
  await tunnelCommand({
@@ -9006,6 +8967,36 @@ Batch options:
9006
8967
  local.close();
9007
8968
  }
9008
8969
  });
8970
+ addTargetEndpointOptions(program.command("proxy").description("Stand in for a participant on the hub (--endpoint*). The participant is this process's stdin/stdout (--stdio) or a target endpoint (--target-endpoint*). Calls the participant emits are forwarded to the hub per --outbound: `signed` (default) re-signs them as this CLI's --principal with capability negotiation, so participants that cannot sign can call gated services; `verbatim` forwards them unsigned; `none` drops them. With --claim, requests the hub routes to <serviceId> are forwarded to the participant. Runs until the participant disconnects or Ctrl-C.")).option("--stdio", "use this process's stdin/stdout as the participant").option("--claim <serviceId>", "claim <serviceId> on the hub and forward its requests to the participant").addOption(new Option("--outbound <mode>", "how participant calls reach the hub").choices([
8971
+ "signed",
8972
+ "verbatim",
8973
+ "none"
8974
+ ]).default("signed")).action(async (cmdOpts) => {
8975
+ const localEp = needEndpoint(endpoint);
8976
+ if (cmdOpts.stdio === hasTargetEndpoint(cmdOpts)) {
8977
+ process.stderr.write("linkrpc: proxy requires exactly one of --stdio or --target-endpoint*.\n");
8978
+ process.exit(2);
8979
+ }
8980
+ const targetEp = cmdOpts.stdio ? void 0 : resolveTargetEndpointOrExit(cmdOpts, program.opts(), "proxy");
8981
+ const hub = await connect(localEp, wireLog("hub"));
8982
+ reportIdentity(await setupSigning(hub.channel, hub.signing, getPrincipalSpec(), { negotiateHubCaps: isHubEndpoint(localEp) }));
8983
+ const participant = targetEp === void 0 ? await connectStdioParticipant() : {
8984
+ connection: await connectTargetEndpoint(targetEp, cmdOpts.claim ?? ""),
8985
+ closed: void 0
8986
+ };
8987
+ try {
8988
+ await proxyCommand({
8989
+ hub,
8990
+ participant: participant.connection,
8991
+ outbound: cmdOpts.outbound,
8992
+ ...cmdOpts.claim !== void 0 ? { claimServiceId: cmdOpts.claim } : {},
8993
+ ...participant.closed !== void 0 ? { stop: participant.closed } : {}
8994
+ });
8995
+ } finally {
8996
+ participant.connection.close();
8997
+ hub.close();
8998
+ }
8999
+ });
9009
9000
  program.command("connect-as <binderServiceId> <slot>").description("Mint a single-use identity token for <slot> from the <binderServiceId> (a service implementing connectionTokenBinder, typically the hub service id), then splice a target endpoint onto the hub as a first-class participant with that slot's managed identity and a granted serviceId namespace (--granted-service-id, defaulting to <slot>). No local hub is started: the CLI proxies the connection (target ⟷ CLI ⟷ hub), so it works with every target kind and --log-messages taps the full flow. The hub-facing bound-token listener is --hub-endpoint (defaults to --endpoint when that is a ws/socket). Runs until Ctrl-C.").option("--hub-endpoint <uri>", "ws/socket bound-token listener to splice the target onto (default: --endpoint)").option("--target-endpoint <uri>", "target endpoint URI (unix:/…, ws[s]://…)").option("--target-endpoint-cmd <command>", "spawn the target as a server; it dials the CLI back").option("--target-endpoint-cmd-stdio <command>", "spawn the target and talk linkrpc over its stdio").option("--target-endpoint-cmd-env <key=value>", "env var for the --target-endpoint-cmd / --target-endpoint-cmd-stdio child (repeatable)", (val, acc) => {
9010
9001
  const eq = val.indexOf("=");
9011
9002
  if (eq === -1) throw new InvalidArgumentError(`expected key=value, got '${val}'`);
@@ -9127,7 +9118,7 @@ Batch options:
9127
9118
  process.once("SIGINT", onSignal);
9128
9119
  process.once("SIGTERM", onSignal);
9129
9120
  try {
9130
- const { runApprovalUi } = await import("./runApprovalUi-BVAPyMfz.js");
9121
+ const { runApprovalUi } = await import("./runApprovalUi-DpoUwBpd.js");
9131
9122
  await runApprovalUi({
9132
9123
  client,
9133
9124
  signal: stop.signal
@@ -9142,7 +9133,7 @@ Batch options:
9142
9133
  registerViewCommands(program, (action) => withChannel(endpoint, getPrincipalSpec(), action));
9143
9134
  program.command("ui").description("Launch the terminal UI (form-based browser of services + methods).").action(async () => {
9144
9135
  const acquired = await acquireConnection(endpoint);
9145
- const { runUi } = await import("./runUi-BrFeYazs.js");
9136
+ const { runUi } = await import("./runUi-BbiA5lOP.js");
9146
9137
  try {
9147
9138
  await runUi({
9148
9139
  endpoint: acquired.ep,
@@ -9525,6 +9516,75 @@ function needEndpoint(endpoint) {
9525
9516
  }
9526
9517
  return endpoint;
9527
9518
  }
9519
+ /** The `--target-endpoint*` options shared by `tunnel` and `proxy`. */
9520
+ function addTargetEndpointOptions(command) {
9521
+ return command.option("--target-endpoint <uri>", "target endpoint URI (unix:/path?token=…, npipe://./pipe/…, ws[s]://host?token=…, cmd:?command=… or cmd-stdio:?command=…)").option("--target-endpoint-cmd <command>", "spawn the target as a server, connect via an injected env socket; the child sees the claimed service id as grantedServiceIdNamespace via hubGrantedServiceId::get").option("--target-endpoint-cmd-stdio <command>", "spawn the target and talk linkrpc over its stdio").option("--target-endpoint-cmd-env <key=value>", "env var for the --target-endpoint-cmd / --target-endpoint-cmd-stdio child (repeatable)", (val, acc) => {
9522
+ const eq = val.indexOf("=");
9523
+ if (eq === -1) throw new InvalidArgumentError(`expected key=value, got '${val}'`);
9524
+ const next = acc ?? {};
9525
+ next[val.slice(0, eq)] = val.slice(eq + 1);
9526
+ return next;
9527
+ }).option("--target-endpoint-token <token>", "token overriding the target URI token (no env fallback for the target)").option("--target-endpoint-cmd-cwd <dir>", "working directory for the --target-endpoint-cmd / --target-endpoint-cmd-stdio child");
9528
+ }
9529
+ function hasTargetEndpoint(opts) {
9530
+ return opts.targetEndpoint !== void 0 || opts.targetEndpointCmd !== void 0 || opts.targetEndpointCmdStdio !== void 0;
9531
+ }
9532
+ function resolveTargetEndpointOrExit(opts, globalOpts, commandName) {
9533
+ const targetRes = resolveTargetEndpoint({
9534
+ ...opts,
9535
+ provisionIdentity: globalOpts.provisionIdentity,
9536
+ provisionIdentitySlot: globalOpts.provisionIdentitySlot
9537
+ });
9538
+ if (targetRes.error) {
9539
+ process.stderr.write(`linkrpc: ${targetRes.error}\n`);
9540
+ process.exit(2);
9541
+ }
9542
+ if (!targetRes.endpoint) {
9543
+ process.stderr.write(`linkrpc: ${commandName} requires --target-endpoint / --target-endpoint-cmd / --target-endpoint-cmd-stdio.\n`);
9544
+ process.exit(2);
9545
+ }
9546
+ const targetEp = targetRes.endpoint;
9547
+ const remoteTok = process.env.LINKRPC_REMOTE_ENDPOINT_TOKEN;
9548
+ return remoteTok !== void 0 && (targetEp.kind === "socket" || targetEp.kind === "ws") && targetEp.token === void 0 ? {
9549
+ ...targetEp,
9550
+ token: remoteTok
9551
+ } : targetEp;
9552
+ }
9553
+ /**
9554
+ * Connect straight to a target endpoint — no routing hub. A spawned cmd-env
9555
+ * child is fronted with a RootOverlay: its root-form calls (identity::*,
9556
+ * hubGrantedServiceId::*, reflection, hubAccess) are served locally while every
9557
+ * forwarded request is relayed to the child through the overlay splitter. This
9558
+ * keeps --provision-identity working (identity::* is served) without the 'hub'
9559
+ * service id / claim-wait dance a full local hub imposes.
9560
+ */
9561
+ async function connectTargetEndpoint(target, grantedNamespace) {
9562
+ if (target.kind !== "cmd-env") return connect(target, wireLog("target"));
9563
+ return connectViaRootOverlay({
9564
+ command: target.command,
9565
+ provisionSlot: target.provisionSlot,
9566
+ env: target.env,
9567
+ ...target.cwd !== void 0 ? { cwd: target.cwd } : {},
9568
+ grantedNamespace,
9569
+ log: wireLog("target")
9570
+ });
9571
+ }
9572
+ /** This process's stdin/stdout as a participant; `closed` resolves when stdin ends. */
9573
+ async function connectStdioParticipant() {
9574
+ let markClosed;
9575
+ const closed = new Promise((resolve) => {
9576
+ markClosed = resolve;
9577
+ });
9578
+ const { transport } = await connectNdjson({
9579
+ input: process.stdin,
9580
+ output: process.stdout,
9581
+ onClose: () => markClosed()
9582
+ });
9583
+ return {
9584
+ connection: connectViaTransport(transport),
9585
+ closed
9586
+ };
9587
+ }
9528
9588
  /**
9529
9589
  * Resolve the ws/socket bound-token listener `connect-as` splices the target
9530
9590
  * onto: an explicit `--hub-endpoint <uri>`, else the fallback endpoint when it
@@ -9609,4 +9669,4 @@ function safeJson(value) {
9609
9669
  //#endregion
9610
9670
  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 };
9611
9671
 
9612
- //# sourceMappingURL=cli-CO0iSiY_.js.map
9672
+ //# sourceMappingURL=cli-Cw9kg0Dt.js.map