@hediet/linkrpc-cli 0.0.3-next.20261021.2 → 0.0.3-next.20261022.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.
package/README.md CHANGED
@@ -793,6 +793,38 @@ 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
+ - Calls the participant emits are forwarded to the hub unsigned by default,
804
+ keeping any `$hubrpc` envelope the participant attached. `--sign` (explicitly
805
+ on the `proxy` command line; the Hub profile's signing default does not
806
+ apply) re-signs them as `--principal`, by default the managed Hub identity,
807
+ and negotiates capabilities, so a participant without signing support can
808
+ call services behind `forwardChecking`. This lends the participant that
809
+ identity. `--no-outbound` drops its calls instead.
810
+ - `--claim <serviceId>` claims the service id and forwards requests the hub
811
+ routes to it to the participant, verbatim (callers' signatures are kept).
812
+
813
+ ```sh
814
+ # A Rust client that cannot sign yet reaches a gated remote service.
815
+ # Point it at this cmd-stdio endpoint and address calls to the remote service id:
816
+ # cmd-stdio:?argv=linkrpc&argv=hub&argv=proxy&argv=--stdio&argv=--sign
817
+ hub proxy --stdio --sign
818
+ hub proxy --stdio --sign --principal user:automation
819
+
820
+ # Serve a stdio program under a service id (equivalent to `tunnel`).
821
+ hub proxy --claim my/service --no-outbound --target-endpoint-cmd-stdio "my-server --stdio"
822
+ ```
823
+
824
+ With `--stdio`, the proxy exits when stdin closes. `tunnel <serviceId>` is
825
+ `proxy --claim <serviceId> --no-outbound`; with `--bidi` it forwards the
826
+ target's calls unsigned.
827
+
796
828
  ### Capabilities
797
829
 
798
830
  `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,37 @@ 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 unsigned, keeping any signature the participant attached. Pass --sign explicitly to re-sign them as --principal (default: the managed Hub identity) with capability negotiation, so participants that cannot sign can call gated services; --no-outbound drops them instead. 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").option("--no-outbound", "do not forward calls the participant emits to the hub").action(async (cmdOpts) => {
8971
+ const localEp = needEndpoint(endpoint);
8972
+ if (cmdOpts.stdio === hasTargetEndpoint(cmdOpts)) {
8973
+ process.stderr.write("linkrpc: proxy requires exactly one of --stdio or --target-endpoint*.\n");
8974
+ process.exit(2);
8975
+ }
8976
+ const signOutbound = g_invocation?.cliOverrides.sign === true;
8977
+ if (signOutbound && !cmdOpts.outbound) {
8978
+ process.stderr.write("linkrpc: proxy --sign has no effect with --no-outbound.\n");
8979
+ process.exit(2);
8980
+ }
8981
+ const targetEp = cmdOpts.stdio ? void 0 : resolveTargetEndpointOrExit(cmdOpts, program.opts(), "proxy");
8982
+ const hub = await connect(localEp, wireLog("hub"));
8983
+ if (resolveSigning(g_invocation?.profile ?? "hub", g_invocation?.values ?? {})) reportIdentity(await setupSigning(hub.channel, hub.signing, getPrincipalSpec(), { negotiateHubCaps: isHubEndpoint(localEp) }));
8984
+ const participant = targetEp === void 0 ? await connectStdioParticipant() : {
8985
+ connection: await connectTargetEndpoint(targetEp, cmdOpts.claim ?? ""),
8986
+ closed: void 0
8987
+ };
8988
+ try {
8989
+ await proxyCommand({
8990
+ hub,
8991
+ participant: participant.connection,
8992
+ outbound: !cmdOpts.outbound ? "none" : signOutbound ? "signed" : "verbatim",
8993
+ ...cmdOpts.claim !== void 0 ? { claimServiceId: cmdOpts.claim } : {},
8994
+ ...participant.closed !== void 0 ? { stop: participant.closed } : {}
8995
+ });
8996
+ } finally {
8997
+ participant.connection.close();
8998
+ hub.close();
8999
+ }
9000
+ });
9009
9001
  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
9002
  const eq = val.indexOf("=");
9011
9003
  if (eq === -1) throw new InvalidArgumentError(`expected key=value, got '${val}'`);
@@ -9127,7 +9119,7 @@ Batch options:
9127
9119
  process.once("SIGINT", onSignal);
9128
9120
  process.once("SIGTERM", onSignal);
9129
9121
  try {
9130
- const { runApprovalUi } = await import("./runApprovalUi-BVAPyMfz.js");
9122
+ const { runApprovalUi } = await import("./runApprovalUi-CkT1KK-X.js");
9131
9123
  await runApprovalUi({
9132
9124
  client,
9133
9125
  signal: stop.signal
@@ -9142,7 +9134,7 @@ Batch options:
9142
9134
  registerViewCommands(program, (action) => withChannel(endpoint, getPrincipalSpec(), action));
9143
9135
  program.command("ui").description("Launch the terminal UI (form-based browser of services + methods).").action(async () => {
9144
9136
  const acquired = await acquireConnection(endpoint);
9145
- const { runUi } = await import("./runUi-BrFeYazs.js");
9137
+ const { runUi } = await import("./runUi-mpVqJ6QU.js");
9146
9138
  try {
9147
9139
  await runUi({
9148
9140
  endpoint: acquired.ep,
@@ -9525,6 +9517,75 @@ function needEndpoint(endpoint) {
9525
9517
  }
9526
9518
  return endpoint;
9527
9519
  }
9520
+ /** The `--target-endpoint*` options shared by `tunnel` and `proxy`. */
9521
+ function addTargetEndpointOptions(command) {
9522
+ 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) => {
9523
+ const eq = val.indexOf("=");
9524
+ if (eq === -1) throw new InvalidArgumentError(`expected key=value, got '${val}'`);
9525
+ const next = acc ?? {};
9526
+ next[val.slice(0, eq)] = val.slice(eq + 1);
9527
+ return next;
9528
+ }).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");
9529
+ }
9530
+ function hasTargetEndpoint(opts) {
9531
+ return opts.targetEndpoint !== void 0 || opts.targetEndpointCmd !== void 0 || opts.targetEndpointCmdStdio !== void 0;
9532
+ }
9533
+ function resolveTargetEndpointOrExit(opts, globalOpts, commandName) {
9534
+ const targetRes = resolveTargetEndpoint({
9535
+ ...opts,
9536
+ provisionIdentity: globalOpts.provisionIdentity,
9537
+ provisionIdentitySlot: globalOpts.provisionIdentitySlot
9538
+ });
9539
+ if (targetRes.error) {
9540
+ process.stderr.write(`linkrpc: ${targetRes.error}\n`);
9541
+ process.exit(2);
9542
+ }
9543
+ if (!targetRes.endpoint) {
9544
+ process.stderr.write(`linkrpc: ${commandName} requires --target-endpoint / --target-endpoint-cmd / --target-endpoint-cmd-stdio.\n`);
9545
+ process.exit(2);
9546
+ }
9547
+ const targetEp = targetRes.endpoint;
9548
+ const remoteTok = process.env.LINKRPC_REMOTE_ENDPOINT_TOKEN;
9549
+ return remoteTok !== void 0 && (targetEp.kind === "socket" || targetEp.kind === "ws") && targetEp.token === void 0 ? {
9550
+ ...targetEp,
9551
+ token: remoteTok
9552
+ } : targetEp;
9553
+ }
9554
+ /**
9555
+ * Connect straight to a target endpoint — no routing hub. A spawned cmd-env
9556
+ * child is fronted with a RootOverlay: its root-form calls (identity::*,
9557
+ * hubGrantedServiceId::*, reflection, hubAccess) are served locally while every
9558
+ * forwarded request is relayed to the child through the overlay splitter. This
9559
+ * keeps --provision-identity working (identity::* is served) without the 'hub'
9560
+ * service id / claim-wait dance a full local hub imposes.
9561
+ */
9562
+ async function connectTargetEndpoint(target, grantedNamespace) {
9563
+ if (target.kind !== "cmd-env") return connect(target, wireLog("target"));
9564
+ return connectViaRootOverlay({
9565
+ command: target.command,
9566
+ provisionSlot: target.provisionSlot,
9567
+ env: target.env,
9568
+ ...target.cwd !== void 0 ? { cwd: target.cwd } : {},
9569
+ grantedNamespace,
9570
+ log: wireLog("target")
9571
+ });
9572
+ }
9573
+ /** This process's stdin/stdout as a participant; `closed` resolves when stdin ends. */
9574
+ async function connectStdioParticipant() {
9575
+ let markClosed;
9576
+ const closed = new Promise((resolve) => {
9577
+ markClosed = resolve;
9578
+ });
9579
+ const { transport } = await connectNdjson({
9580
+ input: process.stdin,
9581
+ output: process.stdout,
9582
+ onClose: () => markClosed()
9583
+ });
9584
+ return {
9585
+ connection: connectViaTransport(transport),
9586
+ closed
9587
+ };
9588
+ }
9528
9589
  /**
9529
9590
  * Resolve the ws/socket bound-token listener `connect-as` splices the target
9530
9591
  * onto: an explicit `--hub-endpoint <uri>`, else the fallback endpoint when it
@@ -9609,4 +9670,4 @@ function safeJson(value) {
9609
9670
  //#endregion
9610
9671
  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
9672
 
9612
- //# sourceMappingURL=cli-CO0iSiY_.js.map
9673
+ //# sourceMappingURL=cli-C0AV_fi4.js.map