claude-use 5.0.0 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -285659,11 +285659,36 @@ var PortableConfigSchema = ConfigProfileSchema.omit({ description: true }).exten
285659
285659
  identity: external_exports.string().min(1).optional(),
285660
285660
  when: WhenSchema.optional()
285661
285661
  });
285662
- var HeadroomGlobalConfigSchema = external_exports.strictObject({
285662
+ var HEADROOM_MODES = ["cache", "token"];
285663
+ var HEADROOM_CCR_MODES = ["default", "lossless", "none"];
285664
+ var HEADROOM_ROLLOUT_CHANNELS = ["beta", "canary", "dev"];
285665
+ var INTERCEPT_CHANNELS = ["canary", "dev"];
285666
+ var READ_MATURATION_CHANNELS = ["beta", "dev"];
285667
+ var HeadroomGlobalConfigObjectSchema = external_exports.strictObject({
285663
285668
  /** Install spec handed to `uv tool install`. Defaults to HEADROOM_DEFAULT_SOURCE. */
285664
285669
  source: external_exports.string().min(1).optional(),
285665
285670
  /** How long the daemon may sit with no registered sessions before the supervisor stops it. Defaults to HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES. */
285666
- idleShutdownMinutes: external_exports.number().int().positive().optional()
285671
+ idleShutdownMinutes: external_exports.number().int().positive().optional(),
285672
+ /** Optimisation mode, passed as `--mode`. Unset leaves headroom's own default (`cache`). */
285673
+ mode: external_exports.enum(HEADROOM_MODES).optional(),
285674
+ /** Keep-ratio for prose and code compression, passed as `--target-ratio`: lower is more aggressive. Unset lets headroom decide. */
285675
+ targetRatio: external_exports.number().gt(0).max(1).optional(),
285676
+ /** How compressed tool output stays recoverable. Unset leaves headroom's own default (`default`). */
285677
+ ccr: external_exports.enum(HEADROOM_CCR_MODES).optional(),
285678
+ /** Headroom's experimental release channel, exported as `HEADROOM_ROLLOUT_CHANNEL`. Required by `interceptToolResults` and `readMaturation`, which are refused without a channel that unlocks them. */
285679
+ rolloutChannel: external_exports.enum(HEADROOM_ROLLOUT_CHANNELS).optional(),
285680
+ /** Opt in to headroom's tool-result interceptors (`--intercept-tool-results`). Needs `rolloutChannel` canary or dev. */
285681
+ interceptToolResults: external_exports.boolean().optional(),
285682
+ /** Opt in to headroom's experimental read maturation (`--read-maturation`). Needs `rolloutChannel` beta or dev. */
285683
+ readMaturation: external_exports.boolean().optional()
285684
+ });
285685
+ var HeadroomGlobalConfigSchema = HeadroomGlobalConfigObjectSchema.superRefine((config2, context) => {
285686
+ if (config2.interceptToolResults === true && !INTERCEPT_CHANNELS.includes(config2.rolloutChannel ?? "")) {
285687
+ context.addIssue({ code: "custom", path: ["interceptToolResults"], message: "interceptToolResults needs rolloutChannel canary or dev" });
285688
+ }
285689
+ if (config2.readMaturation === true && !READ_MATURATION_CHANNELS.includes(config2.rolloutChannel ?? "")) {
285690
+ context.addIssue({ code: "custom", path: ["readMaturation"], message: "readMaturation needs rolloutChannel beta or dev" });
285691
+ }
285667
285692
  });
285668
285693
  var HEADROOM_DEFAULT_SOURCE = "headroom-ai[proxy] @ git+https://github.com/ExaDev/headroom@eb02fa4126450c9905a49394fdec19ae2e7c9c30";
285669
285694
  var HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES = 15;
@@ -285730,6 +285755,8 @@ var HeadroomStateSchema = external_exports.strictObject({
285730
285755
  version: external_exports.string().optional(),
285731
285756
  /** Hash of the allowlist the running proxy was started with, so a provider-file change is detected as drift. */
285732
285757
  allowlistHash: external_exports.string().optional(),
285758
+ /** Hash of the token-saving settings the running proxy was started with, so a settings change is detected as drift. */
285759
+ settingsHash: external_exports.string().optional(),
285733
285760
  /** The install spec the running install was last installed from, so a `source` config change is detected as drift. */
285734
285761
  installedSource: external_exports.string().optional(),
285735
285762
  /** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
@@ -291234,7 +291261,7 @@ function resolveLaunchIdentity(paths, cwd) {
291234
291261
  // package.json
291235
291262
  var package_default = {
291236
291263
  name: "claude-use",
291237
- version: "5.0.0",
291264
+ version: "5.0.1",
291238
291265
  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.",
291239
291266
  license: "Apache-2.0",
291240
291267
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -296096,11 +296123,41 @@ var import_node_fs14 = __toESM(require("node:fs"), 1);
296096
296123
  var import_node_net2 = __toESM(require("node:net"), 1);
296097
296124
  var import_node_child_process5 = require("node:child_process");
296098
296125
 
296126
+ // src/headroom/settings.ts
296127
+ var import_node_crypto12 = require("node:crypto");
296128
+ function settingsOf(config2) {
296129
+ return {
296130
+ ...config2.mode === void 0 ? {} : { mode: config2.mode },
296131
+ ...config2.targetRatio === void 0 ? {} : { targetRatio: config2.targetRatio },
296132
+ ...config2.ccr === void 0 ? {} : { ccr: config2.ccr },
296133
+ ...config2.rolloutChannel === void 0 ? {} : { rolloutChannel: config2.rolloutChannel },
296134
+ ...config2.interceptToolResults === void 0 ? {} : { interceptToolResults: config2.interceptToolResults },
296135
+ ...config2.readMaturation === void 0 ? {} : { readMaturation: config2.readMaturation }
296136
+ };
296137
+ }
296138
+ function settingsArgs(settings2) {
296139
+ return [
296140
+ ...settings2.mode === void 0 ? [] : ["--mode", settings2.mode],
296141
+ ...settings2.targetRatio === void 0 ? [] : ["--target-ratio", String(settings2.targetRatio)],
296142
+ ...settings2.ccr === "lossless" ? ["--lossless"] : [],
296143
+ ...settings2.ccr === "none" ? ["--no-ccr"] : [],
296144
+ ...settings2.interceptToolResults === true ? ["--intercept-tool-results"] : [],
296145
+ ...settings2.readMaturation === true ? ["--read-maturation"] : []
296146
+ ];
296147
+ }
296148
+ function settingsEnv(settings2) {
296149
+ return settings2.rolloutChannel === void 0 ? {} : { HEADROOM_ROLLOUT_CHANNEL: settings2.rolloutChannel };
296150
+ }
296151
+ function hashSettings(settings2) {
296152
+ return (0, import_node_crypto12.createHash)("sha256").update(JSON.stringify([...settingsArgs(settings2), JSON.stringify(settingsEnv(settings2))])).digest("hex");
296153
+ }
296154
+
296099
296155
  // src/headroom/supervisor.ts
296100
296156
  function resolveSupervisorConfig(configured) {
296101
296157
  return {
296102
296158
  source: configured.source ?? HEADROOM_DEFAULT_SOURCE,
296103
- idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES
296159
+ idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES,
296160
+ settings: settingsOf(configured)
296104
296161
  };
296105
296162
  }
296106
296163
  var HEADROOM_BACKOFF_BASE_MS = 500;
@@ -296193,8 +296250,9 @@ function compareVersions2(a2, b) {
296193
296250
  return 0;
296194
296251
  }
296195
296252
  var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
296196
- async function runSupervisor(config2, ports, options = {}) {
296253
+ async function runSupervisor(initialConfig, ports, options = {}) {
296197
296254
  const { fs: fs16, paths } = ports;
296255
+ let config2 = initialConfig;
296198
296256
  const previousState = readHeadroomState(fs16, paths.headroomStateFile);
296199
296257
  let installedSource = previousState?.installedSource;
296200
296258
  let lastPort = previousState?.lastPort;
@@ -296236,6 +296294,7 @@ async function runSupervisor(config2, ports, options = {}) {
296236
296294
  }
296237
296295
  let headroomPid;
296238
296296
  let runningHash;
296297
+ let runningSettingsHash;
296239
296298
  let consecutiveFailures = 0;
296240
296299
  let idleSince;
296241
296300
  let ticks = 0;
@@ -296244,6 +296303,8 @@ async function runSupervisor(config2, ports, options = {}) {
296244
296303
  return HEADROOM_SUPERVISOR_STILL_RUNNING;
296245
296304
  }
296246
296305
  ticks += 1;
296306
+ config2 = ports.readConfig();
296307
+ const settingsHash = hashSettings(config2.settings);
296247
296308
  const allowlist = allowlistOf(ports);
296248
296309
  const allowlistHash = hashAllowlist(allowlist);
296249
296310
  pruneDeadSessions(fs16, paths.headroomSessionsDir, ports.isRunning);
@@ -296253,6 +296314,7 @@ async function runSupervisor(config2, ports, options = {}) {
296253
296314
  ports.log(`claude-use headroom supervisor: headroom pid ${String(headroomPid)} died`);
296254
296315
  headroomPid = void 0;
296255
296316
  runningHash = void 0;
296317
+ runningSettingsHash = void 0;
296256
296318
  writeHeadroomState(fs16, paths.headroomStateFile, {
296257
296319
  supervisorPid: ports.ownPid,
296258
296320
  version: version2,
@@ -296268,12 +296330,13 @@ async function runSupervisor(config2, ports, options = {}) {
296268
296330
  }
296269
296331
  const previousSticky = lastPort;
296270
296332
  const port = lastPort !== void 0 && await ports.isPortFree(lastPort) ? lastPort : await ports.freePort();
296271
- const pid = ports.spawnHeadroom(port, allowlist);
296333
+ const pid = ports.spawnHeadroom(port, allowlist, config2.settings);
296272
296334
  const ready = await waitUntilReady(ports, port);
296273
296335
  if (ready) {
296274
296336
  consecutiveFailures = 0;
296275
296337
  headroomPid = pid;
296276
296338
  runningHash = allowlistHash;
296339
+ runningSettingsHash = settingsHash;
296277
296340
  lastPort = port;
296278
296341
  if (previousSticky !== void 0 && port !== previousSticky) {
296279
296342
  ports.log(
@@ -296286,6 +296349,7 @@ async function runSupervisor(config2, ports, options = {}) {
296286
296349
  port,
296287
296350
  version: version2,
296288
296351
  allowlistHash,
296352
+ settingsHash,
296289
296353
  installedSource: config2.source,
296290
296354
  ...sticky()
296291
296355
  });
@@ -296302,12 +296366,13 @@ async function runSupervisor(config2, ports, options = {}) {
296302
296366
  continue;
296303
296367
  }
296304
296368
  } else {
296305
- if (runningHash !== allowlistHash || installedSource !== config2.source) {
296369
+ if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config2.source) {
296306
296370
  if (listSessions(fs16, paths.headroomSessionsDir).length === 0) {
296307
296371
  ports.log("claude-use headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
296308
296372
  ports.stopProcess(headroomPid);
296309
296373
  headroomPid = void 0;
296310
296374
  runningHash = void 0;
296375
+ runningSettingsHash = void 0;
296311
296376
  continue;
296312
296377
  }
296313
296378
  }
@@ -296357,6 +296422,7 @@ async function waitUntilReady(ports, port) {
296357
296422
  function collectHeadroomStatus(fsPort, paths, isRunning) {
296358
296423
  const state = readHeadroomState(fsPort, paths.headroomStateFile) ?? {};
296359
296424
  const allowlist = currentHeadroomAllowlist(fsPort, paths);
296425
+ const settings2 = settingsOf(readGlobalConfig(paths)?.headroom ?? {});
296360
296426
  return {
296361
296427
  state,
296362
296428
  supervisorAlive: state.supervisorPid !== void 0 && isRunning(state.supervisorPid),
@@ -296367,6 +296433,8 @@ function collectHeadroomStatus(fsPort, paths, isRunning) {
296367
296433
  })),
296368
296434
  allowlist,
296369
296435
  allowlistDrifted: state.allowlistHash !== void 0 && state.allowlistHash !== hashAllowlist(allowlist),
296436
+ settings: settings2,
296437
+ settingsDrifted: state.settingsHash !== void 0 && state.settingsHash !== hashSettings(settings2),
296370
296438
  logPath: paths.headroomLogPath,
296371
296439
  logExists: fsPort.readFileUtf8(paths.headroomLogPath) !== void 0
296372
296440
  };
@@ -296389,6 +296457,9 @@ function formatHeadroomStatus(status) {
296389
296457
  }
296390
296458
  const drift = status.allowlistDrifted ? " (DRIFTED: current provider files differ from the running daemon's allowlist; a restart is pending)" : "";
296391
296459
  lines.push(`allowlist: ${status.allowlist.join(", ")}${drift}`);
296460
+ const configured = Object.entries(status.settings).map(([name, value]) => `${name} ${String(value)}`);
296461
+ const settingsDrift = status.settingsDrifted ? " (DRIFTED: the configured settings differ from the running daemon's; a restart is pending)" : "";
296462
+ lines.push(`settings: ${configured.length === 0 ? "headroom defaults" : configured.join(", ")}${settingsDrift}`);
296392
296463
  if (status.sessions.length === 0) {
296393
296464
  lines.push("sessions: none");
296394
296465
  } else {
@@ -296425,11 +296496,12 @@ var READY_FETCH_TIMEOUT_MS = 2e3;
296425
296496
  function headroomPidRunning(pid) {
296426
296497
  return !exitedHeadroom.has(pid) && realIsProcessRunning(pid);
296427
296498
  }
296428
- function headroomSpawnEnv(parentEnv, allowlist) {
296499
+ function headroomSpawnEnv(parentEnv, allowlist, settings2) {
296429
296500
  return {
296430
296501
  ...parentEnv,
296431
296502
  HEADROOM_ALLOWED_BASE_URLS: allowlist.join(","),
296432
- HEADROOM_HTTP2: parentEnv.HEADROOM_HTTP2 ?? "0"
296503
+ HEADROOM_HTTP2: parentEnv.HEADROOM_HTTP2 ?? "0",
296504
+ ...settingsEnv(settings2)
296433
296505
  };
296434
296506
  }
296435
296507
  function realSupervisorPorts(paths) {
@@ -296447,14 +296519,15 @@ function realSupervisorPorts(paths) {
296447
296519
  isRunning: headroomPidRunning,
296448
296520
  freePort: realFreePort,
296449
296521
  isPortFree: realIsPortFree,
296450
- spawnHeadroom: (port, allowlist) => {
296522
+ readConfig: () => resolveSupervisorConfig(readGlobalConfig(paths)?.headroom ?? {}),
296523
+ spawnHeadroom: (port, allowlist, settings2) => {
296451
296524
  import_node_fs14.default.mkdirSync(paths.logsDir, { recursive: true });
296452
296525
  const logFd = import_node_fs14.default.openSync(paths.headroomLogPath, "a");
296453
296526
  try {
296454
- const child = (0, import_node_child_process5.spawn)("headroom", ["proxy", "--host", "127.0.0.1", "--port", String(port)], {
296527
+ const child = (0, import_node_child_process5.spawn)("headroom", ["proxy", "--host", "127.0.0.1", "--port", String(port), ...settingsArgs(settings2)], {
296455
296528
  detached: true,
296456
296529
  stdio: ["ignore", logFd, logFd],
296457
- env: headroomSpawnEnv(process.env, allowlist)
296530
+ env: headroomSpawnEnv(process.env, allowlist, settings2)
296458
296531
  });
296459
296532
  if (child.pid === void 0) {
296460
296533
  throw new Error("spawning headroom returned no pid");
@@ -296625,10 +296698,10 @@ function buildProgram(deps) {
296625
296698
 
296626
296699
  // src/runClaude.ts
296627
296700
  var import_node_os8 = __toESM(require("node:os"), 1);
296628
- var import_node_crypto13 = require("node:crypto");
296701
+ var import_node_crypto14 = require("node:crypto");
296629
296702
 
296630
296703
  // src/launcher.ts
296631
- var import_node_crypto12 = require("node:crypto");
296704
+ var import_node_crypto13 = require("node:crypto");
296632
296705
  var import_node_path29 = __toESM(require("node:path"), 1);
296633
296706
 
296634
296707
  // src/launcher/cliOverride.ts
@@ -296768,6 +296841,15 @@ function buildFlagArgs(flags) {
296768
296841
  function buildArgv(params) {
296769
296842
  return [...params.toolFlags, ...params.extraFlags, ...params.passthrough];
296770
296843
  }
296844
+ var LOOPBACK_HOSTS = ["127.0.0.1", "localhost", "::1"];
296845
+ function isLoopbackUrl(baseUrl) {
296846
+ const host = new URL(baseUrl).hostname.replace(/^\[|\]$/g, "");
296847
+ return LOOPBACK_HOSTS.some((loopback) => loopback === host);
296848
+ }
296849
+ function withLoopbackHosts(existing) {
296850
+ const listed = (existing ?? "").split(",").map((entry) => entry.trim()).filter((entry) => entry !== "");
296851
+ return [...listed, ...LOOPBACK_HOSTS.filter((host) => !listed.includes(host))].join(",");
296852
+ }
296771
296853
  function buildEnv(params) {
296772
296854
  const env = { ...params.baseEnv };
296773
296855
  const parentCustomHeaders = params.baseEnv.ANTHROPIC_CUSTOM_HEADERS;
@@ -296779,6 +296861,10 @@ function buildEnv(params) {
296779
296861
  const providerEnv = params.provider.definition.env ?? {};
296780
296862
  providerCustomHeaders = providerEnv.ANTHROPIC_CUSTOM_HEADERS;
296781
296863
  env.ANTHROPIC_BASE_URL = params.provider.baseUrl;
296864
+ if (isLoopbackUrl(params.provider.baseUrl)) {
296865
+ env.NO_PROXY = withLoopbackHosts(params.baseEnv.NO_PROXY);
296866
+ env.no_proxy = withLoopbackHosts(params.baseEnv.no_proxy);
296867
+ }
296782
296868
  env.CLAUDE_USE_PROVIDER = params.provider.definition.displayName;
296783
296869
  for (const [key, value] of Object.entries(providerEnv)) {
296784
296870
  env[key] = value;
@@ -297067,7 +297153,7 @@ function runLauncher(params) {
297067
297153
  ...identityCredential === void 0 ? {} : { identityCredential },
297068
297154
  ...frontDoor === void 0 ? {} : { frontdoor: frontDoor },
297069
297155
  ...headroom === void 0 ? {} : { headroom },
297070
- sessionId: (0, import_node_crypto12.randomUUID)()
297156
+ sessionId: (0, import_node_crypto13.randomUUID)()
297071
297157
  });
297072
297158
  const releases = [...headroom === void 0 || headroomPort === void 0 ? [] : [headroomPort.release], ...frontDoorRelease === void 0 ? [] : [frontDoorRelease]];
297073
297159
  if (releases.length === 0) {
@@ -297119,7 +297205,7 @@ function buildFarmRuntime(paths) {
297119
297205
  ...cliOverride === void 0 ? {} : { cliOverride }
297120
297206
  }).input,
297121
297207
  now: () => Date.now(),
297122
- uniqueSuffix: `${String(process.pid)}.${(0, import_node_crypto13.randomUUID)()}`,
297208
+ uniqueSuffix: `${String(process.pid)}.${(0, import_node_crypto14.randomUUID)()}`,
297123
297209
  // Zombie-aware on purpose: a previous launcher that crashed out of a resync without releasing the lock may sit unreaped, still answering signal 0 as alive, and must read as a dead holder so this launch takes the lock over instead of timing out.
297124
297210
  lock: { pid: process.pid, isRunning: realIsProcessRunning, sleep: realSleepSync }
297125
297211
  },
package/dist/index.cjs CHANGED
@@ -224,11 +224,36 @@ var PortableConfigSchema = ConfigProfileSchema.omit({ description: true }).exten
224
224
  identity: import_zod.z.string().min(1).optional(),
225
225
  when: WhenSchema.optional()
226
226
  });
227
- var HeadroomGlobalConfigSchema = import_zod.z.strictObject({
227
+ var HEADROOM_MODES = ["cache", "token"];
228
+ var HEADROOM_CCR_MODES = ["default", "lossless", "none"];
229
+ var HEADROOM_ROLLOUT_CHANNELS = ["beta", "canary", "dev"];
230
+ var INTERCEPT_CHANNELS = ["canary", "dev"];
231
+ var READ_MATURATION_CHANNELS = ["beta", "dev"];
232
+ var HeadroomGlobalConfigObjectSchema = import_zod.z.strictObject({
228
233
  /** Install spec handed to `uv tool install`. Defaults to HEADROOM_DEFAULT_SOURCE. */
229
234
  source: import_zod.z.string().min(1).optional(),
230
235
  /** How long the daemon may sit with no registered sessions before the supervisor stops it. Defaults to HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES. */
231
- idleShutdownMinutes: import_zod.z.number().int().positive().optional()
236
+ idleShutdownMinutes: import_zod.z.number().int().positive().optional(),
237
+ /** Optimisation mode, passed as `--mode`. Unset leaves headroom's own default (`cache`). */
238
+ mode: import_zod.z.enum(HEADROOM_MODES).optional(),
239
+ /** Keep-ratio for prose and code compression, passed as `--target-ratio`: lower is more aggressive. Unset lets headroom decide. */
240
+ targetRatio: import_zod.z.number().gt(0).max(1).optional(),
241
+ /** How compressed tool output stays recoverable. Unset leaves headroom's own default (`default`). */
242
+ ccr: import_zod.z.enum(HEADROOM_CCR_MODES).optional(),
243
+ /** Headroom's experimental release channel, exported as `HEADROOM_ROLLOUT_CHANNEL`. Required by `interceptToolResults` and `readMaturation`, which are refused without a channel that unlocks them. */
244
+ rolloutChannel: import_zod.z.enum(HEADROOM_ROLLOUT_CHANNELS).optional(),
245
+ /** Opt in to headroom's tool-result interceptors (`--intercept-tool-results`). Needs `rolloutChannel` canary or dev. */
246
+ interceptToolResults: import_zod.z.boolean().optional(),
247
+ /** Opt in to headroom's experimental read maturation (`--read-maturation`). Needs `rolloutChannel` beta or dev. */
248
+ readMaturation: import_zod.z.boolean().optional()
249
+ });
250
+ var HeadroomGlobalConfigSchema = HeadroomGlobalConfigObjectSchema.superRefine((config, context) => {
251
+ if (config.interceptToolResults === true && !INTERCEPT_CHANNELS.includes(config.rolloutChannel ?? "")) {
252
+ context.addIssue({ code: "custom", path: ["interceptToolResults"], message: "interceptToolResults needs rolloutChannel canary or dev" });
253
+ }
254
+ if (config.readMaturation === true && !READ_MATURATION_CHANNELS.includes(config.rolloutChannel ?? "")) {
255
+ context.addIssue({ code: "custom", path: ["readMaturation"], message: "readMaturation needs rolloutChannel beta or dev" });
256
+ }
232
257
  });
233
258
  var HEADROOM_DEFAULT_SOURCE = "headroom-ai[proxy] @ git+https://github.com/ExaDev/headroom@eb02fa4126450c9905a49394fdec19ae2e7c9c30";
234
259
  var HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES = 15;
@@ -2531,6 +2556,8 @@ var HeadroomStateSchema = import_zod4.z.strictObject({
2531
2556
  version: import_zod4.z.string().optional(),
2532
2557
  /** Hash of the allowlist the running proxy was started with, so a provider-file change is detected as drift. */
2533
2558
  allowlistHash: import_zod4.z.string().optional(),
2559
+ /** Hash of the token-saving settings the running proxy was started with, so a settings change is detected as drift. */
2560
+ settingsHash: import_zod4.z.string().optional(),
2534
2561
  /** The install spec the running install was last installed from, so a `source` config change is detected as drift. */
2535
2562
  installedSource: import_zod4.z.string().optional(),
2536
2563
  /** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
@@ -2749,11 +2776,41 @@ function ensureHeadroom(params) {
2749
2776
  }
2750
2777
  }
2751
2778
 
2779
+ // src/headroom/settings.ts
2780
+ var import_node_crypto4 = require("node:crypto");
2781
+ function settingsOf(config) {
2782
+ return {
2783
+ ...config.mode === void 0 ? {} : { mode: config.mode },
2784
+ ...config.targetRatio === void 0 ? {} : { targetRatio: config.targetRatio },
2785
+ ...config.ccr === void 0 ? {} : { ccr: config.ccr },
2786
+ ...config.rolloutChannel === void 0 ? {} : { rolloutChannel: config.rolloutChannel },
2787
+ ...config.interceptToolResults === void 0 ? {} : { interceptToolResults: config.interceptToolResults },
2788
+ ...config.readMaturation === void 0 ? {} : { readMaturation: config.readMaturation }
2789
+ };
2790
+ }
2791
+ function settingsArgs(settings) {
2792
+ return [
2793
+ ...settings.mode === void 0 ? [] : ["--mode", settings.mode],
2794
+ ...settings.targetRatio === void 0 ? [] : ["--target-ratio", String(settings.targetRatio)],
2795
+ ...settings.ccr === "lossless" ? ["--lossless"] : [],
2796
+ ...settings.ccr === "none" ? ["--no-ccr"] : [],
2797
+ ...settings.interceptToolResults === true ? ["--intercept-tool-results"] : [],
2798
+ ...settings.readMaturation === true ? ["--read-maturation"] : []
2799
+ ];
2800
+ }
2801
+ function settingsEnv(settings) {
2802
+ return settings.rolloutChannel === void 0 ? {} : { HEADROOM_ROLLOUT_CHANNEL: settings.rolloutChannel };
2803
+ }
2804
+ function hashSettings(settings) {
2805
+ return (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
2806
+ }
2807
+
2752
2808
  // src/headroom/supervisor.ts
2753
2809
  function resolveSupervisorConfig(configured) {
2754
2810
  return {
2755
2811
  source: configured.source ?? HEADROOM_DEFAULT_SOURCE,
2756
- idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES
2812
+ idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES,
2813
+ settings: settingsOf(configured)
2757
2814
  };
2758
2815
  }
2759
2816
  var HEADROOM_BACKOFF_BASE_MS = 500;
@@ -2816,8 +2873,9 @@ function compareVersions(a, b) {
2816
2873
  return 0;
2817
2874
  }
2818
2875
  var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
2819
- async function runSupervisor(config, ports, options = {}) {
2876
+ async function runSupervisor(initialConfig, ports, options = {}) {
2820
2877
  const { fs: fs2, paths } = ports;
2878
+ let config = initialConfig;
2821
2879
  const previousState = readHeadroomState(fs2, paths.headroomStateFile);
2822
2880
  let installedSource = previousState?.installedSource;
2823
2881
  let lastPort = previousState?.lastPort;
@@ -2859,6 +2917,7 @@ async function runSupervisor(config, ports, options = {}) {
2859
2917
  }
2860
2918
  let headroomPid;
2861
2919
  let runningHash;
2920
+ let runningSettingsHash;
2862
2921
  let consecutiveFailures = 0;
2863
2922
  let idleSince;
2864
2923
  let ticks = 0;
@@ -2867,6 +2926,8 @@ async function runSupervisor(config, ports, options = {}) {
2867
2926
  return HEADROOM_SUPERVISOR_STILL_RUNNING;
2868
2927
  }
2869
2928
  ticks += 1;
2929
+ config = ports.readConfig();
2930
+ const settingsHash = hashSettings(config.settings);
2870
2931
  const allowlist = allowlistOf(ports);
2871
2932
  const allowlistHash = hashAllowlist(allowlist);
2872
2933
  pruneDeadSessions(fs2, paths.headroomSessionsDir, ports.isRunning);
@@ -2876,6 +2937,7 @@ async function runSupervisor(config, ports, options = {}) {
2876
2937
  ports.log(`claude-use headroom supervisor: headroom pid ${String(headroomPid)} died`);
2877
2938
  headroomPid = void 0;
2878
2939
  runningHash = void 0;
2940
+ runningSettingsHash = void 0;
2879
2941
  writeHeadroomState(fs2, paths.headroomStateFile, {
2880
2942
  supervisorPid: ports.ownPid,
2881
2943
  version,
@@ -2891,12 +2953,13 @@ async function runSupervisor(config, ports, options = {}) {
2891
2953
  }
2892
2954
  const previousSticky = lastPort;
2893
2955
  const port = lastPort !== void 0 && await ports.isPortFree(lastPort) ? lastPort : await ports.freePort();
2894
- const pid = ports.spawnHeadroom(port, allowlist);
2956
+ const pid = ports.spawnHeadroom(port, allowlist, config.settings);
2895
2957
  const ready = await waitUntilReady(ports, port);
2896
2958
  if (ready) {
2897
2959
  consecutiveFailures = 0;
2898
2960
  headroomPid = pid;
2899
2961
  runningHash = allowlistHash;
2962
+ runningSettingsHash = settingsHash;
2900
2963
  lastPort = port;
2901
2964
  if (previousSticky !== void 0 && port !== previousSticky) {
2902
2965
  ports.log(
@@ -2909,6 +2972,7 @@ async function runSupervisor(config, ports, options = {}) {
2909
2972
  port,
2910
2973
  version,
2911
2974
  allowlistHash,
2975
+ settingsHash,
2912
2976
  installedSource: config.source,
2913
2977
  ...sticky()
2914
2978
  });
@@ -2925,12 +2989,13 @@ async function runSupervisor(config, ports, options = {}) {
2925
2989
  continue;
2926
2990
  }
2927
2991
  } else {
2928
- if (runningHash !== allowlistHash || installedSource !== config.source) {
2992
+ if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
2929
2993
  if (listSessions(fs2, paths.headroomSessionsDir).length === 0) {
2930
2994
  ports.log("claude-use headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
2931
2995
  ports.stopProcess(headroomPid);
2932
2996
  headroomPid = void 0;
2933
2997
  runningHash = void 0;
2998
+ runningSettingsHash = void 0;
2934
2999
  continue;
2935
3000
  }
2936
3001
  }
package/dist/index.mjs CHANGED
@@ -155,11 +155,36 @@ var PortableConfigSchema = ConfigProfileSchema.omit({ description: true }).exten
155
155
  identity: z.string().min(1).optional(),
156
156
  when: WhenSchema.optional()
157
157
  });
158
- var HeadroomGlobalConfigSchema = z.strictObject({
158
+ var HEADROOM_MODES = ["cache", "token"];
159
+ var HEADROOM_CCR_MODES = ["default", "lossless", "none"];
160
+ var HEADROOM_ROLLOUT_CHANNELS = ["beta", "canary", "dev"];
161
+ var INTERCEPT_CHANNELS = ["canary", "dev"];
162
+ var READ_MATURATION_CHANNELS = ["beta", "dev"];
163
+ var HeadroomGlobalConfigObjectSchema = z.strictObject({
159
164
  /** Install spec handed to `uv tool install`. Defaults to HEADROOM_DEFAULT_SOURCE. */
160
165
  source: z.string().min(1).optional(),
161
166
  /** How long the daemon may sit with no registered sessions before the supervisor stops it. Defaults to HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES. */
162
- idleShutdownMinutes: z.number().int().positive().optional()
167
+ idleShutdownMinutes: z.number().int().positive().optional(),
168
+ /** Optimisation mode, passed as `--mode`. Unset leaves headroom's own default (`cache`). */
169
+ mode: z.enum(HEADROOM_MODES).optional(),
170
+ /** Keep-ratio for prose and code compression, passed as `--target-ratio`: lower is more aggressive. Unset lets headroom decide. */
171
+ targetRatio: z.number().gt(0).max(1).optional(),
172
+ /** How compressed tool output stays recoverable. Unset leaves headroom's own default (`default`). */
173
+ ccr: z.enum(HEADROOM_CCR_MODES).optional(),
174
+ /** Headroom's experimental release channel, exported as `HEADROOM_ROLLOUT_CHANNEL`. Required by `interceptToolResults` and `readMaturation`, which are refused without a channel that unlocks them. */
175
+ rolloutChannel: z.enum(HEADROOM_ROLLOUT_CHANNELS).optional(),
176
+ /** Opt in to headroom's tool-result interceptors (`--intercept-tool-results`). Needs `rolloutChannel` canary or dev. */
177
+ interceptToolResults: z.boolean().optional(),
178
+ /** Opt in to headroom's experimental read maturation (`--read-maturation`). Needs `rolloutChannel` beta or dev. */
179
+ readMaturation: z.boolean().optional()
180
+ });
181
+ var HeadroomGlobalConfigSchema = HeadroomGlobalConfigObjectSchema.superRefine((config, context) => {
182
+ if (config.interceptToolResults === true && !INTERCEPT_CHANNELS.includes(config.rolloutChannel ?? "")) {
183
+ context.addIssue({ code: "custom", path: ["interceptToolResults"], message: "interceptToolResults needs rolloutChannel canary or dev" });
184
+ }
185
+ if (config.readMaturation === true && !READ_MATURATION_CHANNELS.includes(config.rolloutChannel ?? "")) {
186
+ context.addIssue({ code: "custom", path: ["readMaturation"], message: "readMaturation needs rolloutChannel beta or dev" });
187
+ }
163
188
  });
164
189
  var HEADROOM_DEFAULT_SOURCE = "headroom-ai[proxy] @ git+https://github.com/ExaDev/headroom@eb02fa4126450c9905a49394fdec19ae2e7c9c30";
165
190
  var HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES = 15;
@@ -2462,6 +2487,8 @@ var HeadroomStateSchema = z4.strictObject({
2462
2487
  version: z4.string().optional(),
2463
2488
  /** Hash of the allowlist the running proxy was started with, so a provider-file change is detected as drift. */
2464
2489
  allowlistHash: z4.string().optional(),
2490
+ /** Hash of the token-saving settings the running proxy was started with, so a settings change is detected as drift. */
2491
+ settingsHash: z4.string().optional(),
2465
2492
  /** The install spec the running install was last installed from, so a `source` config change is detected as drift. */
2466
2493
  installedSource: z4.string().optional(),
2467
2494
  /** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
@@ -2680,11 +2707,41 @@ function ensureHeadroom(params) {
2680
2707
  }
2681
2708
  }
2682
2709
 
2710
+ // src/headroom/settings.ts
2711
+ import { createHash as createHash2 } from "node:crypto";
2712
+ function settingsOf(config) {
2713
+ return {
2714
+ ...config.mode === void 0 ? {} : { mode: config.mode },
2715
+ ...config.targetRatio === void 0 ? {} : { targetRatio: config.targetRatio },
2716
+ ...config.ccr === void 0 ? {} : { ccr: config.ccr },
2717
+ ...config.rolloutChannel === void 0 ? {} : { rolloutChannel: config.rolloutChannel },
2718
+ ...config.interceptToolResults === void 0 ? {} : { interceptToolResults: config.interceptToolResults },
2719
+ ...config.readMaturation === void 0 ? {} : { readMaturation: config.readMaturation }
2720
+ };
2721
+ }
2722
+ function settingsArgs(settings) {
2723
+ return [
2724
+ ...settings.mode === void 0 ? [] : ["--mode", settings.mode],
2725
+ ...settings.targetRatio === void 0 ? [] : ["--target-ratio", String(settings.targetRatio)],
2726
+ ...settings.ccr === "lossless" ? ["--lossless"] : [],
2727
+ ...settings.ccr === "none" ? ["--no-ccr"] : [],
2728
+ ...settings.interceptToolResults === true ? ["--intercept-tool-results"] : [],
2729
+ ...settings.readMaturation === true ? ["--read-maturation"] : []
2730
+ ];
2731
+ }
2732
+ function settingsEnv(settings) {
2733
+ return settings.rolloutChannel === void 0 ? {} : { HEADROOM_ROLLOUT_CHANNEL: settings.rolloutChannel };
2734
+ }
2735
+ function hashSettings(settings) {
2736
+ return createHash2("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
2737
+ }
2738
+
2683
2739
  // src/headroom/supervisor.ts
2684
2740
  function resolveSupervisorConfig(configured) {
2685
2741
  return {
2686
2742
  source: configured.source ?? HEADROOM_DEFAULT_SOURCE,
2687
- idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES
2743
+ idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES,
2744
+ settings: settingsOf(configured)
2688
2745
  };
2689
2746
  }
2690
2747
  var HEADROOM_BACKOFF_BASE_MS = 500;
@@ -2747,8 +2804,9 @@ function compareVersions(a, b) {
2747
2804
  return 0;
2748
2805
  }
2749
2806
  var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
2750
- async function runSupervisor(config, ports, options = {}) {
2807
+ async function runSupervisor(initialConfig, ports, options = {}) {
2751
2808
  const { fs: fs2, paths } = ports;
2809
+ let config = initialConfig;
2752
2810
  const previousState = readHeadroomState(fs2, paths.headroomStateFile);
2753
2811
  let installedSource = previousState?.installedSource;
2754
2812
  let lastPort = previousState?.lastPort;
@@ -2790,6 +2848,7 @@ async function runSupervisor(config, ports, options = {}) {
2790
2848
  }
2791
2849
  let headroomPid;
2792
2850
  let runningHash;
2851
+ let runningSettingsHash;
2793
2852
  let consecutiveFailures = 0;
2794
2853
  let idleSince;
2795
2854
  let ticks = 0;
@@ -2798,6 +2857,8 @@ async function runSupervisor(config, ports, options = {}) {
2798
2857
  return HEADROOM_SUPERVISOR_STILL_RUNNING;
2799
2858
  }
2800
2859
  ticks += 1;
2860
+ config = ports.readConfig();
2861
+ const settingsHash = hashSettings(config.settings);
2801
2862
  const allowlist = allowlistOf(ports);
2802
2863
  const allowlistHash = hashAllowlist(allowlist);
2803
2864
  pruneDeadSessions(fs2, paths.headroomSessionsDir, ports.isRunning);
@@ -2807,6 +2868,7 @@ async function runSupervisor(config, ports, options = {}) {
2807
2868
  ports.log(`claude-use headroom supervisor: headroom pid ${String(headroomPid)} died`);
2808
2869
  headroomPid = void 0;
2809
2870
  runningHash = void 0;
2871
+ runningSettingsHash = void 0;
2810
2872
  writeHeadroomState(fs2, paths.headroomStateFile, {
2811
2873
  supervisorPid: ports.ownPid,
2812
2874
  version,
@@ -2822,12 +2884,13 @@ async function runSupervisor(config, ports, options = {}) {
2822
2884
  }
2823
2885
  const previousSticky = lastPort;
2824
2886
  const port = lastPort !== void 0 && await ports.isPortFree(lastPort) ? lastPort : await ports.freePort();
2825
- const pid = ports.spawnHeadroom(port, allowlist);
2887
+ const pid = ports.spawnHeadroom(port, allowlist, config.settings);
2826
2888
  const ready = await waitUntilReady(ports, port);
2827
2889
  if (ready) {
2828
2890
  consecutiveFailures = 0;
2829
2891
  headroomPid = pid;
2830
2892
  runningHash = allowlistHash;
2893
+ runningSettingsHash = settingsHash;
2831
2894
  lastPort = port;
2832
2895
  if (previousSticky !== void 0 && port !== previousSticky) {
2833
2896
  ports.log(
@@ -2840,6 +2903,7 @@ async function runSupervisor(config, ports, options = {}) {
2840
2903
  port,
2841
2904
  version,
2842
2905
  allowlistHash,
2906
+ settingsHash,
2843
2907
  installedSource: config.source,
2844
2908
  ...sticky()
2845
2909
  });
@@ -2856,12 +2920,13 @@ async function runSupervisor(config, ports, options = {}) {
2856
2920
  continue;
2857
2921
  }
2858
2922
  } else {
2859
- if (runningHash !== allowlistHash || installedSource !== config.source) {
2923
+ if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
2860
2924
  if (listSessions(fs2, paths.headroomSessionsDir).length === 0) {
2861
2925
  ports.log("claude-use headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
2862
2926
  ports.stopProcess(headroomPid);
2863
2927
  headroomPid = void 0;
2864
2928
  runningHash = void 0;
2929
+ runningSettingsHash = void 0;
2865
2930
  continue;
2866
2931
  }
2867
2932
  }
@@ -559,6 +559,28 @@ export declare const PortableConfigSchema: z.ZodObject<{
559
559
  }, z.core.$strict>>;
560
560
  }, z.core.$strict>;
561
561
  export type PortableConfig = z.infer<typeof PortableConfigSchema>;
562
+ declare const HeadroomGlobalConfigSchema: z.ZodObject<{
563
+ source: z.ZodOptional<z.ZodString>;
564
+ idleShutdownMinutes: z.ZodOptional<z.ZodNumber>;
565
+ mode: z.ZodOptional<z.ZodEnum<{
566
+ cache: "cache";
567
+ token: "token";
568
+ }>>;
569
+ targetRatio: z.ZodOptional<z.ZodNumber>;
570
+ ccr: z.ZodOptional<z.ZodEnum<{
571
+ default: "default";
572
+ none: "none";
573
+ lossless: "lossless";
574
+ }>>;
575
+ rolloutChannel: z.ZodOptional<z.ZodEnum<{
576
+ beta: "beta";
577
+ canary: "canary";
578
+ dev: "dev";
579
+ }>>;
580
+ interceptToolResults: z.ZodOptional<z.ZodBoolean>;
581
+ readMaturation: z.ZodOptional<z.ZodBoolean>;
582
+ }, z.core.$strict>;
583
+ export type HeadroomGlobalConfig = z.infer<typeof HeadroomGlobalConfigSchema>;
562
584
  /**
563
585
  * The default `headroom.source` install spec: the ExaDev headroom repository, with the `proxy` extra that provides the `headroom proxy` entry point. Pinned to a full commit SHA (the fork's `exadev-per-session-savings-1` tag), because a branch name can be rebased or deleted and would change or break every fresh install; bump it deliberately when the fork's per-session savings work changes. Kept here rather than in the supervisor because it is the schema's own documented default, referenced by `HeadroomGlobalConfigSchema`'s field docs.
564
586
  */
@@ -608,6 +630,23 @@ export declare const GlobalConfigSchema: z.ZodObject<{
608
630
  headroom: z.ZodOptional<z.ZodObject<{
609
631
  source: z.ZodOptional<z.ZodString>;
610
632
  idleShutdownMinutes: z.ZodOptional<z.ZodNumber>;
633
+ mode: z.ZodOptional<z.ZodEnum<{
634
+ cache: "cache";
635
+ token: "token";
636
+ }>>;
637
+ targetRatio: z.ZodOptional<z.ZodNumber>;
638
+ ccr: z.ZodOptional<z.ZodEnum<{
639
+ default: "default";
640
+ none: "none";
641
+ lossless: "lossless";
642
+ }>>;
643
+ rolloutChannel: z.ZodOptional<z.ZodEnum<{
644
+ beta: "beta";
645
+ canary: "canary";
646
+ dev: "dev";
647
+ }>>;
648
+ interceptToolResults: z.ZodOptional<z.ZodBoolean>;
649
+ readMaturation: z.ZodOptional<z.ZodBoolean>;
611
650
  }, z.core.$strict>>;
612
651
  frontdoor: z.ZodOptional<z.ZodObject<{
613
652
  idleShutdownMinutes: z.ZodOptional<z.ZodNumber>;
@@ -0,0 +1,13 @@
1
+ import type { HeadroomGlobalConfig } from "../config/schema";
2
+ /** The token-saving settings of the `headroom` config block, without its install and lifecycle fields (`source`, `idleShutdownMinutes`). */
3
+ export type HeadroomSettings = Pick<HeadroomGlobalConfig, "mode" | "targetRatio" | "ccr" | "rolloutChannel" | "interceptToolResults" | "readMaturation">;
4
+ /** Picks the settings out of a headroom config block, leaving unset fields unset. */
5
+ export declare function settingsOf(config: Readonly<HeadroomGlobalConfig>): HeadroomSettings;
6
+ /**
7
+ * The `headroom proxy` arguments the settings ask for, after the supervisor's own `--host` and `--port`. Only settings that are set produce a flag, so an empty settings object leaves headroom on its own defaults. `ccr: "default"` is headroom's default and likewise adds nothing.
8
+ */
9
+ export declare function settingsArgs(settings: Readonly<HeadroomSettings>): string[];
10
+ /** The environment the settings add to the daemon's: only the rollout channel, which headroom reads from `HEADROOM_ROLLOUT_CHANNEL` and has no flag for. */
11
+ export declare function settingsEnv(settings: Readonly<HeadroomSettings>): Record<string, string>;
12
+ /** A stable hash of the settings, so drift detection compares one short string against what the running daemon was started with. The same settings hash the same whatever order the config file wrote its keys in. */
13
+ export declare function hashSettings(settings: Readonly<HeadroomSettings>): string;
@@ -13,6 +13,7 @@ export declare const HeadroomStateSchema: z.ZodObject<{
13
13
  lastPort: z.ZodOptional<z.ZodNumber>;
14
14
  version: z.ZodOptional<z.ZodString>;
15
15
  allowlistHash: z.ZodOptional<z.ZodString>;
16
+ settingsHash: z.ZodOptional<z.ZodString>;
16
17
  installedSource: z.ZodOptional<z.ZodString>;
17
18
  lastError: z.ZodOptional<z.ZodString>;
18
19
  }, z.core.$strict>;
@@ -1,4 +1,6 @@
1
+ import { type HeadroomGlobalConfig } from "../config/schema";
1
2
  import type { LayoutPaths } from "../paths";
3
+ import { type HeadroomSettings } from "./settings";
2
4
  import { type HeadroomFs } from "./state";
3
5
  /** Everything the supervisor needs to run the daemon, resolved from the global config and the filesystem before it starts. */
4
6
  export interface HeadroomSupervisorConfig {
@@ -6,12 +8,11 @@ export interface HeadroomSupervisorConfig {
6
8
  readonly source: string;
7
9
  /** `headroom.idleShutdownMinutes`, already defaulted. */
8
10
  readonly idleShutdownMinutes: number;
11
+ /** The token-saving settings (`mode`, `targetRatio`, `ccr`, the rollout channel and its opt-ins), as configured; unset fields leave headroom on its own defaults. */
12
+ readonly settings: HeadroomSettings;
9
13
  }
10
14
  /** How the supervisor resolves the configured source against its defaults. */
11
- export declare function resolveSupervisorConfig(configured: {
12
- readonly source?: string;
13
- readonly idleShutdownMinutes?: number;
14
- }): HeadroomSupervisorConfig;
15
+ export declare function resolveSupervisorConfig(configured: Readonly<HeadroomGlobalConfig>): HeadroomSupervisorConfig;
15
16
  /**
16
17
  * Every effect the supervisor performs, injected so the whole lifecycle is testable against fakes: no real process, port, clock, or HTTP call ever happens in a unit test.
17
18
  */
@@ -35,7 +36,9 @@ export interface SupervisorPorts {
35
36
  /**
36
37
  * Starts `headroom proxy` bound to `port` with the given allowlist, returning its pid. Output goes to the daemon log. The implementation must keep the ChildProcess handle and attach an exit listener (detaching the process is fine; unref'ing a child you still need events from is not, since without the listener nothing reaps it and it lingers as a zombie).
37
38
  */
38
- readonly spawnHeadroom: (port: number, allowlist: readonly string[]) => number;
39
+ readonly spawnHeadroom: (port: number, allowlist: readonly string[], settings: Readonly<HeadroomSettings>) => number;
40
+ /** Reads the headroom config block as it stands on disk now. The supervisor calls it every tick, so a changed `source` or setting is noticed while the daemon runs and applied by the same quiet-registry restart an allowlist change gets. */
41
+ readonly readConfig: () => HeadroomSupervisorConfig;
39
42
  /** Stops a process the supervisor owns, escalating SIGTERM to SIGKILL on a bounded timeout (see `stopSupervisedProcess`). */
40
43
  readonly stopProcess: (pid: number) => void;
41
44
  /** True once `GET /readyz` on the port succeeds. */
@@ -97,6 +100,6 @@ export declare const HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
97
100
  *
98
101
  * Every effect flows through `ports`, so the whole loop is unit-testable with a fake clock, filesystem, and processes. The loop awaits its sleeps, which in the real implementation run on a timer: between ticks the event loop turns, the spawned child's exit event is delivered (and the child thereby reaped), and the next tick's liveness check reads the truth.
99
102
  */
100
- export declare function runSupervisor(config: HeadroomSupervisorConfig, ports: SupervisorPorts, options?: RunSupervisorOptions): Promise<number>;
103
+ export declare function runSupervisor(initialConfig: HeadroomSupervisorConfig, ports: SupervisorPorts, options?: RunSupervisorOptions): Promise<number>;
101
104
  /** The allowlist as it stands right now: every provider's upstream (the front door's origin for a codex provider, which headroom forwards codex traffic back to) plus Claude Code's own API. */
102
105
  export declare function currentHeadroomAllowlist(fs: HeadroomFs, paths: LayoutPaths): readonly string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-use",
3
- "version": "5.0.0",
3
+ "version": "5.0.1",
4
4
  "description": "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Joseph Mearman <joseph@mearman.co.uk>",