agent-shim 8.27.1 → 8.29.0

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/dist/index.mjs CHANGED
@@ -5028,10 +5028,113 @@ function createRcCredentialStore(fs12, dir) {
5028
5028
  // src/frontdoor/rcApi.ts
5029
5029
  import { createORPCClient, ORPCError } from "@orpc/client";
5030
5030
  import { RPCLink } from "@orpc/client/fetch";
5031
+ import { OpenAPIGenerator } from "@orpc/openapi";
5032
+ import { OpenAPIHandler } from "@orpc/openapi/node";
5031
5033
  import { eventIterator, os as os2, withEventMeta } from "@orpc/server";
5032
5034
  import { BodyLimitPlugin, RPCHandler } from "@orpc/server/node";
5033
5035
  import { Agent as Agent3, fetch as undiciFetch } from "undici";
5034
5036
 
5037
+ // package.json
5038
+ var package_default = {
5039
+ name: "agent-shim",
5040
+ version: "8.29.0",
5041
+ description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
5042
+ license: "MIT",
5043
+ author: "Joseph Mearman <joseph@mearman.co.uk>",
5044
+ repository: {
5045
+ type: "git",
5046
+ url: "git+https://github.com/ExaDev/agent-shim.git"
5047
+ },
5048
+ homepage: "https://github.com/ExaDev/agent-shim#readme",
5049
+ bugs: "https://github.com/ExaDev/agent-shim/issues",
5050
+ keywords: [
5051
+ "claude-code",
5052
+ "claude",
5053
+ "cli"
5054
+ ],
5055
+ packageManager: "pnpm@12.4.1+sha512.2e81e399d73fe8390dab25e06aa788ab7a5908248d2f5a370f82b481147a6a7a367bf8048f9a6fdb6460f21a66f0542dedb8b94ca2c8723596741920b1656d4c",
5056
+ type: "module",
5057
+ engines: {
5058
+ node: "^22.18 || >=24"
5059
+ },
5060
+ bin: {
5061
+ "agent-shim": "dist/cli.cjs",
5062
+ "claude-use": "dist/cli.cjs"
5063
+ },
5064
+ files: [
5065
+ "dist/cli.cjs",
5066
+ "dist/index.mjs",
5067
+ "dist/index.cjs",
5068
+ "dist/types"
5069
+ ],
5070
+ scripts: {
5071
+ prepare: "husky && node scripts/build.mts --bundle-only",
5072
+ typecheck: "turbo run _typecheck",
5073
+ _typecheck: "tsc --noEmit",
5074
+ lint: "turbo run _lint",
5075
+ _lint: "eslint . --cache --cache-location .eslintcache --max-warnings 0",
5076
+ test: "turbo run _test",
5077
+ _test: "vitest run",
5078
+ "test:coverage": "turbo run _test:coverage",
5079
+ "_test:coverage": "vitest run --coverage",
5080
+ knip: "turbo run _knip",
5081
+ _knip: "knip --cache",
5082
+ build: "turbo run _build",
5083
+ _build: "node scripts/build.mts",
5084
+ "build:bundle": "node scripts/build.mts --bundle-only",
5085
+ prepublishOnly: "pnpm build:bundle",
5086
+ schema: "node scripts/gen-schema.mts"
5087
+ },
5088
+ dependencies: {
5089
+ "@clack/prompts": "^1.8.0",
5090
+ "@orpc/client": "1.15.4",
5091
+ "@orpc/openapi": "1.15.4",
5092
+ "@orpc/server": "1.15.4",
5093
+ commander: "^15.0.0",
5094
+ cosmiconfig: "^10.0.1",
5095
+ picomatch: "^4.0.7",
5096
+ undici: "7.29.1",
5097
+ zod: "^4.6.1"
5098
+ },
5099
+ devDependencies: {
5100
+ "@commitlint/cli": "^21.2.2",
5101
+ "@commitlint/config-conventional": "^21.2.2",
5102
+ "@exadev/eslint-config": "2.12.1",
5103
+ "@semantic-release/changelog": "^7.0.0",
5104
+ "@semantic-release/git": "^11.0.1",
5105
+ "@semantic-release/npm": "^13.1.5",
5106
+ "@types/node": "^26.5.1",
5107
+ "@types/picomatch": "^4.0.3",
5108
+ "@vitest/coverage-v8": "^4.1.11",
5109
+ "conventional-changelog-conventionalcommits": "^10.4.0",
5110
+ esbuild: "^0.28.2",
5111
+ eslint: "^10.10.0",
5112
+ globals: "^17.12.0",
5113
+ husky: "^9.1.7",
5114
+ jiti: "^2.7.0",
5115
+ knip: "^6.35.1",
5116
+ "lint-staged": "^17.5.0",
5117
+ rollup: "4.64.0",
5118
+ "rollup-plugin-dts": "6.5.1",
5119
+ "semantic-release": "^25.0.9",
5120
+ turbo: "^2.11.5",
5121
+ typescript: "^6.0.3",
5122
+ "typescript-eslint": "^8.70.0",
5123
+ vitest: "^4.1.11"
5124
+ },
5125
+ main: "dist/index.cjs",
5126
+ module: "dist/index.mjs",
5127
+ types: "dist/types/index.d.ts",
5128
+ exports: {
5129
+ ".": {
5130
+ types: "./dist/types/index.d.ts",
5131
+ import: "./dist/index.mjs",
5132
+ require: "./dist/index.cjs"
5133
+ },
5134
+ "./package.json": "./package.json"
5135
+ }
5136
+ };
5137
+
5035
5138
  // src/frontdoor/rcSchemas.ts
5036
5139
  import { z as z4 } from "zod";
5037
5140
  var RcPermissionModeSchema = z4.enum(RC_PERMISSION_MODES);
@@ -5193,18 +5296,23 @@ var RcAnswerOutputSchema = z4.strictObject({
5193
5296
 
5194
5297
  // src/frontdoor/rcApi.ts
5195
5298
  var RC_ORPC_PATH_PREFIX = "/__agent-shim/orpc";
5299
+ var RC_OPENAPI_DOC_PATH = "/openapi.json";
5196
5300
  var RC_SUBSCRIBER_BUFFER_EVENTS = 100;
5301
+ function doorApiBearer(headers) {
5302
+ const presented = headers.authorization;
5303
+ const bearerPrefix = "bearer ".length;
5304
+ return typeof presented === "string" && presented.slice(0, bearerPrefix).toLowerCase() === "bearer " ? presented.slice(bearerPrefix) : void 0;
5305
+ }
5197
5306
  function doorApiAuth(expectedToken) {
5198
5307
  return os2.$context().use(({ context, next }) => {
5199
- const presented = context.headers.authorization;
5200
- const bearerPrefix = "bearer ".length;
5201
- const token = typeof presented === "string" && presented.slice(0, bearerPrefix).toLowerCase() === "bearer " ? presented.slice(bearerPrefix) : void 0;
5308
+ const token = doorApiBearer(context.headers);
5202
5309
  if (token === void 0 || !isLiveCapability(token, [expectedToken])) {
5203
5310
  throw new ORPCError("UNAUTHORIZED", { message: "the front door's typed API demands this generation's control token as a Bearer credential" });
5204
5311
  }
5205
5312
  return next();
5206
5313
  });
5207
5314
  }
5315
+ var RC_API_TAG = "remote-control";
5208
5316
  function createRcApiRouter(deps) {
5209
5317
  const authed = doorApiAuth(deps.expectedToken);
5210
5318
  const delivered = (sessionId, result) => {
@@ -5224,41 +5332,41 @@ function createRcApiRouter(deps) {
5224
5332
  };
5225
5333
  return {
5226
5334
  rc: {
5227
- list: authed.output(RcListOutputSchema).handler(() => ({ sessions: deps.list() })),
5228
- status: authed.input(RcSessionQuerySchema).output(RcStatusOutputSchema).handler(({ input }) => {
5335
+ list: authed.route({ method: "GET", path: "/rest/rc/sessions", summary: "List the observed Remote Control sessions", tags: [RC_API_TAG] }).output(RcListOutputSchema).handler(() => ({ sessions: deps.list() })),
5336
+ status: authed.route({ method: "GET", path: "/rest/rc/status", summary: "Read session status, one session's when named", tags: [RC_API_TAG] }).input(RcSessionQuerySchema).output(RcStatusOutputSchema).handler(({ input }) => {
5229
5337
  const statuses = deps.statusOf(input.session);
5230
5338
  if (input.session !== void 0 && !statuses.some((entry) => entry.id === input.session)) {
5231
5339
  throw new ORPCError("NOT_FOUND", { message: rcSessionNotObservedMessage(input.session) });
5232
5340
  }
5233
5341
  return { statuses };
5234
5342
  }),
5235
- pending: authed.input(RcSessionQuerySchema).output(RcPendingOutputSchema).handler(({ input }) => {
5343
+ pending: authed.route({ method: "GET", path: "/rest/rc/pending", summary: "Read the control requests awaiting an answer", tags: [RC_API_TAG] }).input(RcSessionQuerySchema).output(RcPendingOutputSchema).handler(({ input }) => {
5236
5344
  const pending = deps.pendingOf(input.session);
5237
5345
  if (input.session !== void 0 && deps.statusOf(input.session).length === 0) {
5238
5346
  throw new ORPCError("NOT_FOUND", { message: rcSessionNotObservedMessage(input.session) });
5239
5347
  }
5240
5348
  return { pending };
5241
5349
  }),
5242
- send: authed.input(RcSendInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.inject(input.session, input.text)) })),
5243
- answer: authed.input(RcAnswerInputSchema).output(RcAnswerOutputSchema).handler(async ({ input }) => {
5350
+ send: authed.route({ method: "POST", path: "/rest/rc/send", summary: "Send one message into a session", tags: [RC_API_TAG] }).input(RcSendInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.inject(input.session, input.text)) })),
5351
+ answer: authed.route({ method: "POST", path: "/rest/rc/answer", summary: "Answer one pending control request", tags: [RC_API_TAG] }).input(RcAnswerInputSchema).output(RcAnswerOutputSchema).handler(async ({ input }) => {
5244
5352
  const decision = { approve: input.approve, message: input.approve || input.text === void 0 ? void 0 : input.text };
5245
5353
  return { session: input.session, request: input.request, sequenceNums: delivered(input.session, await deps.answer(input.session, input.request, decision)) };
5246
5354
  }),
5247
- interrupt: authed.input(RcInterruptInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.interrupt(input.session)) })),
5248
- setModel: authed.input(RcSetModelInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.setModel(input.session, input.model)) })),
5249
- setPermissionMode: authed.input(RcSetPermissionModeInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.setPermissionMode(input.session, input.mode)) })),
5250
- endSession: authed.input(RcEndSessionInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.endSession(input.session, input.reason)) })),
5251
- getUsage: authed.input(RcGetUsageInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.getUsage(input.session, input.skipBehaviors)) })),
5252
- getContextUsage: authed.input(RcGetContextUsageInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.getContextUsage(input.session, input.detail)) })),
5253
- readFile: authed.input(RcReadFileInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.readFile(input.session, input.path, { ...input.maxBytes === void 0 ? {} : { maxBytes: input.maxBytes }, ...input.encoding === void 0 ? {} : { encoding: input.encoding } })) })),
5254
- fileSuggestions: authed.input(RcFileSuggestionsInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.fileSuggestions(input.session, input.query)) })),
5255
- keepAlive: authed.input(RcKeepAliveInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.keepAlive(input.session)) })),
5256
- mcpStatus: authed.input(RcMcpStatusInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpStatus(input.session)) })),
5257
- mcpReconnect: authed.input(RcMcpReconnectInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpReconnect(input.session, input.serverName)) })),
5258
- mcpAuthenticate: authed.input(RcMcpAuthenticateInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpAuthenticate(input.session, input.serverName, input.redirectUri)) })),
5259
- mcpOAuthCallbackUrl: authed.input(RcMcpOAuthCallbackUrlInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpOAuthCallbackUrl(input.session, input.serverName, input.callbackUrl)) })),
5260
- teleport: authed.input(RcTeleportInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.teleport(input.session, input.marker)) })),
5261
- subscribe: authed.input(RcSessionQuerySchema).output(eventIterator(RcStreamEventSchema)).handler(async function* ({ input, signal }) {
5355
+ interrupt: authed.route({ method: "POST", path: "/rest/rc/interrupt", summary: "Interrupt a session's running turn", tags: [RC_API_TAG] }).input(RcInterruptInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.interrupt(input.session)) })),
5356
+ setModel: authed.route({ method: "POST", path: "/rest/rc/set-model", summary: "Switch a session's model", tags: [RC_API_TAG] }).input(RcSetModelInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.setModel(input.session, input.model)) })),
5357
+ setPermissionMode: authed.route({ method: "POST", path: "/rest/rc/set-permission-mode", summary: "Switch a session's permission mode", tags: [RC_API_TAG] }).input(RcSetPermissionModeInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.setPermissionMode(input.session, input.mode)) })),
5358
+ endSession: authed.route({ method: "POST", path: "/rest/rc/end-session", summary: "End a session", tags: [RC_API_TAG] }).input(RcEndSessionInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.endSession(input.session, input.reason)) })),
5359
+ getUsage: authed.route({ method: "POST", path: "/rest/rc/get-usage", summary: "Ask a session for its usage snapshot", tags: [RC_API_TAG] }).input(RcGetUsageInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.getUsage(input.session, input.skipBehaviors)) })),
5360
+ getContextUsage: authed.route({ method: "POST", path: "/rest/rc/get-context-usage", summary: "Ask a session for its context usage", tags: [RC_API_TAG] }).input(RcGetContextUsageInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.getContextUsage(input.session, input.detail)) })),
5361
+ readFile: authed.route({ method: "POST", path: "/rest/rc/read-file", summary: "Ask a session to read one file", tags: [RC_API_TAG] }).input(RcReadFileInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.readFile(input.session, input.path, { ...input.maxBytes === void 0 ? {} : { maxBytes: input.maxBytes }, ...input.encoding === void 0 ? {} : { encoding: input.encoding } })) })),
5362
+ fileSuggestions: authed.route({ method: "POST", path: "/rest/rc/file-suggestions", summary: "Ask a session for file-path suggestions", tags: [RC_API_TAG] }).input(RcFileSuggestionsInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.fileSuggestions(input.session, input.query)) })),
5363
+ keepAlive: authed.route({ method: "POST", path: "/rest/rc/keep-alive", summary: "Keep a session's bridge alive", tags: [RC_API_TAG] }).input(RcKeepAliveInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.keepAlive(input.session)) })),
5364
+ mcpStatus: authed.route({ method: "POST", path: "/rest/rc/mcp-status", summary: "Ask a session for its MCP servers' status", tags: [RC_API_TAG] }).input(RcMcpStatusInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpStatus(input.session)) })),
5365
+ mcpReconnect: authed.route({ method: "POST", path: "/rest/rc/mcp-reconnect", summary: "Ask a session to reconnect one MCP server", tags: [RC_API_TAG] }).input(RcMcpReconnectInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpReconnect(input.session, input.serverName)) })),
5366
+ mcpAuthenticate: authed.route({ method: "POST", path: "/rest/rc/mcp-authenticate", summary: "Start OAuth for one MCP server on a session", tags: [RC_API_TAG] }).input(RcMcpAuthenticateInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpAuthenticate(input.session, input.serverName, input.redirectUri)) })),
5367
+ mcpOAuthCallbackUrl: authed.route({ method: "POST", path: "/rest/rc/mcp-oauth-callback-url", summary: "Hand a completed MCP OAuth callback to a session", tags: [RC_API_TAG] }).input(RcMcpOAuthCallbackUrlInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpOAuthCallbackUrl(input.session, input.serverName, input.callbackUrl)) })),
5368
+ teleport: authed.route({ method: "POST", path: "/rest/rc/teleport", summary: "Teleport a session to a conversation marker", tags: [RC_API_TAG] }).input(RcTeleportInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.teleport(input.session, input.marker)) })),
5369
+ subscribe: authed.route({ method: "GET", path: "/rest/rc/events", summary: "Stream a session's Remote Control events (SSE)", tags: [RC_API_TAG] }).input(RcSessionQuerySchema).output(eventIterator(RcStreamEventSchema)).handler(async function* ({ input, signal }) {
5262
5370
  const queue = [];
5263
5371
  let wake;
5264
5372
  const detach = deps.fanout.subscribe(input.session, (event) => {
@@ -5295,15 +5403,44 @@ function createRcApiRouter(deps) {
5295
5403
  }
5296
5404
  };
5297
5405
  }
5298
- function doorApiNodeHandlerOf(router) {
5299
- const handler = new RPCHandler(router, { plugins: [new BodyLimitPlugin({ maxBodySize: CONTROL_BODY_CAP_BYTES })] });
5406
+ function doorApiNodeHandlerOf(router, expectedToken) {
5407
+ const plugins = [new BodyLimitPlugin({ maxBodySize: CONTROL_BODY_CAP_BYTES })];
5408
+ const rpcHandler = new RPCHandler(router, { plugins });
5409
+ const restHandler = new OpenAPIHandler(router, { plugins });
5410
+ let document;
5411
+ const documentOf = async () => document ??= new OpenAPIGenerator().generate(router, { info: { title: "agent-shim front door API", version: package_default.version } });
5300
5412
  return {
5301
5413
  pathPrefix: RC_ORPC_PATH_PREFIX,
5302
- handle: async (request4, response) => await handler.handle(request4, response, { context: { headers: request4.headers }, prefix: RC_ORPC_PATH_PREFIX })
5414
+ handle: async (request4, response) => {
5415
+ if (request4.method === "GET" && request4.url === `${RC_ORPC_PATH_PREFIX}${RC_OPENAPI_DOC_PATH}`) {
5416
+ const token = doorApiBearer(request4.headers);
5417
+ if (token === void 0 || !isLiveCapability(token, [expectedToken])) {
5418
+ const refusal = new ORPCError("UNAUTHORIZED", { message: "the front door's OpenAPI document demands this generation's control token as a Bearer credential" });
5419
+ response.writeHead(HTTP_STATUS.unauthorized, { "content-type": "application/json" });
5420
+ response.end(JSON.stringify(refusal.toJSON()));
5421
+ return { matched: true };
5422
+ }
5423
+ try {
5424
+ const doc = await documentOf();
5425
+ response.writeHead(HTTP_STATUS.ok, { "content-type": "application/json" });
5426
+ response.end(JSON.stringify(doc));
5427
+ } catch (error) {
5428
+ const refusal = new ORPCError("INTERNAL_SERVER_ERROR", { message: `the OpenAPI document could not be generated from the door's own router: ${error instanceof Error ? error.message : String(error)}` });
5429
+ response.writeHead(HTTP_STATUS.internalServerError, { "content-type": "application/json" });
5430
+ response.end(JSON.stringify(refusal.toJSON()));
5431
+ }
5432
+ return { matched: true };
5433
+ }
5434
+ const rest = await restHandler.handle(request4, response, { context: { headers: request4.headers }, prefix: RC_ORPC_PATH_PREFIX });
5435
+ if (rest.matched) {
5436
+ return rest;
5437
+ }
5438
+ return await rpcHandler.handle(request4, response, { context: { headers: request4.headers }, prefix: RC_ORPC_PATH_PREFIX });
5439
+ }
5303
5440
  };
5304
5441
  }
5305
5442
  function createRcApiNodeHandler(deps) {
5306
- return doorApiNodeHandlerOf(createRcApiRouter(deps));
5443
+ return doorApiNodeHandlerOf(createRcApiRouter(deps), deps.expectedToken);
5307
5444
  }
5308
5445
  function frontDoorApiLink(port, ca, token) {
5309
5446
  const dispatcher = new Agent3({ connect: { ca } });
@@ -9775,11 +9912,12 @@ var LaunchLifecycleEventSchema = z20.strictObject({
9775
9912
  });
9776
9913
 
9777
9914
  // src/frontdoor/eventsApi.ts
9915
+ var EVENTS_API_TAG = "events";
9778
9916
  function createEventsApiRouter(deps) {
9779
9917
  const authed = doorApiAuth(deps.expectedToken);
9780
9918
  return {
9781
9919
  events: {
9782
- subscribe: authed.input(DoorEventSourceQuerySchema).output(eventIterator2(DoorEventSchema)).handler(async function* ({ input, signal }) {
9920
+ subscribe: authed.route({ method: "GET", path: "/rest/events", summary: "Stream the door's every event source (SSE)", tags: [EVENTS_API_TAG] }).input(DoorEventSourceQuerySchema).output(eventIterator2(DoorEventSchema)).handler(async function* ({ input, signal }) {
9783
9921
  if (input.sources !== void 0) {
9784
9922
  const live = deps.events.sources();
9785
9923
  const unknown = input.sources.filter((source) => !live.includes(source));
@@ -9812,12 +9950,16 @@ function createEventsApiRouter(deps) {
9812
9950
  }
9813
9951
 
9814
9952
  // src/frontdoor/controlApi.ts
9953
+ var USAGE_API_TAG = "usage";
9954
+ var FRONTDOOR_API_TAG = "frontdoor";
9955
+ var CHECK_API_TAG = "check";
9956
+ var DOCTOR_API_TAG = "doctor";
9815
9957
  function createControlApiRouter(deps) {
9816
9958
  const authed = doorApiAuth(deps.expectedToken);
9817
9959
  return {
9818
9960
  usage: {
9819
- list: authed.output(UsageListOutputSchema).handler(() => ({ snapshots: deps.usageSnapshots() })),
9820
- effectiveWindow: authed.input(UsageWindowsInputSchema).output(UsageWindowsOutputSchema).handler(({ input }) => {
9961
+ list: authed.route({ method: "GET", path: "/rest/usage/snapshots", summary: "List every identity's usage snapshot", tags: [USAGE_API_TAG] }).output(UsageListOutputSchema).handler(() => ({ snapshots: deps.usageSnapshots() })),
9962
+ effectiveWindow: authed.route({ method: "GET", path: "/rest/usage/windows", summary: "Read one identity's effective quota windows", tags: [USAGE_API_TAG] }).input(UsageWindowsInputSchema).output(UsageWindowsOutputSchema).handler(({ input }) => {
9821
9963
  const snapshot = deps.usageSnapshotOf(input.identity);
9822
9964
  if (snapshot === void 0) {
9823
9965
  throw new ORPCError3("NOT_FOUND", { message: `identity ${input.identity} has no usage snapshot yet, so no quota window has been observed for it` });
@@ -9834,7 +9976,7 @@ function createControlApiRouter(deps) {
9834
9976
  ...unified?.sevenDay === void 0 ? {} : { sevenDay: effectiveWindow(unified.sevenDay, now) }
9835
9977
  };
9836
9978
  }),
9837
- live: authed.input(RcSessionQuerySchema).output(UsageLiveOutputSchema).handler(({ input }) => {
9979
+ live: authed.route({ method: "GET", path: "/rest/usage/live", summary: "Read the live rate-limit observations the event backbone files", tags: [USAGE_API_TAG] }).input(RcSessionQuerySchema).output(UsageLiveOutputSchema).handler(({ input }) => {
9838
9980
  const sessions = deps.liveRateLimits(input.session);
9839
9981
  if (input.session !== void 0 && sessions.length === 0) {
9840
9982
  if (deps.list().some((session) => session.id === input.session)) {
@@ -9846,15 +9988,15 @@ function createControlApiRouter(deps) {
9846
9988
  })
9847
9989
  },
9848
9990
  frontdoor: {
9849
- status: authed.output(FrontDoorStatusOutputSchema).handler(() => deps.frontDoorStatus()),
9850
- sessions: authed.output(FrontDoorSessionsOutputSchema).handler(() => ({ sessions: deps.frontDoorStatus().sessions }))
9991
+ status: authed.route({ method: "GET", path: "/rest/frontdoor/status", summary: "Read the front door's status", tags: [FRONTDOOR_API_TAG] }).output(FrontDoorStatusOutputSchema).handler(() => deps.frontDoorStatus()),
9992
+ sessions: authed.route({ method: "GET", path: "/rest/frontdoor/sessions", summary: "Read the front door's session registry", tags: [FRONTDOOR_API_TAG] }).output(FrontDoorSessionsOutputSchema).handler(() => ({ sessions: deps.frontDoorStatus().sessions }))
9851
9993
  },
9852
9994
  check: {
9853
9995
  // The report's own JSON shape is the contract (the same one `check --json` prints and `checkReportSchema.ts` defines), so the procedure returns exactly that conversion.
9854
- run: authed.input(CheckRunInputSchema).output(CheckReportJsonSchema).handler(({ input }) => checkReportToJson(deps.checkReport(input.path, input.identity)))
9996
+ run: authed.route({ method: "GET", path: "/rest/check", summary: "Run the check report for one directory", tags: [CHECK_API_TAG] }).input(CheckRunInputSchema).output(CheckReportJsonSchema).handler(({ input }) => checkReportToJson(deps.checkReport(input.path, input.identity)))
9855
9997
  },
9856
9998
  doctor: {
9857
- run: authed.output(DoctorRunOutputSchema).handler(() => deps.doctorReport())
9999
+ run: authed.route({ method: "GET", path: "/rest/doctor", summary: "Run the doctor report", tags: [DOCTOR_API_TAG] }).output(DoctorRunOutputSchema).handler(() => deps.doctorReport())
9858
10000
  }
9859
10001
  };
9860
10002
  }
@@ -9862,7 +10004,7 @@ function createDoorApiRouter(deps) {
9862
10004
  return { ...createRcApiRouter(deps), ...createControlApiRouter(deps), ...createEventsApiRouter(deps) };
9863
10005
  }
9864
10006
  function createDoorApiNodeHandler(deps) {
9865
- return doorApiNodeHandlerOf(createDoorApiRouter(deps));
10007
+ return doorApiNodeHandlerOf(createDoorApiRouter(deps), deps.expectedToken);
9866
10008
  }
9867
10009
  function frontDoorApiClient(port, ca, token) {
9868
10010
  return createORPCClient2(frontDoorApiLink(port, ca, token));
@@ -11948,106 +12090,6 @@ function removeDirectoryRule(paths, rulePath) {
11948
12090
  import { randomUUID as randomUUID4 } from "node:crypto";
11949
12091
  import path30 from "node:path";
11950
12092
 
11951
- // package.json
11952
- var package_default = {
11953
- name: "agent-shim",
11954
- version: "8.27.1",
11955
- description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
11956
- license: "MIT",
11957
- author: "Joseph Mearman <joseph@mearman.co.uk>",
11958
- repository: {
11959
- type: "git",
11960
- url: "git+https://github.com/ExaDev/agent-shim.git"
11961
- },
11962
- homepage: "https://github.com/ExaDev/agent-shim#readme",
11963
- bugs: "https://github.com/ExaDev/agent-shim/issues",
11964
- keywords: [
11965
- "claude-code",
11966
- "claude",
11967
- "cli"
11968
- ],
11969
- packageManager: "pnpm@12.4.1+sha512.2e81e399d73fe8390dab25e06aa788ab7a5908248d2f5a370f82b481147a6a7a367bf8048f9a6fdb6460f21a66f0542dedb8b94ca2c8723596741920b1656d4c",
11970
- type: "module",
11971
- engines: {
11972
- node: "^22.18 || >=24"
11973
- },
11974
- bin: {
11975
- "agent-shim": "dist/cli.cjs",
11976
- "claude-use": "dist/cli.cjs"
11977
- },
11978
- files: [
11979
- "dist/cli.cjs",
11980
- "dist/index.mjs",
11981
- "dist/index.cjs",
11982
- "dist/types"
11983
- ],
11984
- scripts: {
11985
- prepare: "husky && node scripts/build.mts --bundle-only",
11986
- typecheck: "turbo run _typecheck",
11987
- _typecheck: "tsc --noEmit",
11988
- lint: "turbo run _lint",
11989
- _lint: "eslint . --cache --cache-location .eslintcache --max-warnings 0",
11990
- test: "turbo run _test",
11991
- _test: "vitest run",
11992
- "test:coverage": "turbo run _test:coverage",
11993
- "_test:coverage": "vitest run --coverage",
11994
- knip: "turbo run _knip",
11995
- _knip: "knip --cache",
11996
- build: "turbo run _build",
11997
- _build: "node scripts/build.mts",
11998
- "build:bundle": "node scripts/build.mts --bundle-only",
11999
- prepublishOnly: "pnpm build:bundle",
12000
- schema: "node scripts/gen-schema.mts"
12001
- },
12002
- dependencies: {
12003
- "@clack/prompts": "^1.8.0",
12004
- "@orpc/client": "1.15.4",
12005
- "@orpc/server": "1.15.4",
12006
- commander: "^15.0.0",
12007
- cosmiconfig: "^10.0.1",
12008
- picomatch: "^4.0.7",
12009
- undici: "7.29.1",
12010
- zod: "^4.6.1"
12011
- },
12012
- devDependencies: {
12013
- "@commitlint/cli": "^21.2.2",
12014
- "@commitlint/config-conventional": "^21.2.2",
12015
- "@exadev/eslint-config": "2.12.1",
12016
- "@semantic-release/changelog": "^7.0.0",
12017
- "@semantic-release/git": "^11.0.1",
12018
- "@semantic-release/npm": "^13.1.5",
12019
- "@types/node": "^26.5.1",
12020
- "@types/picomatch": "^4.0.3",
12021
- "@vitest/coverage-v8": "^4.1.11",
12022
- "conventional-changelog-conventionalcommits": "^10.4.0",
12023
- esbuild: "^0.28.2",
12024
- eslint: "^10.10.0",
12025
- globals: "^17.12.0",
12026
- husky: "^9.1.7",
12027
- jiti: "^2.7.0",
12028
- knip: "^6.35.1",
12029
- "lint-staged": "^17.5.0",
12030
- rollup: "4.64.0",
12031
- "rollup-plugin-dts": "6.5.1",
12032
- "semantic-release": "^25.0.9",
12033
- turbo: "^2.11.5",
12034
- typescript: "^6.0.3",
12035
- "typescript-eslint": "^8.70.0",
12036
- vitest: "^4.1.11"
12037
- },
12038
- main: "dist/index.cjs",
12039
- module: "dist/index.mjs",
12040
- types: "dist/types/index.d.ts",
12041
- exports: {
12042
- ".": {
12043
- types: "./dist/types/index.d.ts",
12044
- import: "./dist/index.mjs",
12045
- require: "./dist/index.cjs"
12046
- },
12047
- "./package.json": "./package.json"
12048
- }
12049
- };
12050
-
12051
12093
  // src/launcher/argv.ts
12052
12094
  var ConflictingIdentityError = class extends UsageError {
12053
12095
  constructor(positional, flag) {
@@ -2503,6 +2503,8 @@ interface PrePipelineApi {
2503
2503
  * Mounted on the provider listener beside the bespoke namespace, authenticated by the same per-generation owner-only control token as a Bearer credential: a fresh random value per door start, written owner-only under the front door's state directory, and checked in constant time like every other capability the door accepts. `rc.subscribe` streams the client attachment's fan-out: every event the door's held stream produced, SSE-framed by oRPC's event iterator, each carrying its sequence number as the SSE event id so a consumer's own reconnect can resume exactly as the door's does.
2504
2504
  *
2505
2505
  * This module also owns the mount's router-agnostic plumbing, which the control-plane routers (`controlApi.ts`) serve on the same prefix under the same token: the control-token middleware (`doorApiAuth`), the node-handler builder (`doorApiNodeHandlerOf`) and the TLS-pinned client link (`frontDoorApiLink`).
2506
+ *
2507
+ * Every procedure is route-annotated (method, path, summary, tags) under the mount's one REST namespace (`/rest/...`), so the mount serves the same operations twice over: as the oRPC RPC protocol the typed clients and the web client speak, and as plain REST for consumers with no TypeScript client (curl, scripts, other languages), with the OpenAPI document of the whole surface at `RC_OPENAPI_DOC_PATH` under the same token. The two share one router, one token middleware and one Zod schema source, so nothing can drift between them; an annotated (method, path) pair is served REST-shaped and everything else falls through to the RPC handler, and no RPC procedure path begins with `/rest`, so neither address space can ever capture the other.
2506
2508
  */
2507
2509
  /** The one path prefix the typed API is mounted under, answered before the routed pipeline like the bespoke control routes are. */
2508
2510
  declare const RC_ORPC_PATH_PREFIX = "/__agent-shim/orpc";
@@ -2751,9 +2753,9 @@ declare function createRcApiRouter(deps: RcApiDeps): {
2751
2753
  /** The typed API's router, as the CLI-side client's own type is derived from it. */
2752
2754
  type RcApiRouter = ReturnType<typeof createRcApiRouter>;
2753
2755
  /**
2754
- * Builds the node handler that serves one router of the door's typed API under the mount's one prefix: the pre-pipeline surface the provider listener hands every request under `RC_ORPC_PATH_PREFIX`, answering with `matched: false` for a path under the prefix that names no procedure (which the listener itself answers as a 404). Request bodies are bounded by the same protocol cap the bespoke routes apply. Every router the mount serves (Remote Control and the control plane) goes through this one builder, so the prefix, the context and the cap are stated once.
2756
+ * Builds the node handler that serves one router of the door's typed API under the mount's one prefix, twice over: the OpenAPI handler answers every route-annotated (method, path) pair as plain REST (every annotation under `/rest`, which no RPC procedure path begins with, so the RPC protocol's own POSTs always fall through untouched), and everything else falls to the RPC handler, which answers the oRPC protocol the typed clients and the web client speak. The OpenAPI document of the annotated routes is served at `RC_OPENAPI_DOC_PATH`, behind the same per-generation control token the procedures demand, so a consumer with no TypeScript client reads the whole surface from the mount itself. The pre-pipeline surface the provider listener hands every request under `RC_ORPC_PATH_PREFIX`, answering with `matched: false` for a path under the prefix that names no route and no procedure (which the listener itself answers as a 404). Request bodies are bounded by the same protocol cap the bespoke routes apply, on both handlers. Every router the mount serves (Remote Control and the control plane) goes through this one builder, so the prefix, the context and the cap are stated once.
2755
2757
  */
2756
- declare function doorApiNodeHandlerOf(router: AnyRouter): PrePipelineApi;
2758
+ declare function doorApiNodeHandlerOf(router: AnyRouter, expectedToken: string): PrePipelineApi;
2757
2759
  /**
2758
2760
  * Builds the typed API's node handler for the Remote Control router alone: the pre-pipeline surface the provider listener hands every request under `RC_ORPC_PATH_PREFIX`.
2759
2761
  */
@@ -4439,13 +4441,13 @@ declare function createControlApiRouter(deps: ControlApiDeps): {
4439
4441
  "config-profile": "config-profile";
4440
4442
  identity: "identity";
4441
4443
  provider: "provider";
4442
- pool: "pool";
4443
- keychain: "keychain";
4444
4444
  "ambient-credential": "ambient-credential";
4445
4445
  "binary-discovery": "binary-discovery";
4446
4446
  "claude-shim": "claude-shim";
4447
4447
  "path-resolution": "path-resolution";
4448
4448
  "legacy-name": "legacy-name";
4449
+ pool: "pool";
4450
+ keychain: "keychain";
4449
4451
  "directory-rules": "directory-rules";
4450
4452
  "categories-local": "categories-local";
4451
4453
  "active-identity": "active-identity";
@@ -4974,13 +4976,13 @@ declare function createDoorApiRouter(deps: DoorApiDeps): {
4974
4976
  "config-profile": "config-profile";
4975
4977
  identity: "identity";
4976
4978
  provider: "provider";
4977
- pool: "pool";
4978
- keychain: "keychain";
4979
4979
  "ambient-credential": "ambient-credential";
4980
4980
  "binary-discovery": "binary-discovery";
4981
4981
  "claude-shim": "claude-shim";
4982
4982
  "path-resolution": "path-resolution";
4983
4983
  "legacy-name": "legacy-name";
4984
+ pool: "pool";
4985
+ keychain: "keychain";
4984
4986
  "directory-rules": "directory-rules";
4985
4987
  "categories-local": "categories-local";
4986
4988
  "active-identity": "active-identity";
@@ -5422,13 +5424,13 @@ declare const DoctorFindingSchema: z.ZodObject<{
5422
5424
  "config-profile": "config-profile";
5423
5425
  identity: "identity";
5424
5426
  provider: "provider";
5425
- pool: "pool";
5426
- keychain: "keychain";
5427
5427
  "ambient-credential": "ambient-credential";
5428
5428
  "binary-discovery": "binary-discovery";
5429
5429
  "claude-shim": "claude-shim";
5430
5430
  "path-resolution": "path-resolution";
5431
5431
  "legacy-name": "legacy-name";
5432
+ pool: "pool";
5433
+ keychain: "keychain";
5432
5434
  "directory-rules": "directory-rules";
5433
5435
  "categories-local": "categories-local";
5434
5436
  "active-identity": "active-identity";
@@ -5451,13 +5453,13 @@ declare const DoctorRunOutputSchema: z.ZodObject<{
5451
5453
  "config-profile": "config-profile";
5452
5454
  identity: "identity";
5453
5455
  provider: "provider";
5454
- pool: "pool";
5455
- keychain: "keychain";
5456
5456
  "ambient-credential": "ambient-credential";
5457
5457
  "binary-discovery": "binary-discovery";
5458
5458
  "claude-shim": "claude-shim";
5459
5459
  "path-resolution": "path-resolution";
5460
5460
  "legacy-name": "legacy-name";
5461
+ pool: "pool";
5462
+ keychain: "keychain";
5461
5463
  "directory-rules": "directory-rules";
5462
5464
  "categories-local": "categories-local";
5463
5465
  "active-identity": "active-identity";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-shim",
3
- "version": "8.27.1",
3
+ "version": "8.29.0",
4
4
  "description": "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
5
5
  "license": "MIT",
6
6
  "author": "Joseph Mearman <joseph@mearman.co.uk>",
@@ -51,6 +51,7 @@
51
51
  "dependencies": {
52
52
  "@clack/prompts": "^1.8.0",
53
53
  "@orpc/client": "1.15.4",
54
+ "@orpc/openapi": "1.15.4",
54
55
  "@orpc/server": "1.15.4",
55
56
  "commander": "^15.0.0",
56
57
  "cosmiconfig": "^10.0.1",