agent-shim 8.40.0 → 8.41.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
@@ -5113,7 +5113,7 @@ import { Agent as Agent3, fetch as undiciFetch } from "undici";
5113
5113
  // package.json
5114
5114
  var package_default = {
5115
5115
  name: "agent-shim",
5116
- version: "8.40.0",
5116
+ version: "8.41.0",
5117
5117
  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.",
5118
5118
  license: "MIT",
5119
5119
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -5547,7 +5547,10 @@ function frontDoorRcApiClient(port, ca, token) {
5547
5547
  import { createORPCClient as createORPCClient2, ORPCError as ORPCError3 } from "@orpc/client";
5548
5548
 
5549
5549
  // src/checkReport.ts
5550
- import os5 from "node:os";
5550
+ import os6 from "node:os";
5551
+
5552
+ // src/launchSelection.ts
5553
+ import os4 from "node:os";
5551
5554
 
5552
5555
  // src/launcher/cascade.ts
5553
5556
  import path11 from "node:path";
@@ -5719,319 +5722,127 @@ function loadIdentity(identitiesDir, name, fs12) {
5719
5722
  return loadConfigFile(path12.join(identitiesDir, name, "identity.json"), IdentitySchema, fs12.readConfigFile);
5720
5723
  }
5721
5724
 
5722
- // src/paths.ts
5723
- import fs3 from "node:fs";
5724
- import os3 from "node:os";
5725
- import path14 from "node:path";
5726
-
5727
- // src/headroom/state.ts
5728
- import { createHash as createHash3 } from "node:crypto";
5729
- import path13 from "node:path";
5730
- import { z as z5 } from "zod";
5731
- var HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
5732
- var HEADROOM_STATE_SCHEMA_VERSION = 2;
5733
- var HeadroomStateSchema = z5.strictObject({
5734
- /** The supervisor process owning the daemon. Alive means someone is keeping headroom running. */
5735
- supervisorPid: z5.number().int().positive().optional(),
5736
- /** The headroom proxy process itself. Absent while the supervisor is between restarts. */
5737
- headroomPid: z5.number().int().positive().optional(),
5738
- /**
5739
- * The unix socket the proxy serves on, inside the owner-only socket directory. Absent until the proxy has passed its readiness check, so "socketPath is set" is itself the ready signal a launcher polls for. The front door reads it on every hop, so a restart (which keeps the generation's path) is followed without anything being relaunched.
5740
- */
5741
- socketPath: z5.string().min(1).optional(),
5742
- /** The `headroom --version` output of the running install. */
5743
- version: z5.string().optional(),
5744
- /** Hash of the allowlist the running proxy was started with, so a provider-file change is detected as drift. */
5745
- allowlistHash: z5.string().optional(),
5746
- /** Hash of the token-saving settings the running proxy was started with, so a settings change is detected as drift. */
5747
- settingsHash: z5.string().optional(),
5748
- /** The install spec the running install was last installed from, so a `source` config change is detected as drift. */
5749
- installedSource: z5.string().optional(),
5750
- /** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
5751
- lastError: z5.string().optional()
5752
- });
5753
- var HeadroomSessionSchema = z5.strictObject({ pid: z5.number().int().positive(), startedAt: z5.number(), supervisorPid: z5.number().int().positive() });
5754
- var HeadroomLockSchema = z5.strictObject({ pid: z5.number().int().positive(), at: z5.number() });
5755
- function parseJson(raw, schema) {
5756
- if (raw === void 0 || raw.trim() === "") {
5757
- return void 0;
5758
- }
5759
- const parsed = schema.safeParse(JSON.parse(raw));
5760
- return parsed.success ? parsed.data : void 0;
5761
- }
5762
- function readHeadroomState(fs12, stateFile) {
5763
- try {
5764
- return parseJson(fs12.readFileUtf8(stateFile), HeadroomStateSchema);
5765
- } catch {
5766
- return void 0;
5767
- }
5768
- }
5769
- function writeHeadroomState(fs12, stateFile, state) {
5770
- fs12.mkdirp(path13.dirname(stateFile));
5771
- fs12.writeFilePrivate(stateFile, `${JSON.stringify(state, null, 2)}
5772
- `);
5773
- }
5774
- function headroomAllowlist(providers) {
5775
- const urls = /* @__PURE__ */ new Set([HEADROOM_ANTHROPIC_UPSTREAM]);
5776
- for (const provider of providers) {
5777
- urls.add(provider.baseUrl);
5778
- }
5779
- return [...urls].sort();
5780
- }
5781
- function headroomUpstreams(providers, frontDoorOrigin2) {
5782
- const upstreams = providers.filter((provider) => !isCodexProvider(provider)).map((provider) => ({ baseUrl: provider.baseUrl }));
5783
- if (providers.length > 0 && frontDoorOrigin2 !== void 0) {
5784
- upstreams.push({ baseUrl: frontDoorOrigin2 });
5785
- }
5786
- return upstreams;
5787
- }
5788
- function hashAllowlist(allowlist) {
5789
- return createHash3("sha256").update(allowlist.join("\n")).digest("hex");
5790
- }
5791
- function readAllProviders(fs12, providersDir) {
5792
- const result = [];
5793
- for (const name of fs12.readdir(providersDir)) {
5794
- if (!name.endsWith(".json")) {
5795
- continue;
5796
- }
5797
- try {
5798
- const parsed = parseJson(fs12.readFileUtf8(path13.join(providersDir, name)), ProviderSchema);
5799
- if (parsed !== void 0) {
5800
- result.push({ name: name.slice(0, -".json".length), provider: parsed });
5801
- }
5802
- } catch {
5803
- continue;
5804
- }
5805
- }
5806
- return result;
5807
- }
5808
- function writeSession(fs12, sessionsDir, session) {
5809
- fs12.mkdirp(sessionsDir);
5810
- fs12.writeFileUtf8(path13.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
5811
- `);
5812
- }
5813
- function removeSession(fs12, sessionsDir, pid) {
5814
- fs12.removeRecursive(path13.join(sessionsDir, `${String(pid)}.json`));
5815
- }
5816
- function listSessions(fs12, sessionsDir) {
5817
- const sessions = [];
5818
- for (const name of fs12.readdir(sessionsDir)) {
5819
- if (!name.endsWith(".json")) {
5820
- continue;
5821
- }
5822
- try {
5823
- const parsed = parseJson(fs12.readFileUtf8(path13.join(sessionsDir, name)), HeadroomSessionSchema);
5824
- if (parsed !== void 0) {
5825
- sessions.push(parsed);
5826
- }
5827
- } catch {
5828
- continue;
5829
- }
5830
- }
5831
- return sessions.sort((a, b) => a.pid - b.pid);
5832
- }
5833
- function sessionsForSupervisor(fs12, sessionsDir, supervisorPid) {
5834
- return listSessions(fs12, sessionsDir).filter((session) => session.supervisorPid === supervisorPid);
5835
- }
5836
- function pruneDeadSessions(fs12, sessionsDir, isRunning) {
5837
- const removed = [];
5838
- for (const session of listSessions(fs12, sessionsDir)) {
5839
- if (!isRunning(session.pid)) {
5840
- removeSession(fs12, sessionsDir, session.pid);
5841
- removed.push(session.pid);
5842
- }
5843
- }
5844
- return removed;
5845
- }
5846
- function readStartLock(fs12, lockFile) {
5847
- try {
5848
- return parseJson(fs12.readFileUtf8(lockFile), HeadroomLockSchema);
5849
- } catch {
5850
- return void 0;
5851
- }
5852
- }
5853
-
5854
- // src/paths.ts
5855
- function resolveAgentShimHome(env = process.env, home = os3.homedir(), isDirectory = isExistingDirectory) {
5856
- const fromEnv = env.AGENT_SHIM_HOME;
5857
- if (fromEnv !== void 0 && fromEnv !== "") {
5858
- return fromEnv;
5859
- }
5860
- const current = path14.join(home, HOME_DIRNAME);
5861
- const legacy = path14.join(home, LEGACY_HOME_DIRNAME);
5862
- return !isDirectory(current) && isDirectory(legacy) ? legacy : current;
5863
- }
5864
- function isExistingDirectory(candidate) {
5865
- return fs3.statSync(candidate, { throwIfNoEntry: false })?.isDirectory() ?? false;
5866
- }
5867
- function resolveClaudeHome() {
5868
- const fromEnv = process.env.AGENT_SHIM_CLAUDE_HOME;
5869
- if (fromEnv !== void 0 && fromEnv !== "") {
5870
- return fromEnv;
5871
- }
5872
- return path14.join(os3.homedir(), ".claude");
5873
- }
5874
- function buildLayoutPaths(root) {
5875
- return {
5876
- root,
5877
- identitiesDir: path14.join(root, "identities"),
5878
- configProfilesDir: path14.join(root, "config-profiles"),
5879
- providersDir: path14.join(root, "providers"),
5880
- directoryRulesFile: path14.join(root, "directory-rules.json"),
5881
- activeIdentityFile: path14.join(root, "active-identity"),
5882
- globalConfigFile: path14.join(root, "config.json"),
5883
- categoriesLocalFile: path14.join(root, "categories.local.json"),
5884
- claudeShimFile: path14.join(root, "claude-shim.json"),
5885
- headroomDir: path14.join(root, "headroom"),
5886
- headroomStateFile: path14.join(root, "headroom", `state.v${String(HEADROOM_STATE_SCHEMA_VERSION)}.json`),
5887
- headroomSocketDir: path14.join(root, "headroom", "run"),
5888
- headroomLockFile: path14.join(root, "headroom", "start.lock"),
5889
- headroomSessionsDir: path14.join(root, "headroom", "sessions"),
5890
- logsDir: path14.join(root, "logs"),
5891
- headroomLogPath: path14.join(root, "logs", "headroom.log"),
5892
- frontdoorDir: path14.join(root, "frontdoor"),
5893
- frontdoorCaDir: path14.join(root, "frontdoor", "ca"),
5894
- frontdoorCaCertFile: path14.join(root, "frontdoor", "ca", "ca.pem"),
5895
- frontdoorCaKeyFile: path14.join(root, "frontdoor", "ca", "ca.key"),
5896
- frontdoorCaBundlesDir: path14.join(root, "frontdoor", "ca", "bundles"),
5897
- chatgptSignInFile: path14.join(root, "codex", "chatgpt-sign-in.json"),
5898
- frontdoorStateFile: path14.join(root, "frontdoor", "state.json"),
5899
- frontdoorLockFile: path14.join(root, "frontdoor", "start.lock"),
5900
- frontdoorControlTokenFile: path14.join(root, "frontdoor", "control-token"),
5901
- frontdoorSessionsDir: path14.join(root, "frontdoor", "sessions"),
5902
- frontdoorRcCredentialsDir: path14.join(root, "frontdoor", "rc-credentials"),
5903
- frontdoorLogPath: path14.join(root, "logs", "frontdoor.log"),
5904
- usageDir: path14.join(root, "usage"),
5905
- usageLogDir: path14.join(root, "usage", "log"),
5906
- usageSnapshotsDir: path14.join(root, "usage", "snapshots"),
5907
- usagePicksFile: path14.join(root, "usage", "picks.json")
5908
- };
5909
- }
5910
- function resolveLayoutPaths() {
5911
- return buildLayoutPaths(resolveAgentShimHome());
5912
- }
5913
-
5914
5725
  // src/usage/poolPick.ts
5915
- import path17 from "node:path";
5916
- import { z as z9 } from "zod";
5726
+ import path15 from "node:path";
5727
+ import { z as z8 } from "zod";
5917
5728
 
5918
5729
  // src/usage/account.ts
5919
- import path15 from "node:path";
5730
+ import path13 from "node:path";
5920
5731
 
5921
5732
  // src/usage/schema.ts
5922
- import { z as z6 } from "zod";
5733
+ import { z as z5 } from "zod";
5923
5734
  var USAGE_SCHEMA_VERSION = 1;
5924
- var VersionSchema = z6.literal(USAGE_SCHEMA_VERSION);
5925
- var InstantSchema = z6.iso.datetime();
5926
- var TokenCountSchema = z6.number().int().nonnegative();
5927
- var TokenUsageSchema = z6.strictObject({
5735
+ var VersionSchema = z5.literal(USAGE_SCHEMA_VERSION);
5736
+ var InstantSchema = z5.iso.datetime();
5737
+ var TokenCountSchema = z5.number().int().nonnegative();
5738
+ var TokenUsageSchema = z5.strictObject({
5928
5739
  inputTokens: TokenCountSchema.optional(),
5929
5740
  outputTokens: TokenCountSchema.optional(),
5930
5741
  cacheCreationInputTokens: TokenCountSchema.optional(),
5931
5742
  cacheReadInputTokens: TokenCountSchema.optional()
5932
5743
  });
5933
5744
  var LIMIT_KINDS = ["rate-limited", "quota-exhausted"];
5934
- var LimitClassificationSchema = z6.strictObject({
5935
- kind: z6.enum(LIMIT_KINDS),
5745
+ var LimitClassificationSchema = z5.strictObject({
5746
+ kind: z5.enum(LIMIT_KINDS),
5936
5747
  /** Seconds to wait before retrying, from the response's `retry-after`. */
5937
- retryAfterSeconds: z6.number().nonnegative().optional(),
5748
+ retryAfterSeconds: z5.number().nonnegative().optional(),
5938
5749
  /** When the limiting window resets, from the upstream's own reset header. */
5939
5750
  resetAt: InstantSchema.optional(),
5940
5751
  /** The window the upstream named as the binding one (`anthropic-ratelimit-unified-representative-claim`), such as `five_hour` or `seven_day`. */
5941
- window: z6.string().optional(),
5752
+ window: z5.string().optional(),
5942
5753
  /** The signals the classification rests on, each `<name>=<value>` from a response header or the error body's `type` or `code`. Never the error message. */
5943
- evidence: z6.array(z6.string())
5754
+ evidence: z5.array(z5.string())
5944
5755
  });
5945
- var UsageRecordSchema = z6.strictObject({
5756
+ var UsageRecordSchema = z5.strictObject({
5946
5757
  schemaVersion: VersionSchema,
5947
5758
  /** When the front door received the request. */
5948
5759
  at: InstantSchema,
5949
5760
  /** The launching identity, when the launch resolved one. */
5950
- identity: z6.string().optional(),
5761
+ identity: z5.string().optional(),
5951
5762
  /** `anthropic` for an OAuth session's request to Claude Code's own API, otherwise the provider's name. */
5952
- provider: z6.string(),
5763
+ provider: z5.string(),
5953
5764
  /** The launch's session id, generated per launch. */
5954
- sessionId: z6.string().optional(),
5765
+ sessionId: z5.string().optional(),
5955
5766
  /** The project the launch ran in (its git repository root), when the launch sent one: headroom launches do. */
5956
- project: z6.string().optional(),
5767
+ project: z5.string().optional(),
5957
5768
  /** The front-door route that served the request. */
5958
- route: z6.string(),
5959
- method: z6.string(),
5769
+ route: z5.string(),
5770
+ method: z5.string(),
5960
5771
  /** The API path as the upstream sees it (`/v1/messages`), without the provider prefix or a query string. */
5961
- endpoint: z6.string(),
5772
+ endpoint: z5.string(),
5962
5773
  /** The model the response named. */
5963
- model: z6.string().optional(),
5964
- status: z6.number().int(),
5774
+ model: z5.string().optional(),
5775
+ status: z5.number().int(),
5965
5776
  /** Milliseconds from the request's arrival to its response head. */
5966
- latencyMs: z6.number().int().nonnegative(),
5777
+ latencyMs: z5.number().int().nonnegative(),
5967
5778
  /** Milliseconds from the request's arrival to the end of its response body. */
5968
- durationMs: z6.number().int().nonnegative(),
5779
+ durationMs: z5.number().int().nonnegative(),
5969
5780
  /** Whether the client received the whole body. */
5970
- outcome: z6.enum(["completed", "aborted"]),
5781
+ outcome: z5.enum(["completed", "aborted"]),
5971
5782
  usage: TokenUsageSchema.optional(),
5972
5783
  /** The response's quota and rate-limit headers, by name (see `isRateLimitHeader`). */
5973
- rateLimitHeaders: z6.record(z6.string(), z6.string()).optional(),
5784
+ rateLimitHeaders: z5.record(z5.string(), z5.string()).optional(),
5974
5785
  limit: LimitClassificationSchema.optional(),
5975
5786
  /** The upstream's request id (`request-id`), for matching a record to the provider's own logs. */
5976
- requestId: z6.string().optional()
5787
+ requestId: z5.string().optional()
5977
5788
  });
5978
- var QuotaWindowSchema = z6.strictObject({
5789
+ var QuotaWindowSchema = z5.strictObject({
5979
5790
  /** The fraction of the window used, as the upstream reports it (0 to 1). */
5980
- utilization: z6.number().nonnegative().optional(),
5791
+ utilization: z5.number().nonnegative().optional(),
5981
5792
  resetsAt: InstantSchema.optional(),
5982
5793
  /** The window's own status (`allowed`, `allowed_warning`, `rejected`). */
5983
- status: z6.string().optional()
5794
+ status: z5.string().optional()
5984
5795
  });
5985
- var UnifiedRateLimitSchema = z6.strictObject({
5796
+ var UnifiedRateLimitSchema = z5.strictObject({
5986
5797
  /** The overall status: `allowed`, `allowed_warning` or `rejected`. */
5987
- status: z6.string().optional(),
5798
+ status: z5.string().optional(),
5988
5799
  fiveHour: QuotaWindowSchema.optional(),
5989
5800
  sevenDay: QuotaWindowSchema.optional(),
5990
5801
  /** The window that currently binds. */
5991
- representativeClaim: z6.string().optional(),
5802
+ representativeClaim: z5.string().optional(),
5992
5803
  /** When the binding window resets. */
5993
5804
  resetAt: InstantSchema.optional(),
5994
5805
  /** Whether usage beyond the plan (extra usage) is available: `allowed` or `rejected`. */
5995
- overageStatus: z6.string().optional()
5806
+ overageStatus: z5.string().optional()
5996
5807
  });
5997
- var RateLimitStateSchema = z6.strictObject({
5808
+ var RateLimitStateSchema = z5.strictObject({
5998
5809
  observedAt: InstantSchema,
5999
5810
  /** Every quota and rate-limit header on that response, by name. */
6000
- headers: z6.record(z6.string(), z6.string()),
5811
+ headers: z5.record(z5.string(), z5.string()),
6001
5812
  unified: UnifiedRateLimitSchema.optional()
6002
5813
  });
6003
5814
  var LimitEventSchema = LimitClassificationSchema.extend({
6004
5815
  observedAt: InstantSchema,
6005
- status: z6.number().int()
5816
+ status: z5.number().int()
6006
5817
  });
6007
- var ProviderQuotaWindowSchema = z6.strictObject({
5818
+ var ProviderQuotaWindowSchema = z5.strictObject({
6008
5819
  /** What the window measures, in the provider's terms (`tokens`, `tool-calls`). */
6009
- measures: z6.string(),
5820
+ measures: z5.string(),
6010
5821
  /** The window's length in milliseconds, when the provider's unit is one this project knows; absent for an unrecognised unit, with `period` carrying the raw code. */
6011
- periodMs: z6.number().int().positive().optional(),
5822
+ periodMs: z5.number().int().positive().optional(),
6012
5823
  /** The provider's own description of the period when `periodMs` could not be derived, such as `unit 4 x 1`. */
6013
- period: z6.string().optional(),
5824
+ period: z5.string().optional(),
6014
5825
  /** The fraction of the window used (0 to 1). */
6015
- utilization: z6.number().nonnegative(),
5826
+ utilization: z5.number().nonnegative(),
6016
5827
  resetsAt: InstantSchema.optional(),
6017
5828
  /** Absolute counts, when the provider reports them for this window. */
6018
- limit: z6.number().nonnegative().optional(),
6019
- used: z6.number().nonnegative().optional(),
6020
- remaining: z6.number().nonnegative().optional()
5829
+ limit: z5.number().nonnegative().optional(),
5830
+ used: z5.number().nonnegative().optional(),
5831
+ remaining: z5.number().nonnegative().optional()
6021
5832
  });
6022
- var ProviderQuotaSchema = z6.strictObject({
5833
+ var ProviderQuotaSchema = z5.strictObject({
6023
5834
  observedAt: InstantSchema,
6024
5835
  /** Which usage endpoint produced it (`z.ai`). */
6025
- source: z6.string(),
5836
+ source: z5.string(),
6026
5837
  /** The subscription level the endpoint reports, when it does. */
6027
- level: z6.string().optional(),
6028
- windows: z6.array(ProviderQuotaWindowSchema)
5838
+ level: z5.string().optional(),
5839
+ windows: z5.array(ProviderQuotaWindowSchema)
6029
5840
  });
6030
- var ProviderUsageStateSchema = z6.strictObject({
5841
+ var ProviderUsageStateSchema = z5.strictObject({
6031
5842
  /** When the provider last served a request for the identity through the front door. Absent while the state holds only a rate-limit state fetched from the provider's usage endpoint, for an identity that has made no request. */
6032
5843
  lastRequestAt: InstantSchema.optional(),
6033
- lastStatus: z6.number().int().optional(),
6034
- lastModel: z6.string().optional(),
5844
+ lastStatus: z5.number().int().optional(),
5845
+ lastModel: z5.string().optional(),
6035
5846
  /** The latest rate-limit state, from the last response that carried any rate-limit header. */
6036
5847
  rateLimit: RateLimitStateSchema.optional(),
6037
5848
  /** The latest classified refusal. It stays after later successes, so a reader compares its `resetAt` (or `observedAt` plus `retryAfterSeconds`) with the current time. */
@@ -6039,31 +5850,31 @@ var ProviderUsageStateSchema = z6.strictObject({
6039
5850
  /** The latest quota the provider's usage endpoint reported, for providers whose quota is not visible in response headers. */
6040
5851
  quota: ProviderQuotaSchema.optional()
6041
5852
  });
6042
- var AccountMetadataSchema = z6.strictObject({
6043
- accountUuid: z6.string().optional(),
6044
- emailAddress: z6.string().optional(),
6045
- displayName: z6.string().optional(),
6046
- organizationUuid: z6.string().optional(),
6047
- organizationName: z6.string().optional(),
6048
- organizationType: z6.string().optional(),
6049
- organizationRole: z6.string().optional(),
6050
- workspaceRole: z6.string().optional(),
6051
- billingType: z6.string().optional(),
6052
- seatTier: z6.string().optional(),
6053
- organizationRateLimitTier: z6.string().optional(),
6054
- userRateLimitTier: z6.string().optional(),
6055
- hasExtraUsageEnabled: z6.boolean().optional(),
6056
- subscriptionCreatedAt: z6.string().optional(),
6057
- accountCreatedAt: z6.string().optional()
5853
+ var AccountMetadataSchema = z5.strictObject({
5854
+ accountUuid: z5.string().optional(),
5855
+ emailAddress: z5.string().optional(),
5856
+ displayName: z5.string().optional(),
5857
+ organizationUuid: z5.string().optional(),
5858
+ organizationName: z5.string().optional(),
5859
+ organizationType: z5.string().optional(),
5860
+ organizationRole: z5.string().optional(),
5861
+ workspaceRole: z5.string().optional(),
5862
+ billingType: z5.string().optional(),
5863
+ seatTier: z5.string().optional(),
5864
+ organizationRateLimitTier: z5.string().optional(),
5865
+ userRateLimitTier: z5.string().optional(),
5866
+ hasExtraUsageEnabled: z5.boolean().optional(),
5867
+ subscriptionCreatedAt: z5.string().optional(),
5868
+ accountCreatedAt: z5.string().optional()
6058
5869
  });
6059
- var UsageSnapshotSchema = z6.strictObject({
5870
+ var UsageSnapshotSchema = z5.strictObject({
6060
5871
  schemaVersion: VersionSchema,
6061
- identity: z6.string(),
5872
+ identity: z5.string(),
6062
5873
  updatedAt: InstantSchema,
6063
5874
  /** The identity's account metadata as of `updatedAt`. */
6064
5875
  account: AccountMetadataSchema.optional(),
6065
5876
  /** Latest state per provider, keyed by provider name (`anthropic` for OAuth sessions). */
6066
- providers: z6.record(z6.string(), ProviderUsageStateSchema)
5877
+ providers: z5.record(z5.string(), ProviderUsageStateSchema)
6067
5878
  });
6068
5879
 
6069
5880
  // src/usage/account.ts
@@ -6083,7 +5894,7 @@ function isIdentityName(name) {
6083
5894
  return IdentitySchema.shape.name.safeParse(name).success;
6084
5895
  }
6085
5896
  function claudeJsonPath(identitiesDir, identity) {
6086
- return path15.join(identitiesDir, identity, CLAUDE_JSON);
5897
+ return path13.join(identitiesDir, identity, CLAUDE_JSON);
6087
5898
  }
6088
5899
  function readAccountMetadata(fs12, identitiesDir, identity) {
6089
5900
  if (!isIdentityName(identity)) {
@@ -6122,20 +5933,20 @@ function readAccountMetadata(fs12, identitiesDir, identity) {
6122
5933
  }
6123
5934
 
6124
5935
  // src/usage/plan.ts
6125
- import { z as z7 } from "zod";
6126
- var PlanClassSchema = z7.union([
6127
- z7.strictObject({
6128
- kind: z7.literal("subscription"),
5936
+ import { z as z6 } from "zod";
5937
+ var PlanClassSchema = z6.union([
5938
+ z6.strictObject({
5939
+ kind: z6.literal("subscription"),
6129
5940
  /** The plan's size as a multiple of the smallest subscription tier (Max 5x is 5, Max 20x is 20). */
6130
- capacity: z7.number().positive(),
5941
+ capacity: z6.number().positive(),
6131
5942
  /** False when the tier string named no multiplier and no tier this project knows, so `capacity` is the neutral 1 rather than a reading. */
6132
- recognised: z7.boolean(),
5943
+ recognised: z6.boolean(),
6133
5944
  /** The tier string the classification rests on, when the account had one. */
6134
- tier: z7.string().optional()
5945
+ tier: z6.string().optional()
6135
5946
  }),
6136
- z7.strictObject({
6137
- kind: z7.literal("pay-per-use"),
6138
- tier: z7.string().optional()
5947
+ z6.strictObject({
5948
+ kind: z6.literal("pay-per-use"),
5949
+ tier: z6.string().optional()
6139
5950
  })
6140
5951
  ]);
6141
5952
  var TIER_MULTIPLIER = /(?:^|_)(\d+)x$/;
@@ -6204,17 +6015,17 @@ function parseUnifiedRateLimit(headers) {
6204
6015
  }
6205
6016
 
6206
6017
  // src/usage/scan.ts
6207
- import { z as z8 } from "zod";
6208
- var AnthropicUsageSchema = z8.looseObject({
6209
- input_tokens: z8.number().int().nonnegative().nullish(),
6210
- output_tokens: z8.number().int().nonnegative().nullish(),
6211
- cache_creation_input_tokens: z8.number().int().nonnegative().nullish(),
6212
- cache_read_input_tokens: z8.number().int().nonnegative().nullish()
6018
+ import { z as z7 } from "zod";
6019
+ var AnthropicUsageSchema = z7.looseObject({
6020
+ input_tokens: z7.number().int().nonnegative().nullish(),
6021
+ output_tokens: z7.number().int().nonnegative().nullish(),
6022
+ cache_creation_input_tokens: z7.number().int().nonnegative().nullish(),
6023
+ cache_read_input_tokens: z7.number().int().nonnegative().nullish()
6213
6024
  });
6214
- var ErrorObjectSchema = z8.looseObject({
6215
- type: z8.string().optional(),
6216
- code: z8.union([z8.string(), z8.number()]).optional(),
6217
- message: z8.string().optional()
6025
+ var ErrorObjectSchema = z7.looseObject({
6026
+ type: z7.string().optional(),
6027
+ code: z7.union([z7.string(), z7.number()]).optional(),
6028
+ message: z7.string().optional()
6218
6029
  });
6219
6030
 
6220
6031
  // src/usage/middleware.ts
@@ -6501,7 +6312,7 @@ function rankPool(input) {
6501
6312
  }
6502
6313
 
6503
6314
  // src/usage/read.ts
6504
- import path16 from "node:path";
6315
+ import path14 from "node:path";
6505
6316
  var SEGMENT_RE = /^(\d{4}-\d{2}-\d{2})\.(\d+)\.jsonl$/;
6506
6317
  var DAY_MS = 864e5;
6507
6318
  function listLogSegments(fs12, logDir) {
@@ -6522,7 +6333,7 @@ function readUsageLog(fs12, logDir, options = {}) {
6522
6333
  if (sinceMs !== void 0 && segment.endsAt <= sinceMs) {
6523
6334
  continue;
6524
6335
  }
6525
- const contents = fs12.readFileUtf8(path16.join(logDir, segment.name));
6336
+ const contents = fs12.readFileUtf8(path14.join(logDir, segment.name));
6526
6337
  if (contents === void 0) {
6527
6338
  continue;
6528
6339
  }
@@ -6564,7 +6375,7 @@ function snapshotPath(snapshotsDir, identity) {
6564
6375
  if (!isIdentityName(identity)) {
6565
6376
  throw new Error(`"${identity}" is not a valid identity name.`);
6566
6377
  }
6567
- return path16.join(snapshotsDir, `${identity}.json`);
6378
+ return path14.join(snapshotsDir, `${identity}.json`);
6568
6379
  }
6569
6380
  function readUsageSnapshot(fs12, snapshotsDir, identity) {
6570
6381
  const file = snapshotPath(snapshotsDir, identity);
@@ -6611,10 +6422,10 @@ var PoolGraphError = class extends CliError {
6611
6422
  }
6612
6423
  };
6613
6424
  var SCHEMA_VERSION = 1;
6614
- var PickEntrySchema = z9.strictObject({ identity: z9.string().min(1), at: z9.iso.datetime() });
6615
- var PicksFileSchema = z9.strictObject({
6616
- schemaVersion: z9.literal(SCHEMA_VERSION),
6617
- picks: z9.record(z9.string().min(1), PickEntrySchema)
6425
+ var PickEntrySchema = z8.strictObject({ identity: z8.string().min(1), at: z8.iso.datetime() });
6426
+ var PicksFileSchema = z8.strictObject({
6427
+ schemaVersion: z8.literal(SCHEMA_VERSION),
6428
+ picks: z8.record(z8.string().min(1), PickEntrySchema)
6618
6429
  });
6619
6430
  function errorMessage(error) {
6620
6431
  return error instanceof Error ? error.message : String(error);
@@ -6742,7 +6553,7 @@ function recordStickyPick(fs12, picksFile, directory, identity, nowMs) {
6742
6553
  const current = readPicksFile(fs12, picksFile).file.picks;
6743
6554
  const kept = Object.entries(current).filter(([, entry]) => nowMs - Date.parse(entry.at) <= USAGE_RETENTION_MS);
6744
6555
  const next = { schemaVersion: SCHEMA_VERSION, picks: { ...Object.fromEntries(kept), [directory]: { identity, at: new Date(nowMs).toISOString() } } };
6745
- fs12.mkdirPrivate(path17.dirname(picksFile));
6556
+ fs12.mkdirPrivate(path15.dirname(picksFile));
6746
6557
  fs12.writeFilePrivate(picksFile, `${JSON.stringify(next, null, 2)}
6747
6558
  `);
6748
6559
  }
@@ -6784,8 +6595,8 @@ function collectPoolPick(params) {
6784
6595
  }
6785
6596
 
6786
6597
  // src/configProfilesStore.ts
6787
- import fs4 from "node:fs";
6788
- import path18 from "node:path";
6598
+ import fs3 from "node:fs";
6599
+ import path16 from "node:path";
6789
6600
  var ProfileNotFoundError = class extends CliError {
6790
6601
  constructor(profileName) {
6791
6602
  super(`No configuration profile named "${profileName}". Run \`agent-shim profile add ${profileName}\` first.`);
@@ -6811,10 +6622,10 @@ var InvalidCategoryNameError = class extends CliError {
6811
6622
  categoryName;
6812
6623
  };
6813
6624
  function profileJsonPath(paths, name) {
6814
- return path18.join(paths.configProfilesDir, `${name}.json`);
6625
+ return path16.join(paths.configProfilesDir, `${name}.json`);
6815
6626
  }
6816
6627
  function profileExists(paths, name) {
6817
- return fs4.existsSync(profileJsonPath(paths, name));
6628
+ return fs3.existsSync(profileJsonPath(paths, name));
6818
6629
  }
6819
6630
  function requireProfileExists(paths, name) {
6820
6631
  if (!profileExists(paths, name)) {
@@ -6840,10 +6651,10 @@ function createProfile(paths, name, extendsList, description) {
6840
6651
  return parsed.data;
6841
6652
  }
6842
6653
  function listProfiles(paths) {
6843
- if (!fs4.existsSync(paths.configProfilesDir)) {
6654
+ if (!fs3.existsSync(paths.configProfilesDir)) {
6844
6655
  return [];
6845
6656
  }
6846
- const names = fs4.readdirSync(paths.configProfilesDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort();
6657
+ const names = fs3.readdirSync(paths.configProfilesDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort();
6847
6658
  const result = [];
6848
6659
  for (const name of names) {
6849
6660
  const profile = readProfile(paths, name);
@@ -6906,7 +6717,7 @@ function setProfileMetadata(paths, name, patch) {
6906
6717
  }
6907
6718
  function removeProfile(paths, name) {
6908
6719
  requireProfileExists(paths, name);
6909
- fs4.rmSync(profileJsonPath(paths, name));
6720
+ fs3.rmSync(profileJsonPath(paths, name));
6910
6721
  }
6911
6722
 
6912
6723
  // src/poolStore.ts
@@ -7008,1450 +6819,1684 @@ function removePool(paths, name) {
7008
6819
  writePools(paths, remaining);
7009
6820
  }
7010
6821
 
7011
- // src/realCredentialCache.ts
7012
- import { spawnSync } from "node:child_process";
7013
- import fs5 from "node:fs";
6822
+ // src/realPorts.ts
6823
+ import { spawn, spawnSync } from "node:child_process";
6824
+ import { createHash as createHash5 } from "node:crypto";
6825
+ import fs4 from "node:fs";
6826
+ import os3 from "node:os";
7014
6827
  import path19 from "node:path";
6828
+ import { isSea } from "node:sea";
7015
6829
 
7016
- // src/credentialCache.ts
7017
- import { z as z10 } from "zod";
7018
- var MS_PER_UNIT = { s: 1e3, m: 6e4, h: 36e5, d: 864e5 };
7019
- var CachedCredentialSchema = z10.strictObject({
7020
- token: z10.string().min(1),
7021
- fetchedAt: z10.number().int().nonnegative(),
7022
- source: CredentialSourceSchema
6830
+ // src/headroom/state.ts
6831
+ import { createHash as createHash3 } from "node:crypto";
6832
+ import path17 from "node:path";
6833
+ import { z as z9 } from "zod";
6834
+ var HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
6835
+ var HEADROOM_STATE_SCHEMA_VERSION = 2;
6836
+ var HeadroomStateSchema = z9.strictObject({
6837
+ /** The supervisor process owning the daemon. Alive means someone is keeping headroom running. */
6838
+ supervisorPid: z9.number().int().positive().optional(),
6839
+ /** The headroom proxy process itself. Absent while the supervisor is between restarts. */
6840
+ headroomPid: z9.number().int().positive().optional(),
6841
+ /**
6842
+ * The unix socket the proxy serves on, inside the owner-only socket directory. Absent until the proxy has passed its readiness check, so "socketPath is set" is itself the ready signal a launcher polls for. The front door reads it on every hop, so a restart (which keeps the generation's path) is followed without anything being relaunched.
6843
+ */
6844
+ socketPath: z9.string().min(1).optional(),
6845
+ /** The `headroom --version` output of the running install. */
6846
+ version: z9.string().optional(),
6847
+ /** Hash of the allowlist the running proxy was started with, so a provider-file change is detected as drift. */
6848
+ allowlistHash: z9.string().optional(),
6849
+ /** Hash of the token-saving settings the running proxy was started with, so a settings change is detected as drift. */
6850
+ settingsHash: z9.string().optional(),
6851
+ /** The install spec the running install was last installed from, so a `source` config change is detected as drift. */
6852
+ installedSource: z9.string().optional(),
6853
+ /** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
6854
+ lastError: z9.string().optional()
7023
6855
  });
7024
- function effectiveStore(cache, platform) {
7025
- return cache.store ?? (platform === "darwin" ? "keychain" : "file");
7026
- }
7027
- function ttlMs(cache) {
7028
- if (cache.ttl === void 0) {
6856
+ var HeadroomSessionSchema = z9.strictObject({ pid: z9.number().int().positive(), startedAt: z9.number(), supervisorPid: z9.number().int().positive() });
6857
+ var HeadroomLockSchema = z9.strictObject({ pid: z9.number().int().positive(), at: z9.number() });
6858
+ function parseJson(raw, schema) {
6859
+ if (raw === void 0 || raw.trim() === "") {
7029
6860
  return void 0;
7030
6861
  }
7031
- const multiplier = MS_PER_UNIT[cache.ttl.slice(-1)];
7032
- if (multiplier === void 0) {
7033
- throw new Error(`credential cache ttl "${cache.ttl}" has no recognised unit`);
7034
- }
7035
- return Number(cache.ttl.slice(0, -1)) * multiplier;
6862
+ const parsed = schema.safeParse(JSON.parse(raw));
6863
+ return parsed.success ? parsed.data : void 0;
7036
6864
  }
7037
- function isFresh(entry, cache, now) {
7038
- const limit = ttlMs(cache);
7039
- if (entry.fetchedAt > now) {
7040
- return false;
6865
+ function readHeadroomState(fs12, stateFile) {
6866
+ try {
6867
+ return parseJson(fs12.readFileUtf8(stateFile), HeadroomStateSchema);
6868
+ } catch {
6869
+ return void 0;
7041
6870
  }
7042
- return limit === void 0 || now - entry.fetchedAt < limit;
7043
6871
  }
7044
- function cacheAgeMs(entry, now) {
7045
- return Math.max(0, now - entry.fetchedAt);
6872
+ function writeHeadroomState(fs12, stateFile, state) {
6873
+ fs12.mkdirp(path17.dirname(stateFile));
6874
+ fs12.writeFilePrivate(stateFile, `${JSON.stringify(state, null, 2)}
6875
+ `);
7046
6876
  }
7047
- function describeCachedCredential(params) {
7048
- const store = effectiveStore(params.cache, params.platform);
7049
- let entry;
7050
- try {
7051
- entry = params.port.read(store, params.owner);
7052
- } catch (error) {
7053
- return { store, status: "unreadable", reason: error instanceof Error ? error.message : String(error) };
7054
- }
7055
- if (entry === void 0) {
7056
- return { store, status: "empty" };
6877
+ function headroomAllowlist(providers) {
6878
+ const urls = /* @__PURE__ */ new Set([HEADROOM_ANTHROPIC_UPSTREAM]);
6879
+ for (const provider of providers) {
6880
+ urls.add(provider.baseUrl);
7057
6881
  }
7058
- const limit = ttlMs(params.cache);
7059
- const ageMs = cacheAgeMs(entry, params.nowMs);
7060
- return {
7061
- store,
7062
- status: isFresh(entry, params.cache, params.nowMs) ? "fresh" : "expired",
7063
- ageMs,
7064
- ...limit === void 0 ? {} : { expiresInMs: limit - ageMs }
7065
- };
6882
+ return [...urls].sort();
7066
6883
  }
7067
- function formatCachedCredentialState(state) {
7068
- if (state.status === "empty") {
7069
- return `no cached entry in the ${state.store} store yet`;
7070
- }
7071
- if (state.status === "unreadable") {
7072
- return `the ${state.store} store cannot be read here: ${state.reason}`;
7073
- }
7074
- if (state.status === "expired" && (state.expiresInMs === void 0 || state.expiresInMs > 0)) {
7075
- return "cached with a timestamp in the future, treated as expired";
6884
+ function headroomUpstreams(providers, frontDoorOrigin2) {
6885
+ const upstreams = providers.filter((provider) => !isCodexProvider(provider)).map((provider) => ({ baseUrl: provider.baseUrl }));
6886
+ if (providers.length > 0 && frontDoorOrigin2 !== void 0) {
6887
+ upstreams.push({ baseUrl: frontDoorOrigin2 });
7076
6888
  }
7077
- const age = `cached ${formatAge(state.ageMs)} ago`;
7078
- if (state.expiresInMs === void 0) {
7079
- return `${age}, no expiry`;
6889
+ return upstreams;
6890
+ }
6891
+ function hashAllowlist(allowlist) {
6892
+ return createHash3("sha256").update(allowlist.join("\n")).digest("hex");
6893
+ }
6894
+ function readAllProviders(fs12, providersDir) {
6895
+ const result = [];
6896
+ for (const name of fs12.readdir(providersDir)) {
6897
+ if (!name.endsWith(".json")) {
6898
+ continue;
6899
+ }
6900
+ try {
6901
+ const parsed = parseJson(fs12.readFileUtf8(path17.join(providersDir, name)), ProviderSchema);
6902
+ if (parsed !== void 0) {
6903
+ result.push({ name: name.slice(0, -".json".length), provider: parsed });
6904
+ }
6905
+ } catch {
6906
+ continue;
6907
+ }
7080
6908
  }
7081
- return state.status === "fresh" ? `${age}, expires in ${formatAge(state.expiresInMs)}` : `${age}, expired ${formatAge(-state.expiresInMs)} ago`;
6909
+ return result;
7082
6910
  }
7083
-
7084
- // src/realCredentialCache.ts
7085
- var KEYCHAIN_CACHE_SERVICE = "agent-shim-credential-cache";
7086
- var CACHE_DIR_MODE = 448;
7087
- var CACHE_FILE_MODE = 384;
7088
- function cacheFileNameFor(owner) {
7089
- return `${owner.replace(/[^A-Za-z0-9._-]/g, "_")}.json`;
6911
+ function writeSession(fs12, sessionsDir, session) {
6912
+ fs12.mkdirp(sessionsDir);
6913
+ fs12.writeFileUtf8(path17.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
6914
+ `);
7090
6915
  }
7091
- function parseEntry(raw) {
7092
- let json;
6916
+ function removeSession(fs12, sessionsDir, pid) {
6917
+ fs12.removeRecursive(path17.join(sessionsDir, `${String(pid)}.json`));
6918
+ }
6919
+ function listSessions(fs12, sessionsDir) {
6920
+ const sessions = [];
6921
+ for (const name of fs12.readdir(sessionsDir)) {
6922
+ if (!name.endsWith(".json")) {
6923
+ continue;
6924
+ }
6925
+ try {
6926
+ const parsed = parseJson(fs12.readFileUtf8(path17.join(sessionsDir, name)), HeadroomSessionSchema);
6927
+ if (parsed !== void 0) {
6928
+ sessions.push(parsed);
6929
+ }
6930
+ } catch {
6931
+ continue;
6932
+ }
6933
+ }
6934
+ return sessions.sort((a, b) => a.pid - b.pid);
6935
+ }
6936
+ function sessionsForSupervisor(fs12, sessionsDir, supervisorPid) {
6937
+ return listSessions(fs12, sessionsDir).filter((session) => session.supervisorPid === supervisorPid);
6938
+ }
6939
+ function pruneDeadSessions(fs12, sessionsDir, isRunning) {
6940
+ const removed = [];
6941
+ for (const session of listSessions(fs12, sessionsDir)) {
6942
+ if (!isRunning(session.pid)) {
6943
+ removeSession(fs12, sessionsDir, session.pid);
6944
+ removed.push(session.pid);
6945
+ }
6946
+ }
6947
+ return removed;
6948
+ }
6949
+ function readStartLock(fs12, lockFile) {
7093
6950
  try {
7094
- json = JSON.parse(raw);
6951
+ return parseJson(fs12.readFileUtf8(lockFile), HeadroomLockSchema);
7095
6952
  } catch {
7096
6953
  return void 0;
7097
6954
  }
7098
- const parsed = CachedCredentialSchema.safeParse(json);
7099
- return parsed.success ? parsed.data : void 0;
7100
6955
  }
7101
- function requireKeychain() {
7102
- if (process.platform !== "darwin") {
7103
- throw new Error("the keychain credential store needs macOS; use --credential-cache-store file");
6956
+
6957
+ // src/headroom/ensure.ts
6958
+ var HEADROOM_START_TIMEOUT_MS = 12e4;
6959
+ var HEADROOM_LAUNCHER_POLL_MS = 250;
6960
+ var HeadroomStartError = class extends CliError {
6961
+ constructor(message) {
6962
+ super(message);
6963
+ this.name = "HeadroomStartError";
7104
6964
  }
7105
- }
7106
- function security(args, stdin) {
7107
- const result = spawnSync("security", args, { encoding: "utf8", ...stdin === void 0 ? {} : { input: stdin }, stdio: ["pipe", "pipe", "ignore"] });
7108
- return { status: result.status, stdout: result.stdout };
7109
- }
7110
- function createRealCredentialCache(root) {
7111
- const dir = path19.join(root, "credential-cache");
7112
- const filePath = (owner) => path19.join(dir, cacheFileNameFor(owner));
7113
- return {
7114
- read(store, owner) {
7115
- if (store === "file") {
7116
- try {
7117
- return parseEntry(fs5.readFileSync(filePath(owner), "utf8"));
7118
- } catch {
7119
- return void 0;
6965
+ };
6966
+ function ensureHeadroom(params) {
6967
+ const { paths, ports } = params;
6968
+ const deadline = ports.now() + HEADROOM_START_TIMEOUT_MS;
6969
+ let spawnedSupervisor = false;
6970
+ let awaitingAllowlist = false;
6971
+ for (; ; ) {
6972
+ const state = readHeadroomState(ports.fs, paths.headroomStateFile);
6973
+ if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
6974
+ const daemonUp = state.socketPath !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
6975
+ if (daemonUp && state.socketPath !== void 0) {
6976
+ const required = params.requiredAllowlistHash?.();
6977
+ if (required === void 0 || state.allowlistHash === required) {
6978
+ writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
6979
+ return { socketPath: state.socketPath };
7120
6980
  }
6981
+ const holders = sessionsForSupervisor(ports.fs, paths.headroomSessionsDir, state.supervisorPid).filter((session) => ports.isRunning(session.pid));
6982
+ if (holders.length > 0) {
6983
+ throw new HeadroomStartError(
6984
+ `the running headroom daemon was started before this launch's front door address and would refuse every request this launch sends through it. It restarts once the launches using it end (pid ${holders.map((session) => String(session.pid)).join(", ")}); end them and launch again, or launch with --no-headroom.`
6985
+ );
6986
+ }
6987
+ awaitingAllowlist = true;
7121
6988
  }
7122
- requireKeychain();
7123
- const result = security(["find-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner, "-w"]);
7124
- return result.status === 0 ? parseEntry(Buffer.from(result.stdout.trim(), "base64").toString("utf8")) : void 0;
7125
- },
7126
- write(store, owner, entry) {
7127
- const serialised = JSON.stringify(entry);
7128
- if (store === "file") {
7129
- fs5.mkdirSync(dir, { recursive: true, mode: CACHE_DIR_MODE });
7130
- fs5.chmodSync(dir, CACHE_DIR_MODE);
7131
- const target = filePath(owner);
7132
- const temp = `${target}.${String(process.pid)}.tmp`;
7133
- fs5.writeFileSync(temp, serialised, { mode: CACHE_FILE_MODE });
7134
- fs5.renameSync(temp, target);
7135
- return;
7136
- }
7137
- requireKeychain();
7138
- const encoded = Buffer.from(serialised, "utf8").toString("base64");
7139
- const result = security(["-i"], `add-generic-password -U -s ${KEYCHAIN_CACHE_SERVICE} -a ${JSON.stringify(owner)} -w ${encoded}
7140
- `);
7141
- if (result.status !== 0) {
7142
- throw new Error(`could not write the keychain credential cache for ${owner}`);
6989
+ if (state.lastError !== void 0) {
6990
+ throw new HeadroomStartError(
6991
+ `the headroom daemon reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.headroomLogPath})`
6992
+ );
7143
6993
  }
7144
- },
7145
- remove(store, owner) {
7146
- if (store === "file") {
7147
- fs5.rmSync(filePath(owner), { force: true });
7148
- return;
6994
+ } else if (!spawnedSupervisor) {
6995
+ ports.fs.mkdirp(paths.headroomDir);
6996
+ const created = ports.fs.writeFileExclusive(
6997
+ paths.headroomLockFile,
6998
+ `${JSON.stringify({ pid: params.launcherPid, at: ports.now() })}
6999
+ `
7000
+ );
7001
+ if (created) {
7002
+ ports.spawnSupervisor(paths);
7003
+ spawnedSupervisor = true;
7004
+ } else {
7005
+ const lock = readStartLock(ports.fs, paths.headroomLockFile);
7006
+ if (lock === void 0 || !ports.isRunning(lock.pid)) {
7007
+ ports.fs.removeRecursive(paths.headroomLockFile);
7008
+ continue;
7009
+ }
7149
7010
  }
7150
- requireKeychain();
7151
- security(["delete-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner]);
7152
7011
  }
7012
+ if (ports.now() >= deadline) {
7013
+ const lastError = state?.lastError;
7014
+ throw new HeadroomStartError(
7015
+ `the headroom daemon did not become ready within ${String(HEADROOM_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + (awaitingAllowlist ? " (the running daemon's allowlist does not admit this launch's front door, and it did not restart)" : "") + `. Daemon log: ${paths.headroomLogPath}`
7016
+ );
7017
+ }
7018
+ ports.sleep(HEADROOM_LAUNCHER_POLL_MS);
7019
+ }
7020
+ }
7021
+
7022
+ // src/headroom/settings.ts
7023
+ import { createHash as createHash4 } from "node:crypto";
7024
+ function settingsOf(config) {
7025
+ return {
7026
+ ...config.mode === void 0 ? {} : { mode: config.mode },
7027
+ ...config.targetRatio === void 0 ? {} : { targetRatio: config.targetRatio },
7028
+ ...config.ccr === void 0 ? {} : { ccr: config.ccr },
7029
+ ...config.rolloutChannel === void 0 ? {} : { rolloutChannel: config.rolloutChannel },
7030
+ ...config.interceptToolResults === void 0 ? {} : { interceptToolResults: config.interceptToolResults },
7031
+ ...config.readMaturation === void 0 ? {} : { readMaturation: config.readMaturation }
7153
7032
  };
7154
7033
  }
7155
- function realCredentialCacheEnv(paths) {
7156
- return { port: createRealCredentialCache(paths.root), platform: process.platform, now: () => Date.now() };
7034
+ function settingsArgs(settings) {
7035
+ return [
7036
+ ...settings.mode === void 0 ? [] : ["--mode", settings.mode],
7037
+ ...settings.targetRatio === void 0 ? [] : ["--target-ratio", String(settings.targetRatio)],
7038
+ ...settings.ccr === "lossless" ? ["--lossless"] : [],
7039
+ ...settings.ccr === "none" ? ["--no-ccr"] : [],
7040
+ ...settings.interceptToolResults === true ? ["--intercept-tool-results"] : [],
7041
+ ...settings.readMaturation === true ? ["--read-maturation"] : []
7042
+ ];
7043
+ }
7044
+ function settingsEnv(settings) {
7045
+ return settings.rolloutChannel === void 0 ? {} : { HEADROOM_ROLLOUT_CHANNEL: settings.rolloutChannel };
7046
+ }
7047
+ function hashSettings(settings) {
7048
+ return createHash4("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
7157
7049
  }
7158
7050
 
7159
- // src/providersStore.ts
7160
- import fs6 from "node:fs";
7161
- import path20 from "node:path";
7162
-
7163
- // src/credential.ts
7164
- var CREDENTIAL_UNAVAILABLE_EXIT = 64;
7165
- var CREDENTIAL_COMMAND_TIMEOUT_MS = 1e4;
7166
- var CREDENTIAL_INTERACTIVE_TIMEOUT_MS = 12e4;
7167
- var GROUP_OTHER_PERMISSION_BITS = 63;
7168
- var PERMISSION_BITS = 511;
7051
+ // src/headroom/socket.ts
7052
+ import path18 from "node:path";
7053
+ var GROUP_OR_OTHER_BITS = 63;
7169
7054
  var OCTAL_RADIX = 8;
7170
- function commandArgv(source) {
7171
- if ("command" in source) {
7172
- return source.command;
7173
- }
7174
- if ("op" in source) {
7175
- return ["op", "read", source.op];
7176
- }
7177
- const account = source.keychain.account;
7178
- return ["security", "find-generic-password", "-s", source.keychain.service, ...account === void 0 ? [] : ["-a", account], "-w"];
7055
+ var MODE_DIGITS = 4;
7056
+ var HEADROOM_SOCKET_SUFFIX = ".sock";
7057
+ var HEADROOM_SOCKET_NAME = /^(\d+)\.sock$/;
7058
+ var UNIX_SOCKET_PATH_MAX_BYTES = 103;
7059
+ var PLATFORMS_WITHOUT_UNIX_SOCKETS = ["win32"];
7060
+ function headroomSocketPath(paths, supervisorPid) {
7061
+ return path18.join(paths.headroomSocketDir, `${String(supervisorPid)}${HEADROOM_SOCKET_SUFFIX}`);
7179
7062
  }
7180
- function isInteractiveSource(source, env) {
7181
- if ("command" in source || "keychain" in source) {
7182
- return source.interactive ?? false;
7183
- }
7184
- if ("op" in source) {
7185
- return source.interactive ?? (env.OP_SERVICE_ACCOUNT_TOKEN ?? "") === "";
7186
- }
7187
- return false;
7063
+ function platformRefusal(platform) {
7064
+ return PLATFORMS_WITHOUT_UNIX_SOCKETS.includes(platform) ? `headroom routing needs a unix domain socket, which headroom cannot serve on ${platform}; agent-shim never falls back to a TCP port for this hop` : void 0;
7188
7065
  }
7189
- function summariseSource(source) {
7190
- if ("env" in source) {
7191
- return { kind: "env", variable: source.env };
7066
+ function ownerOnlyRefusal(label, target, stat, expected, uid) {
7067
+ if (stat === void 0) {
7068
+ return `the headroom ${label} ${target} does not exist`;
7192
7069
  }
7193
- if ("file" in source) {
7194
- return { kind: "file", path: source.file };
7070
+ if (stat.kind === "symlink") {
7071
+ return `the headroom ${label} ${target} is a symlink; it must be the real ${expected === "dir" ? "directory" : "socket"}`;
7195
7072
  }
7196
- if ("command" in source) {
7197
- return { kind: "command", program: source.command[0] };
7073
+ if (stat.kind !== expected) {
7074
+ return `the headroom ${label} ${target} is not a ${expected === "dir" ? "directory" : "socket"}`;
7198
7075
  }
7199
- if ("op" in source) {
7200
- return { kind: "op", reference: source.op };
7076
+ if (stat.uid !== uid) {
7077
+ return `the headroom ${label} ${target} is owned by uid ${String(stat.uid)}, not by this user (uid ${String(uid)})`;
7201
7078
  }
7202
- if ("keychain" in source) {
7203
- return {
7204
- kind: "keychain",
7205
- service: source.keychain.service,
7206
- ...source.keychain.account === void 0 ? {} : { account: source.keychain.account }
7207
- };
7079
+ if ((stat.mode & GROUP_OR_OTHER_BITS) !== 0) {
7080
+ return `the headroom ${label} ${target} has mode ${stat.mode.toString(OCTAL_RADIX).padStart(MODE_DIGITS, "0")}, which lets other users reach it; it must be accessible to its owner only`;
7208
7081
  }
7209
- return { kind: "literal" };
7210
- }
7211
- function summariseCredential(credential) {
7212
- return {
7213
- target: credential.target ?? "bearer",
7214
- sources: credential.sources.map(summariseSource),
7215
- ...credential.cache === void 0 ? {} : { cache: credential.cache }
7216
- };
7082
+ return void 0;
7217
7083
  }
7218
- function formatSourceSummary(summary) {
7219
- switch (summary.kind) {
7220
- case "env":
7221
- return `env ${summary.variable}`;
7222
- case "file":
7223
- return `file ${summary.path}`;
7224
- case "command":
7225
- return `command ${summary.program}`;
7226
- case "op":
7227
- return `op ${summary.reference}`;
7228
- case "keychain":
7229
- return `keychain ${summary.service}${summary.account === void 0 ? "" : ` (account ${summary.account})`}`;
7230
- case "literal":
7231
- return "literal (non-secret placeholder)";
7232
- default:
7233
- return summary;
7084
+ function verifyHeadroomSocket(socketPath, ports) {
7085
+ const unsupported = platformRefusal(ports.platform);
7086
+ if (unsupported !== void 0) {
7087
+ return { refused: unsupported };
7234
7088
  }
7089
+ const uid = ports.currentUid();
7090
+ const directory = path18.dirname(socketPath);
7091
+ const refusal = ownerOnlyRefusal("socket directory", directory, ports.lstat(directory), "dir", uid) ?? ownerOnlyRefusal("socket", socketPath, ports.lstat(socketPath), "socket", uid);
7092
+ return refusal === void 0 ? { socketPath } : { refused: refusal };
7235
7093
  }
7236
- function describeSource(source) {
7237
- return formatSourceSummary(summariseSource(source));
7238
- }
7239
- function formatCredentialSummary(summary) {
7240
- const cache = summary.cache === void 0 ? "" : `, cached ${summary.cache.ttl === void 0 ? "with no expiry" : `for ${summary.cache.ttl}`}${summary.cache.store === void 0 ? "" : ` in the ${summary.cache.store} store`}`;
7241
- return `${summary.target} from ${summary.sources.map(formatSourceSummary).join(", then ")}${cache}`;
7242
- }
7243
- function describeCredential(credential) {
7244
- return formatCredentialSummary(summariseCredential(credential));
7245
- }
7246
- function trySource(source, params) {
7247
- const none = (reason, warn = false) => ({ ok: false, reason, warn });
7248
- if ("env" in source) {
7249
- const value = params.env[source.env];
7250
- return value === void 0 || value === "" ? none("is unset or empty") : { ok: true, token: value };
7251
- }
7252
- if ("literal" in source) {
7253
- return { ok: true, token: source.literal };
7254
- }
7255
- if ("file" in source) {
7256
- const read = params.port.readSecretFile(source.file);
7257
- if (!read.found) {
7258
- return none("does not exist");
7259
- }
7260
- if (read.mode !== void 0 && (read.mode & GROUP_OTHER_PERMISSION_BITS) !== 0) {
7261
- return none(
7262
- `is readable or writable by group or others (mode ${(read.mode & PERMISSION_BITS).toString(OCTAL_RADIX)}); run \`chmod 600 ${source.file}\``,
7263
- true
7264
- );
7265
- }
7266
- const token2 = read.content.trim();
7267
- return token2 === "" ? none("is empty") : { ok: true, token: token2 };
7268
- }
7269
- const argv = commandArgv(source);
7270
- const interactive = isInteractiveSource(source, params.env);
7271
- if (interactive && !params.port.personPresent()) {
7272
- return none("needs a person to approve it, but there is no terminal or desktop session");
7094
+ function prepareHeadroomSocket(fs12, ports, paths, supervisorPid) {
7095
+ const unsupported = platformRefusal(ports.platform);
7096
+ if (unsupported !== void 0) {
7097
+ return { refused: unsupported };
7273
7098
  }
7274
- const timeoutMs = source.timeoutMs ?? (interactive ? CREDENTIAL_INTERACTIVE_TIMEOUT_MS : CREDENTIAL_COMMAND_TIMEOUT_MS);
7275
- const result = params.port.runCommand(argv, { timeoutMs, interactive });
7276
- if (result.timedOut) {
7277
- return none(`timed out after ${String(timeoutMs)}ms`);
7099
+ const socketPath = headroomSocketPath(paths, supervisorPid);
7100
+ const length = Buffer.byteLength(socketPath, "utf8");
7101
+ if (length > UNIX_SOCKET_PATH_MAX_BYTES) {
7102
+ return {
7103
+ refused: `the headroom socket path ${socketPath} is ${String(length)} bytes, longer than the ${String(UNIX_SOCKET_PATH_MAX_BYTES)} a unix socket path can hold; set AGENT_SHIM_HOME to a shorter directory`
7104
+ };
7278
7105
  }
7279
- if (result.status !== 0) {
7280
- const stderr = result.stderr.trim();
7281
- const outcome = result.status === null ? "could not be run or was killed by a signal" : `exited with status ${String(result.status)}`;
7282
- return none(`${outcome}${stderr === "" ? "" : `: ${stderr}`}`);
7106
+ if (ports.lstat(paths.headroomSocketDir)?.kind === "symlink") {
7107
+ return { refused: `the headroom socket directory ${paths.headroomSocketDir} is a symlink; it must be the real directory` };
7283
7108
  }
7284
- const token = result.stdout.trim();
7285
- return token === "" ? none("printed no token") : { ok: true, token };
7286
- }
7287
- function warmCommandFor(subject) {
7288
- const [kind, name] = subject.split(" ");
7289
- return kind === "provider" ? `agent-shim credential warm --provider ${name ?? ""}` : `agent-shim credential warm ${name ?? ""}`;
7109
+ fs12.mkdirPrivate(paths.headroomSocketDir);
7110
+ const refusal = ownerOnlyRefusal("socket directory", paths.headroomSocketDir, ports.lstat(paths.headroomSocketDir), "dir", ports.currentUid());
7111
+ return refusal === void 0 ? { socketPath } : { refused: refusal };
7290
7112
  }
7291
- function resolveCredential(params) {
7292
- const target = params.credential.target ?? "bearer";
7293
- const cacheBlock = params.credential.cache;
7294
- const cache = cacheBlock === void 0 ? void 0 : params.port.cache;
7295
- const store = cacheBlock === void 0 || cache === void 0 ? void 0 : effectiveStore(cacheBlock, cache.platform);
7296
- if (cacheBlock !== void 0 && cache !== void 0 && store !== void 0 && params.refresh !== true) {
7297
- const entry = cache.port.read(store, params.subject);
7298
- if (entry !== void 0 && isFresh(entry, cacheBlock, cache.now())) {
7299
- return { ok: true, credential: { target, token: entry.token, source: entry.source, warnings: [], cachedAt: entry.fetchedAt } };
7113
+ function pruneStaleHeadroomSockets(fs12, paths, isRunning) {
7114
+ const removed = [];
7115
+ for (const name of fs12.readdir(paths.headroomSocketDir)) {
7116
+ const pid = HEADROOM_SOCKET_NAME.exec(name)?.[1];
7117
+ if (pid === void 0 || isRunning(Number.parseInt(pid, 10))) {
7118
+ continue;
7300
7119
  }
7120
+ fs12.removeRecursive(path18.join(paths.headroomSocketDir, name));
7121
+ removed.push(name);
7301
7122
  }
7302
- const attempts = [];
7303
- const warnings = [];
7304
- for (const source of params.credential.sources) {
7305
- const outcome = trySource(source, params);
7306
- if (outcome.ok) {
7307
- if (cache !== void 0 && store !== void 0) {
7308
- cache.port.write(store, params.subject, { token: outcome.token, fetchedAt: cache.now(), source });
7309
- }
7310
- return { ok: true, credential: { target, token: outcome.token, source, warnings } };
7311
- }
7312
- const line = `${describeSource(source)} ${outcome.reason}`;
7313
- attempts.push(line);
7314
- if (outcome.warn) {
7315
- warnings.push(`agent-shim: ${params.subject}: skipped ${line}`);
7316
- }
7123
+ return removed;
7124
+ }
7125
+
7126
+ // src/headroom/source.ts
7127
+ import { z as z10 } from "zod";
7128
+ var GIT_SPEC_RE = /git\+[^@\s]+(?:@([^\s#]+))?/;
7129
+ var COMMIT_SHA_RE = /^[0-9a-f]{40}$/i;
7130
+ var DirectUrlSchema = z10.object({ vcs_info: z10.object({ commit_id: z10.string() }) });
7131
+ function isMovingGitSource(spec) {
7132
+ const match = GIT_SPEC_RE.exec(spec);
7133
+ if (match === null) {
7134
+ return false;
7317
7135
  }
7318
- const hint = cacheBlock === void 0 ? "" : `; fill the cache from a terminal with \`${warmCommandFor(params.subject)}\``;
7319
- return { ok: false, message: `agent-shim: ${params.subject} has no usable credential: ${attempts.join("; ")}${hint}` };
7136
+ const ref = match[1];
7137
+ return ref === void 0 || !COMMIT_SHA_RE.test(ref);
7320
7138
  }
7321
- function credentialVariables(target, token) {
7139
+ function pinnedGitCommit(spec) {
7140
+ const ref = GIT_SPEC_RE.exec(spec)?.[1];
7141
+ return ref !== void 0 && COMMIT_SHA_RE.test(ref) ? ref.toLowerCase() : void 0;
7142
+ }
7143
+
7144
+ // src/headroom/supervisor.ts
7145
+ function resolveSupervisorConfig(configured) {
7322
7146
  return {
7323
- ANTHROPIC_AUTH_TOKEN: target === "bearer" ? token : void 0,
7324
- ANTHROPIC_API_KEY: target === "apiKey" ? token : void 0,
7325
- CLAUDE_CODE_OAUTH_TOKEN: target === "oauthToken" ? token : void 0
7147
+ source: configured.source ?? HEADROOM_DEFAULT_SOURCE,
7148
+ idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES,
7149
+ settings: settingsOf(configured)
7326
7150
  };
7327
7151
  }
7328
-
7329
- // src/providersStore.ts
7330
- var ProviderNotFoundError = class extends CliError {
7331
- constructor(name) {
7332
- super(`No provider named "${name}". Run \`agent-shim provider add ${name}\` first.`);
7333
- this.name = name;
7334
- this.name = "ProviderNotFoundError";
7152
+ var HEADROOM_BACKOFF_BASE_MS = 500;
7153
+ var HEADROOM_BACKOFF_CAP_MS = 3e4;
7154
+ var HEADROOM_START_RETRY_BUDGET = 5;
7155
+ var HEADROOM_READY_TIMEOUT_MS = 15e3;
7156
+ var HEADROOM_POLL_MS = 1e3;
7157
+ var MS_PER_MINUTE2 = 6e4;
7158
+ function backoffForAttempt(attempt) {
7159
+ return Math.min(HEADROOM_BACKOFF_CAP_MS, HEADROOM_BACKOFF_BASE_MS * 2 ** (attempt - 1));
7160
+ }
7161
+ function versionSatisfies(version, source) {
7162
+ if (version === void 0) {
7163
+ return false;
7335
7164
  }
7336
- name;
7337
- };
7338
- var ProviderAlreadyExistsError = class extends CliError {
7339
- constructor(providerName) {
7340
- super(`A provider named "${providerName}" already exists.`);
7341
- this.providerName = providerName;
7342
- this.name = "ProviderAlreadyExistsError";
7165
+ const specifier = /==|>=|<=|!=|>|<|~=/.exec(source);
7166
+ if (specifier === null) {
7167
+ return true;
7343
7168
  }
7344
- providerName;
7345
- };
7346
- var InvalidProviderNameError = class extends CliError {
7347
- constructor(attemptedName) {
7348
- super(
7349
- `"${attemptedName}" is not a valid provider name. Provider names must start with a letter or number and may then contain letters, numbers, dots, hyphens, underscores, and at signs.`
7350
- );
7351
- this.attemptedName = attemptedName;
7352
- this.name = "InvalidProviderNameError";
7169
+ const installed = parseVersion(version);
7170
+ const wanted = parseVersion(source.slice(specifier.index + specifier[0].length));
7171
+ if (installed === void 0 || wanted === void 0) {
7172
+ return true;
7353
7173
  }
7354
- attemptedName;
7355
- };
7356
- var PROVIDER_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._@-]*$/;
7357
- function providerJsonPath(paths, name) {
7358
- return path20.join(paths.providersDir, `${name}.json`);
7359
- }
7360
- function providerExists(paths, name) {
7361
- return fs6.existsSync(providerJsonPath(paths, name));
7362
- }
7363
- var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
7364
- var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
7365
- var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
7366
- function isRecord4(value) {
7367
- return typeof value === "object" && value !== null && !Array.isArray(value);
7368
- }
7369
- function legacyProviderConversion(raw) {
7370
- if (!isRecord4(raw) || "credential" in raw) {
7371
- return void 0;
7174
+ const cmp = compareVersions2(installed, wanted);
7175
+ switch (specifier[0]) {
7176
+ case "==":
7177
+ return cmp === 0;
7178
+ case "!=":
7179
+ return cmp !== 0;
7180
+ case ">=":
7181
+ return cmp >= 0;
7182
+ case "<=":
7183
+ return cmp <= 0;
7184
+ case ">":
7185
+ return cmp > 0;
7186
+ case "<":
7187
+ return cmp < 0;
7188
+ case "~=":
7189
+ return compareVersions2(installed.slice(0, Math.max(0, wanted.length - 1)), wanted.slice(0, Math.max(0, wanted.length - 1))) === 0 && cmp >= 0;
7190
+ default:
7191
+ return true;
7372
7192
  }
7373
- const env = isRecord4(raw.env) ? raw.env : void 0;
7374
- const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
7375
- const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
7376
- if (fields.length === 0) {
7193
+ }
7194
+ function parseVersion(input) {
7195
+ const match = /(\d+)(\.\d+)*/.exec(input);
7196
+ if (match === null) {
7377
7197
  return void 0;
7378
7198
  }
7379
- const sources = [];
7380
- if (typeof raw.tokenEnv === "string") {
7381
- sources.push({ env: raw.tokenEnv });
7382
- }
7383
- if (Array.isArray(raw.tokenCommand)) {
7384
- sources.push({ command: raw.tokenCommand });
7199
+ return match[0].split(".").map((part) => Number.parseInt(part, 10));
7200
+ }
7201
+ function compareVersions2(a, b) {
7202
+ for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
7203
+ const left = a[index] ?? 0;
7204
+ const right = b[index] ?? 0;
7205
+ if (left !== right) {
7206
+ return left - right;
7207
+ }
7385
7208
  }
7386
- if (typeof env?.ANTHROPIC_AUTH_TOKEN === "string" && env.ANTHROPIC_AUTH_TOKEN !== "") {
7387
- sources.push({ literal: LEGACY_LITERAL_PLACEHOLDER });
7209
+ return 0;
7210
+ }
7211
+ var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
7212
+ async function runSupervisor(initialConfig, ports, options = {}) {
7213
+ const { fs: fs12, paths } = ports;
7214
+ let config = initialConfig;
7215
+ const previousState = readHeadroomState(fs12, paths.headroomStateFile);
7216
+ if (previousState?.supervisorPid !== void 0 && previousState.supervisorPid !== ports.ownPid && ports.isRunning(previousState.supervisorPid)) {
7217
+ ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: supervisor ${String(previousState.supervisorPid)} already owns the daemon; exiting`);
7218
+ return 0;
7388
7219
  }
7389
- const remainingEnv = env === void 0 ? void 0 : Object.fromEntries(Object.entries(env).filter(([key]) => !LEGACY_ENV_CREDENTIAL_KEYS.includes(key)));
7390
- const replacedKeys = [...LEGACY_PROVIDER_FIELDS, "env"];
7391
- const rest = Object.fromEntries(Object.entries(raw).filter(([key]) => !replacedKeys.includes(key)));
7392
- return {
7393
- fields,
7394
- replacement: {
7395
- ...rest,
7396
- credential: { sources, ...raw.authScheme === "apiKey" ? { target: "apiKey" } : {} },
7397
- ...remainingEnv === void 0 || Object.keys(remainingEnv).length === 0 ? {} : { env: remainingEnv }
7220
+ let installedSource = previousState?.installedSource;
7221
+ const ownsState = () => {
7222
+ const owner = readHeadroomState(fs12, paths.headroomStateFile)?.supervisorPid;
7223
+ return owner === void 0 || owner === ports.ownPid || !ports.isRunning(owner);
7224
+ };
7225
+ const writeOwnState = (state) => {
7226
+ if (!ownsState()) {
7227
+ return false;
7398
7228
  }
7229
+ writeHeadroomState(fs12, paths.headroomStateFile, state);
7230
+ return true;
7399
7231
  };
7400
- }
7401
- var LegacyProviderFileError = class extends CliError {
7402
- constructor(filePath, conversion) {
7403
- super(
7404
- `${filePath} uses ${conversion.fields.join(", ")}, which a credential block replaced. Rewrite it as:
7405
- ` + JSON.stringify(conversion.replacement, null, 2)
7406
- );
7407
- this.filePath = filePath;
7408
- this.conversion = conversion;
7409
- this.name = "LegacyProviderFileError";
7232
+ const fail = (message) => {
7233
+ if (writeOwnState({ lastError: message })) {
7234
+ fs12.removeRecursive(paths.headroomLockFile);
7235
+ }
7236
+ ports.log(`agent-shim headroom supervisor: ${message}`);
7237
+ return 1;
7238
+ };
7239
+ const ensureInstalled = () => {
7240
+ const version2 = ports.headroomVersion();
7241
+ const versionOk = version2 !== void 0 && versionSatisfies(version2, config.source);
7242
+ const pinned = pinnedGitCommit(config.source);
7243
+ const commitOk = pinned === void 0 || ports.installedCommit() === pinned;
7244
+ if (versionOk && commitOk && (installedSource === void 0 || installedSource === config.source)) {
7245
+ installedSource = config.source;
7246
+ return true;
7247
+ }
7248
+ ports.log(`agent-shim headroom supervisor: installing headroom from ${config.source}`);
7249
+ const result = ports.install(config.source);
7250
+ if (!result.ok) {
7251
+ return false;
7252
+ }
7253
+ installedSource = config.source;
7254
+ return true;
7255
+ };
7256
+ const socket = prepareHeadroomSocket(fs12, ports.socketTrust, paths, ports.ownPid);
7257
+ if (socket.refused !== void 0) {
7258
+ return fail(socket.refused);
7410
7259
  }
7411
- filePath;
7412
- conversion;
7413
- };
7414
- function parseProviderFile(filePath, raw) {
7415
- const conversion = legacyProviderConversion(raw);
7416
- if (conversion !== void 0) {
7417
- throw new LegacyProviderFileError(filePath, conversion);
7260
+ const { socketPath } = socket;
7261
+ pruneStaleHeadroomSockets(fs12, paths, ports.isRunning);
7262
+ if (!ensureInstalled()) {
7263
+ return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
7418
7264
  }
7419
- const parsed = ProviderSchema.safeParse(raw);
7420
- if (!parsed.success) {
7421
- throw new ConfigValidationError(filePath, parsed.error.issues);
7265
+ const version = ports.headroomVersion() ?? "unknown";
7266
+ writeOwnState({ supervisorPid: ports.ownPid, version, installedSource });
7267
+ ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: managing headroom on allowlist [${allowlistOf(ports).join(", ")}]`);
7268
+ const orphanPid = previousState?.headroomPid;
7269
+ if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
7270
+ ports.log(`agent-shim headroom supervisor: stopping daemon pid ${String(orphanPid)} left behind by the previous supervisor`);
7271
+ ports.stopProcess(orphanPid);
7272
+ }
7273
+ let headroomPid;
7274
+ let runningHash;
7275
+ let runningSettingsHash;
7276
+ let consecutiveFailures = 0;
7277
+ let idleSince;
7278
+ let superseded = false;
7279
+ let ticks = 0;
7280
+ for (; ; ) {
7281
+ if (options.tickLimit !== void 0 && ticks >= options.tickLimit) {
7282
+ return HEADROOM_SUPERVISOR_STILL_RUNNING;
7283
+ }
7284
+ ticks += 1;
7285
+ config = ports.readConfig();
7286
+ const settingsHash = hashSettings(config.settings);
7287
+ const allowlist = allowlistOf(ports);
7288
+ const allowlistHash = hashAllowlist(allowlist);
7289
+ pruneDeadSessions(fs12, paths.headroomSessionsDir, ports.isRunning);
7290
+ const ownsNow = ownsState();
7291
+ if (!ownsNow && !superseded) {
7292
+ ports.log("agent-shim headroom supervisor: another supervisor now owns the state file; serving this daemon's own sessions until they end");
7293
+ }
7294
+ superseded = !ownsNow;
7295
+ const crashed = headroomPid !== void 0 && !ports.isRunning(headroomPid);
7296
+ if (headroomPid === void 0 || crashed) {
7297
+ if (crashed) {
7298
+ ports.log(`agent-shim headroom supervisor: headroom pid ${String(headroomPid)} died`);
7299
+ headroomPid = void 0;
7300
+ runningHash = void 0;
7301
+ runningSettingsHash = void 0;
7302
+ writeOwnState({
7303
+ supervisorPid: ports.ownPid,
7304
+ version,
7305
+ installedSource
7306
+ });
7307
+ }
7308
+ if (consecutiveFailures >= HEADROOM_START_RETRY_BUDGET) {
7309
+ return fail(`headroom failed to become ready ${String(consecutiveFailures)} times in a row; giving up`);
7310
+ }
7311
+ if (installedSource !== config.source && !ensureInstalled()) {
7312
+ return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
7313
+ }
7314
+ const pid = ports.spawnHeadroom(socketPath, allowlist, config.settings);
7315
+ const ready = await waitUntilReady(ports, socketPath);
7316
+ if (ready) {
7317
+ consecutiveFailures = 0;
7318
+ headroomPid = pid;
7319
+ runningHash = allowlistHash;
7320
+ runningSettingsHash = settingsHash;
7321
+ const recorded = writeOwnState({
7322
+ supervisorPid: ports.ownPid,
7323
+ headroomPid: pid,
7324
+ socketPath,
7325
+ version,
7326
+ allowlistHash,
7327
+ settingsHash,
7328
+ installedSource: config.source
7329
+ });
7330
+ if (recorded) {
7331
+ fs12.removeRecursive(paths.headroomLockFile);
7332
+ }
7333
+ idleSince = void 0;
7334
+ ports.log(`agent-shim headroom supervisor: headroom pid ${String(pid)} ready on unix socket ${socketPath}`);
7335
+ } else {
7336
+ ports.stopProcess(pid);
7337
+ consecutiveFailures += 1;
7338
+ ports.log(
7339
+ `agent-shim headroom supervisor: headroom did not become ready on unix socket ${socketPath} (attempt ${String(consecutiveFailures)} of ${String(HEADROOM_START_RETRY_BUDGET)})`
7340
+ );
7341
+ await ports.sleep(backoffForAttempt(consecutiveFailures));
7342
+ continue;
7343
+ }
7344
+ } else {
7345
+ if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
7346
+ if (sessionsForSupervisor(fs12, paths.headroomSessionsDir, ports.ownPid).length === 0) {
7347
+ ports.log("agent-shim headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
7348
+ ports.stopProcess(headroomPid);
7349
+ headroomPid = void 0;
7350
+ runningHash = void 0;
7351
+ runningSettingsHash = void 0;
7352
+ continue;
7353
+ }
7354
+ }
7355
+ const sessions = sessionsForSupervisor(fs12, paths.headroomSessionsDir, ports.ownPid);
7356
+ if (sessions.length === 0) {
7357
+ idleSince ??= ports.now();
7358
+ const idleMs = ports.now() - idleSince;
7359
+ if (idleMs >= config.idleShutdownMinutes * MS_PER_MINUTE2) {
7360
+ ports.log(
7361
+ `agent-shim headroom supervisor: no sessions for ${String(config.idleShutdownMinutes)} minute(s); stopping headroom and exiting`
7362
+ );
7363
+ ports.stopProcess(headroomPid);
7364
+ writeOwnState({ version });
7365
+ return 0;
7366
+ }
7367
+ } else {
7368
+ idleSince = void 0;
7369
+ }
7370
+ }
7371
+ await ports.sleep(HEADROOM_POLL_MS);
7422
7372
  }
7423
- return parsed.data;
7424
7373
  }
7425
- function readProvider(paths, name) {
7426
- const filePath = providerJsonPath(paths, name);
7427
- if (!fs6.existsSync(filePath)) {
7428
- return void 0;
7374
+ function currentHeadroomAllowlist(fs12, paths) {
7375
+ const providers = readAllProviders(fs12, paths.providersDir).map((entry) => entry.provider);
7376
+ return headroomAllowlist(headroomUpstreams(providers, frontDoorOrigin(readFrontDoorState(fs12, paths.frontdoorStateFile))));
7377
+ }
7378
+ function allowlistOf(ports) {
7379
+ return currentHeadroomAllowlist(ports.fs, ports.paths);
7380
+ }
7381
+ async function waitUntilReady(ports, socketPath) {
7382
+ const deadline = ports.now() + HEADROOM_READY_TIMEOUT_MS;
7383
+ for (; ; ) {
7384
+ if (await ports.ready(socketPath)) {
7385
+ return true;
7386
+ }
7387
+ if (ports.now() >= deadline) {
7388
+ return false;
7389
+ }
7390
+ await ports.sleep(HEADROOM_POLL_MS);
7429
7391
  }
7430
- const raw = JSON.parse(fs6.readFileSync(filePath, "utf8"));
7431
- return parseProviderFile(filePath, raw);
7432
7392
  }
7433
- function loadProvider(providersDir, name, port) {
7434
- const filePath = path20.join(providersDir, `${name}.json`);
7435
- const raw = port.readConfigFile(filePath);
7436
- return raw === void 0 ? void 0 : parseProviderFile(filePath, raw);
7393
+
7394
+ // src/realPorts.ts
7395
+ function isEnoent3(error) {
7396
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
7437
7397
  }
7438
- function listProviders(paths) {
7439
- const names = listProviderNames(
7440
- (dir) => fs6.readdirSync(dir),
7441
- paths.providersDir
7442
- );
7443
- const result = [];
7444
- for (const name of names) {
7445
- let provider;
7398
+ var realFsPort = {
7399
+ readFileUtf8(filePath) {
7446
7400
  try {
7447
- provider = readProvider(paths, name);
7401
+ return fs4.readFileSync(filePath, "utf8");
7448
7402
  } catch (error) {
7449
- if (!(error instanceof ConfigValidationError || error instanceof LegacyProviderFileError)) {
7450
- throw error;
7403
+ if (isEnoent3(error)) {
7404
+ return void 0;
7451
7405
  }
7452
- console.error(`agent-shim: providers/${name}.json is invalid and was skipped: ${error.message}`);
7453
- continue;
7406
+ throw error;
7454
7407
  }
7455
- if (provider !== void 0) {
7456
- result.push({ name, provider });
7408
+ },
7409
+ readConfigFile: cosmiconfigReader(),
7410
+ readdir(dirPath) {
7411
+ try {
7412
+ return fs4.readdirSync(dirPath);
7413
+ } catch (error) {
7414
+ if (isEnoent3(error)) {
7415
+ return [];
7416
+ }
7417
+ throw error;
7457
7418
  }
7458
7419
  }
7459
- return result;
7420
+ };
7421
+ function isErrorWithCode(error, code) {
7422
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
7460
7423
  }
7461
- function listProviderNames(readdir, providersDir) {
7462
- let entries;
7463
- try {
7464
- entries = readdir(providersDir);
7465
- } catch {
7466
- return [];
7424
+ var OWNER_ONLY_DIR_MODE = 448;
7425
+ var OWNER_ONLY_FILE_MODE = 384;
7426
+ var PERMISSION_BITS = 511;
7427
+ var realFarmFs = {
7428
+ lstat(filePath) {
7429
+ let stat;
7430
+ try {
7431
+ stat = fs4.lstatSync(filePath);
7432
+ } catch (error) {
7433
+ if (isEnoent3(error)) {
7434
+ return void 0;
7435
+ }
7436
+ throw error;
7437
+ }
7438
+ const kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "dir" : "file";
7439
+ return { kind, mtimeMs: stat.mtimeMs, sizeBytes: stat.size };
7440
+ },
7441
+ readdir(dirPath) {
7442
+ try {
7443
+ return fs4.readdirSync(dirPath);
7444
+ } catch (error) {
7445
+ if (isEnoent3(error) || isErrorWithCode(error, "ENOTDIR")) {
7446
+ return [];
7447
+ }
7448
+ throw error;
7449
+ }
7450
+ },
7451
+ mkdirp(dirPath) {
7452
+ fs4.mkdirSync(dirPath, { recursive: true });
7453
+ },
7454
+ symlink(target, linkPath) {
7455
+ fs4.symlinkSync(target, linkPath);
7456
+ },
7457
+ rename(from, to) {
7458
+ fs4.renameSync(from, to);
7459
+ },
7460
+ removeRecursive(targetPath) {
7461
+ fs4.rmSync(targetPath, { recursive: true, force: true });
7462
+ },
7463
+ copyRecursive(from, to) {
7464
+ fs4.cpSync(from, to, { recursive: true, force: true, verbatimSymlinks: true });
7465
+ },
7466
+ readFileUtf8(filePath) {
7467
+ try {
7468
+ return fs4.readFileSync(filePath, "utf8");
7469
+ } catch (error) {
7470
+ if (isEnoent3(error)) {
7471
+ return void 0;
7472
+ }
7473
+ throw error;
7474
+ }
7475
+ },
7476
+ writeFileUtf8(filePath, contents) {
7477
+ fs4.writeFileSync(filePath, contents, "utf8");
7478
+ },
7479
+ mkdirPrivate(dirPath) {
7480
+ fs4.mkdirSync(dirPath, { recursive: true, mode: OWNER_ONLY_DIR_MODE });
7481
+ fs4.chmodSync(dirPath, OWNER_ONLY_DIR_MODE);
7482
+ },
7483
+ writeFilePrivate(filePath, contents) {
7484
+ const temp = `${filePath}.${String(process.pid)}.tmp`;
7485
+ fs4.writeFileSync(temp, contents, { encoding: "utf8", mode: OWNER_ONLY_FILE_MODE });
7486
+ fs4.chmodSync(temp, OWNER_ONLY_FILE_MODE);
7487
+ fs4.renameSync(temp, filePath);
7488
+ },
7489
+ appendFilePrivate(filePath, contents) {
7490
+ const descriptor = fs4.openSync(filePath, "a", OWNER_ONLY_FILE_MODE);
7491
+ try {
7492
+ if ((fs4.fstatSync(descriptor).mode & PERMISSION_BITS) !== OWNER_ONLY_FILE_MODE) {
7493
+ fs4.fchmodSync(descriptor, OWNER_ONLY_FILE_MODE);
7494
+ }
7495
+ const bytes = Buffer.from(contents, "utf8");
7496
+ let written = 0;
7497
+ while (written < bytes.length) {
7498
+ written += fs4.writeSync(descriptor, bytes, written, bytes.length - written);
7499
+ }
7500
+ } finally {
7501
+ fs4.closeSync(descriptor);
7502
+ }
7503
+ },
7504
+ writeFileExclusive(filePath, contents) {
7505
+ try {
7506
+ fs4.writeFileSync(filePath, contents, { encoding: "utf8", flag: "wx" });
7507
+ return true;
7508
+ } catch (error) {
7509
+ if (isErrorWithCode(error, "EEXIST")) {
7510
+ return false;
7511
+ }
7512
+ throw error;
7513
+ }
7514
+ },
7515
+ hashFile(filePath) {
7516
+ try {
7517
+ return createHash5("sha256").update(fs4.readFileSync(filePath)).digest("hex");
7518
+ } catch (error) {
7519
+ if (isEnoent3(error) || isErrorWithCode(error, "EISDIR")) {
7520
+ return void 0;
7521
+ }
7522
+ throw error;
7523
+ }
7467
7524
  }
7468
- return entries.filter((entry) => entry.endsWith(".json")).map((entry) => entry.slice(0, -".json".length)).sort();
7525
+ };
7526
+ var INT32_BYTE_LENGTH = 4;
7527
+ function realSleepSync(ms) {
7528
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(INT32_BYTE_LENGTH)), 0, 0, ms);
7469
7529
  }
7470
- function describeProviderEndpoint(provider) {
7471
- return isCodexProvider(provider) ? "codex translation daemon" : provider.baseUrl;
7530
+ function realIsProcessAlive(pid) {
7531
+ try {
7532
+ process.kill(pid, 0);
7533
+ return true;
7534
+ } catch (error) {
7535
+ return isErrorWithCode(error, "EPERM");
7536
+ }
7472
7537
  }
7473
- var ProviderKindMismatchError = class extends UsageError {
7474
- constructor(message) {
7475
- super(message);
7476
- this.name = "ProviderKindMismatchError";
7538
+ function realIsProcessZombie(pid) {
7539
+ const result = spawnSync("ps", ["-o", "stat=", "-p", String(pid)], { encoding: "utf8" });
7540
+ return result.status === 0 && result.stdout.trim().startsWith("Z");
7541
+ }
7542
+ function realIsProcessRunning(pid) {
7543
+ return realIsProcessAlive(pid) && !realIsProcessZombie(pid);
7544
+ }
7545
+ var realRunPort = {
7546
+ run(command, args) {
7547
+ const result = spawnSync(command, [...args], { encoding: "utf8" });
7548
+ return { status: result.status, stdout: result.stdout, stderr: result.stderr };
7477
7549
  }
7478
7550
  };
7479
- function isEmptyCodexConfig(config) {
7480
- return config === void 0 || config.defaultModel === void 0 && config.effort === void 0 && config.login === void 0 && (config.models === void 0 || Object.keys(config.models).length === 0);
7551
+ function expandHome2(filePath) {
7552
+ return filePath.startsWith("~/") ? path19.join(os3.homedir(), filePath.slice(1)) : filePath;
7481
7553
  }
7482
- function addProvider(paths, name, input) {
7483
- if (providerExists(paths, name)) {
7484
- throw new ProviderAlreadyExistsError(name);
7485
- }
7486
- if (!PROVIDER_NAME_RE.test(name)) {
7487
- throw new InvalidProviderNameError(name);
7488
- }
7489
- const kind = input.kind ?? "http";
7490
- if (kind === "codex" && input.baseUrl !== void 0) {
7491
- throw new ProviderKindMismatchError("A codex provider has no base URL: the launcher points its sessions at the front-door daemon. Drop --base-url.");
7492
- }
7493
- if (kind === "http" && input.baseUrl === void 0) {
7494
- throw new ProviderKindMismatchError("An http provider needs --base-url.");
7495
- }
7496
- if (kind === "http" && !isEmptyCodexConfig(input.codex)) {
7497
- throw new ProviderKindMismatchError("Codex settings apply only to a codex provider (--kind codex).");
7554
+ var MACOS_GUI_SESSION_MANAGER = "Aqua";
7555
+ function hasDesktopSession() {
7556
+ if (process.platform === "darwin") {
7557
+ const result = spawnSync("launchctl", ["managername"], { encoding: "utf8" });
7558
+ return result.status === 0 && result.stdout.trim() === MACOS_GUI_SESSION_MANAGER;
7498
7559
  }
7499
- const shared = {
7500
- displayName: input.displayName,
7501
- credential: { sources: input.sources, ...input.target === void 0 ? {} : { target: input.target } },
7502
- ...input.env === void 0 || Object.keys(input.env).length === 0 ? {} : { env: input.env }
7503
- };
7504
- const parsed = ProviderSchema.safeParse(
7505
- kind === "codex" ? { kind, ...shared, ...isEmptyCodexConfig(input.codex) ? {} : { codex: input.codex } } : { ...shared, baseUrl: input.baseUrl }
7506
- );
7507
- if (!parsed.success) {
7508
- throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
7560
+ if (process.platform === "win32") {
7561
+ return false;
7509
7562
  }
7510
- writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
7511
- return parsed.data;
7563
+ return (process.env.DISPLAY ?? "") !== "" || (process.env.WAYLAND_DISPLAY ?? "") !== "";
7512
7564
  }
7513
- function updateProvider(paths, name, input) {
7514
- const existing = readProvider(paths, name);
7515
- if (existing === void 0) {
7516
- throw new ProviderNotFoundError(name);
7517
- }
7518
- const unset = new Set(input.unsetEnv);
7519
- const env = Object.fromEntries(Object.entries({ ...existing.env, ...input.env }).filter(([key]) => !unset.has(key)));
7520
- const target = input.target ?? existing.credential.target;
7521
- const cache = input.cache === void 0 ? existing.credential.cache : input.cache === false ? void 0 : input.cache;
7522
- if (isCodexProvider(existing) && input.baseUrl !== void 0) {
7523
- throw new ProviderKindMismatchError(`Provider "${name}" is a codex provider, which has no base URL.`);
7524
- }
7525
- if (!isCodexProvider(existing) && !isEmptyCodexConfig(input.codex)) {
7526
- throw new ProviderKindMismatchError(`Provider "${name}" is an http provider; codex settings apply only to a codex provider.`);
7565
+ var realCredentialPort = {
7566
+ readSecretFile(filePath) {
7567
+ const resolved = expandHome2(filePath);
7568
+ let content;
7569
+ let stat;
7570
+ try {
7571
+ stat = fs4.statSync(resolved);
7572
+ content = fs4.readFileSync(resolved, "utf8");
7573
+ } catch (error) {
7574
+ if (isEnoent3(error)) {
7575
+ return { found: false };
7576
+ }
7577
+ throw error;
7578
+ }
7579
+ return { found: true, content, mode: process.platform === "win32" ? void 0 : stat.mode };
7580
+ },
7581
+ runCommand(argv, options) {
7582
+ const [command, ...args] = argv;
7583
+ const result = spawnSync(command, args, {
7584
+ encoding: "utf8",
7585
+ timeout: options.timeoutMs,
7586
+ stdio: options.interactive ? ["inherit", "pipe", "inherit"] : ["ignore", "pipe", "pipe"]
7587
+ });
7588
+ if (result.error !== void 0) {
7589
+ const timedOut = isErrorWithCode(result.error, "ETIMEDOUT");
7590
+ return { status: result.status, stdout: "", stderr: timedOut ? "" : result.error.message, timedOut };
7591
+ }
7592
+ return { status: result.status, stdout: result.stdout, stderr: options.interactive ? "" : result.stderr, timedOut: false };
7593
+ },
7594
+ personPresent: () => process.stdin.isTTY || hasDesktopSession()
7595
+ };
7596
+ function resolveGitBranch(run, cwd) {
7597
+ const result = run.run("git", ["-C", cwd, "rev-parse", "--abbrev-ref", "HEAD"]);
7598
+ if (result.status !== 0) {
7599
+ return {};
7527
7600
  }
7528
- const shared = {
7529
- ...input.displayName === void 0 ? {} : { displayName: input.displayName },
7530
- credential: {
7531
- sources: input.sources ?? existing.credential.sources,
7532
- ...target === void 0 ? {} : { target },
7533
- ...cache === void 0 ? {} : { cache }
7534
- },
7535
- env: Object.keys(env).length === 0 ? void 0 : env
7536
- };
7537
- let candidate;
7538
- if (isCodexProvider(existing)) {
7539
- const codex = {
7540
- ...existing.codex,
7541
- ...input.codex,
7542
- ...existing.codex?.models === void 0 && input.codex?.models === void 0 ? {} : { models: { ...existing.codex?.models, ...input.codex?.models } }
7543
- };
7544
- candidate = { ...existing, ...shared, codex: isEmptyCodexConfig(codex) ? void 0 : codex };
7545
- } else {
7546
- candidate = { ...existing, ...shared, ...input.baseUrl === void 0 ? {} : { baseUrl: input.baseUrl } };
7601
+ const name = result.stdout.trim();
7602
+ if (name === "") {
7603
+ return {};
7547
7604
  }
7548
- const parsed = ProviderSchema.safeParse(candidate);
7549
- if (!parsed.success) {
7550
- throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
7605
+ if (name === "HEAD") {
7606
+ return { branchDetached: true };
7551
7607
  }
7552
- writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
7553
- return parsed.data;
7608
+ return { branch: name, branchDetached: false };
7554
7609
  }
7555
- function removeProvider(paths, name) {
7556
- if (!providerExists(paths, name)) {
7557
- throw new ProviderNotFoundError(name);
7610
+ function resolveGitRoot(run, cwd) {
7611
+ const result = run.run("git", ["-C", cwd, "rev-parse", "--show-toplevel"]);
7612
+ if (result.status !== 0) {
7613
+ return void 0;
7558
7614
  }
7559
- fs6.rmSync(providerJsonPath(paths, name));
7615
+ const root = result.stdout.trim();
7616
+ return root === "" ? void 0 : root;
7560
7617
  }
7561
- function cascadeProviderName(cascade) {
7562
- let name;
7563
- for (const layer of assembleCascade(cascade).layers) {
7564
- if (layer.launch?.provider !== void 0) {
7565
- name = layer.launch.provider;
7618
+ function detachedDaemonSpawnOptions(logFd, env) {
7619
+ return { detached: true, stdio: ["ignore", logFd, logFd], env };
7620
+ }
7621
+ function spawnDaemonThrough(executable) {
7622
+ return (paths, subcommand, logPath) => spawnDetachedDaemon({ command: executable, args: [subcommand] }, paths, subcommand, logPath);
7623
+ }
7624
+ function spawnDaemonThroughScript(script) {
7625
+ return (paths, subcommand, logPath) => spawnDetachedDaemon({ command: process.execPath, args: [script, subcommand] }, paths, subcommand, logPath);
7626
+ }
7627
+ function spawnDetachedDaemon(invocation, paths, subcommand, logPath) {
7628
+ fs4.mkdirSync(path19.dirname(logPath), { recursive: true });
7629
+ const logFd = fs4.openSync(logPath, "a");
7630
+ try {
7631
+ const child = spawn(invocation.command, invocation.args, detachedDaemonSpawnOptions(logFd, { ...process.env, AGENT_SHIM_HOME: paths.root }));
7632
+ child.unref();
7633
+ if (child.pid === void 0) {
7634
+ throw new Error(`spawning ${subcommand} returned no pid`);
7566
7635
  }
7636
+ return child.pid;
7637
+ } finally {
7638
+ fs4.closeSync(logFd);
7567
7639
  }
7568
- return name;
7569
7640
  }
7570
- function resolveProvider(params) {
7571
- const cliName = params.cliProvider !== void 0 && params.cliProvider !== "" ? params.cliProvider : void 0;
7572
- const name = cliName ?? (params.cascade === void 0 ? void 0 : cascadeProviderName(params.cascade));
7573
- if (name === void 0) {
7574
- return void 0;
7575
- }
7576
- const definition = loadProvider(params.paths.providersDir, name, params.port);
7577
- if (definition === void 0) {
7578
- const known = listProviderNames(params.port.readdir, params.paths.providersDir);
7579
- return {
7580
- ok: false,
7581
- status: 1,
7582
- message: `agent-shim: no provider named "${name}". ` + (known.length > 0 ? `Known providers: ${known.join(", ")}.` : "No providers are defined yet; run `agent-shim provider add`.")
7583
- };
7584
- }
7585
- if (params.credentials === void 0) {
7586
- return { ok: false, status: 1, message: `agent-shim: provider ${name} needs its credential resolved, but this launcher has no credential port wired` };
7587
- }
7588
- const resolution = resolveCredential({ credential: definition.credential, env: params.env, port: params.credentials, subject: `provider ${name}` });
7589
- if (!resolution.ok) {
7590
- return { ok: false, status: CREDENTIAL_UNAVAILABLE_EXIT, message: resolution.message };
7591
- }
7592
- return { ok: true, provider: { name, definition, credential: resolution.credential }, warnings: resolution.credential.warnings };
7641
+ function spawnHeadroomSupervisor(paths, spawnDaemon) {
7642
+ return spawnDaemon(paths, "__headroom-supervisor", paths.headroomLogPath);
7593
7643
  }
7594
-
7595
- // src/realPorts.ts
7596
- import { spawn, spawnSync as spawnSync2 } from "node:child_process";
7597
- import { createHash as createHash5 } from "node:crypto";
7598
- import fs7 from "node:fs";
7599
- import os4 from "node:os";
7600
- import path22 from "node:path";
7601
- import { isSea } from "node:sea";
7602
-
7603
- // src/headroom/ensure.ts
7604
- var HEADROOM_START_TIMEOUT_MS = 12e4;
7605
- var HEADROOM_LAUNCHER_POLL_MS = 250;
7606
- var HeadroomStartError = class extends CliError {
7607
- constructor(message) {
7608
- super(message);
7609
- this.name = "HeadroomStartError";
7610
- }
7611
- };
7612
- function ensureHeadroom(params) {
7613
- const { paths, ports } = params;
7614
- const deadline = ports.now() + HEADROOM_START_TIMEOUT_MS;
7615
- let spawnedSupervisor = false;
7616
- let awaitingAllowlist = false;
7617
- for (; ; ) {
7618
- const state = readHeadroomState(ports.fs, paths.headroomStateFile);
7619
- if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
7620
- const daemonUp = state.socketPath !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
7621
- if (daemonUp && state.socketPath !== void 0) {
7622
- const required = params.requiredAllowlistHash?.();
7623
- if (required === void 0 || state.allowlistHash === required) {
7624
- writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
7625
- return { socketPath: state.socketPath };
7626
- }
7627
- const holders = sessionsForSupervisor(ports.fs, paths.headroomSessionsDir, state.supervisorPid).filter((session) => ports.isRunning(session.pid));
7628
- if (holders.length > 0) {
7629
- throw new HeadroomStartError(
7630
- `the running headroom daemon was started before this launch's front door address and would refuse every request this launch sends through it. It restarts once the launches using it end (pid ${holders.map((session) => String(session.pid)).join(", ")}); end them and launch again, or launch with --no-headroom.`
7631
- );
7632
- }
7633
- awaitingAllowlist = true;
7634
- }
7635
- if (state.lastError !== void 0) {
7636
- throw new HeadroomStartError(
7637
- `the headroom daemon reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.headroomLogPath})`
7638
- );
7639
- }
7640
- } else if (!spawnedSupervisor) {
7641
- ports.fs.mkdirp(paths.headroomDir);
7642
- const created = ports.fs.writeFileExclusive(
7643
- paths.headroomLockFile,
7644
- `${JSON.stringify({ pid: params.launcherPid, at: ports.now() })}
7645
- `
7646
- );
7647
- if (created) {
7648
- ports.spawnSupervisor(paths);
7649
- spawnedSupervisor = true;
7650
- } else {
7651
- const lock = readStartLock(ports.fs, paths.headroomLockFile);
7652
- if (lock === void 0 || !ports.isRunning(lock.pid)) {
7653
- ports.fs.removeRecursive(paths.headroomLockFile);
7654
- continue;
7655
- }
7656
- }
7644
+ var SOCKET_PERMISSION_BITS = 511;
7645
+ var realHeadroomSocketTrust = {
7646
+ platform: process.platform,
7647
+ currentUid: () => {
7648
+ if (process.getuid === void 0) {
7649
+ throw new Error(`no POSIX user id on ${process.platform}`);
7657
7650
  }
7658
- if (ports.now() >= deadline) {
7659
- const lastError = state?.lastError;
7660
- throw new HeadroomStartError(
7661
- `the headroom daemon did not become ready within ${String(HEADROOM_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + (awaitingAllowlist ? " (the running daemon's allowlist does not admit this launch's front door, and it did not restart)" : "") + `. Daemon log: ${paths.headroomLogPath}`
7662
- );
7651
+ return process.getuid();
7652
+ },
7653
+ lstat: (target) => {
7654
+ let stat;
7655
+ try {
7656
+ stat = fs4.lstatSync(target);
7657
+ } catch (error) {
7658
+ if (isEnoent3(error)) {
7659
+ return void 0;
7660
+ }
7661
+ throw error;
7663
7662
  }
7664
- ports.sleep(HEADROOM_LAUNCHER_POLL_MS);
7663
+ const kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "dir" : stat.isSocket() ? "socket" : "other";
7664
+ return { kind, uid: stat.uid, mode: stat.mode & SOCKET_PERMISSION_BITS };
7665
7665
  }
7666
- }
7667
-
7668
- // src/headroom/settings.ts
7669
- import { createHash as createHash4 } from "node:crypto";
7670
- function settingsOf(config) {
7666
+ };
7667
+ function realHeadroomPort(paths, options) {
7671
7668
  return {
7672
- ...config.mode === void 0 ? {} : { mode: config.mode },
7673
- ...config.targetRatio === void 0 ? {} : { targetRatio: config.targetRatio },
7674
- ...config.ccr === void 0 ? {} : { ccr: config.ccr },
7675
- ...config.rolloutChannel === void 0 ? {} : { rolloutChannel: config.rolloutChannel },
7676
- ...config.interceptToolResults === void 0 ? {} : { interceptToolResults: config.interceptToolResults },
7677
- ...config.readMaturation === void 0 ? {} : { readMaturation: config.readMaturation }
7669
+ ensure: ({ routesProvider }) => {
7670
+ const up = ensureHeadroom({
7671
+ paths,
7672
+ launcherPid: process.pid,
7673
+ ...routesProvider ? { requiredAllowlistHash: () => hashAllowlist(currentHeadroomAllowlist(realFarmFs, paths)) } : {},
7674
+ ports: {
7675
+ fs: realFarmFs,
7676
+ // Zombie-aware on purpose: a supervisor that died while still a child of this launcher sits unreaped until the launcher itself exits, and a defunct supervisor answering signal 0 as alive would stretch every launch to the full start timeout.
7677
+ isRunning: realIsProcessRunning,
7678
+ now: () => Date.now(),
7679
+ sleep: realSleepSync,
7680
+ spawnSupervisor: (layout) => spawnHeadroomSupervisor(layout, options.spawnDaemon)
7681
+ }
7682
+ });
7683
+ return {
7684
+ socketPath: up.socketPath,
7685
+ projectId: resolveGitRoot(realRunPort, options.cwd) ?? options.cwd
7686
+ };
7687
+ },
7688
+ release: () => {
7689
+ removeSession(realFarmFs, paths.headroomSessionsDir, process.pid);
7690
+ }
7678
7691
  };
7679
7692
  }
7680
- function settingsArgs(settings) {
7681
- return [
7682
- ...settings.mode === void 0 ? [] : ["--mode", settings.mode],
7683
- ...settings.targetRatio === void 0 ? [] : ["--target-ratio", String(settings.targetRatio)],
7684
- ...settings.ccr === "lossless" ? ["--lossless"] : [],
7685
- ...settings.ccr === "none" ? ["--no-ccr"] : [],
7686
- ...settings.interceptToolResults === true ? ["--intercept-tool-results"] : [],
7687
- ...settings.readMaturation === true ? ["--read-maturation"] : []
7688
- ];
7693
+ var realProcPort = {
7694
+ env: process.env,
7695
+ argv: process.argv.slice(2),
7696
+ exit: (code) => process.exit(code)
7697
+ };
7698
+ var EXECUTE_BITS_MASK = 73;
7699
+ function listVersionsDir(dir) {
7700
+ let entries;
7701
+ try {
7702
+ entries = fs4.readdirSync(dir, { withFileTypes: true });
7703
+ } catch (error) {
7704
+ if (isEnoent3(error)) {
7705
+ return [];
7706
+ }
7707
+ throw error;
7708
+ }
7709
+ return entries.filter((entry) => entry.isFile()).map((entry) => {
7710
+ const fullPath = path19.join(dir, entry.name);
7711
+ const stat = fs4.statSync(fullPath);
7712
+ return {
7713
+ name: entry.name,
7714
+ isFile: true,
7715
+ isExecutable: (stat.mode & EXECUTE_BITS_MASK) !== 0,
7716
+ sizeBytes: stat.size
7717
+ };
7718
+ });
7689
7719
  }
7690
- function settingsEnv(settings) {
7691
- return settings.rolloutChannel === void 0 ? {} : { HEADROOM_ROLLOUT_CHANNEL: settings.rolloutChannel };
7720
+ var WINDOWS_DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PS1";
7721
+ function windowsPathExtensions(pathext) {
7722
+ return (pathext ?? WINDOWS_DEFAULT_PATHEXT).split(";").map((ext) => ext.trim()).filter((ext) => ext.length > 0);
7692
7723
  }
7693
- function hashSettings(settings) {
7694
- return createHash4("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
7724
+ function resolveExecutableCandidate(dir, name, env) {
7725
+ const candidate = path19.join(dir, name);
7726
+ if (env.platform === "win32") {
7727
+ const extensions = windowsPathExtensions(env.pathext);
7728
+ const alreadyHasRecognisedExtension = extensions.some((ext) => candidate.toLowerCase().endsWith(ext.toLowerCase()));
7729
+ if (alreadyHasRecognisedExtension) {
7730
+ return env.statFileMode(candidate) !== void 0 ? candidate : void 0;
7731
+ }
7732
+ for (const ext of extensions) {
7733
+ const withExt = candidate + ext;
7734
+ if (env.statFileMode(withExt) !== void 0) {
7735
+ return withExt;
7736
+ }
7737
+ }
7738
+ return void 0;
7739
+ }
7740
+ const mode = env.statFileMode(candidate);
7741
+ return mode !== void 0 && (mode & EXECUTE_BITS_MASK) !== 0 ? candidate : void 0;
7695
7742
  }
7696
-
7697
- // src/headroom/socket.ts
7698
- import path21 from "node:path";
7699
- var GROUP_OR_OTHER_BITS = 63;
7700
- var OCTAL_RADIX2 = 8;
7701
- var MODE_DIGITS = 4;
7702
- var HEADROOM_SOCKET_SUFFIX = ".sock";
7703
- var HEADROOM_SOCKET_NAME = /^(\d+)\.sock$/;
7704
- var UNIX_SOCKET_PATH_MAX_BYTES = 103;
7705
- var PLATFORMS_WITHOUT_UNIX_SOCKETS = ["win32"];
7706
- function headroomSocketPath(paths, supervisorPid) {
7707
- return path21.join(paths.headroomSocketDir, `${String(supervisorPid)}${HEADROOM_SOCKET_SUFFIX}`);
7743
+ function realStatFileMode(candidate) {
7744
+ try {
7745
+ const stat = fs4.statSync(candidate);
7746
+ return stat.isFile() ? stat.mode : void 0;
7747
+ } catch (error) {
7748
+ if (isEnoent3(error)) {
7749
+ return void 0;
7750
+ }
7751
+ throw error;
7752
+ }
7708
7753
  }
7709
- function platformRefusal(platform) {
7710
- return PLATFORMS_WITHOUT_UNIX_SOCKETS.includes(platform) ? `headroom routing needs a unix domain socket, which headroom cannot serve on ${platform}; agent-shim never falls back to a TCP port for this hop` : void 0;
7754
+ function findExecutableInDir(dir, name) {
7755
+ return resolveExecutableCandidate(dir, name, {
7756
+ platform: process.platform,
7757
+ pathext: process.env.PATHEXT,
7758
+ statFileMode: realStatFileMode
7759
+ });
7711
7760
  }
7712
- function ownerOnlyRefusal(label, target, stat, expected, uid) {
7713
- if (stat === void 0) {
7714
- return `the headroom ${label} ${target} does not exist`;
7715
- }
7716
- if (stat.kind === "symlink") {
7717
- return `the headroom ${label} ${target} is a symlink; it must be the real ${expected === "dir" ? "directory" : "socket"}`;
7718
- }
7719
- if (stat.kind !== expected) {
7720
- return `the headroom ${label} ${target} is not a ${expected === "dir" ? "directory" : "socket"}`;
7721
- }
7722
- if (stat.uid !== uid) {
7723
- return `the headroom ${label} ${target} is owned by uid ${String(stat.uid)}, not by this user (uid ${String(uid)})`;
7724
- }
7725
- if ((stat.mode & GROUP_OR_OTHER_BITS) !== 0) {
7726
- return `the headroom ${label} ${target} has mode ${stat.mode.toString(OCTAL_RADIX2).padStart(MODE_DIGITS, "0")}, which lets other users reach it; it must be accessible to its owner only`;
7761
+ function searchPathForExecutable(pathDirs, name, findExecutable) {
7762
+ for (const dir of pathDirs) {
7763
+ const found = findExecutable(dir, name);
7764
+ if (found !== void 0) {
7765
+ return found;
7766
+ }
7727
7767
  }
7728
7768
  return void 0;
7729
7769
  }
7730
- function verifyHeadroomSocket(socketPath, ports) {
7731
- const unsupported = platformRefusal(ports.platform);
7732
- if (unsupported !== void 0) {
7733
- return { refused: unsupported };
7770
+ function resolveOwnExecutablePath(env) {
7771
+ if (env.argv1 !== void 0 && !env.argv1.includes("/") && !env.argv1.includes("\\")) {
7772
+ return searchPathForExecutable(env.pathDirs, env.argv1, env.findExecutableInDir) ?? env.execPath;
7734
7773
  }
7735
- const uid = ports.currentUid();
7736
- const directory = path21.dirname(socketPath);
7737
- const refusal = ownerOnlyRefusal("socket directory", directory, ports.lstat(directory), "dir", uid) ?? ownerOnlyRefusal("socket", socketPath, ports.lstat(socketPath), "socket", uid);
7738
- return refusal === void 0 ? { socketPath } : { refused: refusal };
7739
- }
7740
- function prepareHeadroomSocket(fs12, ports, paths, supervisorPid) {
7741
- const unsupported = platformRefusal(ports.platform);
7742
- if (unsupported !== void 0) {
7743
- return { refused: unsupported };
7744
- }
7745
- const socketPath = headroomSocketPath(paths, supervisorPid);
7746
- const length = Buffer.byteLength(socketPath, "utf8");
7747
- if (length > UNIX_SOCKET_PATH_MAX_BYTES) {
7748
- return {
7749
- refused: `the headroom socket path ${socketPath} is ${String(length)} bytes, longer than the ${String(UNIX_SOCKET_PATH_MAX_BYTES)} a unix socket path can hold; set AGENT_SHIM_HOME to a shorter directory`
7750
- };
7751
- }
7752
- if (ports.lstat(paths.headroomSocketDir)?.kind === "symlink") {
7753
- return { refused: `the headroom socket directory ${paths.headroomSocketDir} is a symlink; it must be the real directory` };
7774
+ const candidate = env.argv1 === void 0 ? env.execPath : path19.resolve(env.cwd, env.argv1);
7775
+ const candidateDir = path19.dirname(candidate);
7776
+ const candidateDirOnPath = env.pathDirs.some((dir) => path19.resolve(dir) === candidateDir);
7777
+ if (candidateDirOnPath) {
7778
+ return candidate;
7754
7779
  }
7755
- fs12.mkdirPrivate(paths.headroomSocketDir);
7756
- const refusal = ownerOnlyRefusal("socket directory", paths.headroomSocketDir, ports.lstat(paths.headroomSocketDir), "dir", ports.currentUid());
7757
- return refusal === void 0 ? { socketPath } : { refused: refusal };
7780
+ const viaPath = searchPathForExecutable(env.pathDirs, path19.basename(candidate), env.findExecutableInDir);
7781
+ return viaPath ?? candidate;
7758
7782
  }
7759
- function pruneStaleHeadroomSockets(fs12, paths, isRunning) {
7760
- const removed = [];
7761
- for (const name of fs12.readdir(paths.headroomSocketDir)) {
7762
- const pid = HEADROOM_SOCKET_NAME.exec(name)?.[1];
7763
- if (pid === void 0 || isRunning(Number.parseInt(pid, 10))) {
7764
- continue;
7783
+ function realOwnExecutablePath() {
7784
+ return resolveOwnExecutablePath({
7785
+ argv1: process.argv[1],
7786
+ execPath: process.execPath,
7787
+ cwd: process.cwd(),
7788
+ pathDirs: (process.env.PATH ?? "").split(path19.delimiter).filter((dir) => dir !== ""),
7789
+ findExecutableInDir
7790
+ });
7791
+ }
7792
+ function resolveContentSourcePath(env) {
7793
+ return env.isSea ? env.execPath : env.argv1 ?? env.execPath;
7794
+ }
7795
+ function realContentSourcePath() {
7796
+ return resolveContentSourcePath({ isSea: isSea(), execPath: process.execPath, argv1: process.argv[1] });
7797
+ }
7798
+ function realResolveClaudeBinary(isOwnBinary) {
7799
+ return (request4) => discoverClaudeBinary({
7800
+ listVersionsDir,
7801
+ pathDirs: (process.env.PATH ?? "").split(path19.delimiter).filter((dir) => dir !== ""),
7802
+ findExecutableInDir,
7803
+ isOwnBinary,
7804
+ ...request4?.version === void 0 ? {} : { version: request4.version }
7805
+ });
7806
+ }
7807
+ function realInstalledClaudeVersions() {
7808
+ return installedVersions(listVersionsDir(defaultVersionsDir()));
7809
+ }
7810
+
7811
+ // src/launchSelection.ts
7812
+ function selectLaunch(params, loaded) {
7813
+ const { paths, cwd } = params;
7814
+ const selections = readDirectorySelections(loaded);
7815
+ const decidedIdentity = decideIdentity({
7816
+ env: params.env,
7817
+ argv0Identity: params.identity,
7818
+ directoryPinnedIdentity: selections.identity,
7819
+ readActiveIdentityFile: () => {
7820
+ const raw = realFsPort.readFileUtf8(paths.activeIdentityFile);
7821
+ if (raw === void 0) {
7822
+ return void 0;
7823
+ }
7824
+ const trimmed = raw.trim();
7825
+ return trimmed === "" ? void 0 : trimmed;
7765
7826
  }
7766
- fs12.removeRecursive(path21.join(paths.headroomSocketDir, name));
7767
- removed.push(name);
7827
+ });
7828
+ let poolPick;
7829
+ if (decidedIdentity.pool !== void 0) {
7830
+ const pools = loaded.globalConfig?.pools ?? {};
7831
+ if (pools[decidedIdentity.pool] === void 0) {
7832
+ throw new PoolNotFoundError(decidedIdentity.pool);
7833
+ }
7834
+ poolPick = collectPoolPick({ paths, fs: realFsPort, usageFs: realFarmFs, poolName: decidedIdentity.pool, pools, directory: cwd, nowMs: Date.now() });
7768
7835
  }
7769
- return removed;
7836
+ const identity = poolPick?.pick === void 0 ? decidedIdentity : { ...decidedIdentity, name: poolPick.pick };
7837
+ const loadedIdentity = identity.name === void 0 ? void 0 : loadIdentity(paths.identitiesDir, identity.name, realFsPort);
7838
+ const configProfile = decideConfigProfile({
7839
+ env: params.env,
7840
+ ...params.configProfile === void 0 ? {} : { cliFlagConfigProfile: params.configProfile },
7841
+ directoryRuleConfigProfile: selections.configProfile,
7842
+ identityDefaultConfigProfile: loadedIdentity?.config.defaultConfigProfile,
7843
+ globalDefaultConfigProfile: loaded.globalConfig?.defaultConfigProfile
7844
+ });
7845
+ return {
7846
+ selection: { identity, ...poolPick === void 0 ? {} : { poolPick }, configProfile },
7847
+ ...loadedIdentity === void 0 ? {} : { identityConfig: loadedIdentity.config }
7848
+ };
7849
+ }
7850
+ function resolveLaunchSelection(params) {
7851
+ const loaded = loadCascadeInput({ paths: params.paths, home: os4.homedir(), cwd: params.cwd, read: cosmiconfigReader() });
7852
+ return selectLaunch(params, loaded).selection;
7770
7853
  }
7771
7854
 
7772
- // src/headroom/source.ts
7773
- import { z as z11 } from "zod";
7774
- var GIT_SPEC_RE = /git\+[^@\s]+(?:@([^\s#]+))?/;
7775
- var COMMIT_SHA_RE = /^[0-9a-f]{40}$/i;
7776
- var DirectUrlSchema = z11.object({ vcs_info: z11.object({ commit_id: z11.string() }) });
7777
- function isMovingGitSource(spec) {
7778
- const match = GIT_SPEC_RE.exec(spec);
7779
- if (match === null) {
7780
- return false;
7855
+ // src/paths.ts
7856
+ import fs5 from "node:fs";
7857
+ import os5 from "node:os";
7858
+ import path20 from "node:path";
7859
+ function resolveAgentShimHome(env = process.env, home = os5.homedir(), isDirectory = isExistingDirectory) {
7860
+ const fromEnv = env.AGENT_SHIM_HOME;
7861
+ if (fromEnv !== void 0 && fromEnv !== "") {
7862
+ return fromEnv;
7781
7863
  }
7782
- const ref = match[1];
7783
- return ref === void 0 || !COMMIT_SHA_RE.test(ref);
7864
+ const current = path20.join(home, HOME_DIRNAME);
7865
+ const legacy = path20.join(home, LEGACY_HOME_DIRNAME);
7866
+ return !isDirectory(current) && isDirectory(legacy) ? legacy : current;
7784
7867
  }
7785
- function pinnedGitCommit(spec) {
7786
- const ref = GIT_SPEC_RE.exec(spec)?.[1];
7787
- return ref !== void 0 && COMMIT_SHA_RE.test(ref) ? ref.toLowerCase() : void 0;
7868
+ function isExistingDirectory(candidate) {
7869
+ return fs5.statSync(candidate, { throwIfNoEntry: false })?.isDirectory() ?? false;
7788
7870
  }
7789
-
7790
- // src/headroom/supervisor.ts
7791
- function resolveSupervisorConfig(configured) {
7871
+ function resolveClaudeHome() {
7872
+ const fromEnv = process.env.AGENT_SHIM_CLAUDE_HOME;
7873
+ if (fromEnv !== void 0 && fromEnv !== "") {
7874
+ return fromEnv;
7875
+ }
7876
+ return path20.join(os5.homedir(), ".claude");
7877
+ }
7878
+ function buildLayoutPaths(root) {
7792
7879
  return {
7793
- source: configured.source ?? HEADROOM_DEFAULT_SOURCE,
7794
- idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES,
7795
- settings: settingsOf(configured)
7880
+ root,
7881
+ identitiesDir: path20.join(root, "identities"),
7882
+ configProfilesDir: path20.join(root, "config-profiles"),
7883
+ providersDir: path20.join(root, "providers"),
7884
+ directoryRulesFile: path20.join(root, "directory-rules.json"),
7885
+ activeIdentityFile: path20.join(root, "active-identity"),
7886
+ globalConfigFile: path20.join(root, "config.json"),
7887
+ categoriesLocalFile: path20.join(root, "categories.local.json"),
7888
+ claudeShimFile: path20.join(root, "claude-shim.json"),
7889
+ headroomDir: path20.join(root, "headroom"),
7890
+ headroomStateFile: path20.join(root, "headroom", `state.v${String(HEADROOM_STATE_SCHEMA_VERSION)}.json`),
7891
+ headroomSocketDir: path20.join(root, "headroom", "run"),
7892
+ headroomLockFile: path20.join(root, "headroom", "start.lock"),
7893
+ headroomSessionsDir: path20.join(root, "headroom", "sessions"),
7894
+ logsDir: path20.join(root, "logs"),
7895
+ headroomLogPath: path20.join(root, "logs", "headroom.log"),
7896
+ frontdoorDir: path20.join(root, "frontdoor"),
7897
+ frontdoorCaDir: path20.join(root, "frontdoor", "ca"),
7898
+ frontdoorCaCertFile: path20.join(root, "frontdoor", "ca", "ca.pem"),
7899
+ frontdoorCaKeyFile: path20.join(root, "frontdoor", "ca", "ca.key"),
7900
+ frontdoorCaBundlesDir: path20.join(root, "frontdoor", "ca", "bundles"),
7901
+ chatgptSignInFile: path20.join(root, "codex", "chatgpt-sign-in.json"),
7902
+ frontdoorStateFile: path20.join(root, "frontdoor", "state.json"),
7903
+ frontdoorLockFile: path20.join(root, "frontdoor", "start.lock"),
7904
+ frontdoorControlTokenFile: path20.join(root, "frontdoor", "control-token"),
7905
+ frontdoorSessionsDir: path20.join(root, "frontdoor", "sessions"),
7906
+ frontdoorRcCredentialsDir: path20.join(root, "frontdoor", "rc-credentials"),
7907
+ frontdoorLogPath: path20.join(root, "logs", "frontdoor.log"),
7908
+ usageDir: path20.join(root, "usage"),
7909
+ usageLogDir: path20.join(root, "usage", "log"),
7910
+ usageSnapshotsDir: path20.join(root, "usage", "snapshots"),
7911
+ usagePicksFile: path20.join(root, "usage", "picks.json")
7796
7912
  };
7797
7913
  }
7798
- var HEADROOM_BACKOFF_BASE_MS = 500;
7799
- var HEADROOM_BACKOFF_CAP_MS = 3e4;
7800
- var HEADROOM_START_RETRY_BUDGET = 5;
7801
- var HEADROOM_READY_TIMEOUT_MS = 15e3;
7802
- var HEADROOM_POLL_MS = 1e3;
7803
- var MS_PER_MINUTE2 = 6e4;
7804
- function backoffForAttempt(attempt) {
7805
- return Math.min(HEADROOM_BACKOFF_CAP_MS, HEADROOM_BACKOFF_BASE_MS * 2 ** (attempt - 1));
7914
+ function resolveLayoutPaths() {
7915
+ return buildLayoutPaths(resolveAgentShimHome());
7806
7916
  }
7807
- function versionSatisfies(version, source) {
7808
- if (version === void 0) {
7809
- return false;
7810
- }
7811
- const specifier = /==|>=|<=|!=|>|<|~=/.exec(source);
7812
- if (specifier === null) {
7813
- return true;
7814
- }
7815
- const installed = parseVersion(version);
7816
- const wanted = parseVersion(source.slice(specifier.index + specifier[0].length));
7817
- if (installed === void 0 || wanted === void 0) {
7818
- return true;
7819
- }
7820
- const cmp = compareVersions2(installed, wanted);
7821
- switch (specifier[0]) {
7822
- case "==":
7823
- return cmp === 0;
7824
- case "!=":
7825
- return cmp !== 0;
7826
- case ">=":
7827
- return cmp >= 0;
7828
- case "<=":
7829
- return cmp <= 0;
7830
- case ">":
7831
- return cmp > 0;
7832
- case "<":
7833
- return cmp < 0;
7834
- case "~=":
7835
- return compareVersions2(installed.slice(0, Math.max(0, wanted.length - 1)), wanted.slice(0, Math.max(0, wanted.length - 1))) === 0 && cmp >= 0;
7836
- default:
7837
- return true;
7838
- }
7917
+
7918
+ // src/realCredentialCache.ts
7919
+ import { spawnSync as spawnSync2 } from "node:child_process";
7920
+ import fs6 from "node:fs";
7921
+ import path21 from "node:path";
7922
+
7923
+ // src/credentialCache.ts
7924
+ import { z as z11 } from "zod";
7925
+ var MS_PER_UNIT = { s: 1e3, m: 6e4, h: 36e5, d: 864e5 };
7926
+ var CachedCredentialSchema = z11.strictObject({
7927
+ token: z11.string().min(1),
7928
+ fetchedAt: z11.number().int().nonnegative(),
7929
+ source: CredentialSourceSchema
7930
+ });
7931
+ function effectiveStore(cache, platform) {
7932
+ return cache.store ?? (platform === "darwin" ? "keychain" : "file");
7839
7933
  }
7840
- function parseVersion(input) {
7841
- const match = /(\d+)(\.\d+)*/.exec(input);
7842
- if (match === null) {
7934
+ function ttlMs(cache) {
7935
+ if (cache.ttl === void 0) {
7843
7936
  return void 0;
7844
7937
  }
7845
- return match[0].split(".").map((part) => Number.parseInt(part, 10));
7938
+ const multiplier = MS_PER_UNIT[cache.ttl.slice(-1)];
7939
+ if (multiplier === void 0) {
7940
+ throw new Error(`credential cache ttl "${cache.ttl}" has no recognised unit`);
7941
+ }
7942
+ return Number(cache.ttl.slice(0, -1)) * multiplier;
7846
7943
  }
7847
- function compareVersions2(a, b) {
7848
- for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
7849
- const left = a[index] ?? 0;
7850
- const right = b[index] ?? 0;
7851
- if (left !== right) {
7852
- return left - right;
7853
- }
7944
+ function isFresh(entry, cache, now) {
7945
+ const limit = ttlMs(cache);
7946
+ if (entry.fetchedAt > now) {
7947
+ return false;
7854
7948
  }
7855
- return 0;
7949
+ return limit === void 0 || now - entry.fetchedAt < limit;
7856
7950
  }
7857
- var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
7858
- async function runSupervisor(initialConfig, ports, options = {}) {
7859
- const { fs: fs12, paths } = ports;
7860
- let config = initialConfig;
7861
- const previousState = readHeadroomState(fs12, paths.headroomStateFile);
7862
- if (previousState?.supervisorPid !== void 0 && previousState.supervisorPid !== ports.ownPid && ports.isRunning(previousState.supervisorPid)) {
7863
- ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: supervisor ${String(previousState.supervisorPid)} already owns the daemon; exiting`);
7864
- return 0;
7951
+ function cacheAgeMs(entry, now) {
7952
+ return Math.max(0, now - entry.fetchedAt);
7953
+ }
7954
+ function describeCachedCredential(params) {
7955
+ const store = effectiveStore(params.cache, params.platform);
7956
+ let entry;
7957
+ try {
7958
+ entry = params.port.read(store, params.owner);
7959
+ } catch (error) {
7960
+ return { store, status: "unreadable", reason: error instanceof Error ? error.message : String(error) };
7865
7961
  }
7866
- let installedSource = previousState?.installedSource;
7867
- const ownsState = () => {
7868
- const owner = readHeadroomState(fs12, paths.headroomStateFile)?.supervisorPid;
7869
- return owner === void 0 || owner === ports.ownPid || !ports.isRunning(owner);
7870
- };
7871
- const writeOwnState = (state) => {
7872
- if (!ownsState()) {
7873
- return false;
7874
- }
7875
- writeHeadroomState(fs12, paths.headroomStateFile, state);
7876
- return true;
7877
- };
7878
- const fail = (message) => {
7879
- if (writeOwnState({ lastError: message })) {
7880
- fs12.removeRecursive(paths.headroomLockFile);
7881
- }
7882
- ports.log(`agent-shim headroom supervisor: ${message}`);
7883
- return 1;
7884
- };
7885
- const ensureInstalled = () => {
7886
- const version2 = ports.headroomVersion();
7887
- const versionOk = version2 !== void 0 && versionSatisfies(version2, config.source);
7888
- const pinned = pinnedGitCommit(config.source);
7889
- const commitOk = pinned === void 0 || ports.installedCommit() === pinned;
7890
- if (versionOk && commitOk && (installedSource === void 0 || installedSource === config.source)) {
7891
- installedSource = config.source;
7892
- return true;
7893
- }
7894
- ports.log(`agent-shim headroom supervisor: installing headroom from ${config.source}`);
7895
- const result = ports.install(config.source);
7896
- if (!result.ok) {
7897
- return false;
7898
- }
7899
- installedSource = config.source;
7900
- return true;
7962
+ if (entry === void 0) {
7963
+ return { store, status: "empty" };
7964
+ }
7965
+ const limit = ttlMs(params.cache);
7966
+ const ageMs = cacheAgeMs(entry, params.nowMs);
7967
+ return {
7968
+ store,
7969
+ status: isFresh(entry, params.cache, params.nowMs) ? "fresh" : "expired",
7970
+ ageMs,
7971
+ ...limit === void 0 ? {} : { expiresInMs: limit - ageMs }
7901
7972
  };
7902
- const socket = prepareHeadroomSocket(fs12, ports.socketTrust, paths, ports.ownPid);
7903
- if (socket.refused !== void 0) {
7904
- return fail(socket.refused);
7973
+ }
7974
+ function formatCachedCredentialState(state) {
7975
+ if (state.status === "empty") {
7976
+ return `no cached entry in the ${state.store} store yet`;
7905
7977
  }
7906
- const { socketPath } = socket;
7907
- pruneStaleHeadroomSockets(fs12, paths, ports.isRunning);
7908
- if (!ensureInstalled()) {
7909
- return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
7978
+ if (state.status === "unreadable") {
7979
+ return `the ${state.store} store cannot be read here: ${state.reason}`;
7910
7980
  }
7911
- const version = ports.headroomVersion() ?? "unknown";
7912
- writeOwnState({ supervisorPid: ports.ownPid, version, installedSource });
7913
- ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: managing headroom on allowlist [${allowlistOf(ports).join(", ")}]`);
7914
- const orphanPid = previousState?.headroomPid;
7915
- if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
7916
- ports.log(`agent-shim headroom supervisor: stopping daemon pid ${String(orphanPid)} left behind by the previous supervisor`);
7917
- ports.stopProcess(orphanPid);
7981
+ if (state.status === "expired" && (state.expiresInMs === void 0 || state.expiresInMs > 0)) {
7982
+ return "cached with a timestamp in the future, treated as expired";
7918
7983
  }
7919
- let headroomPid;
7920
- let runningHash;
7921
- let runningSettingsHash;
7922
- let consecutiveFailures = 0;
7923
- let idleSince;
7924
- let superseded = false;
7925
- let ticks = 0;
7926
- for (; ; ) {
7927
- if (options.tickLimit !== void 0 && ticks >= options.tickLimit) {
7928
- return HEADROOM_SUPERVISOR_STILL_RUNNING;
7929
- }
7930
- ticks += 1;
7931
- config = ports.readConfig();
7932
- const settingsHash = hashSettings(config.settings);
7933
- const allowlist = allowlistOf(ports);
7934
- const allowlistHash = hashAllowlist(allowlist);
7935
- pruneDeadSessions(fs12, paths.headroomSessionsDir, ports.isRunning);
7936
- const ownsNow = ownsState();
7937
- if (!ownsNow && !superseded) {
7938
- ports.log("agent-shim headroom supervisor: another supervisor now owns the state file; serving this daemon's own sessions until they end");
7939
- }
7940
- superseded = !ownsNow;
7941
- const crashed = headroomPid !== void 0 && !ports.isRunning(headroomPid);
7942
- if (headroomPid === void 0 || crashed) {
7943
- if (crashed) {
7944
- ports.log(`agent-shim headroom supervisor: headroom pid ${String(headroomPid)} died`);
7945
- headroomPid = void 0;
7946
- runningHash = void 0;
7947
- runningSettingsHash = void 0;
7948
- writeOwnState({
7949
- supervisorPid: ports.ownPid,
7950
- version,
7951
- installedSource
7952
- });
7953
- }
7954
- if (consecutiveFailures >= HEADROOM_START_RETRY_BUDGET) {
7955
- return fail(`headroom failed to become ready ${String(consecutiveFailures)} times in a row; giving up`);
7956
- }
7957
- if (installedSource !== config.source && !ensureInstalled()) {
7958
- return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
7959
- }
7960
- const pid = ports.spawnHeadroom(socketPath, allowlist, config.settings);
7961
- const ready = await waitUntilReady(ports, socketPath);
7962
- if (ready) {
7963
- consecutiveFailures = 0;
7964
- headroomPid = pid;
7965
- runningHash = allowlistHash;
7966
- runningSettingsHash = settingsHash;
7967
- const recorded = writeOwnState({
7968
- supervisorPid: ports.ownPid,
7969
- headroomPid: pid,
7970
- socketPath,
7971
- version,
7972
- allowlistHash,
7973
- settingsHash,
7974
- installedSource: config.source
7975
- });
7976
- if (recorded) {
7977
- fs12.removeRecursive(paths.headroomLockFile);
7978
- }
7979
- idleSince = void 0;
7980
- ports.log(`agent-shim headroom supervisor: headroom pid ${String(pid)} ready on unix socket ${socketPath}`);
7981
- } else {
7982
- ports.stopProcess(pid);
7983
- consecutiveFailures += 1;
7984
- ports.log(
7985
- `agent-shim headroom supervisor: headroom did not become ready on unix socket ${socketPath} (attempt ${String(consecutiveFailures)} of ${String(HEADROOM_START_RETRY_BUDGET)})`
7986
- );
7987
- await ports.sleep(backoffForAttempt(consecutiveFailures));
7988
- continue;
7989
- }
7990
- } else {
7991
- if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
7992
- if (sessionsForSupervisor(fs12, paths.headroomSessionsDir, ports.ownPid).length === 0) {
7993
- ports.log("agent-shim headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
7994
- ports.stopProcess(headroomPid);
7995
- headroomPid = void 0;
7996
- runningHash = void 0;
7997
- runningSettingsHash = void 0;
7998
- continue;
7999
- }
8000
- }
8001
- const sessions = sessionsForSupervisor(fs12, paths.headroomSessionsDir, ports.ownPid);
8002
- if (sessions.length === 0) {
8003
- idleSince ??= ports.now();
8004
- const idleMs = ports.now() - idleSince;
8005
- if (idleMs >= config.idleShutdownMinutes * MS_PER_MINUTE2) {
8006
- ports.log(
8007
- `agent-shim headroom supervisor: no sessions for ${String(config.idleShutdownMinutes)} minute(s); stopping headroom and exiting`
8008
- );
8009
- ports.stopProcess(headroomPid);
8010
- writeOwnState({ version });
8011
- return 0;
8012
- }
8013
- } else {
8014
- idleSince = void 0;
8015
- }
8016
- }
8017
- await ports.sleep(HEADROOM_POLL_MS);
7984
+ const age = `cached ${formatAge(state.ageMs)} ago`;
7985
+ if (state.expiresInMs === void 0) {
7986
+ return `${age}, no expiry`;
8018
7987
  }
7988
+ return state.status === "fresh" ? `${age}, expires in ${formatAge(state.expiresInMs)}` : `${age}, expired ${formatAge(-state.expiresInMs)} ago`;
8019
7989
  }
8020
- function currentHeadroomAllowlist(fs12, paths) {
8021
- const providers = readAllProviders(fs12, paths.providersDir).map((entry) => entry.provider);
8022
- return headroomAllowlist(headroomUpstreams(providers, frontDoorOrigin(readFrontDoorState(fs12, paths.frontdoorStateFile))));
8023
- }
8024
- function allowlistOf(ports) {
8025
- return currentHeadroomAllowlist(ports.fs, ports.paths);
7990
+
7991
+ // src/realCredentialCache.ts
7992
+ var KEYCHAIN_CACHE_SERVICE = "agent-shim-credential-cache";
7993
+ var CACHE_DIR_MODE = 448;
7994
+ var CACHE_FILE_MODE = 384;
7995
+ function cacheFileNameFor(owner) {
7996
+ return `${owner.replace(/[^A-Za-z0-9._-]/g, "_")}.json`;
8026
7997
  }
8027
- async function waitUntilReady(ports, socketPath) {
8028
- const deadline = ports.now() + HEADROOM_READY_TIMEOUT_MS;
8029
- for (; ; ) {
8030
- if (await ports.ready(socketPath)) {
8031
- return true;
8032
- }
8033
- if (ports.now() >= deadline) {
8034
- return false;
8035
- }
8036
- await ports.sleep(HEADROOM_POLL_MS);
7998
+ function parseEntry(raw) {
7999
+ let json;
8000
+ try {
8001
+ json = JSON.parse(raw);
8002
+ } catch {
8003
+ return void 0;
8037
8004
  }
8005
+ const parsed = CachedCredentialSchema.safeParse(json);
8006
+ return parsed.success ? parsed.data : void 0;
8038
8007
  }
8039
-
8040
- // src/realPorts.ts
8041
- function isEnoent3(error) {
8042
- return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
8043
- }
8044
- var realFsPort = {
8045
- readFileUtf8(filePath) {
8046
- try {
8047
- return fs7.readFileSync(filePath, "utf8");
8048
- } catch (error) {
8049
- if (isEnoent3(error)) {
8050
- return void 0;
8051
- }
8052
- throw error;
8053
- }
8054
- },
8055
- readConfigFile: cosmiconfigReader(),
8056
- readdir(dirPath) {
8057
- try {
8058
- return fs7.readdirSync(dirPath);
8059
- } catch (error) {
8060
- if (isEnoent3(error)) {
8061
- return [];
8062
- }
8063
- throw error;
8064
- }
8008
+ function requireKeychain() {
8009
+ if (process.platform !== "darwin") {
8010
+ throw new Error("the keychain credential store needs macOS; use --credential-cache-store file");
8065
8011
  }
8066
- };
8067
- function isErrorWithCode(error, code) {
8068
- return typeof error === "object" && error !== null && "code" in error && error.code === code;
8069
8012
  }
8070
- var OWNER_ONLY_DIR_MODE = 448;
8071
- var OWNER_ONLY_FILE_MODE = 384;
8072
- var PERMISSION_BITS2 = 511;
8073
- var realFarmFs = {
8074
- lstat(filePath) {
8075
- let stat;
8076
- try {
8077
- stat = fs7.lstatSync(filePath);
8078
- } catch (error) {
8079
- if (isEnoent3(error)) {
8080
- return void 0;
8081
- }
8082
- throw error;
8083
- }
8084
- const kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "dir" : "file";
8085
- return { kind, mtimeMs: stat.mtimeMs, sizeBytes: stat.size };
8086
- },
8087
- readdir(dirPath) {
8088
- try {
8089
- return fs7.readdirSync(dirPath);
8090
- } catch (error) {
8091
- if (isEnoent3(error) || isErrorWithCode(error, "ENOTDIR")) {
8092
- return [];
8093
- }
8094
- throw error;
8095
- }
8096
- },
8097
- mkdirp(dirPath) {
8098
- fs7.mkdirSync(dirPath, { recursive: true });
8099
- },
8100
- symlink(target, linkPath) {
8101
- fs7.symlinkSync(target, linkPath);
8102
- },
8103
- rename(from, to) {
8104
- fs7.renameSync(from, to);
8105
- },
8106
- removeRecursive(targetPath) {
8107
- fs7.rmSync(targetPath, { recursive: true, force: true });
8108
- },
8109
- copyRecursive(from, to) {
8110
- fs7.cpSync(from, to, { recursive: true, force: true, verbatimSymlinks: true });
8111
- },
8112
- readFileUtf8(filePath) {
8113
- try {
8114
- return fs7.readFileSync(filePath, "utf8");
8115
- } catch (error) {
8116
- if (isEnoent3(error)) {
8117
- return void 0;
8118
- }
8119
- throw error;
8120
- }
8121
- },
8122
- writeFileUtf8(filePath, contents) {
8123
- fs7.writeFileSync(filePath, contents, "utf8");
8124
- },
8125
- mkdirPrivate(dirPath) {
8126
- fs7.mkdirSync(dirPath, { recursive: true, mode: OWNER_ONLY_DIR_MODE });
8127
- fs7.chmodSync(dirPath, OWNER_ONLY_DIR_MODE);
8128
- },
8129
- writeFilePrivate(filePath, contents) {
8130
- const temp = `${filePath}.${String(process.pid)}.tmp`;
8131
- fs7.writeFileSync(temp, contents, { encoding: "utf8", mode: OWNER_ONLY_FILE_MODE });
8132
- fs7.chmodSync(temp, OWNER_ONLY_FILE_MODE);
8133
- fs7.renameSync(temp, filePath);
8134
- },
8135
- appendFilePrivate(filePath, contents) {
8136
- const descriptor = fs7.openSync(filePath, "a", OWNER_ONLY_FILE_MODE);
8137
- try {
8138
- if ((fs7.fstatSync(descriptor).mode & PERMISSION_BITS2) !== OWNER_ONLY_FILE_MODE) {
8139
- fs7.fchmodSync(descriptor, OWNER_ONLY_FILE_MODE);
8013
+ function security(args, stdin) {
8014
+ const result = spawnSync2("security", args, { encoding: "utf8", ...stdin === void 0 ? {} : { input: stdin }, stdio: ["pipe", "pipe", "ignore"] });
8015
+ return { status: result.status, stdout: result.stdout };
8016
+ }
8017
+ function createRealCredentialCache(root) {
8018
+ const dir = path21.join(root, "credential-cache");
8019
+ const filePath = (owner) => path21.join(dir, cacheFileNameFor(owner));
8020
+ return {
8021
+ read(store, owner) {
8022
+ if (store === "file") {
8023
+ try {
8024
+ return parseEntry(fs6.readFileSync(filePath(owner), "utf8"));
8025
+ } catch {
8026
+ return void 0;
8027
+ }
8140
8028
  }
8141
- const bytes = Buffer.from(contents, "utf8");
8142
- let written = 0;
8143
- while (written < bytes.length) {
8144
- written += fs7.writeSync(descriptor, bytes, written, bytes.length - written);
8029
+ requireKeychain();
8030
+ const result = security(["find-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner, "-w"]);
8031
+ return result.status === 0 ? parseEntry(Buffer.from(result.stdout.trim(), "base64").toString("utf8")) : void 0;
8032
+ },
8033
+ write(store, owner, entry) {
8034
+ const serialised = JSON.stringify(entry);
8035
+ if (store === "file") {
8036
+ fs6.mkdirSync(dir, { recursive: true, mode: CACHE_DIR_MODE });
8037
+ fs6.chmodSync(dir, CACHE_DIR_MODE);
8038
+ const target = filePath(owner);
8039
+ const temp = `${target}.${String(process.pid)}.tmp`;
8040
+ fs6.writeFileSync(temp, serialised, { mode: CACHE_FILE_MODE });
8041
+ fs6.renameSync(temp, target);
8042
+ return;
8145
8043
  }
8146
- } finally {
8147
- fs7.closeSync(descriptor);
8148
- }
8149
- },
8150
- writeFileExclusive(filePath, contents) {
8151
- try {
8152
- fs7.writeFileSync(filePath, contents, { encoding: "utf8", flag: "wx" });
8153
- return true;
8154
- } catch (error) {
8155
- if (isErrorWithCode(error, "EEXIST")) {
8156
- return false;
8044
+ requireKeychain();
8045
+ const encoded = Buffer.from(serialised, "utf8").toString("base64");
8046
+ const result = security(["-i"], `add-generic-password -U -s ${KEYCHAIN_CACHE_SERVICE} -a ${JSON.stringify(owner)} -w ${encoded}
8047
+ `);
8048
+ if (result.status !== 0) {
8049
+ throw new Error(`could not write the keychain credential cache for ${owner}`);
8157
8050
  }
8158
- throw error;
8159
- }
8160
- },
8161
- hashFile(filePath) {
8162
- try {
8163
- return createHash5("sha256").update(fs7.readFileSync(filePath)).digest("hex");
8164
- } catch (error) {
8165
- if (isEnoent3(error) || isErrorWithCode(error, "EISDIR")) {
8166
- return void 0;
8051
+ },
8052
+ remove(store, owner) {
8053
+ if (store === "file") {
8054
+ fs6.rmSync(filePath(owner), { force: true });
8055
+ return;
8167
8056
  }
8168
- throw error;
8057
+ requireKeychain();
8058
+ security(["delete-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner]);
8169
8059
  }
8060
+ };
8061
+ }
8062
+ function realCredentialCacheEnv(paths) {
8063
+ return { port: createRealCredentialCache(paths.root), platform: process.platform, now: () => Date.now() };
8064
+ }
8065
+
8066
+ // src/providersStore.ts
8067
+ import fs7 from "node:fs";
8068
+ import path22 from "node:path";
8069
+
8070
+ // src/credential.ts
8071
+ var CREDENTIAL_UNAVAILABLE_EXIT = 64;
8072
+ var CREDENTIAL_COMMAND_TIMEOUT_MS = 1e4;
8073
+ var CREDENTIAL_INTERACTIVE_TIMEOUT_MS = 12e4;
8074
+ var GROUP_OTHER_PERMISSION_BITS = 63;
8075
+ var PERMISSION_BITS2 = 511;
8076
+ var OCTAL_RADIX2 = 8;
8077
+ function commandArgv(source) {
8078
+ if ("command" in source) {
8079
+ return source.command;
8170
8080
  }
8171
- };
8172
- var INT32_BYTE_LENGTH = 4;
8173
- function realSleepSync(ms) {
8174
- Atomics.wait(new Int32Array(new SharedArrayBuffer(INT32_BYTE_LENGTH)), 0, 0, ms);
8081
+ if ("op" in source) {
8082
+ return ["op", "read", source.op];
8083
+ }
8084
+ const account = source.keychain.account;
8085
+ return ["security", "find-generic-password", "-s", source.keychain.service, ...account === void 0 ? [] : ["-a", account], "-w"];
8175
8086
  }
8176
- function realIsProcessAlive(pid) {
8177
- try {
8178
- process.kill(pid, 0);
8179
- return true;
8180
- } catch (error) {
8181
- return isErrorWithCode(error, "EPERM");
8087
+ function isInteractiveSource(source, env) {
8088
+ if ("command" in source || "keychain" in source) {
8089
+ return source.interactive ?? false;
8090
+ }
8091
+ if ("op" in source) {
8092
+ return source.interactive ?? (env.OP_SERVICE_ACCOUNT_TOKEN ?? "") === "";
8182
8093
  }
8094
+ return false;
8183
8095
  }
8184
- function realIsProcessZombie(pid) {
8185
- const result = spawnSync2("ps", ["-o", "stat=", "-p", String(pid)], { encoding: "utf8" });
8186
- return result.status === 0 && result.stdout.trim().startsWith("Z");
8096
+ function summariseSource(source) {
8097
+ if ("env" in source) {
8098
+ return { kind: "env", variable: source.env };
8099
+ }
8100
+ if ("file" in source) {
8101
+ return { kind: "file", path: source.file };
8102
+ }
8103
+ if ("command" in source) {
8104
+ return { kind: "command", program: source.command[0] };
8105
+ }
8106
+ if ("op" in source) {
8107
+ return { kind: "op", reference: source.op };
8108
+ }
8109
+ if ("keychain" in source) {
8110
+ return {
8111
+ kind: "keychain",
8112
+ service: source.keychain.service,
8113
+ ...source.keychain.account === void 0 ? {} : { account: source.keychain.account }
8114
+ };
8115
+ }
8116
+ return { kind: "literal" };
8187
8117
  }
8188
- function realIsProcessRunning(pid) {
8189
- return realIsProcessAlive(pid) && !realIsProcessZombie(pid);
8118
+ function summariseCredential(credential) {
8119
+ return {
8120
+ target: credential.target ?? "bearer",
8121
+ sources: credential.sources.map(summariseSource),
8122
+ ...credential.cache === void 0 ? {} : { cache: credential.cache }
8123
+ };
8190
8124
  }
8191
- var realRunPort = {
8192
- run(command, args) {
8193
- const result = spawnSync2(command, [...args], { encoding: "utf8" });
8194
- return { status: result.status, stdout: result.stdout, stderr: result.stderr };
8125
+ function formatSourceSummary(summary) {
8126
+ switch (summary.kind) {
8127
+ case "env":
8128
+ return `env ${summary.variable}`;
8129
+ case "file":
8130
+ return `file ${summary.path}`;
8131
+ case "command":
8132
+ return `command ${summary.program}`;
8133
+ case "op":
8134
+ return `op ${summary.reference}`;
8135
+ case "keychain":
8136
+ return `keychain ${summary.service}${summary.account === void 0 ? "" : ` (account ${summary.account})`}`;
8137
+ case "literal":
8138
+ return "literal (non-secret placeholder)";
8139
+ default:
8140
+ return summary;
8195
8141
  }
8196
- };
8197
- function expandHome2(filePath) {
8198
- return filePath.startsWith("~/") ? path22.join(os4.homedir(), filePath.slice(1)) : filePath;
8199
8142
  }
8200
- var MACOS_GUI_SESSION_MANAGER = "Aqua";
8201
- function hasDesktopSession() {
8202
- if (process.platform === "darwin") {
8203
- const result = spawnSync2("launchctl", ["managername"], { encoding: "utf8" });
8204
- return result.status === 0 && result.stdout.trim() === MACOS_GUI_SESSION_MANAGER;
8143
+ function describeSource(source) {
8144
+ return formatSourceSummary(summariseSource(source));
8145
+ }
8146
+ function formatCredentialSummary(summary) {
8147
+ const cache = summary.cache === void 0 ? "" : `, cached ${summary.cache.ttl === void 0 ? "with no expiry" : `for ${summary.cache.ttl}`}${summary.cache.store === void 0 ? "" : ` in the ${summary.cache.store} store`}`;
8148
+ return `${summary.target} from ${summary.sources.map(formatSourceSummary).join(", then ")}${cache}`;
8149
+ }
8150
+ function describeCredential(credential) {
8151
+ return formatCredentialSummary(summariseCredential(credential));
8152
+ }
8153
+ function trySource(source, params) {
8154
+ const none = (reason, warn = false) => ({ ok: false, reason, warn });
8155
+ if ("env" in source) {
8156
+ const value = params.env[source.env];
8157
+ return value === void 0 || value === "" ? none("is unset or empty") : { ok: true, token: value };
8205
8158
  }
8206
- if (process.platform === "win32") {
8207
- return false;
8159
+ if ("literal" in source) {
8160
+ return { ok: true, token: source.literal };
8208
8161
  }
8209
- return (process.env.DISPLAY ?? "") !== "" || (process.env.WAYLAND_DISPLAY ?? "") !== "";
8210
- }
8211
- var realCredentialPort = {
8212
- readSecretFile(filePath) {
8213
- const resolved = expandHome2(filePath);
8214
- let content;
8215
- let stat;
8216
- try {
8217
- stat = fs7.statSync(resolved);
8218
- content = fs7.readFileSync(resolved, "utf8");
8219
- } catch (error) {
8220
- if (isEnoent3(error)) {
8221
- return { found: false };
8222
- }
8223
- throw error;
8162
+ if ("file" in source) {
8163
+ const read = params.port.readSecretFile(source.file);
8164
+ if (!read.found) {
8165
+ return none("does not exist");
8224
8166
  }
8225
- return { found: true, content, mode: process.platform === "win32" ? void 0 : stat.mode };
8226
- },
8227
- runCommand(argv, options) {
8228
- const [command, ...args] = argv;
8229
- const result = spawnSync2(command, args, {
8230
- encoding: "utf8",
8231
- timeout: options.timeoutMs,
8232
- stdio: options.interactive ? ["inherit", "pipe", "inherit"] : ["ignore", "pipe", "pipe"]
8233
- });
8234
- if (result.error !== void 0) {
8235
- const timedOut = isErrorWithCode(result.error, "ETIMEDOUT");
8236
- return { status: result.status, stdout: "", stderr: timedOut ? "" : result.error.message, timedOut };
8167
+ if (read.mode !== void 0 && (read.mode & GROUP_OTHER_PERMISSION_BITS) !== 0) {
8168
+ return none(
8169
+ `is readable or writable by group or others (mode ${(read.mode & PERMISSION_BITS2).toString(OCTAL_RADIX2)}); run \`chmod 600 ${source.file}\``,
8170
+ true
8171
+ );
8237
8172
  }
8238
- return { status: result.status, stdout: result.stdout, stderr: options.interactive ? "" : result.stderr, timedOut: false };
8239
- },
8240
- personPresent: () => process.stdin.isTTY || hasDesktopSession()
8241
- };
8242
- function resolveGitBranch(run, cwd) {
8243
- const result = run.run("git", ["-C", cwd, "rev-parse", "--abbrev-ref", "HEAD"]);
8244
- if (result.status !== 0) {
8245
- return {};
8173
+ const token2 = read.content.trim();
8174
+ return token2 === "" ? none("is empty") : { ok: true, token: token2 };
8246
8175
  }
8247
- const name = result.stdout.trim();
8248
- if (name === "") {
8249
- return {};
8176
+ const argv = commandArgv(source);
8177
+ const interactive = isInteractiveSource(source, params.env);
8178
+ if (interactive && !params.port.personPresent()) {
8179
+ return none("needs a person to approve it, but there is no terminal or desktop session");
8250
8180
  }
8251
- if (name === "HEAD") {
8252
- return { branchDetached: true };
8181
+ const timeoutMs = source.timeoutMs ?? (interactive ? CREDENTIAL_INTERACTIVE_TIMEOUT_MS : CREDENTIAL_COMMAND_TIMEOUT_MS);
8182
+ const result = params.port.runCommand(argv, { timeoutMs, interactive });
8183
+ if (result.timedOut) {
8184
+ return none(`timed out after ${String(timeoutMs)}ms`);
8253
8185
  }
8254
- return { branch: name, branchDetached: false };
8255
- }
8256
- function resolveGitRoot(run, cwd) {
8257
- const result = run.run("git", ["-C", cwd, "rev-parse", "--show-toplevel"]);
8258
8186
  if (result.status !== 0) {
8259
- return void 0;
8187
+ const stderr = result.stderr.trim();
8188
+ const outcome = result.status === null ? "could not be run or was killed by a signal" : `exited with status ${String(result.status)}`;
8189
+ return none(`${outcome}${stderr === "" ? "" : `: ${stderr}`}`);
8260
8190
  }
8261
- const root = result.stdout.trim();
8262
- return root === "" ? void 0 : root;
8263
- }
8264
- function detachedDaemonSpawnOptions(logFd, env) {
8265
- return { detached: true, stdio: ["ignore", logFd, logFd], env };
8266
- }
8267
- function spawnDaemonThrough(executable) {
8268
- return (paths, subcommand, logPath) => spawnDetachedDaemon({ command: executable, args: [subcommand] }, paths, subcommand, logPath);
8191
+ const token = result.stdout.trim();
8192
+ return token === "" ? none("printed no token") : { ok: true, token };
8269
8193
  }
8270
- function spawnDaemonThroughScript(script) {
8271
- return (paths, subcommand, logPath) => spawnDetachedDaemon({ command: process.execPath, args: [script, subcommand] }, paths, subcommand, logPath);
8194
+ function warmCommandFor(subject) {
8195
+ const [kind, name] = subject.split(" ");
8196
+ return kind === "provider" ? `agent-shim credential warm --provider ${name ?? ""}` : `agent-shim credential warm ${name ?? ""}`;
8272
8197
  }
8273
- function spawnDetachedDaemon(invocation, paths, subcommand, logPath) {
8274
- fs7.mkdirSync(path22.dirname(logPath), { recursive: true });
8275
- const logFd = fs7.openSync(logPath, "a");
8276
- try {
8277
- const child = spawn(invocation.command, invocation.args, detachedDaemonSpawnOptions(logFd, { ...process.env, AGENT_SHIM_HOME: paths.root }));
8278
- child.unref();
8279
- if (child.pid === void 0) {
8280
- throw new Error(`spawning ${subcommand} returned no pid`);
8198
+ function resolveCredential(params) {
8199
+ const target = params.credential.target ?? "bearer";
8200
+ const cacheBlock = params.credential.cache;
8201
+ const cache = cacheBlock === void 0 ? void 0 : params.port.cache;
8202
+ const store = cacheBlock === void 0 || cache === void 0 ? void 0 : effectiveStore(cacheBlock, cache.platform);
8203
+ if (cacheBlock !== void 0 && cache !== void 0 && store !== void 0 && params.refresh !== true) {
8204
+ const entry = cache.port.read(store, params.subject);
8205
+ if (entry !== void 0 && isFresh(entry, cacheBlock, cache.now())) {
8206
+ return { ok: true, credential: { target, token: entry.token, source: entry.source, warnings: [], cachedAt: entry.fetchedAt } };
8281
8207
  }
8282
- return child.pid;
8283
- } finally {
8284
- fs7.closeSync(logFd);
8285
8208
  }
8286
- }
8287
- function spawnHeadroomSupervisor(paths, spawnDaemon) {
8288
- return spawnDaemon(paths, "__headroom-supervisor", paths.headroomLogPath);
8289
- }
8290
- var SOCKET_PERMISSION_BITS = 511;
8291
- var realHeadroomSocketTrust = {
8292
- platform: process.platform,
8293
- currentUid: () => {
8294
- if (process.getuid === void 0) {
8295
- throw new Error(`no POSIX user id on ${process.platform}`);
8296
- }
8297
- return process.getuid();
8298
- },
8299
- lstat: (target) => {
8300
- let stat;
8301
- try {
8302
- stat = fs7.lstatSync(target);
8303
- } catch (error) {
8304
- if (isEnoent3(error)) {
8305
- return void 0;
8209
+ const attempts = [];
8210
+ const warnings = [];
8211
+ for (const source of params.credential.sources) {
8212
+ const outcome = trySource(source, params);
8213
+ if (outcome.ok) {
8214
+ if (cache !== void 0 && store !== void 0) {
8215
+ cache.port.write(store, params.subject, { token: outcome.token, fetchedAt: cache.now(), source });
8306
8216
  }
8307
- throw error;
8217
+ return { ok: true, credential: { target, token: outcome.token, source, warnings } };
8308
8218
  }
8309
- const kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "dir" : stat.isSocket() ? "socket" : "other";
8310
- return { kind, uid: stat.uid, mode: stat.mode & SOCKET_PERMISSION_BITS };
8219
+ const line = `${describeSource(source)} ${outcome.reason}`;
8220
+ attempts.push(line);
8221
+ if (outcome.warn) {
8222
+ warnings.push(`agent-shim: ${params.subject}: skipped ${line}`);
8223
+ }
8224
+ }
8225
+ const hint = cacheBlock === void 0 ? "" : `; fill the cache from a terminal with \`${warmCommandFor(params.subject)}\``;
8226
+ return { ok: false, message: `agent-shim: ${params.subject} has no usable credential: ${attempts.join("; ")}${hint}` };
8227
+ }
8228
+ function credentialVariables(target, token) {
8229
+ return {
8230
+ ANTHROPIC_AUTH_TOKEN: target === "bearer" ? token : void 0,
8231
+ ANTHROPIC_API_KEY: target === "apiKey" ? token : void 0,
8232
+ CLAUDE_CODE_OAUTH_TOKEN: target === "oauthToken" ? token : void 0
8233
+ };
8234
+ }
8235
+
8236
+ // src/providersStore.ts
8237
+ var ProviderNotFoundError = class extends CliError {
8238
+ constructor(name) {
8239
+ super(`No provider named "${name}". Run \`agent-shim provider add ${name}\` first.`);
8240
+ this.name = name;
8241
+ this.name = "ProviderNotFoundError";
8311
8242
  }
8243
+ name;
8312
8244
  };
8313
- function realHeadroomPort(paths, options) {
8245
+ var ProviderAlreadyExistsError = class extends CliError {
8246
+ constructor(providerName) {
8247
+ super(`A provider named "${providerName}" already exists.`);
8248
+ this.providerName = providerName;
8249
+ this.name = "ProviderAlreadyExistsError";
8250
+ }
8251
+ providerName;
8252
+ };
8253
+ var InvalidProviderNameError = class extends CliError {
8254
+ constructor(attemptedName) {
8255
+ super(
8256
+ `"${attemptedName}" is not a valid provider name. Provider names must start with a letter or number and may then contain letters, numbers, dots, hyphens, underscores, and at signs.`
8257
+ );
8258
+ this.attemptedName = attemptedName;
8259
+ this.name = "InvalidProviderNameError";
8260
+ }
8261
+ attemptedName;
8262
+ };
8263
+ var PROVIDER_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._@-]*$/;
8264
+ function providerJsonPath(paths, name) {
8265
+ return path22.join(paths.providersDir, `${name}.json`);
8266
+ }
8267
+ function providerExists(paths, name) {
8268
+ return fs7.existsSync(providerJsonPath(paths, name));
8269
+ }
8270
+ var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
8271
+ var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
8272
+ var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
8273
+ function isRecord4(value) {
8274
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8275
+ }
8276
+ function legacyProviderConversion(raw) {
8277
+ if (!isRecord4(raw) || "credential" in raw) {
8278
+ return void 0;
8279
+ }
8280
+ const env = isRecord4(raw.env) ? raw.env : void 0;
8281
+ const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
8282
+ const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
8283
+ if (fields.length === 0) {
8284
+ return void 0;
8285
+ }
8286
+ const sources = [];
8287
+ if (typeof raw.tokenEnv === "string") {
8288
+ sources.push({ env: raw.tokenEnv });
8289
+ }
8290
+ if (Array.isArray(raw.tokenCommand)) {
8291
+ sources.push({ command: raw.tokenCommand });
8292
+ }
8293
+ if (typeof env?.ANTHROPIC_AUTH_TOKEN === "string" && env.ANTHROPIC_AUTH_TOKEN !== "") {
8294
+ sources.push({ literal: LEGACY_LITERAL_PLACEHOLDER });
8295
+ }
8296
+ const remainingEnv = env === void 0 ? void 0 : Object.fromEntries(Object.entries(env).filter(([key]) => !LEGACY_ENV_CREDENTIAL_KEYS.includes(key)));
8297
+ const replacedKeys = [...LEGACY_PROVIDER_FIELDS, "env"];
8298
+ const rest = Object.fromEntries(Object.entries(raw).filter(([key]) => !replacedKeys.includes(key)));
8314
8299
  return {
8315
- ensure: ({ routesProvider }) => {
8316
- const up = ensureHeadroom({
8317
- paths,
8318
- launcherPid: process.pid,
8319
- ...routesProvider ? { requiredAllowlistHash: () => hashAllowlist(currentHeadroomAllowlist(realFarmFs, paths)) } : {},
8320
- ports: {
8321
- fs: realFarmFs,
8322
- // Zombie-aware on purpose: a supervisor that died while still a child of this launcher sits unreaped until the launcher itself exits, and a defunct supervisor answering signal 0 as alive would stretch every launch to the full start timeout.
8323
- isRunning: realIsProcessRunning,
8324
- now: () => Date.now(),
8325
- sleep: realSleepSync,
8326
- spawnSupervisor: (layout) => spawnHeadroomSupervisor(layout, options.spawnDaemon)
8327
- }
8328
- });
8329
- return {
8330
- socketPath: up.socketPath,
8331
- projectId: resolveGitRoot(realRunPort, options.cwd) ?? options.cwd
8332
- };
8333
- },
8334
- release: () => {
8335
- removeSession(realFarmFs, paths.headroomSessionsDir, process.pid);
8300
+ fields,
8301
+ replacement: {
8302
+ ...rest,
8303
+ credential: { sources, ...raw.authScheme === "apiKey" ? { target: "apiKey" } : {} },
8304
+ ...remainingEnv === void 0 || Object.keys(remainingEnv).length === 0 ? {} : { env: remainingEnv }
8336
8305
  }
8337
8306
  };
8338
8307
  }
8339
- var realProcPort = {
8340
- env: process.env,
8341
- argv: process.argv.slice(2),
8342
- exit: (code) => process.exit(code)
8308
+ var LegacyProviderFileError = class extends CliError {
8309
+ constructor(filePath, conversion) {
8310
+ super(
8311
+ `${filePath} uses ${conversion.fields.join(", ")}, which a credential block replaced. Rewrite it as:
8312
+ ` + JSON.stringify(conversion.replacement, null, 2)
8313
+ );
8314
+ this.filePath = filePath;
8315
+ this.conversion = conversion;
8316
+ this.name = "LegacyProviderFileError";
8317
+ }
8318
+ filePath;
8319
+ conversion;
8343
8320
  };
8344
- var EXECUTE_BITS_MASK = 73;
8345
- function listVersionsDir(dir) {
8346
- let entries;
8347
- try {
8348
- entries = fs7.readdirSync(dir, { withFileTypes: true });
8349
- } catch (error) {
8350
- if (isEnoent3(error)) {
8351
- return [];
8352
- }
8353
- throw error;
8321
+ function parseProviderFile(filePath, raw) {
8322
+ const conversion = legacyProviderConversion(raw);
8323
+ if (conversion !== void 0) {
8324
+ throw new LegacyProviderFileError(filePath, conversion);
8354
8325
  }
8355
- return entries.filter((entry) => entry.isFile()).map((entry) => {
8356
- const fullPath = path22.join(dir, entry.name);
8357
- const stat = fs7.statSync(fullPath);
8358
- return {
8359
- name: entry.name,
8360
- isFile: true,
8361
- isExecutable: (stat.mode & EXECUTE_BITS_MASK) !== 0,
8362
- sizeBytes: stat.size
8363
- };
8364
- });
8326
+ const parsed = ProviderSchema.safeParse(raw);
8327
+ if (!parsed.success) {
8328
+ throw new ConfigValidationError(filePath, parsed.error.issues);
8329
+ }
8330
+ return parsed.data;
8365
8331
  }
8366
- var WINDOWS_DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PS1";
8367
- function windowsPathExtensions(pathext) {
8368
- return (pathext ?? WINDOWS_DEFAULT_PATHEXT).split(";").map((ext) => ext.trim()).filter((ext) => ext.length > 0);
8332
+ function readProvider(paths, name) {
8333
+ const filePath = providerJsonPath(paths, name);
8334
+ if (!fs7.existsSync(filePath)) {
8335
+ return void 0;
8336
+ }
8337
+ const raw = JSON.parse(fs7.readFileSync(filePath, "utf8"));
8338
+ return parseProviderFile(filePath, raw);
8369
8339
  }
8370
- function resolveExecutableCandidate(dir, name, env) {
8371
- const candidate = path22.join(dir, name);
8372
- if (env.platform === "win32") {
8373
- const extensions = windowsPathExtensions(env.pathext);
8374
- const alreadyHasRecognisedExtension = extensions.some((ext) => candidate.toLowerCase().endsWith(ext.toLowerCase()));
8375
- if (alreadyHasRecognisedExtension) {
8376
- return env.statFileMode(candidate) !== void 0 ? candidate : void 0;
8377
- }
8378
- for (const ext of extensions) {
8379
- const withExt = candidate + ext;
8380
- if (env.statFileMode(withExt) !== void 0) {
8381
- return withExt;
8340
+ function loadProvider(providersDir, name, port) {
8341
+ const filePath = path22.join(providersDir, `${name}.json`);
8342
+ const raw = port.readConfigFile(filePath);
8343
+ return raw === void 0 ? void 0 : parseProviderFile(filePath, raw);
8344
+ }
8345
+ function listProviders(paths) {
8346
+ const names = listProviderNames(
8347
+ (dir) => fs7.readdirSync(dir),
8348
+ paths.providersDir
8349
+ );
8350
+ const result = [];
8351
+ for (const name of names) {
8352
+ let provider;
8353
+ try {
8354
+ provider = readProvider(paths, name);
8355
+ } catch (error) {
8356
+ if (!(error instanceof ConfigValidationError || error instanceof LegacyProviderFileError)) {
8357
+ throw error;
8382
8358
  }
8359
+ console.error(`agent-shim: providers/${name}.json is invalid and was skipped: ${error.message}`);
8360
+ continue;
8361
+ }
8362
+ if (provider !== void 0) {
8363
+ result.push({ name, provider });
8383
8364
  }
8384
- return void 0;
8385
8365
  }
8386
- const mode = env.statFileMode(candidate);
8387
- return mode !== void 0 && (mode & EXECUTE_BITS_MASK) !== 0 ? candidate : void 0;
8366
+ return result;
8388
8367
  }
8389
- function realStatFileMode(candidate) {
8368
+ function listProviderNames(readdir, providersDir) {
8369
+ let entries;
8390
8370
  try {
8391
- const stat = fs7.statSync(candidate);
8392
- return stat.isFile() ? stat.mode : void 0;
8393
- } catch (error) {
8394
- if (isEnoent3(error)) {
8395
- return void 0;
8396
- }
8397
- throw error;
8371
+ entries = readdir(providersDir);
8372
+ } catch {
8373
+ return [];
8398
8374
  }
8375
+ return entries.filter((entry) => entry.endsWith(".json")).map((entry) => entry.slice(0, -".json".length)).sort();
8399
8376
  }
8400
- function findExecutableInDir(dir, name) {
8401
- return resolveExecutableCandidate(dir, name, {
8402
- platform: process.platform,
8403
- pathext: process.env.PATHEXT,
8404
- statFileMode: realStatFileMode
8405
- });
8377
+ function describeProviderEndpoint(provider) {
8378
+ return isCodexProvider(provider) ? "codex translation daemon" : provider.baseUrl;
8406
8379
  }
8407
- function searchPathForExecutable(pathDirs, name, findExecutable) {
8408
- for (const dir of pathDirs) {
8409
- const found = findExecutable(dir, name);
8410
- if (found !== void 0) {
8411
- return found;
8412
- }
8380
+ var ProviderKindMismatchError = class extends UsageError {
8381
+ constructor(message) {
8382
+ super(message);
8383
+ this.name = "ProviderKindMismatchError";
8413
8384
  }
8414
- return void 0;
8385
+ };
8386
+ function isEmptyCodexConfig(config) {
8387
+ return config === void 0 || config.defaultModel === void 0 && config.effort === void 0 && config.login === void 0 && (config.models === void 0 || Object.keys(config.models).length === 0);
8415
8388
  }
8416
- function resolveOwnExecutablePath(env) {
8417
- if (env.argv1 !== void 0 && !env.argv1.includes("/") && !env.argv1.includes("\\")) {
8418
- return searchPathForExecutable(env.pathDirs, env.argv1, env.findExecutableInDir) ?? env.execPath;
8389
+ function addProvider(paths, name, input) {
8390
+ if (providerExists(paths, name)) {
8391
+ throw new ProviderAlreadyExistsError(name);
8419
8392
  }
8420
- const candidate = env.argv1 === void 0 ? env.execPath : path22.resolve(env.cwd, env.argv1);
8421
- const candidateDir = path22.dirname(candidate);
8422
- const candidateDirOnPath = env.pathDirs.some((dir) => path22.resolve(dir) === candidateDir);
8423
- if (candidateDirOnPath) {
8424
- return candidate;
8393
+ if (!PROVIDER_NAME_RE.test(name)) {
8394
+ throw new InvalidProviderNameError(name);
8425
8395
  }
8426
- const viaPath = searchPathForExecutable(env.pathDirs, path22.basename(candidate), env.findExecutableInDir);
8427
- return viaPath ?? candidate;
8428
- }
8429
- function realOwnExecutablePath() {
8430
- return resolveOwnExecutablePath({
8431
- argv1: process.argv[1],
8432
- execPath: process.execPath,
8433
- cwd: process.cwd(),
8434
- pathDirs: (process.env.PATH ?? "").split(path22.delimiter).filter((dir) => dir !== ""),
8435
- findExecutableInDir
8436
- });
8396
+ const kind = input.kind ?? "http";
8397
+ if (kind === "codex" && input.baseUrl !== void 0) {
8398
+ throw new ProviderKindMismatchError("A codex provider has no base URL: the launcher points its sessions at the front-door daemon. Drop --base-url.");
8399
+ }
8400
+ if (kind === "http" && input.baseUrl === void 0) {
8401
+ throw new ProviderKindMismatchError("An http provider needs --base-url.");
8402
+ }
8403
+ if (kind === "http" && !isEmptyCodexConfig(input.codex)) {
8404
+ throw new ProviderKindMismatchError("Codex settings apply only to a codex provider (--kind codex).");
8405
+ }
8406
+ const shared = {
8407
+ displayName: input.displayName,
8408
+ credential: { sources: input.sources, ...input.target === void 0 ? {} : { target: input.target } },
8409
+ ...input.env === void 0 || Object.keys(input.env).length === 0 ? {} : { env: input.env }
8410
+ };
8411
+ const parsed = ProviderSchema.safeParse(
8412
+ kind === "codex" ? { kind, ...shared, ...isEmptyCodexConfig(input.codex) ? {} : { codex: input.codex } } : { ...shared, baseUrl: input.baseUrl }
8413
+ );
8414
+ if (!parsed.success) {
8415
+ throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
8416
+ }
8417
+ writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
8418
+ return parsed.data;
8437
8419
  }
8438
- function resolveContentSourcePath(env) {
8439
- return env.isSea ? env.execPath : env.argv1 ?? env.execPath;
8420
+ function updateProvider(paths, name, input) {
8421
+ const existing = readProvider(paths, name);
8422
+ if (existing === void 0) {
8423
+ throw new ProviderNotFoundError(name);
8424
+ }
8425
+ const unset = new Set(input.unsetEnv);
8426
+ const env = Object.fromEntries(Object.entries({ ...existing.env, ...input.env }).filter(([key]) => !unset.has(key)));
8427
+ const target = input.target ?? existing.credential.target;
8428
+ const cache = input.cache === void 0 ? existing.credential.cache : input.cache === false ? void 0 : input.cache;
8429
+ if (isCodexProvider(existing) && input.baseUrl !== void 0) {
8430
+ throw new ProviderKindMismatchError(`Provider "${name}" is a codex provider, which has no base URL.`);
8431
+ }
8432
+ if (!isCodexProvider(existing) && !isEmptyCodexConfig(input.codex)) {
8433
+ throw new ProviderKindMismatchError(`Provider "${name}" is an http provider; codex settings apply only to a codex provider.`);
8434
+ }
8435
+ const shared = {
8436
+ ...input.displayName === void 0 ? {} : { displayName: input.displayName },
8437
+ credential: {
8438
+ sources: input.sources ?? existing.credential.sources,
8439
+ ...target === void 0 ? {} : { target },
8440
+ ...cache === void 0 ? {} : { cache }
8441
+ },
8442
+ env: Object.keys(env).length === 0 ? void 0 : env
8443
+ };
8444
+ let candidate;
8445
+ if (isCodexProvider(existing)) {
8446
+ const codex = {
8447
+ ...existing.codex,
8448
+ ...input.codex,
8449
+ ...existing.codex?.models === void 0 && input.codex?.models === void 0 ? {} : { models: { ...existing.codex?.models, ...input.codex?.models } }
8450
+ };
8451
+ candidate = { ...existing, ...shared, codex: isEmptyCodexConfig(codex) ? void 0 : codex };
8452
+ } else {
8453
+ candidate = { ...existing, ...shared, ...input.baseUrl === void 0 ? {} : { baseUrl: input.baseUrl } };
8454
+ }
8455
+ const parsed = ProviderSchema.safeParse(candidate);
8456
+ if (!parsed.success) {
8457
+ throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
8458
+ }
8459
+ writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
8460
+ return parsed.data;
8440
8461
  }
8441
- function realContentSourcePath() {
8442
- return resolveContentSourcePath({ isSea: isSea(), execPath: process.execPath, argv1: process.argv[1] });
8462
+ function removeProvider(paths, name) {
8463
+ if (!providerExists(paths, name)) {
8464
+ throw new ProviderNotFoundError(name);
8465
+ }
8466
+ fs7.rmSync(providerJsonPath(paths, name));
8443
8467
  }
8444
- function realResolveClaudeBinary(isOwnBinary) {
8445
- return (request4) => discoverClaudeBinary({
8446
- listVersionsDir,
8447
- pathDirs: (process.env.PATH ?? "").split(path22.delimiter).filter((dir) => dir !== ""),
8448
- findExecutableInDir,
8449
- isOwnBinary,
8450
- ...request4?.version === void 0 ? {} : { version: request4.version }
8451
- });
8468
+ function cascadeProviderName(cascade) {
8469
+ let name;
8470
+ for (const layer of assembleCascade(cascade).layers) {
8471
+ if (layer.launch?.provider !== void 0) {
8472
+ name = layer.launch.provider;
8473
+ }
8474
+ }
8475
+ return name;
8452
8476
  }
8453
- function realInstalledClaudeVersions() {
8454
- return installedVersions(listVersionsDir(defaultVersionsDir()));
8477
+ function resolveProvider(params) {
8478
+ const cliName = params.cliProvider !== void 0 && params.cliProvider !== "" ? params.cliProvider : void 0;
8479
+ const name = cliName ?? (params.cascade === void 0 ? void 0 : cascadeProviderName(params.cascade));
8480
+ if (name === void 0) {
8481
+ return void 0;
8482
+ }
8483
+ const definition = loadProvider(params.paths.providersDir, name, params.port);
8484
+ if (definition === void 0) {
8485
+ const known = listProviderNames(params.port.readdir, params.paths.providersDir);
8486
+ return {
8487
+ ok: false,
8488
+ status: 1,
8489
+ message: `agent-shim: no provider named "${name}". ` + (known.length > 0 ? `Known providers: ${known.join(", ")}.` : "No providers are defined yet; run `agent-shim provider add`.")
8490
+ };
8491
+ }
8492
+ if (params.credentials === void 0) {
8493
+ return { ok: false, status: 1, message: `agent-shim: provider ${name} needs its credential resolved, but this launcher has no credential port wired` };
8494
+ }
8495
+ const resolution = resolveCredential({ credential: definition.credential, env: params.env, port: params.credentials, subject: `provider ${name}` });
8496
+ if (!resolution.ok) {
8497
+ return { ok: false, status: CREDENTIAL_UNAVAILABLE_EXIT, message: resolution.message };
8498
+ }
8499
+ return { ok: true, provider: { name, definition, credential: resolution.credential }, warnings: resolution.credential.warnings };
8455
8500
  }
8456
8501
 
8457
8502
  // src/checkReport.ts
@@ -8890,42 +8935,14 @@ function loadCheckProvider(paths, name) {
8890
8935
  }
8891
8936
  function collectCheckReport(params) {
8892
8937
  const { paths, cwd } = params;
8893
- const home = os5.homedir();
8938
+ const home = os6.homedir();
8894
8939
  const claudeHome = resolveClaudeHome();
8895
8940
  const read = cosmiconfigReader();
8896
8941
  const classification = loadClassification(paths);
8897
8942
  const loaded = loadCascadeInput({ paths, home, cwd, read });
8898
- const selections = readDirectorySelections(loaded);
8899
8943
  const git = resolveGitBranch(realRunPort, cwd);
8900
- const decidedIdentity = decideIdentity({
8901
- env: params.env,
8902
- argv0Identity: params.identity,
8903
- directoryPinnedIdentity: selections.identity,
8904
- readActiveIdentityFile: () => {
8905
- const raw = realFsPort.readFileUtf8(paths.activeIdentityFile);
8906
- if (raw === void 0) {
8907
- return void 0;
8908
- }
8909
- const trimmed = raw.trim();
8910
- return trimmed === "" ? void 0 : trimmed;
8911
- }
8912
- });
8913
- let poolPick;
8914
- if (decidedIdentity.pool !== void 0) {
8915
- const pools = loaded.globalConfig?.pools ?? {};
8916
- if (pools[decidedIdentity.pool] === void 0) {
8917
- throw new PoolNotFoundError(decidedIdentity.pool);
8918
- }
8919
- poolPick = collectPoolPick({ paths, fs: realFsPort, usageFs: realFarmFs, poolName: decidedIdentity.pool, pools, directory: cwd, nowMs: Date.now() });
8920
- }
8921
- const identityDecision = poolPick?.pick === void 0 ? decidedIdentity : { ...decidedIdentity, name: poolPick.pick };
8922
- const loadedIdentity = identityDecision.name === void 0 ? void 0 : loadIdentity(paths.identitiesDir, identityDecision.name, realFsPort);
8923
- const configProfileDecision = decideConfigProfile({
8924
- env: params.env,
8925
- directoryRuleConfigProfile: selections.configProfile,
8926
- identityDefaultConfigProfile: loadedIdentity?.config.defaultConfigProfile,
8927
- globalDefaultConfigProfile: loaded.globalConfig?.defaultConfigProfile
8928
- });
8944
+ const { selection, identityConfig } = selectLaunch({ paths, cwd, env: params.env, ...params.identity === void 0 ? {} : { identity: params.identity } }, loaded);
8945
+ const { identity: identityDecision, poolPick, configProfile: configProfileDecision } = selection;
8929
8946
  const cascade = loadCascadeInput({
8930
8947
  paths,
8931
8948
  home,
@@ -8956,7 +8973,7 @@ function collectCheckReport(params) {
8956
8973
  ...poolPick === void 0 ? {} : { poolPick },
8957
8974
  ...configProfileDecision.name === void 0 ? {} : { configProfileName: configProfileDecision.name },
8958
8975
  configProfileSource: configProfileDecision.source,
8959
- ...loadedIdentity === void 0 ? {} : { identity: loadedIdentity.config },
8976
+ ...identityConfig === void 0 ? {} : { identity: identityConfig },
8960
8977
  settingsFiles,
8961
8978
  run: realRunPort,
8962
8979
  credentialCache: realCredentialCacheEnv(paths),
@@ -12830,14 +12847,14 @@ function resolvePoolLaunch(params) {
12830
12847
  }
12831
12848
 
12832
12849
  // src/launcher/spawn.ts
12833
- import os6 from "node:os";
12850
+ import os7 from "node:os";
12834
12851
  var SIGNAL_EXIT_CODE_OFFSET = 128;
12835
12852
  function exitCodeFor(result) {
12836
12853
  if (result.status !== null) {
12837
12854
  return result.status;
12838
12855
  }
12839
12856
  if (result.signal !== null) {
12840
- return SIGNAL_EXIT_CODE_OFFSET + os6.constants.signals[result.signal];
12857
+ return SIGNAL_EXIT_CODE_OFFSET + os7.constants.signals[result.signal];
12841
12858
  }
12842
12859
  return 1;
12843
12860
  }
@@ -13238,7 +13255,7 @@ function runLauncher(params) {
13238
13255
  }
13239
13256
 
13240
13257
  // src/launchWiring.ts
13241
- import os7 from "node:os";
13258
+ import os8 from "node:os";
13242
13259
  import { randomUUID as randomUUID5 } from "node:crypto";
13243
13260
 
13244
13261
  // src/ownCli.ts
@@ -13403,7 +13420,7 @@ function realFrontDoorPort(paths, spawnDaemon) {
13403
13420
 
13404
13421
  // src/launchWiring.ts
13405
13422
  function buildFarmRuntime(paths, cwd) {
13406
- const home = os7.homedir();
13423
+ const home = os8.homedir();
13407
13424
  const read = cosmiconfigReader();
13408
13425
  const classification = loadClassification(paths);
13409
13426
  const loaded = loadCascadeInput({ paths, home, cwd, read });
@@ -13739,6 +13756,7 @@ export {
13739
13756
  resolveAgentShimHome,
13740
13757
  resolveClaudeHome,
13741
13758
  resolveDecisions,
13759
+ resolveLaunchSelection,
13742
13760
  resolveLayoutPaths,
13743
13761
  resolveSupervisorConfig,
13744
13762
  resyncFarm,