agent-shim 8.22.0 → 8.23.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/README.md CHANGED
@@ -145,7 +145,7 @@ agent-shim frontdoor rc watch [<cse_id>] [--json]
145
145
  agent-shim frontdoor rc selfhost mint <identity> [--force] [--json]
146
146
  agent-shim usage [--identity <name>] [--provider <name>] [--since <duration>] [--refresh] [--json]
147
147
  agent-shim account show [<identity>] [--refresh] [--json]
148
- agent-shim update [--check] [--json]
148
+ agent-shim update [--check] [--mode <off|notify|auto>] [--json]
149
149
  agent-shim completion <bash|zsh|fish>
150
150
  agent-shim shim enable [--dir <path>] [--force]
151
151
  agent-shim shim disable [--dir <path>] [--force]
package/dist/cli.cjs CHANGED
@@ -55361,6 +55361,11 @@ var FrontDoorGlobalConfigSchema = external_exports.strictObject({
55361
55361
  /** How long the front door may sit with no registered sessions before it closes and exits. Defaults to FRONTDOOR_DEFAULT_IDLE_SHUTDOWN_MINUTES. */
55362
55362
  idleShutdownMinutes: external_exports.number().int().positive().optional()
55363
55363
  });
55364
+ var UPDATE_MODES = ["off", "notify", "auto"];
55365
+ var UpdateGlobalConfigSchema = external_exports.strictObject({
55366
+ /** What a launch does about a newer release of agent-shim itself; see `UPDATE_MODES`. Absent means `off`. */
55367
+ mode: external_exports.enum(UPDATE_MODES).optional()
55368
+ });
55364
55369
  var GlobalConfigSchema = external_exports.strictObject({
55365
55370
  $schema: external_exports.string().optional(),
55366
55371
  defaultConfigProfile: external_exports.string().min(1).optional(),
@@ -55370,6 +55375,7 @@ var GlobalConfigSchema = external_exports.strictObject({
55370
55375
  launch: LaunchSchema.optional(),
55371
55376
  headroom: HeadroomGlobalConfigSchema.optional(),
55372
55377
  frontdoor: FrontDoorGlobalConfigSchema.optional(),
55378
+ update: UpdateGlobalConfigSchema.optional(),
55373
55379
  pools: external_exports.record(PoolNameSchema, PoolSchema).optional()
55374
55380
  });
55375
55381
  var IdentitySchema = external_exports.strictObject({
@@ -61790,7 +61796,7 @@ function registerIdentityCommand(program2, deps) {
61790
61796
  // package.json
61791
61797
  var package_default = {
61792
61798
  name: "agent-shim",
61793
- version: "8.22.0",
61799
+ version: "8.23.0",
61794
61800
  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.",
61795
61801
  license: "MIT",
61796
61802
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -77539,6 +77545,7 @@ function registerRunCommand(program2, handler) {
77539
77545
  }
77540
77546
 
77541
77547
  // src/update/commands.ts
77548
+ var import_node_child_process7 = require("node:child_process");
77542
77549
  var import_node_fs17 = __toESM(require("node:fs"), 1);
77543
77550
  var import_node_os7 = __toESM(require("node:os"), 1);
77544
77551
  var import_node_path41 = __toESM(require("node:path"), 1);
@@ -77874,11 +77881,41 @@ function formatUpdateReport(report) {
77874
77881
  return report.action;
77875
77882
  }
77876
77883
  }
77884
+ function setUpdateMode(paths, mode) {
77885
+ const existing = readGlobalConfig(paths);
77886
+ applyPatch(paths.globalConfigFile, GlobalConfigSchema, { update: { ...existing?.update, mode } }, { defaults: {} });
77887
+ }
77888
+ function realLaunchUpdatePort(paths) {
77889
+ return {
77890
+ effectiveUrl: realUpdatePorts.http.effectiveUrl,
77891
+ readStamp: (filePath) => realUpdatePorts.fs.readFileUtf8(filePath),
77892
+ writeStamp: (filePath, contents) => {
77893
+ writeTextAtomic(filePath, contents);
77894
+ },
77895
+ now: () => Date.now(),
77896
+ spawnDetached: (args) => {
77897
+ const invocation = selfInvocation(args);
77898
+ const child = (0, import_node_child_process7.spawn)(invocation.command, invocation.args, { detached: true, stdio: "ignore", env: { ...process.env, AGENT_SHIM_HOME: paths.root } });
77899
+ child.unref();
77900
+ },
77901
+ writeErr: (line) => {
77902
+ console.error(line);
77903
+ }
77904
+ };
77905
+ }
77877
77906
  function registerUpdateCommand(program2, deps, ports = realUpdatePorts) {
77878
77907
  withExamples(
77879
77908
  program2.command("update").description(
77880
- "Download the newest release binary and install it over the running one. An installation that belongs to a package manager (Homebrew, npm, Scoop) names its channel and its upgrade command instead of updating in place."
77881
- ).option("--check", "Only report whether a newer release exists; download and change nothing.").option("--json", "Print the result as JSON.").action(async (options) => {
77909
+ "Download the newest release binary and install it over the running one. An installation that belongs to a package manager (Homebrew, npm, Scoop) names its channel and its upgrade command instead of updating in place. With --mode, set the launch-time update mode in the global config instead of updating now."
77910
+ ).option("--check", "Only report whether a newer release exists; download and change nothing.").addOption(new Option("--mode <mode>", "Set what a launch does about a newer release (off: nothing; notify: print one line; auto: also apply it in the background) in the global config, then exit.").choices(UPDATE_MODES)).option("--json", "Print the result as JSON.").action(async (options) => {
77911
+ const mode = options.mode;
77912
+ if (mode !== void 0) {
77913
+ setUpdateMode(deps.paths, mode);
77914
+ reportMutation(options.json, { action: "updated", kind: "update", name: "mode", value: { mode } }, () => {
77915
+ console.log(`update mode set to ${mode}`);
77916
+ });
77917
+ return;
77918
+ }
77882
77919
  const report = await runSelfUpdate(ports, {
77883
77920
  currentVersion: package_default.version,
77884
77921
  platform: process.platform,
@@ -77894,7 +77931,7 @@ function registerUpdateCommand(program2, deps, ports = realUpdatePorts) {
77894
77931
  }
77895
77932
  console.log(formatUpdateReport(report));
77896
77933
  }),
77897
- ["agent-shim update", "agent-shim update --check", "agent-shim update --json"]
77934
+ ["agent-shim update", "agent-shim update --check", "agent-shim update --mode auto", "agent-shim update --json"]
77898
77935
  );
77899
77936
  }
77900
77937
 
@@ -78479,6 +78516,68 @@ function spawnClaude(params) {
78479
78516
  return params.proc.exit(exitCodeFor(result));
78480
78517
  }
78481
78518
 
78519
+ // src/update/launchHook.ts
78520
+ var UPDATE_CHECK_COOLDOWN_MS = 864e5;
78521
+ function resolveUpdateMode(globalConfig2) {
78522
+ return globalConfig2?.update?.mode ?? "off";
78523
+ }
78524
+ function updateCheckDue(contents, nowMs, cooldownMs = UPDATE_CHECK_COOLDOWN_MS) {
78525
+ if (contents === void 0) {
78526
+ return true;
78527
+ }
78528
+ const checkedAt = Date.parse(contents.trim());
78529
+ if (Number.isNaN(checkedAt)) {
78530
+ return true;
78531
+ }
78532
+ const age = nowMs - checkedAt;
78533
+ return age < 0 || age >= cooldownMs;
78534
+ }
78535
+ var NOOP_HANDLE = { markChildStarted: () => void 0 };
78536
+ function updateNotifyLine(currentVersion, latest) {
78537
+ return `agent-shim: update available: ${currentVersion} -> ${latest} (run \`agent-shim update\` to install it)`;
78538
+ }
78539
+ function runLaunchUpdateCheck(params) {
78540
+ const { mode, checkPath, currentVersion, port } = params;
78541
+ if (mode === "off" || port === void 0) {
78542
+ return NOOP_HANDLE;
78543
+ }
78544
+ let childStarted = false;
78545
+ try {
78546
+ if (!updateCheckDue(port.readStamp(checkPath), port.now())) {
78547
+ return NOOP_HANDLE;
78548
+ }
78549
+ port.writeStamp(checkPath, new Date(port.now()).toISOString());
78550
+ } catch {
78551
+ return NOOP_HANDLE;
78552
+ }
78553
+ const answer = (url2) => {
78554
+ try {
78555
+ const latest = releaseTagFromUrl(url2);
78556
+ if (latest === void 0 || compareVersions3(latest, currentVersion) <= 0) {
78557
+ return;
78558
+ }
78559
+ if (mode === "auto") {
78560
+ port.spawnDetached(["update"]);
78561
+ }
78562
+ if (!childStarted) {
78563
+ port.writeErr(updateNotifyLine(currentVersion, latest));
78564
+ }
78565
+ } catch {
78566
+ }
78567
+ };
78568
+ port.effectiveUrl(LATEST_RELEASE_URL).then(
78569
+ (url2) => {
78570
+ answer(url2);
78571
+ },
78572
+ () => void 0
78573
+ );
78574
+ return {
78575
+ markChildStarted: () => {
78576
+ childStarted = true;
78577
+ }
78578
+ };
78579
+ }
78580
+
78482
78581
  // src/launcher.ts
78483
78582
  function resolveIdentityCredential(identity, credential, env, port) {
78484
78583
  if (port === void 0) {
@@ -78644,6 +78743,12 @@ function prepareLaunch(params) {
78644
78743
  log.info(
78645
78744
  `agent-shim: identity ${identityDecision.name ?? "(none)"} (${identityDecision.source}${poolExplanation === void 0 ? "" : `, ${poolExplanation}`}), config profile ${configProfileDecision.name ?? "(none)"} (${configProfileDecision.source})` + (resolvedProvider === void 0 ? "" : `, provider ${resolvedProvider.name} (credential ${resolvedProvider.credential.target} from ${describeSource(resolvedProvider.credential.source)})`) + (identityCredential === void 0 ? "" : `, identity credential ${identityCredential.target} from ${describeSource(identityCredential.source)}`)
78646
78745
  );
78746
+ const updateCheck = runLaunchUpdateCheck({
78747
+ mode: params.updateMode ?? "off",
78748
+ checkPath: import_node_path43.default.join(paths.root, "update.check"),
78749
+ currentVersion: package_default.version,
78750
+ port: params.update
78751
+ });
78647
78752
  let cascadeLaunch;
78648
78753
  if (farmContext !== void 0) {
78649
78754
  const { farm: resyncFarmRuntime, identity: resyncIdentity, cascade } = farmContext;
@@ -78749,11 +78854,12 @@ function prepareLaunch(params) {
78749
78854
  registered();
78750
78855
  }
78751
78856
  };
78752
- return { bin: discovered.path, args: finalArgv, env: finalEnv, release };
78857
+ return { bin: discovered.path, args: finalArgv, env: finalEnv, release, markChildStarted: updateCheck.markChildStarted };
78753
78858
  }
78754
78859
  function runLauncher(params) {
78755
78860
  const plan = prepareLaunch(params);
78756
78861
  try {
78862
+ plan.markChildStarted();
78757
78863
  spawnClaude({ bin: plan.bin, args: plan.args, env: plan.env, spawn: params.spawn, proc: params.proc, beforeExit: plan.release });
78758
78864
  } finally {
78759
78865
  plan.release();
@@ -78768,6 +78874,7 @@ function buildFarmRuntime(paths, cwd) {
78768
78874
  const loaded = loadCascadeInput({ paths, home, cwd, read });
78769
78875
  const selections = readDirectorySelections(loaded);
78770
78876
  const git = resolveGitBranch(realRunPort, cwd);
78877
+ const updateMode = resolveUpdateMode(loaded.globalConfig);
78771
78878
  return {
78772
78879
  runtime: {
78773
78880
  fs: realFarmFs,
@@ -78793,7 +78900,8 @@ function buildFarmRuntime(paths, cwd) {
78793
78900
  ...selections.identity === void 0 ? {} : { directoryIdentity: selections.identity },
78794
78901
  ...selections.configProfile === void 0 ? {} : { directoryConfigProfile: selections.configProfile },
78795
78902
  ...loaded.globalConfig?.defaultConfigProfile === void 0 ? {} : { globalDefaultConfigProfile: loaded.globalConfig.defaultConfigProfile },
78796
- ...loaded.globalConfig?.pools === void 0 ? {} : { pools: loaded.globalConfig.pools }
78903
+ ...loaded.globalConfig?.pools === void 0 ? {} : { pools: loaded.globalConfig.pools },
78904
+ ...updateMode === "off" ? {} : { updateMode }
78797
78905
  };
78798
78906
  }
78799
78907
  function realClaudeBinaryResolver(paths) {
@@ -78815,6 +78923,7 @@ function realPrepareLaunchParams(paths, options) {
78815
78923
  ...farm.directoryConfigProfile === void 0 ? {} : { directoryRuleConfigProfile: farm.directoryConfigProfile },
78816
78924
  ...farm.globalDefaultConfigProfile === void 0 ? {} : { globalDefaultConfigProfile: farm.globalDefaultConfigProfile },
78817
78925
  ...farm.pools === void 0 ? {} : { pools: farm.pools },
78926
+ ...farm.updateMode === void 0 ? {} : { updateMode: farm.updateMode },
78818
78927
  ...options.allowMissingConfigProfile === void 0 ? {} : { allowMissingConfigProfile: options.allowMissingConfigProfile }
78819
78928
  };
78820
78929
  }
@@ -78918,6 +79027,8 @@ async function runClaude(argvOverride) {
78918
79027
  farm,
78919
79028
  allowMissingConfigProfile
78920
79029
  }),
79030
+ // Wired here rather than in realPrepareLaunchParams because the port re-invokes this very binary: correct for the agent-shim command line, wrong for a library host whose own executable is not agent-shim.
79031
+ update: realLaunchUpdatePort(paths),
78921
79032
  spawn: realSpawnPort
78922
79033
  });
78923
79034
  }
package/dist/index.cjs CHANGED
@@ -640,6 +640,11 @@ var FrontDoorGlobalConfigSchema = import_zod.z.strictObject({
640
640
  /** How long the front door may sit with no registered sessions before it closes and exits. Defaults to FRONTDOOR_DEFAULT_IDLE_SHUTDOWN_MINUTES. */
641
641
  idleShutdownMinutes: import_zod.z.number().int().positive().optional()
642
642
  });
643
+ var UPDATE_MODES = ["off", "notify", "auto"];
644
+ var UpdateGlobalConfigSchema = import_zod.z.strictObject({
645
+ /** What a launch does about a newer release of agent-shim itself; see `UPDATE_MODES`. Absent means `off`. */
646
+ mode: import_zod.z.enum(UPDATE_MODES).optional()
647
+ });
643
648
  var GlobalConfigSchema = import_zod.z.strictObject({
644
649
  $schema: import_zod.z.string().optional(),
645
650
  defaultConfigProfile: import_zod.z.string().min(1).optional(),
@@ -649,6 +654,7 @@ var GlobalConfigSchema = import_zod.z.strictObject({
649
654
  launch: LaunchSchema.optional(),
650
655
  headroom: HeadroomGlobalConfigSchema.optional(),
651
656
  frontdoor: FrontDoorGlobalConfigSchema.optional(),
657
+ update: UpdateGlobalConfigSchema.optional(),
652
658
  pools: import_zod.z.record(PoolNameSchema, PoolSchema).optional()
653
659
  });
654
660
  var IdentitySchema = import_zod.z.strictObject({
@@ -11457,6 +11463,106 @@ function removeDirectoryRule(paths, rulePath) {
11457
11463
  var import_node_crypto10 = require("node:crypto");
11458
11464
  var import_node_path30 = __toESM(require("node:path"), 1);
11459
11465
 
11466
+ // package.json
11467
+ var package_default = {
11468
+ name: "agent-shim",
11469
+ version: "8.23.0",
11470
+ 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.",
11471
+ license: "MIT",
11472
+ author: "Joseph Mearman <joseph@mearman.co.uk>",
11473
+ repository: {
11474
+ type: "git",
11475
+ url: "git+https://github.com/ExaDev/agent-shim.git"
11476
+ },
11477
+ homepage: "https://github.com/ExaDev/agent-shim#readme",
11478
+ bugs: "https://github.com/ExaDev/agent-shim/issues",
11479
+ keywords: [
11480
+ "claude-code",
11481
+ "claude",
11482
+ "cli"
11483
+ ],
11484
+ packageManager: "pnpm@12.4.1+sha512.2e81e399d73fe8390dab25e06aa788ab7a5908248d2f5a370f82b481147a6a7a367bf8048f9a6fdb6460f21a66f0542dedb8b94ca2c8723596741920b1656d4c",
11485
+ type: "module",
11486
+ engines: {
11487
+ node: "^22.18 || >=24"
11488
+ },
11489
+ bin: {
11490
+ "agent-shim": "dist/cli.cjs",
11491
+ "claude-use": "dist/cli.cjs"
11492
+ },
11493
+ files: [
11494
+ "dist/cli.cjs",
11495
+ "dist/index.mjs",
11496
+ "dist/index.cjs",
11497
+ "dist/types"
11498
+ ],
11499
+ scripts: {
11500
+ prepare: "husky && node scripts/build.mts --bundle-only",
11501
+ typecheck: "turbo run _typecheck",
11502
+ _typecheck: "tsc --noEmit",
11503
+ lint: "turbo run _lint",
11504
+ _lint: "eslint . --cache --cache-location .eslintcache --max-warnings 0",
11505
+ test: "turbo run _test",
11506
+ _test: "vitest run",
11507
+ "test:coverage": "turbo run _test:coverage",
11508
+ "_test:coverage": "vitest run --coverage",
11509
+ knip: "turbo run _knip",
11510
+ _knip: "knip --cache",
11511
+ build: "turbo run _build",
11512
+ _build: "node scripts/build.mts",
11513
+ "build:bundle": "node scripts/build.mts --bundle-only",
11514
+ prepublishOnly: "pnpm build:bundle",
11515
+ schema: "node scripts/gen-schema.mts"
11516
+ },
11517
+ dependencies: {
11518
+ "@clack/prompts": "^1.8.0",
11519
+ "@orpc/client": "1.15.4",
11520
+ "@orpc/server": "1.15.4",
11521
+ commander: "^15.0.0",
11522
+ cosmiconfig: "^10.0.1",
11523
+ picomatch: "^4.0.7",
11524
+ undici: "7.29.1",
11525
+ zod: "^4.6.1"
11526
+ },
11527
+ devDependencies: {
11528
+ "@commitlint/cli": "^21.2.2",
11529
+ "@commitlint/config-conventional": "^21.2.2",
11530
+ "@exadev/eslint-config": "2.12.1",
11531
+ "@semantic-release/changelog": "^7.0.0",
11532
+ "@semantic-release/git": "^11.0.1",
11533
+ "@semantic-release/npm": "^13.1.5",
11534
+ "@types/node": "^26.5.1",
11535
+ "@types/picomatch": "^4.0.3",
11536
+ "@vitest/coverage-v8": "^4.1.11",
11537
+ "conventional-changelog-conventionalcommits": "^10.4.0",
11538
+ esbuild: "^0.28.2",
11539
+ eslint: "^10.10.0",
11540
+ globals: "^17.12.0",
11541
+ husky: "^9.1.7",
11542
+ jiti: "^2.7.0",
11543
+ knip: "^6.35.1",
11544
+ "lint-staged": "^17.5.0",
11545
+ rollup: "4.64.0",
11546
+ "rollup-plugin-dts": "6.5.1",
11547
+ "semantic-release": "^25.0.9",
11548
+ turbo: "^2.11.5",
11549
+ typescript: "^6.0.3",
11550
+ "typescript-eslint": "^8.70.0",
11551
+ vitest: "^4.1.11"
11552
+ },
11553
+ main: "dist/index.cjs",
11554
+ module: "dist/index.mjs",
11555
+ types: "dist/types/index.d.ts",
11556
+ exports: {
11557
+ ".": {
11558
+ types: "./dist/types/index.d.ts",
11559
+ import: "./dist/index.mjs",
11560
+ require: "./dist/index.cjs"
11561
+ },
11562
+ "./package.json": "./package.json"
11563
+ }
11564
+ };
11565
+
11460
11566
  // src/launcher/argv.ts
11461
11567
  var ConflictingIdentityError = class extends UsageError {
11462
11568
  constructor(positional, flag) {
@@ -11929,6 +12035,88 @@ function spawnClaude(params) {
11929
12035
  return params.proc.exit(exitCodeFor(result));
11930
12036
  }
11931
12037
 
12038
+ // src/update/update.ts
12039
+ var LATEST_RELEASE_URL = "https://github.com/ExaDev/agent-shim/releases/latest";
12040
+ function compareVersions3(left, right) {
12041
+ const parse = (version) => {
12042
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
12043
+ if (match === null) {
12044
+ throw new Error(`not a plain release version: ${version}`);
12045
+ }
12046
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
12047
+ };
12048
+ const leftParts = parse(left);
12049
+ const rightParts = parse(right);
12050
+ return leftParts.map((part, index) => part - (rightParts[index] ?? 0)).find((difference) => difference !== 0) ?? 0;
12051
+ }
12052
+ function releaseTagFromUrl(url) {
12053
+ const segment = url.split("?")[0]?.split("/").at(-1);
12054
+ const candidate = segment?.startsWith("v") === true ? segment.slice(1) : void 0;
12055
+ return candidate !== void 0 && /^\d+\.\d+\.\d+$/.test(candidate) ? candidate : void 0;
12056
+ }
12057
+
12058
+ // src/update/launchHook.ts
12059
+ var UPDATE_CHECK_COOLDOWN_MS = 864e5;
12060
+ function resolveUpdateMode(globalConfig) {
12061
+ return globalConfig?.update?.mode ?? "off";
12062
+ }
12063
+ function updateCheckDue(contents, nowMs, cooldownMs = UPDATE_CHECK_COOLDOWN_MS) {
12064
+ if (contents === void 0) {
12065
+ return true;
12066
+ }
12067
+ const checkedAt = Date.parse(contents.trim());
12068
+ if (Number.isNaN(checkedAt)) {
12069
+ return true;
12070
+ }
12071
+ const age = nowMs - checkedAt;
12072
+ return age < 0 || age >= cooldownMs;
12073
+ }
12074
+ var NOOP_HANDLE = { markChildStarted: () => void 0 };
12075
+ function updateNotifyLine(currentVersion, latest) {
12076
+ return `agent-shim: update available: ${currentVersion} -> ${latest} (run \`agent-shim update\` to install it)`;
12077
+ }
12078
+ function runLaunchUpdateCheck(params) {
12079
+ const { mode, checkPath, currentVersion, port } = params;
12080
+ if (mode === "off" || port === void 0) {
12081
+ return NOOP_HANDLE;
12082
+ }
12083
+ let childStarted = false;
12084
+ try {
12085
+ if (!updateCheckDue(port.readStamp(checkPath), port.now())) {
12086
+ return NOOP_HANDLE;
12087
+ }
12088
+ port.writeStamp(checkPath, new Date(port.now()).toISOString());
12089
+ } catch {
12090
+ return NOOP_HANDLE;
12091
+ }
12092
+ const answer = (url) => {
12093
+ try {
12094
+ const latest = releaseTagFromUrl(url);
12095
+ if (latest === void 0 || compareVersions3(latest, currentVersion) <= 0) {
12096
+ return;
12097
+ }
12098
+ if (mode === "auto") {
12099
+ port.spawnDetached(["update"]);
12100
+ }
12101
+ if (!childStarted) {
12102
+ port.writeErr(updateNotifyLine(currentVersion, latest));
12103
+ }
12104
+ } catch {
12105
+ }
12106
+ };
12107
+ port.effectiveUrl(LATEST_RELEASE_URL).then(
12108
+ (url) => {
12109
+ answer(url);
12110
+ },
12111
+ () => void 0
12112
+ );
12113
+ return {
12114
+ markChildStarted: () => {
12115
+ childStarted = true;
12116
+ }
12117
+ };
12118
+ }
12119
+
11932
12120
  // src/launcher.ts
11933
12121
  function resolveIdentityCredential(identity, credential, env, port) {
11934
12122
  if (port === void 0) {
@@ -12094,6 +12282,12 @@ function prepareLaunch(params) {
12094
12282
  log.info(
12095
12283
  `agent-shim: identity ${identityDecision.name ?? "(none)"} (${identityDecision.source}${poolExplanation === void 0 ? "" : `, ${poolExplanation}`}), config profile ${configProfileDecision.name ?? "(none)"} (${configProfileDecision.source})` + (resolvedProvider === void 0 ? "" : `, provider ${resolvedProvider.name} (credential ${resolvedProvider.credential.target} from ${describeSource(resolvedProvider.credential.source)})`) + (identityCredential === void 0 ? "" : `, identity credential ${identityCredential.target} from ${describeSource(identityCredential.source)}`)
12096
12284
  );
12285
+ const updateCheck = runLaunchUpdateCheck({
12286
+ mode: params.updateMode ?? "off",
12287
+ checkPath: import_node_path30.default.join(paths.root, "update.check"),
12288
+ currentVersion: package_default.version,
12289
+ port: params.update
12290
+ });
12097
12291
  let cascadeLaunch;
12098
12292
  if (farmContext !== void 0) {
12099
12293
  const { farm: resyncFarmRuntime, identity: resyncIdentity, cascade } = farmContext;
@@ -12199,11 +12393,12 @@ function prepareLaunch(params) {
12199
12393
  registered();
12200
12394
  }
12201
12395
  };
12202
- return { bin: discovered.path, args: finalArgv, env: finalEnv, release };
12396
+ return { bin: discovered.path, args: finalArgv, env: finalEnv, release, markChildStarted: updateCheck.markChildStarted };
12203
12397
  }
12204
12398
  function runLauncher(params) {
12205
12399
  const plan = prepareLaunch(params);
12206
12400
  try {
12401
+ plan.markChildStarted();
12207
12402
  spawnClaude({ bin: plan.bin, args: plan.args, env: plan.env, spawn: params.spawn, proc: params.proc, beforeExit: plan.release });
12208
12403
  } finally {
12209
12404
  plan.release();
@@ -12366,6 +12561,7 @@ function buildFarmRuntime(paths, cwd) {
12366
12561
  const loaded = loadCascadeInput({ paths, home, cwd, read });
12367
12562
  const selections = readDirectorySelections(loaded);
12368
12563
  const git = resolveGitBranch(realRunPort, cwd);
12564
+ const updateMode = resolveUpdateMode(loaded.globalConfig);
12369
12565
  return {
12370
12566
  runtime: {
12371
12567
  fs: realFarmFs,
@@ -12391,7 +12587,8 @@ function buildFarmRuntime(paths, cwd) {
12391
12587
  ...selections.identity === void 0 ? {} : { directoryIdentity: selections.identity },
12392
12588
  ...selections.configProfile === void 0 ? {} : { directoryConfigProfile: selections.configProfile },
12393
12589
  ...loaded.globalConfig?.defaultConfigProfile === void 0 ? {} : { globalDefaultConfigProfile: loaded.globalConfig.defaultConfigProfile },
12394
- ...loaded.globalConfig?.pools === void 0 ? {} : { pools: loaded.globalConfig.pools }
12590
+ ...loaded.globalConfig?.pools === void 0 ? {} : { pools: loaded.globalConfig.pools },
12591
+ ...updateMode === "off" ? {} : { updateMode }
12395
12592
  };
12396
12593
  }
12397
12594
  function realClaudeBinaryResolver(paths) {
@@ -12413,6 +12610,7 @@ function realPrepareLaunchParams(paths, options) {
12413
12610
  ...farm.directoryConfigProfile === void 0 ? {} : { directoryRuleConfigProfile: farm.directoryConfigProfile },
12414
12611
  ...farm.globalDefaultConfigProfile === void 0 ? {} : { globalDefaultConfigProfile: farm.globalDefaultConfigProfile },
12415
12612
  ...farm.pools === void 0 ? {} : { pools: farm.pools },
12613
+ ...farm.updateMode === void 0 ? {} : { updateMode: farm.updateMode },
12416
12614
  ...options.allowMissingConfigProfile === void 0 ? {} : { allowMissingConfigProfile: options.allowMissingConfigProfile }
12417
12615
  };
12418
12616
  }
package/dist/index.mjs CHANGED
@@ -368,6 +368,11 @@ var FrontDoorGlobalConfigSchema = z.strictObject({
368
368
  /** How long the front door may sit with no registered sessions before it closes and exits. Defaults to FRONTDOOR_DEFAULT_IDLE_SHUTDOWN_MINUTES. */
369
369
  idleShutdownMinutes: z.number().int().positive().optional()
370
370
  });
371
+ var UPDATE_MODES = ["off", "notify", "auto"];
372
+ var UpdateGlobalConfigSchema = z.strictObject({
373
+ /** What a launch does about a newer release of agent-shim itself; see `UPDATE_MODES`. Absent means `off`. */
374
+ mode: z.enum(UPDATE_MODES).optional()
375
+ });
371
376
  var GlobalConfigSchema = z.strictObject({
372
377
  $schema: z.string().optional(),
373
378
  defaultConfigProfile: z.string().min(1).optional(),
@@ -377,6 +382,7 @@ var GlobalConfigSchema = z.strictObject({
377
382
  launch: LaunchSchema.optional(),
378
383
  headroom: HeadroomGlobalConfigSchema.optional(),
379
384
  frontdoor: FrontDoorGlobalConfigSchema.optional(),
385
+ update: UpdateGlobalConfigSchema.optional(),
380
386
  pools: z.record(PoolNameSchema, PoolSchema).optional()
381
387
  });
382
388
  var IdentitySchema = z.strictObject({
@@ -11185,6 +11191,106 @@ function removeDirectoryRule(paths, rulePath) {
11185
11191
  import { randomUUID as randomUUID4 } from "node:crypto";
11186
11192
  import path30 from "node:path";
11187
11193
 
11194
+ // package.json
11195
+ var package_default = {
11196
+ name: "agent-shim",
11197
+ version: "8.23.0",
11198
+ 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.",
11199
+ license: "MIT",
11200
+ author: "Joseph Mearman <joseph@mearman.co.uk>",
11201
+ repository: {
11202
+ type: "git",
11203
+ url: "git+https://github.com/ExaDev/agent-shim.git"
11204
+ },
11205
+ homepage: "https://github.com/ExaDev/agent-shim#readme",
11206
+ bugs: "https://github.com/ExaDev/agent-shim/issues",
11207
+ keywords: [
11208
+ "claude-code",
11209
+ "claude",
11210
+ "cli"
11211
+ ],
11212
+ packageManager: "pnpm@12.4.1+sha512.2e81e399d73fe8390dab25e06aa788ab7a5908248d2f5a370f82b481147a6a7a367bf8048f9a6fdb6460f21a66f0542dedb8b94ca2c8723596741920b1656d4c",
11213
+ type: "module",
11214
+ engines: {
11215
+ node: "^22.18 || >=24"
11216
+ },
11217
+ bin: {
11218
+ "agent-shim": "dist/cli.cjs",
11219
+ "claude-use": "dist/cli.cjs"
11220
+ },
11221
+ files: [
11222
+ "dist/cli.cjs",
11223
+ "dist/index.mjs",
11224
+ "dist/index.cjs",
11225
+ "dist/types"
11226
+ ],
11227
+ scripts: {
11228
+ prepare: "husky && node scripts/build.mts --bundle-only",
11229
+ typecheck: "turbo run _typecheck",
11230
+ _typecheck: "tsc --noEmit",
11231
+ lint: "turbo run _lint",
11232
+ _lint: "eslint . --cache --cache-location .eslintcache --max-warnings 0",
11233
+ test: "turbo run _test",
11234
+ _test: "vitest run",
11235
+ "test:coverage": "turbo run _test:coverage",
11236
+ "_test:coverage": "vitest run --coverage",
11237
+ knip: "turbo run _knip",
11238
+ _knip: "knip --cache",
11239
+ build: "turbo run _build",
11240
+ _build: "node scripts/build.mts",
11241
+ "build:bundle": "node scripts/build.mts --bundle-only",
11242
+ prepublishOnly: "pnpm build:bundle",
11243
+ schema: "node scripts/gen-schema.mts"
11244
+ },
11245
+ dependencies: {
11246
+ "@clack/prompts": "^1.8.0",
11247
+ "@orpc/client": "1.15.4",
11248
+ "@orpc/server": "1.15.4",
11249
+ commander: "^15.0.0",
11250
+ cosmiconfig: "^10.0.1",
11251
+ picomatch: "^4.0.7",
11252
+ undici: "7.29.1",
11253
+ zod: "^4.6.1"
11254
+ },
11255
+ devDependencies: {
11256
+ "@commitlint/cli": "^21.2.2",
11257
+ "@commitlint/config-conventional": "^21.2.2",
11258
+ "@exadev/eslint-config": "2.12.1",
11259
+ "@semantic-release/changelog": "^7.0.0",
11260
+ "@semantic-release/git": "^11.0.1",
11261
+ "@semantic-release/npm": "^13.1.5",
11262
+ "@types/node": "^26.5.1",
11263
+ "@types/picomatch": "^4.0.3",
11264
+ "@vitest/coverage-v8": "^4.1.11",
11265
+ "conventional-changelog-conventionalcommits": "^10.4.0",
11266
+ esbuild: "^0.28.2",
11267
+ eslint: "^10.10.0",
11268
+ globals: "^17.12.0",
11269
+ husky: "^9.1.7",
11270
+ jiti: "^2.7.0",
11271
+ knip: "^6.35.1",
11272
+ "lint-staged": "^17.5.0",
11273
+ rollup: "4.64.0",
11274
+ "rollup-plugin-dts": "6.5.1",
11275
+ "semantic-release": "^25.0.9",
11276
+ turbo: "^2.11.5",
11277
+ typescript: "^6.0.3",
11278
+ "typescript-eslint": "^8.70.0",
11279
+ vitest: "^4.1.11"
11280
+ },
11281
+ main: "dist/index.cjs",
11282
+ module: "dist/index.mjs",
11283
+ types: "dist/types/index.d.ts",
11284
+ exports: {
11285
+ ".": {
11286
+ types: "./dist/types/index.d.ts",
11287
+ import: "./dist/index.mjs",
11288
+ require: "./dist/index.cjs"
11289
+ },
11290
+ "./package.json": "./package.json"
11291
+ }
11292
+ };
11293
+
11188
11294
  // src/launcher/argv.ts
11189
11295
  var ConflictingIdentityError = class extends UsageError {
11190
11296
  constructor(positional, flag) {
@@ -11657,6 +11763,88 @@ function spawnClaude(params) {
11657
11763
  return params.proc.exit(exitCodeFor(result));
11658
11764
  }
11659
11765
 
11766
+ // src/update/update.ts
11767
+ var LATEST_RELEASE_URL = "https://github.com/ExaDev/agent-shim/releases/latest";
11768
+ function compareVersions3(left, right) {
11769
+ const parse = (version) => {
11770
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
11771
+ if (match === null) {
11772
+ throw new Error(`not a plain release version: ${version}`);
11773
+ }
11774
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
11775
+ };
11776
+ const leftParts = parse(left);
11777
+ const rightParts = parse(right);
11778
+ return leftParts.map((part, index) => part - (rightParts[index] ?? 0)).find((difference) => difference !== 0) ?? 0;
11779
+ }
11780
+ function releaseTagFromUrl(url) {
11781
+ const segment = url.split("?")[0]?.split("/").at(-1);
11782
+ const candidate = segment?.startsWith("v") === true ? segment.slice(1) : void 0;
11783
+ return candidate !== void 0 && /^\d+\.\d+\.\d+$/.test(candidate) ? candidate : void 0;
11784
+ }
11785
+
11786
+ // src/update/launchHook.ts
11787
+ var UPDATE_CHECK_COOLDOWN_MS = 864e5;
11788
+ function resolveUpdateMode(globalConfig) {
11789
+ return globalConfig?.update?.mode ?? "off";
11790
+ }
11791
+ function updateCheckDue(contents, nowMs, cooldownMs = UPDATE_CHECK_COOLDOWN_MS) {
11792
+ if (contents === void 0) {
11793
+ return true;
11794
+ }
11795
+ const checkedAt = Date.parse(contents.trim());
11796
+ if (Number.isNaN(checkedAt)) {
11797
+ return true;
11798
+ }
11799
+ const age = nowMs - checkedAt;
11800
+ return age < 0 || age >= cooldownMs;
11801
+ }
11802
+ var NOOP_HANDLE = { markChildStarted: () => void 0 };
11803
+ function updateNotifyLine(currentVersion, latest) {
11804
+ return `agent-shim: update available: ${currentVersion} -> ${latest} (run \`agent-shim update\` to install it)`;
11805
+ }
11806
+ function runLaunchUpdateCheck(params) {
11807
+ const { mode, checkPath, currentVersion, port } = params;
11808
+ if (mode === "off" || port === void 0) {
11809
+ return NOOP_HANDLE;
11810
+ }
11811
+ let childStarted = false;
11812
+ try {
11813
+ if (!updateCheckDue(port.readStamp(checkPath), port.now())) {
11814
+ return NOOP_HANDLE;
11815
+ }
11816
+ port.writeStamp(checkPath, new Date(port.now()).toISOString());
11817
+ } catch {
11818
+ return NOOP_HANDLE;
11819
+ }
11820
+ const answer = (url) => {
11821
+ try {
11822
+ const latest = releaseTagFromUrl(url);
11823
+ if (latest === void 0 || compareVersions3(latest, currentVersion) <= 0) {
11824
+ return;
11825
+ }
11826
+ if (mode === "auto") {
11827
+ port.spawnDetached(["update"]);
11828
+ }
11829
+ if (!childStarted) {
11830
+ port.writeErr(updateNotifyLine(currentVersion, latest));
11831
+ }
11832
+ } catch {
11833
+ }
11834
+ };
11835
+ port.effectiveUrl(LATEST_RELEASE_URL).then(
11836
+ (url) => {
11837
+ answer(url);
11838
+ },
11839
+ () => void 0
11840
+ );
11841
+ return {
11842
+ markChildStarted: () => {
11843
+ childStarted = true;
11844
+ }
11845
+ };
11846
+ }
11847
+
11660
11848
  // src/launcher.ts
11661
11849
  function resolveIdentityCredential(identity, credential, env, port) {
11662
11850
  if (port === void 0) {
@@ -11822,6 +12010,12 @@ function prepareLaunch(params) {
11822
12010
  log.info(
11823
12011
  `agent-shim: identity ${identityDecision.name ?? "(none)"} (${identityDecision.source}${poolExplanation === void 0 ? "" : `, ${poolExplanation}`}), config profile ${configProfileDecision.name ?? "(none)"} (${configProfileDecision.source})` + (resolvedProvider === void 0 ? "" : `, provider ${resolvedProvider.name} (credential ${resolvedProvider.credential.target} from ${describeSource(resolvedProvider.credential.source)})`) + (identityCredential === void 0 ? "" : `, identity credential ${identityCredential.target} from ${describeSource(identityCredential.source)}`)
11824
12012
  );
12013
+ const updateCheck = runLaunchUpdateCheck({
12014
+ mode: params.updateMode ?? "off",
12015
+ checkPath: path30.join(paths.root, "update.check"),
12016
+ currentVersion: package_default.version,
12017
+ port: params.update
12018
+ });
11825
12019
  let cascadeLaunch;
11826
12020
  if (farmContext !== void 0) {
11827
12021
  const { farm: resyncFarmRuntime, identity: resyncIdentity, cascade } = farmContext;
@@ -11927,11 +12121,12 @@ function prepareLaunch(params) {
11927
12121
  registered();
11928
12122
  }
11929
12123
  };
11930
- return { bin: discovered.path, args: finalArgv, env: finalEnv, release };
12124
+ return { bin: discovered.path, args: finalArgv, env: finalEnv, release, markChildStarted: updateCheck.markChildStarted };
11931
12125
  }
11932
12126
  function runLauncher(params) {
11933
12127
  const plan = prepareLaunch(params);
11934
12128
  try {
12129
+ plan.markChildStarted();
11935
12130
  spawnClaude({ bin: plan.bin, args: plan.args, env: plan.env, spawn: params.spawn, proc: params.proc, beforeExit: plan.release });
11936
12131
  } finally {
11937
12132
  plan.release();
@@ -12094,6 +12289,7 @@ function buildFarmRuntime(paths, cwd) {
12094
12289
  const loaded = loadCascadeInput({ paths, home, cwd, read });
12095
12290
  const selections = readDirectorySelections(loaded);
12096
12291
  const git = resolveGitBranch(realRunPort, cwd);
12292
+ const updateMode = resolveUpdateMode(loaded.globalConfig);
12097
12293
  return {
12098
12294
  runtime: {
12099
12295
  fs: realFarmFs,
@@ -12119,7 +12315,8 @@ function buildFarmRuntime(paths, cwd) {
12119
12315
  ...selections.identity === void 0 ? {} : { directoryIdentity: selections.identity },
12120
12316
  ...selections.configProfile === void 0 ? {} : { directoryConfigProfile: selections.configProfile },
12121
12317
  ...loaded.globalConfig?.defaultConfigProfile === void 0 ? {} : { globalDefaultConfigProfile: loaded.globalConfig.defaultConfigProfile },
12122
- ...loaded.globalConfig?.pools === void 0 ? {} : { pools: loaded.globalConfig.pools }
12318
+ ...loaded.globalConfig?.pools === void 0 ? {} : { pools: loaded.globalConfig.pools },
12319
+ ...updateMode === "off" ? {} : { updateMode }
12123
12320
  };
12124
12321
  }
12125
12322
  function realClaudeBinaryResolver(paths) {
@@ -12141,6 +12338,7 @@ function realPrepareLaunchParams(paths, options) {
12141
12338
  ...farm.directoryConfigProfile === void 0 ? {} : { directoryRuleConfigProfile: farm.directoryConfigProfile },
12142
12339
  ...farm.globalDefaultConfigProfile === void 0 ? {} : { globalDefaultConfigProfile: farm.globalDefaultConfigProfile },
12143
12340
  ...farm.pools === void 0 ? {} : { pools: farm.pools },
12341
+ ...farm.updateMode === void 0 ? {} : { updateMode: farm.updateMode },
12144
12342
  ...options.allowMissingConfigProfile === void 0 ? {} : { allowMissingConfigProfile: options.allowMissingConfigProfile }
12145
12343
  };
12146
12344
  }
@@ -542,6 +542,11 @@ declare const HeadroomGlobalConfigSchema: z.ZodObject<{
542
542
  readMaturation: z.ZodOptional<z.ZodBoolean>;
543
543
  }, z.core.$strict>;
544
544
  type HeadroomGlobalConfig = z.infer<typeof HeadroomGlobalConfigSchema>;
545
+ /**
546
+ * The launch-time update modes. `off` never touches the network at launch; `notify` checks for a newer release on a cooldown and prints one line naming it and the command to apply it; `auto` additionally applies the update in the background.
547
+ */
548
+ declare const UPDATE_MODES: readonly ["off", "notify", "auto"];
549
+ type UpdateMode = (typeof UPDATE_MODES)[number];
545
550
  /** The user-global `~/.agent-shim/config.json`. */
546
551
  declare const GlobalConfigSchema: z.ZodObject<{
547
552
  $schema: z.ZodOptional<z.ZodString>;
@@ -602,6 +607,13 @@ declare const GlobalConfigSchema: z.ZodObject<{
602
607
  frontdoor: z.ZodOptional<z.ZodObject<{
603
608
  idleShutdownMinutes: z.ZodOptional<z.ZodNumber>;
604
609
  }, z.core.$strict>>;
610
+ update: z.ZodOptional<z.ZodObject<{
611
+ mode: z.ZodOptional<z.ZodEnum<{
612
+ auto: "auto";
613
+ off: "off";
614
+ notify: "notify";
615
+ }>>;
616
+ }, z.core.$strict>>;
605
617
  pools: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
606
618
  identities: z.ZodArray<z.ZodString>;
607
619
  preference: z.ZodOptional<z.ZodEnum<{
@@ -3998,13 +4010,13 @@ declare function createControlApiRouter(deps: ControlApiDeps): {
3998
4010
  "config-profile": "config-profile";
3999
4011
  identity: "identity";
4000
4012
  provider: "provider";
4013
+ pool: "pool";
4014
+ keychain: "keychain";
4001
4015
  "ambient-credential": "ambient-credential";
4002
4016
  "binary-discovery": "binary-discovery";
4003
4017
  "claude-shim": "claude-shim";
4004
4018
  "path-resolution": "path-resolution";
4005
4019
  "legacy-name": "legacy-name";
4006
- pool: "pool";
4007
- keychain: "keychain";
4008
4020
  "directory-rules": "directory-rules";
4009
4021
  "categories-local": "categories-local";
4010
4022
  "active-identity": "active-identity";
@@ -4475,13 +4487,13 @@ declare function createDoorApiRouter(deps: DoorApiDeps): {
4475
4487
  "config-profile": "config-profile";
4476
4488
  identity: "identity";
4477
4489
  provider: "provider";
4490
+ pool: "pool";
4491
+ keychain: "keychain";
4478
4492
  "ambient-credential": "ambient-credential";
4479
4493
  "binary-discovery": "binary-discovery";
4480
4494
  "claude-shim": "claude-shim";
4481
4495
  "path-resolution": "path-resolution";
4482
4496
  "legacy-name": "legacy-name";
4483
- pool: "pool";
4484
- keychain: "keychain";
4485
4497
  "directory-rules": "directory-rules";
4486
4498
  "categories-local": "categories-local";
4487
4499
  "active-identity": "active-identity";
@@ -4769,13 +4781,13 @@ declare const DoctorFindingSchema: z.ZodObject<{
4769
4781
  "config-profile": "config-profile";
4770
4782
  identity: "identity";
4771
4783
  provider: "provider";
4784
+ pool: "pool";
4785
+ keychain: "keychain";
4772
4786
  "ambient-credential": "ambient-credential";
4773
4787
  "binary-discovery": "binary-discovery";
4774
4788
  "claude-shim": "claude-shim";
4775
4789
  "path-resolution": "path-resolution";
4776
4790
  "legacy-name": "legacy-name";
4777
- pool: "pool";
4778
- keychain: "keychain";
4779
4791
  "directory-rules": "directory-rules";
4780
4792
  "categories-local": "categories-local";
4781
4793
  "active-identity": "active-identity";
@@ -4798,13 +4810,13 @@ declare const DoctorRunOutputSchema: z.ZodObject<{
4798
4810
  "config-profile": "config-profile";
4799
4811
  identity: "identity";
4800
4812
  provider: "provider";
4813
+ pool: "pool";
4814
+ keychain: "keychain";
4801
4815
  "ambient-credential": "ambient-credential";
4802
4816
  "binary-discovery": "binary-discovery";
4803
4817
  "claude-shim": "claude-shim";
4804
4818
  "path-resolution": "path-resolution";
4805
4819
  "legacy-name": "legacy-name";
4806
- pool: "pool";
4807
- keychain: "keychain";
4808
4820
  "directory-rules": "directory-rules";
4809
4821
  "categories-local": "categories-local";
4810
4822
  "active-identity": "active-identity";
@@ -5672,6 +5684,24 @@ interface CliOverride {
5672
5684
  };
5673
5685
  }
5674
5686
 
5687
+ /**
5688
+ * Everything the launch-time update check needs from its host, injected so tests run it against scripted answers and no launch ever reaches GitHub by accident. Deliberately narrower than `UpdatePorts`: the launch path never downloads anything (the detached `agent-shim update` does), so only the release check, the cooldown stamp, the clock, the background spawn and the terminal are wired.
5689
+ */
5690
+ interface UpdateLaunchPort {
5691
+ /** Resolves the URL the `releases/latest` redirect lands on, exactly as `agent-shim update` resolves the newest version. */
5692
+ readonly effectiveUrl: (url: string) => Promise<string>;
5693
+ /** Reads the cooldown stamp, or undefined when it does not exist. */
5694
+ readonly readStamp: (filePath: string) => string | undefined;
5695
+ /** Writes the cooldown stamp atomically: a reader either sees the previous whole stamp or the new one, never a partial write. */
5696
+ readonly writeStamp: (filePath: string, contents: string) => void;
5697
+ /** The clock the cooldown reads, in epoch milliseconds. */
5698
+ readonly now: () => number;
5699
+ /** Re-invokes this very binary with `args` as a detached child whose stdio is ignored and which is unref'd, so it outlives the launch and never holds the terminal. */
5700
+ readonly spawnDetached: (args: readonly string[]) => void;
5701
+ /** Writes one line to the launching terminal's stderr. */
5702
+ readonly writeErr: (line: string) => void;
5703
+ }
5704
+
5675
5705
  /**
5676
5706
  * Everything the farm resync needs that the launcher itself has no way to produce: a real filesystem, a real clock, the working directory, and a way to load the cascade for it.
5677
5707
  *
@@ -5732,6 +5762,10 @@ interface PrepareLaunchParams {
5732
5762
  readonly credentials?: CredentialPort;
5733
5763
  /** True when the user was asked on a terminal whether to create the selected configuration profile and chose to launch without it. Without that explicit choice, a selected profile with no file is refused rather than silently skipped. */
5734
5764
  readonly allowMissingConfigProfile?: boolean;
5765
+ /** The user-global `update.mode`, resolved from the same global config the launcher already loads. Absent means `off`, and `off` runs no launch-time update check at all. */
5766
+ readonly updateMode?: UpdateMode;
5767
+ /** Wires the launch-time update check behind `update.mode`. Omitted by a caller that cannot resolve releases, re-invoke this binary, or write to the terminal; a mode of `notify` or `auto` with no port wired simply checks nothing. */
5768
+ readonly update?: UpdateLaunchPort;
5735
5769
  }
5736
5770
  /** Inputs to `runLauncher`: a launch's inputs plus the port that spawns the child. */
5737
5771
  interface RunLauncherParams extends PrepareLaunchParams {
@@ -5746,11 +5780,13 @@ interface LaunchPlan {
5746
5780
  readonly env: Readonly<Record<string, string | undefined>>;
5747
5781
  /** Releases the daemon session registrations this launch holds. Call it when the child has exited; it is safe to call more than once. */
5748
5782
  readonly release: () => void;
5783
+ /** Marks the moment this plan's child is spawned: the launch-time update check suppresses its notify line from then on, since a line printed after the child took over the terminal would land mid-session. Call it immediately before spawning; it is safe to call more than once. */
5784
+ readonly markChildStarted: () => void;
5749
5785
  }
5750
5786
  /**
5751
5787
  * Prepares one `claude` launch, in order:
5752
5788
  *
5753
- * `CLAUDE_CONFIG_DIR` escape-hatch check, then the identity/config-profile decision, the provider decision, the identity's own credential, the ambient-credential guard, farm resync, version discovery, flag resolution (and, when headroom resolved on, daemon bring-up), the extra-flags split, and the final argument list and environment, returned as a `LaunchPlan` for the caller to spawn. It refuses through `params.proc.exit`, so a caller that is not a command line process passes a `proc` whose `exit` throws.
5789
+ * `CLAUDE_CONFIG_DIR` escape-hatch check, then the identity/config-profile decision, the provider decision, the identity's own credential, the ambient-credential guard, the config-gated launch-time update check (fired after the decision line and never awaited; see `runLaunchUpdateCheck`), farm resync, version discovery, flag resolution (and, when headroom resolved on, daemon bring-up), the extra-flags split, and the final argument list and environment, returned as a `LaunchPlan` for the caller to spawn. It refuses through `params.proc.exit`, so a caller that is not a command line process passes a `proc` whose `exit` throws.
5754
5790
  *
5755
5791
  * A launch that resolves an identity with no `identity.json`, or a configuration profile with no file, is refused with exit 1 naming the missing name and how it was selected: silently proceeding would create a brand-new login for a mistyped `@name`, or launch with a whole cascade layer missing. On a terminal, `src/runClaude.ts` offers to create either before this runs.
5756
5792
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-shim",
3
- "version": "8.22.0",
3
+ "version": "8.23.0",
4
4
  "description": "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
5
5
  "license": "MIT",
6
6
  "author": "Joseph Mearman <joseph@mearman.co.uk>",