claudish 7.65.0 → 7.66.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.
Files changed (2) hide show
  1. package/dist/index.js +1550 -383
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -731,7 +731,7 @@ var init_onepassword_config = __esm(() => {
731
731
  });
732
732
 
733
733
  // src/version.ts
734
- var VERSION = "7.65.0";
734
+ var VERSION = "7.66.1";
735
735
 
736
736
  // src/logger.ts
737
737
  var exports_logger = {};
@@ -27554,6 +27554,7 @@ __export(exports_profile_config, {
27554
27554
  getModelMapping: () => getModelMapping,
27555
27555
  getProfile: () => getProfile,
27556
27556
  getProfileNames: () => getProfileNames,
27557
+ isKeychainEnabled: () => isKeychainEnabled,
27557
27558
  isLocalProviderEnabled: () => isLocalProviderEnabled,
27558
27559
  isProjectDirectory: () => isProjectDirectory,
27559
27560
  listAllProfiles: () => listAllProfiles,
@@ -27569,6 +27570,7 @@ __export(exports_profile_config, {
27569
27570
  setConfigFileOverride: () => setConfigFileOverride,
27570
27571
  setDefaultProfile: () => setDefaultProfile,
27571
27572
  setEndpoint: () => setEndpoint,
27573
+ setKeychainEnabled: () => setKeychainEnabled,
27572
27574
  setProfile: () => setProfile
27573
27575
  });
27574
27576
  import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
@@ -27621,6 +27623,9 @@ function loadConfig() {
27621
27623
  if (config2.onepasswordEnvironments !== undefined) {
27622
27624
  merged.onepasswordEnvironments = config2.onepasswordEnvironments;
27623
27625
  }
27626
+ if (config2.keychain !== undefined) {
27627
+ merged.keychain = config2.keychain;
27628
+ }
27624
27629
  if (config2.anthropicApiBilling !== undefined) {
27625
27630
  merged.anthropicApiBilling = config2.anthropicApiBilling;
27626
27631
  }
@@ -27910,6 +27915,20 @@ function removeApiKey(envVar) {
27910
27915
  saveConfig(config2);
27911
27916
  }
27912
27917
  }
27918
+ function isKeychainEnabled() {
27919
+ try {
27920
+ return loadConfig().keychain?.enabled === true;
27921
+ } catch {
27922
+ return false;
27923
+ }
27924
+ }
27925
+ function setKeychainEnabled(enabled) {
27926
+ const config2 = loadConfig();
27927
+ if (!config2.keychain)
27928
+ config2.keychain = {};
27929
+ config2.keychain.enabled = enabled;
27930
+ saveConfig(config2);
27931
+ }
27913
27932
  function getEndpoint(name) {
27914
27933
  const config2 = loadConfig();
27915
27934
  return config2.endpoints?.[name];
@@ -31940,13 +31959,20 @@ function defaultDeleteStore() {
31940
31959
  function defaultRunAgyRefresh() {
31941
31960
  const agy = locateAgyBinary();
31942
31961
  if (!agy)
31943
- return;
31962
+ return { kind: "not-installed" };
31944
31963
  try {
31945
31964
  execFileSync(agy, ["models"], {
31946
- stdio: ["ignore", "ignore", "ignore"],
31965
+ stdio: ["ignore", "ignore", "pipe"],
31966
+ encoding: "utf8",
31947
31967
  timeout: AGY_REFRESH_TIMEOUT_MS
31948
31968
  });
31949
- } catch {} finally {
31969
+ return { kind: "ran" };
31970
+ } catch (err) {
31971
+ const e = err;
31972
+ if (e.code === "ETIMEDOUT" || e.signal === "SIGTERM")
31973
+ return { kind: "timeout" };
31974
+ return { kind: "failed", detail: (e.stderr ?? "").trim().slice(0, 300) };
31975
+ } finally {
31950
31976
  invalidateReadStoreMemo();
31951
31977
  }
31952
31978
  }
@@ -32019,13 +32045,25 @@ async function resolveValidToken(deps) {
32019
32045
  return rec.token.access_token;
32020
32046
  }
32021
32047
  log("[Antigravity] Access token expired/near-expiry \u2014 asking the Antigravity CLI to refresh.");
32022
- deps.runAgyRefresh();
32048
+ const outcome = deps.runAgyRefresh();
32023
32049
  const refreshedRec = parseRecord(deps.readStore());
32024
32050
  if (refreshedRec && !needsRefresh(refreshedRec.token, deps.now())) {
32025
32051
  log("[Antigravity] Shared token refreshed by the Antigravity CLI.");
32026
32052
  return refreshedRec.token.access_token;
32027
32053
  }
32028
- throw new Error("[Antigravity] Antigravity session expired and couldn't be refreshed. " + "Run `claudish login antigravity` (installs/authenticates the Antigravity CLI).");
32054
+ throw new Error(`[Antigravity] ${describeRefreshFailure(outcome)}`);
32055
+ }
32056
+ function describeRefreshFailure(outcome) {
32057
+ switch (outcome.kind) {
32058
+ case "not-installed":
32059
+ return "The Antigravity CLI (`agy`) is not installed, so the expired session could not be " + "refreshed. Run `claudish login antigravity` \u2014 it installs and authenticates it.";
32060
+ case "timeout":
32061
+ return `The Antigravity CLI did not finish within ${Math.round(AGY_REFRESH_TIMEOUT_MS / 1000)}s, ` + "so the session could not be refreshed. `agy` auto-updates itself (a large download) " + "and is unusably slow while it does \u2014 this usually clears on its own. Try again in a " + "minute; run `agy models` to see what it is doing. You are most likely still signed in.";
32062
+ case "failed":
32063
+ return "The Antigravity CLI could not refresh the session" + (outcome.detail ? `: ${outcome.detail}` : ".") + " If it reports being signed out, run `claudish login antigravity`.";
32064
+ default:
32065
+ return "The Antigravity session is expired and the Antigravity CLI refreshed it without " + "producing a valid token \u2014 the session has most likely been revoked. " + "Run `claudish login antigravity`.";
32066
+ }
32029
32067
  }
32030
32068
  function getValidAntigravityAccessToken(deps = defaultDeps) {
32031
32069
  if (inFlight)
@@ -32065,7 +32103,7 @@ function _resetAntigravityTokenState() {
32065
32103
  cachedHasToken = null;
32066
32104
  invalidateReadStoreMemo();
32067
32105
  }
32068
- var KC_SERVICE = "gemini", KC_ACCOUNT = "antigravity", PREFIX = "go-keyring-base64:", EXPIRY_SKEW_MS = 120000, AGY_REFRESH_TIMEOUT_MS = 40000, READ_STORE_TTL_MS = 3000, cachedRawStore = null, defaultDeps, cachedHasToken = null, HAS_TOKEN_TTL_MS = 5000, inFlight = null;
32106
+ var KC_SERVICE = "gemini", KC_ACCOUNT = "antigravity", PREFIX = "go-keyring-base64:", EXPIRY_SKEW_MS = 120000, AGY_REFRESH_TIMEOUT_MS = 12000, READ_STORE_TTL_MS = 3000, cachedRawStore = null, defaultDeps, cachedHasToken = null, HAS_TOKEN_TTL_MS = 5000, inFlight = null;
32069
32107
  var init_antigravity_token = __esm(() => {
32070
32108
  init_logger();
32071
32109
  defaultDeps = {
@@ -32081,6 +32119,7 @@ var init_antigravity_token = __esm(() => {
32081
32119
  var exports_antigravity_user = {};
32082
32120
  __export(exports_antigravity_user, {
32083
32121
  _resetAntigravityServedModelsCache: () => _resetAntigravityServedModelsCache,
32122
+ antigravityHost: () => antigravityHost,
32084
32123
  buildAntigravityUserAgent: () => buildAntigravityUserAgent,
32085
32124
  getAntigravityTierDisplayName: () => getAntigravityTierDisplayName,
32086
32125
  getAntigravityTierFullName: () => getAntigravityTierFullName,
@@ -32089,6 +32128,13 @@ __export(exports_antigravity_user, {
32089
32128
  retrieveUserQuota: () => retrieveUserQuota,
32090
32129
  setupAntigravityUser: () => setupAntigravityUser
32091
32130
  });
32131
+ function antigravityHost() {
32132
+ const override = process.env.AICODE_ENDPOINT_URL?.trim();
32133
+ return override ? override.replace(/\/+$/, "") : ANTIGRAVITY_DEFAULT_HOST;
32134
+ }
32135
+ function apiBase() {
32136
+ return `${antigravityHost()}/v1internal`;
32137
+ }
32092
32138
  function makeTerminalSetupError(message) {
32093
32139
  const err = new Error(message);
32094
32140
  err.terminal = true;
@@ -32105,7 +32151,7 @@ function resetAntigravityUserCache() {
32105
32151
  agServedCacheAt = 0;
32106
32152
  }
32107
32153
  async function callLoadCodeAssistAntigravity(accessToken) {
32108
- const res = await fetch(`${ANTIGRAVITY_API_BASE}:loadCodeAssist`, {
32154
+ const res = await fetch(`${apiBase()}:loadCodeAssist`, {
32109
32155
  method: "POST",
32110
32156
  headers: {
32111
32157
  Authorization: `Bearer ${accessToken}`,
@@ -32153,7 +32199,7 @@ function getAntigravityTierFullName() {
32153
32199
  }
32154
32200
  async function retrieveUserQuota(accessToken, projectId) {
32155
32201
  try {
32156
- const res = await fetch(`${ANTIGRAVITY_API_BASE}:retrieveUserQuota`, {
32202
+ const res = await fetch(`${apiBase()}:retrieveUserQuota`, {
32157
32203
  method: "POST",
32158
32204
  headers: {
32159
32205
  Authorization: `Bearer ${accessToken}`,
@@ -32178,7 +32224,7 @@ async function getServedAntigravityModels(accessToken, projectId, opts) {
32178
32224
  return agServedCache;
32179
32225
  }
32180
32226
  try {
32181
- const res = await fetch(`${ANTIGRAVITY_API_BASE}:fetchAvailableModels`, {
32227
+ const res = await fetch(`${apiBase()}:fetchAvailableModels`, {
32182
32228
  method: "POST",
32183
32229
  headers: {
32184
32230
  Authorization: `Bearer ${accessToken}`,
@@ -32206,7 +32252,26 @@ async function getServedAntigravityModels(accessToken, projectId, opts) {
32206
32252
  }
32207
32253
  meta3[id] = entry;
32208
32254
  }
32209
- agServedCache = { servedIds, defaultId, meta: meta3 };
32255
+ const excludedIds = new Set;
32256
+ for (const [id, record4] of Object.entries(data.models ?? {})) {
32257
+ if (record4?.isInternal === true)
32258
+ excludedIds.add(id);
32259
+ }
32260
+ for (const list of [
32261
+ data.tabModelIds,
32262
+ data.imageGenerationModelIds,
32263
+ data.audioTranscriptionModelIds
32264
+ ]) {
32265
+ for (const id of list ?? [])
32266
+ excludedIds.add(id);
32267
+ }
32268
+ const deprecatedReplacements = {};
32269
+ for (const [oldId, info] of Object.entries(data.deprecatedModelIds ?? {})) {
32270
+ excludedIds.add(oldId);
32271
+ if (info?.newModelId)
32272
+ deprecatedReplacements[oldId] = info.newModelId;
32273
+ }
32274
+ agServedCache = { servedIds, defaultId, meta: meta3, excludedIds, deprecatedReplacements };
32210
32275
  agServedCacheAt = now;
32211
32276
  return agServedCache;
32212
32277
  }
@@ -32224,7 +32289,7 @@ function _resetAntigravityServedModelsCache() {
32224
32289
  agServedCache = null;
32225
32290
  agServedCacheAt = 0;
32226
32291
  }
32227
- var ANTIGRAVITY_API_BASE = "https://cloudcode-pa.googleapis.com/v1internal", SERVED_MODELS_TTL_MS, ANTIGRAVITY_IDE_TYPE = "ANTIGRAVITY", cachedAgProjectId = null, cachedAgTierId = null, cachedAgTierName = null, agServedCache = null, agServedCacheAt = 0;
32292
+ var ANTIGRAVITY_DEFAULT_HOST = "https://daily-cloudcode-pa.googleapis.com", SERVED_MODELS_TTL_MS, ANTIGRAVITY_IDE_TYPE = "ANTIGRAVITY", cachedAgProjectId = null, cachedAgTierId = null, cachedAgTierName = null, agServedCache = null, agServedCacheAt = 0;
32228
32293
  var init_antigravity_user = __esm(() => {
32229
32294
  init_logger();
32230
32295
  SERVED_MODELS_TTL_MS = 10 * 60 * 1000;
@@ -35117,26 +35182,35 @@ function hasQuotaExhaustionWording(errorBody) {
35117
35182
  const lower = (errorBody || "").toLowerCase();
35118
35183
  return EXHAUSTION_PHRASES.some((phrase) => lower.includes(phrase));
35119
35184
  }
35185
+ function hasPlanLimitWording(errorBody) {
35186
+ const lower = (errorBody || "").toLowerCase();
35187
+ if (BALANCE_PHRASES.some((phrase) => lower.includes(phrase)))
35188
+ return false;
35189
+ return PLAN_LIMIT_PHRASES.some((phrase) => lower.includes(phrase));
35190
+ }
35120
35191
  function isQuotaExhaustionError(status, errorBody) {
35121
35192
  if (status !== 401 && status !== 403 && status !== 429)
35122
35193
  return false;
35123
35194
  return hasQuotaExhaustionWording(errorBody);
35124
35195
  }
35125
- var EXHAUSTION_PHRASES;
35196
+ var BALANCE_PHRASES, PLAN_LIMIT_PHRASES, EXHAUSTION_PHRASES;
35126
35197
  var init_quota_exhaustion = __esm(() => {
35127
- EXHAUSTION_PHRASES = [
35198
+ BALANCE_PHRASES = [
35199
+ "insufficient balance",
35200
+ "insufficient_quota",
35201
+ "out of credits",
35202
+ "credit balance"
35203
+ ];
35204
+ PLAN_LIMIT_PHRASES = [
35128
35205
  "usage limit",
35129
35206
  "billing cycle",
35130
35207
  "quota",
35131
- "insufficient balance",
35132
- "insufficient_quota",
35133
35208
  "upgrade your plan",
35134
35209
  "exceeded your current",
35135
- "out of credits",
35136
- "credit balance",
35137
35210
  "daily limit",
35138
35211
  "plan limit"
35139
35212
  ];
35213
+ EXHAUSTION_PHRASES = [...BALANCE_PHRASES, ...PLAN_LIMIT_PHRASES];
35140
35214
  });
35141
35215
 
35142
35216
  // src/handlers/shared/gemini-queue.ts
@@ -35277,6 +35351,9 @@ var init_gemini_queue = __esm(() => {
35277
35351
 
35278
35352
  // src/providers/transport/antigravity.ts
35279
35353
  import { randomUUID } from "crypto";
35354
+ function antigravityEndpoint() {
35355
+ return `${antigravityHost()}/v1internal:streamGenerateContent?alt=sse`;
35356
+ }
35280
35357
  function rankReasoningSuffix(suffix) {
35281
35358
  const rank = REASONING_TIER_RANK[suffix.toLowerCase()];
35282
35359
  return rank === undefined ? Number.MAX_SAFE_INTEGER : rank;
@@ -35343,7 +35420,12 @@ function classify429(responseBody) {
35343
35420
  return { terminal: false, retryDelayMs: retryDelayMs ?? 60000, reason };
35344
35421
  }
35345
35422
  }
35346
- return { terminal: false, retryDelayMs, reason };
35423
+ return {
35424
+ terminal: false,
35425
+ retryDelayMs,
35426
+ reason,
35427
+ unattributed: reason === undefined && retryDelayMs === undefined
35428
+ };
35347
35429
  } catch {
35348
35430
  return null;
35349
35431
  }
@@ -35389,7 +35471,7 @@ class AntigravityProviderTransport {
35389
35471
  return this._activeModelName;
35390
35472
  }
35391
35473
  getEndpoint() {
35392
- return ANTIGRAVITY_ENDPOINT;
35474
+ return antigravityEndpoint();
35393
35475
  }
35394
35476
  async getHeaders() {
35395
35477
  if (this.cachedAuth)
@@ -35477,8 +35559,8 @@ class AntigravityProviderTransport {
35477
35559
  return await this.explainTerminalQuota(response, bodyText, classification.reason);
35478
35560
  }
35479
35561
  if (attempt < MAX_RETRY_ATTEMPTS) {
35480
- const delay = classification.retryDelayMs ?? DEFAULT_RATE_LIMIT_DELAY_MS;
35481
- logStderr(`[Antigravity] Rate limited (${classification.reason || "unknown"}), retrying in ${(delay / 1000).toFixed(1)}s (attempt ${attempt}/${MAX_RETRY_ATTEMPTS})`);
35562
+ const delay = classification.retryDelayMs ?? (classification.unattributed ? UNATTRIBUTED_RETRY_DELAY_MS : DEFAULT_RATE_LIMIT_DELAY_MS);
35563
+ logStderr(`[Antigravity] Rate limited (${classification.reason || "unattributed \u2014 server gave no reason or retry delay"}), retrying in ${(delay / 1000).toFixed(1)}s (attempt ${attempt}/${MAX_RETRY_ATTEMPTS})`);
35482
35564
  if (attempt === 1) {
35483
35565
  await this.logQuotaInfo();
35484
35566
  }
@@ -35665,7 +35747,7 @@ ${lines.join(`
35665
35747
  }
35666
35748
  }
35667
35749
  }
35668
- var ANTIGRAVITY_BASE = "https://cloudcode-pa.googleapis.com", ANTIGRAVITY_ENDPOINT, MAX_RETRY_ATTEMPTS = 3, DEFAULT_RATE_LIMIT_DELAY_MS = 1e4, QUOTA_CHECK_TIMEOUT_MS = 3000, REASONING_TIER_RANK;
35750
+ var MAX_RETRY_ATTEMPTS = 3, DEFAULT_RATE_LIMIT_DELAY_MS = 1e4, UNATTRIBUTED_RETRY_DELAY_MS = 1000, QUOTA_CHECK_TIMEOUT_MS = 3000, REASONING_TIER_RANK;
35669
35751
  var init_antigravity2 = __esm(() => {
35670
35752
  init_model_catalog();
35671
35753
  init_antigravity_token();
@@ -35673,7 +35755,6 @@ var init_antigravity2 = __esm(() => {
35673
35755
  init_authority();
35674
35756
  init_gemini_queue();
35675
35757
  init_logger();
35676
- ANTIGRAVITY_ENDPOINT = `${ANTIGRAVITY_BASE}/v1internal:streamGenerateContent?alt=sse`;
35677
35758
  REASONING_TIER_RANK = {
35678
35759
  high: 0,
35679
35760
  medium: 1,
@@ -35731,6 +35812,257 @@ var init_antigravity_credential = __esm(() => {
35731
35812
  init_antigravity_user();
35732
35813
  });
35733
35814
 
35815
+ // src/providers/keychain.ts
35816
+ function syncRun(args, stdin) {
35817
+ try {
35818
+ const proc = Bun.spawnSync([SECURITY_BIN, ...args], {
35819
+ stdin: stdin === undefined ? "ignore" : new TextEncoder().encode(stdin),
35820
+ stdout: "pipe",
35821
+ stderr: "pipe",
35822
+ timeout: SPAWN_TIMEOUT_MS
35823
+ });
35824
+ return normalizeResult(proc.exitCode, proc.signalCode, decode3(proc.stdout), decode3(proc.stderr));
35825
+ } catch (err) {
35826
+ return { code: 1, stdout: "", stderr: err instanceof Error ? err.message : String(err) };
35827
+ }
35828
+ }
35829
+ async function asyncRun(args, stdin) {
35830
+ try {
35831
+ const proc = Bun.spawn([SECURITY_BIN, ...args], {
35832
+ stdin: stdin === undefined ? "ignore" : new TextEncoder().encode(stdin),
35833
+ stdout: "pipe",
35834
+ stderr: "pipe",
35835
+ timeout: SPAWN_TIMEOUT_MS
35836
+ });
35837
+ const [stdout, stderr, exitCode] = await Promise.all([
35838
+ new Response(proc.stdout).text(),
35839
+ new Response(proc.stderr).text(),
35840
+ proc.exited
35841
+ ]);
35842
+ return normalizeResult(exitCode, proc.signalCode, stdout, stderr);
35843
+ } catch (err) {
35844
+ return { code: 1, stdout: "", stderr: err instanceof Error ? err.message : String(err) };
35845
+ }
35846
+ }
35847
+ function decode3(buf) {
35848
+ return buf ? new TextDecoder().decode(buf) : "";
35849
+ }
35850
+ function normalizeResult(exitCode, signalCode, stdout, stderr) {
35851
+ if (typeof exitCode === "number")
35852
+ return { code: exitCode, stdout, stderr };
35853
+ const detail = signalCode ? `killed by ${signalCode}` : "terminated without an exit code";
35854
+ return { code: -1, stdout, stderr: stderr.trim() || `security ${detail}` };
35855
+ }
35856
+ function invalidateKeychainCache() {
35857
+ listMemo = null;
35858
+ valueMemo.clear();
35859
+ }
35860
+ function now() {
35861
+ return Date.now();
35862
+ }
35863
+ function isValidKeychainVarName(name) {
35864
+ return ENV_VAR_NAME.test(name);
35865
+ }
35866
+ function describeUnstorableValue(value) {
35867
+ if (value.length === 0)
35868
+ return "value is empty";
35869
+ if (CONTROL_CHARS.test(value)) {
35870
+ return "value contains control characters (tab/newline/etc), which the keychain read path cannot represent unambiguously";
35871
+ }
35872
+ return null;
35873
+ }
35874
+ function isKeychainSupported() {
35875
+ return deps.platform() === "darwin";
35876
+ }
35877
+ function keychainUnavailableReason() {
35878
+ if (!isKeychainSupported()) {
35879
+ return `the macOS Keychain is only available on macOS (this is ${deps.platform()})`;
35880
+ }
35881
+ return null;
35882
+ }
35883
+ function stripOneTrailingNewline(out) {
35884
+ return out.endsWith(`
35885
+ `) ? out.slice(0, -1) : out;
35886
+ }
35887
+ function keychainFileArgs() {
35888
+ const file2 = process.env.CLAUDISH_KEYCHAIN_FILE;
35889
+ return file2 ? [file2] : [];
35890
+ }
35891
+ function findArgs(envVar) {
35892
+ return [
35893
+ "find-generic-password",
35894
+ "-s",
35895
+ KEYCHAIN_SERVICE,
35896
+ "-a",
35897
+ envVar,
35898
+ "-w",
35899
+ ...keychainFileArgs()
35900
+ ];
35901
+ }
35902
+ function interpretRead(envVar, res) {
35903
+ if (res.code === 0) {
35904
+ const value = stripOneTrailingNewline(res.stdout);
35905
+ return value.length > 0 ? value : null;
35906
+ }
35907
+ if (res.code === EXIT_ITEM_NOT_FOUND)
35908
+ return null;
35909
+ throw new KeychainError(`Keychain lookup for ${envVar} failed: ${res.stderr.trim() || `security exited ${res.code}`}`, res.code);
35910
+ }
35911
+ function readKeychainSecret(envVar) {
35912
+ if (!isKeychainSupported())
35913
+ return null;
35914
+ const cached2 = valueMemo.get(envVar);
35915
+ const at = now();
35916
+ if (cached2 && at - cached2.at < MEMO_TTL_MS)
35917
+ return cached2.value;
35918
+ const value = interpretRead(envVar, deps.run(findArgs(envVar)));
35919
+ valueMemo.set(envVar, { at, value });
35920
+ return value;
35921
+ }
35922
+ async function readKeychainSecretAsync(envVar) {
35923
+ if (!isKeychainSupported())
35924
+ return null;
35925
+ const cached2 = valueMemo.get(envVar);
35926
+ const at = now();
35927
+ if (cached2 && at - cached2.at < MEMO_TTL_MS)
35928
+ return cached2.value;
35929
+ const value = interpretRead(envVar, await deps.runAsync(findArgs(envVar)));
35930
+ valueMemo.set(envVar, { at, value });
35931
+ return value;
35932
+ }
35933
+ function enumerateKeychainVars() {
35934
+ if (!isKeychainSupported())
35935
+ return { names: [], failed: false };
35936
+ const at = now();
35937
+ if (listMemo && at - listMemo.at < MEMO_TTL_MS)
35938
+ return listMemo.value;
35939
+ const res = deps.run(["dump-keychain", ...keychainFileArgs()]);
35940
+ const value = res.code === 0 ? { names: parseDumpAccounts(res.stdout), failed: false } : {
35941
+ names: [],
35942
+ failed: true,
35943
+ error: res.stderr.trim() || `security exited ${res.code}`
35944
+ };
35945
+ listMemo = { at, value };
35946
+ return value;
35947
+ }
35948
+ function listKeychainVars() {
35949
+ return enumerateKeychainVars().names;
35950
+ }
35951
+ function parseDumpAccounts(dump) {
35952
+ const found = new Set;
35953
+ for (const block of dump.split(/\nkeychain: /)) {
35954
+ if (!block.includes(SVCE_MATCH))
35955
+ continue;
35956
+ const m = block.match(ACCT_ATTR);
35957
+ if (m?.[1] && isValidKeychainVarName(m[1]))
35958
+ found.add(m[1]);
35959
+ }
35960
+ return Array.from(found).sort();
35961
+ }
35962
+ function lookupKeychainVar(envVar) {
35963
+ if (!isKeychainSupported())
35964
+ return { present: false, failed: false };
35965
+ const cached2 = valueMemo.get(envVar);
35966
+ if (cached2 && now() - cached2.at < MEMO_TTL_MS && cached2.value !== null) {
35967
+ return { present: true, failed: false };
35968
+ }
35969
+ const listed = enumerateKeychainVars();
35970
+ return { present: listed.names.includes(envVar), failed: listed.failed };
35971
+ }
35972
+ function toHex(value) {
35973
+ return Buffer.from(value, "utf8").toString("hex");
35974
+ }
35975
+ function quoteForStdin(value) {
35976
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
35977
+ }
35978
+ function writeKeychainSecret(envVar, value) {
35979
+ const unsupported2 = keychainUnavailableReason();
35980
+ if (unsupported2)
35981
+ throw new KeychainError(`Cannot write ${envVar}: ${unsupported2}`);
35982
+ if (!isValidKeychainVarName(envVar)) {
35983
+ throw new KeychainError(`Cannot write "${envVar}": not a valid environment variable name`);
35984
+ }
35985
+ const unstorable = describeUnstorableValue(value);
35986
+ if (unstorable)
35987
+ throw new KeychainError(`Cannot store ${envVar}: ${unstorable}`);
35988
+ invalidateKeychainCache();
35989
+ const cmd = [
35990
+ "add-generic-password",
35991
+ "-s",
35992
+ quoteForStdin(KEYCHAIN_SERVICE),
35993
+ "-a",
35994
+ quoteForStdin(envVar),
35995
+ "-l",
35996
+ quoteForStdin(`${KEYCHAIN_SERVICE}: ${envVar}`),
35997
+ "-D",
35998
+ quoteForStdin("application password"),
35999
+ "-j",
36000
+ quoteForStdin("Stored by claudish"),
36001
+ "-X",
36002
+ quoteForStdin(toHex(value)),
36003
+ "-U",
36004
+ "-T",
36005
+ quoteForStdin(SECURITY_BIN),
36006
+ ...keychainFileArgs().map(quoteForStdin)
36007
+ ].join(" ");
36008
+ const res = deps.run(["-i"], `${cmd}
36009
+ `);
36010
+ if (res.code !== 0) {
36011
+ throw new KeychainError(`Failed to store ${envVar} in the keychain: ${res.stderr.trim() || `security exited ${res.code}`}`, res.code);
36012
+ }
36013
+ invalidateKeychainCache();
36014
+ const readBack = readKeychainSecret(envVar);
36015
+ if (readBack !== value) {
36016
+ throw new KeychainError(`Keychain write for ${envVar} did not round-trip \u2014 the stored value differs from what was written. ` + "The item may exceed the keychain's size limit; nothing should be assumed saved.");
36017
+ }
36018
+ }
36019
+ function deleteKeychainSecret(envVar) {
36020
+ if (!isKeychainSupported())
36021
+ return false;
36022
+ invalidateKeychainCache();
36023
+ const res = deps.run([
36024
+ "delete-generic-password",
36025
+ "-s",
36026
+ KEYCHAIN_SERVICE,
36027
+ "-a",
36028
+ envVar,
36029
+ ...keychainFileArgs()
36030
+ ]);
36031
+ invalidateKeychainCache();
36032
+ if (res.code === 0)
36033
+ return true;
36034
+ if (res.code === EXIT_ITEM_NOT_FOUND)
36035
+ return false;
36036
+ throw new KeychainError(`Failed to delete ${envVar} from the keychain: ${res.stderr.trim() || `security exited ${res.code}`}`, res.code);
36037
+ }
36038
+ function valueTail2(value) {
36039
+ if (value.length <= 6)
36040
+ return "\u2022\u2022\u2022\u2022";
36041
+ return `\u2022\u2022\u2022\u2022${value.slice(-4)}`;
36042
+ }
36043
+ var KEYCHAIN_SERVICE = "claudish", SECURITY_BIN = "/usr/bin/security", EXIT_ITEM_NOT_FOUND = 44, MEMO_TTL_MS = 3000, SPAWN_TIMEOUT_MS = 1e4, KeychainError, defaultDeps2, deps, listMemo = null, valueMemo, CONTROL_CHARS, ENV_VAR_NAME, ACCT_ATTR, SVCE_MATCH;
36044
+ var init_keychain = __esm(() => {
36045
+ KeychainError = class KeychainError extends Error {
36046
+ exitCode;
36047
+ constructor(message, exitCode) {
36048
+ super(message);
36049
+ this.exitCode = exitCode;
36050
+ this.name = "KeychainError";
36051
+ }
36052
+ };
36053
+ defaultDeps2 = {
36054
+ platform: () => process.platform,
36055
+ run: syncRun,
36056
+ runAsync: asyncRun
36057
+ };
36058
+ deps = defaultDeps2;
36059
+ valueMemo = new Map;
36060
+ CONTROL_CHARS = /[\u0000-\u001F\u007F]/;
36061
+ ENV_VAR_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
36062
+ ACCT_ATTR = /"acct"<blob>=(?:0x[0-9A-Fa-f]*\s+)?"([^"]*)"/;
36063
+ SVCE_MATCH = `"svce"<blob>="${KEYCHAIN_SERVICE}"`;
36064
+ });
36065
+
35734
36066
  // src/auth/credentials/local-api-key.ts
35735
36067
  function resolveLocalApiKey(q) {
35736
36068
  return realValue(process.env[q.envVar]) || (q.aliases ?? []).map((a) => realValue(process.env[a])).find((v) => !!v) || realValue(getApiKey(q.envVar));
@@ -35747,6 +36079,108 @@ var init_local_api_key = __esm(() => {
35747
36079
  init_profile_config();
35748
36080
  });
35749
36081
 
36082
+ // src/auth/credentials/keychain-source.ts
36083
+ function warnOnce2(message) {
36084
+ if (warnedMessages2.has(message))
36085
+ return;
36086
+ warnedMessages2.add(message);
36087
+ console.error(message);
36088
+ }
36089
+ function recordKeychainHydratedVar(envVar) {
36090
+ hydratedVars.add(envVar);
36091
+ }
36092
+ function isKeychainHydratedVar(envVar) {
36093
+ return hydratedVars.has(envVar);
36094
+ }
36095
+ function hasKeychainSource() {
36096
+ if (process.env.CLAUDISH_DISABLE_KEYCHAIN === "1")
36097
+ return false;
36098
+ if (!isKeychainSupported())
36099
+ return false;
36100
+ return isKeychainEnabled();
36101
+ }
36102
+ function resolveKeychainKeyForEnvVars(wanted) {
36103
+ if (!hasKeychainSource())
36104
+ return { failed: false };
36105
+ let failed = false;
36106
+ try {
36107
+ for (const name of wanted) {
36108
+ if (!name)
36109
+ continue;
36110
+ const { present, failed: lookupFailed } = lookupKeychainVar(name);
36111
+ if (lookupFailed)
36112
+ failed = true;
36113
+ if (!present)
36114
+ continue;
36115
+ const value = readKeychainSecret(name);
36116
+ if (value)
36117
+ return { value, failed: false };
36118
+ }
36119
+ } catch (err) {
36120
+ warnOnce2(`[claudish] macOS Keychain lookup skipped: ${err instanceof KeychainError ? err.message : String(err)}`);
36121
+ return { failed: true };
36122
+ }
36123
+ if (failed) {
36124
+ warnOnce2("[claudish] macOS Keychain could not be enumerated \u2014 treating this provider as unresolved rather than uncredentialed.");
36125
+ }
36126
+ return { failed };
36127
+ }
36128
+ async function hydrateKeychainIntoEnv() {
36129
+ if (!hasKeychainSource())
36130
+ return 0;
36131
+ let names;
36132
+ try {
36133
+ const listed = enumerateKeychainVars();
36134
+ if (listed.failed) {
36135
+ warnOnce2(`[claudish] macOS Keychain enumeration skipped: ${listed.error ?? "unknown error"}`);
36136
+ return 0;
36137
+ }
36138
+ names = listed.names;
36139
+ } catch (err) {
36140
+ warnOnce2(`[claudish] macOS Keychain enumeration skipped: ${String(err)}`);
36141
+ return 0;
36142
+ }
36143
+ const missing = names.filter((n) => !resolveLocalApiKey({ envVar: n }));
36144
+ if (missing.length === 0)
36145
+ return 0;
36146
+ const results = await Promise.all(missing.map(async (name) => {
36147
+ try {
36148
+ return { name, value: await readKeychainSecretAsync(name) };
36149
+ } catch (err) {
36150
+ warnOnce2(`[claudish] macOS Keychain read for ${name} skipped: ${err instanceof KeychainError ? err.message : String(err)}`);
36151
+ return { name, value: null };
36152
+ }
36153
+ }));
36154
+ let hydrated = 0;
36155
+ for (const { name, value } of results) {
36156
+ if (!value || resolveLocalApiKey({ envVar: name }))
36157
+ continue;
36158
+ process.env[name] = value;
36159
+ recordKeychainHydratedVar(name);
36160
+ hydrated++;
36161
+ }
36162
+ return hydrated;
36163
+ }
36164
+ function keychainHasAnyOf(names) {
36165
+ if (!hasKeychainSource())
36166
+ return false;
36167
+ try {
36168
+ for (const name of names) {
36169
+ if (name && lookupKeychainVar(name).present)
36170
+ return true;
36171
+ }
36172
+ } catch {}
36173
+ return false;
36174
+ }
36175
+ var warnedMessages2, hydratedVars;
36176
+ var init_keychain_source = __esm(() => {
36177
+ init_profile_config();
36178
+ init_keychain();
36179
+ init_local_api_key();
36180
+ warnedMessages2 = new Set;
36181
+ hydratedVars = new Set;
36182
+ });
36183
+
35750
36184
  // src/auth/credentials/api-key-credential.ts
35751
36185
  import { existsSync as existsSync12 } from "fs";
35752
36186
  import { homedir as homedir18 } from "os";
@@ -35758,7 +36192,6 @@ class ApiKeyCredentialProvider {
35758
36192
  aliases;
35759
36193
  authScheme;
35760
36194
  staticHeaders;
35761
- publicKeyFallback;
35762
36195
  oauthFallback;
35763
36196
  declaredKey;
35764
36197
  cachedKey;
@@ -35769,7 +36202,6 @@ class ApiKeyCredentialProvider {
35769
36202
  this.aliases = descriptor.aliases ?? [];
35770
36203
  this.authScheme = descriptor.authScheme ?? "bearer";
35771
36204
  this.staticHeaders = descriptor.staticHeaders ?? {};
35772
- this.publicKeyFallback = descriptor.publicKeyFallback;
35773
36205
  this.oauthFallback = descriptor.oauthFallback;
35774
36206
  this.declaredKey = descriptor.declaredKey;
35775
36207
  }
@@ -35803,6 +36235,17 @@ class ApiKeyCredentialProvider {
35803
36235
  this.cachedKey = local;
35804
36236
  return local;
35805
36237
  }
36238
+ let keychainFailed = false;
36239
+ if (hasKeychainSource()) {
36240
+ const kc = resolveKeychainKeyForEnvVars([this.envVar, ...this.aliases]);
36241
+ if (kc.value) {
36242
+ process.env[this.envVar] = kc.value;
36243
+ recordKeychainHydratedVar(this.envVar);
36244
+ this.cachedKey = kc.value;
36245
+ return kc.value;
36246
+ }
36247
+ keychainFailed = kc.failed;
36248
+ }
35806
36249
  if (hasOpSources()) {
35807
36250
  const wanted = new Set([this.envVar, ...this.aliases]);
35808
36251
  const resolved = await resolveOpKeyForEnvVars(wanted, {
@@ -35817,7 +36260,8 @@ class ApiKeyCredentialProvider {
35817
36260
  }
35818
36261
  return "";
35819
36262
  }
35820
- this.cachedKey = "";
36263
+ if (!keychainFailed)
36264
+ this.cachedKey = "";
35821
36265
  return "";
35822
36266
  })();
35823
36267
  try {
@@ -35829,8 +36273,6 @@ class ApiKeyCredentialProvider {
35829
36273
  async isAvailable(opts) {
35830
36274
  if (this.authScheme === "none")
35831
36275
  return true;
35832
- if (this.publicKeyFallback)
35833
- return true;
35834
36276
  if (this.resolveFromEnvConfig())
35835
36277
  return true;
35836
36278
  if (this.hasOauthFallbackFile())
@@ -35846,7 +36288,7 @@ class ApiKeyCredentialProvider {
35846
36288
  if (this.authScheme === "none") {
35847
36289
  return { headers: { ...this.staticHeaders } };
35848
36290
  }
35849
- const key = await this.resolveKey({ allowOpPrompt: ctx.allowOpPrompt }) || this.publicKeyFallback || "";
36291
+ const key = await this.resolveKey({ allowOpPrompt: ctx.allowOpPrompt });
35850
36292
  let headers;
35851
36293
  if (this.authScheme === "x-api-key") {
35852
36294
  headers = { "x-api-key": key, ...this.staticHeaders };
@@ -35860,6 +36302,7 @@ class ApiKeyCredentialProvider {
35860
36302
  }
35861
36303
  var init_api_key_credential = __esm(() => {
35862
36304
  init_env_placeholder();
36305
+ init_keychain_source();
35863
36306
  init_local_api_key();
35864
36307
  init_op_source();
35865
36308
  });
@@ -37112,7 +37555,6 @@ class CredentialAuthority {
37112
37555
  envVar: def.apiKeyEnvVar,
37113
37556
  aliases: def.apiKeyAliases,
37114
37557
  authScheme: normalizeAuthScheme(def.authScheme),
37115
- publicKeyFallback: def.publicKeyFallback,
37116
37558
  oauthFallback: def.oauthFallback
37117
37559
  }), [def.name, ...RUNTIME_NAME_ALIASES[def.name] ?? []]);
37118
37560
  }
@@ -37528,8 +37970,8 @@ async function fetchDevinAllowedUids(apiKey) {
37528
37970
  return uids;
37529
37971
  }
37530
37972
  async function getServedDevinModels(opts) {
37531
- const now = Date.now();
37532
- if (!opts?.force && rosterCache && now - rosterCacheAt < ROSTER_TTL_MS)
37973
+ const now2 = Date.now();
37974
+ if (!opts?.force && rosterCache && now2 - rosterCacheAt < ROSTER_TTL_MS)
37533
37975
  return rosterCache;
37534
37976
  const apiKey = opts?.apiKey ?? readDevinApiKey();
37535
37977
  if (!apiKey)
@@ -37547,7 +37989,7 @@ async function getServedDevinModels(opts) {
37547
37989
  log("[Devin] entitlement unknown \u2014 using the full config list (superset)");
37548
37990
  }
37549
37991
  rosterCache = served;
37550
- rosterCacheAt = now;
37992
+ rosterCacheAt = now2;
37551
37993
  return served;
37552
37994
  } catch (err) {
37553
37995
  log(`[Devin] served-model discovery error: ${err}`);
@@ -37575,12 +38017,12 @@ function nonEmpty(value) {
37575
38017
  function groupKeyOf(entry) {
37576
38018
  return nonEmpty(entry.groupLabel) ?? nonEmpty(entry.family) ?? entry.wireId;
37577
38019
  }
37578
- function offerIsLive(offer, now = Date.now()) {
38020
+ function offerIsLive(offer, now2 = Date.now()) {
37579
38021
  if (!offer)
37580
38022
  return false;
37581
38023
  if (offer.expiresAt === undefined)
37582
38024
  return true;
37583
- return offer.expiresAt * 1000 > now;
38025
+ return offer.expiresAt * 1000 > now2;
37584
38026
  }
37585
38027
 
37586
38028
  // src/providers/model-resolvers/devin.ts
@@ -37815,6 +38257,9 @@ async function fetchDevinRoster() {
37815
38257
  return { id: wireId, ...rest };
37816
38258
  });
37817
38259
  }
38260
+ function isUndeclaredEditorInternal(id) {
38261
+ return id.startsWith("tab_");
38262
+ }
37818
38263
  async function fetchAntigravityRoster() {
37819
38264
  const { getValidAntigravityAccessToken: getValidAntigravityAccessToken2 } = await Promise.resolve().then(() => (init_antigravity_token(), exports_antigravity_token));
37820
38265
  const { setupAntigravityUser: setupAntigravityUser2, getServedAntigravityModels: getServedAntigravityModels2 } = await Promise.resolve().then(() => (init_antigravity_user(), exports_antigravity_user));
@@ -37822,10 +38267,12 @@ async function fetchAntigravityRoster() {
37822
38267
  if (!token)
37823
38268
  return [];
37824
38269
  const { projectId } = await setupAntigravityUser2(token);
37825
- const { servedIds, meta: meta3 } = await getServedAntigravityModels2(token, projectId);
37826
- return servedIds.map((id) => {
38270
+ const { servedIds, meta: meta3, excludedIds } = await getServedAntigravityModels2(token, projectId);
38271
+ const declaredExcluded = excludedIds ?? new Set;
38272
+ const selectable = servedIds.filter((id) => !declaredExcluded.has(id) && !isUndeclaredEditorInternal(id));
38273
+ return selectable.map((id) => {
37827
38274
  const m = meta3[id];
37828
- return m?.contextWindow ? { id, contextWindow: m.contextWindow } : { id };
38275
+ return m?.contextWindow ? { id, contextWindow: m.contextWindow, ignoreCatalogReleaseDate: true } : { id, ignoreCatalogReleaseDate: true };
37829
38276
  });
37830
38277
  }
37831
38278
  async function fetchOllamaRoster() {
@@ -38752,13 +39199,6 @@ var init_vision_proxy = __esm(() => {
38752
39199
  });
38753
39200
 
38754
39201
  // src/providers/model-parser.ts
38755
- function warnGoAliasDeprecatedOnce() {
38756
- if (_goDeprecationWarned)
38757
- return;
38758
- _goDeprecationWarned = true;
38759
- process.stderr.write(`[claudish] go@ is deprecated \u2014 use ag@<model> (Antigravity). Routing there.
38760
- `);
38761
- }
38762
39202
  function parseModelChain(modelSpec) {
38763
39203
  const parts = modelSpec.split(MODEL_CHAIN_SEPARATOR).map((s) => s.trim()).filter(Boolean);
38764
39204
  return parts.length > 0 ? parts : [modelSpec];
@@ -38785,8 +39225,6 @@ function parseModelSpec(modelSpec) {
38785
39225
  concurrency = Number.parseInt(concurrencyMatch[2], 10);
38786
39226
  }
38787
39227
  const provider = PROVIDER_SHORTCUTS[providerPart] || providerPart;
38788
- if (providerPart === "go")
38789
- warnGoAliasDeprecatedOnce();
38790
39228
  return {
38791
39229
  provider,
38792
39230
  model: modelPart,
@@ -38800,8 +39238,6 @@ function parseModelSpec(modelSpec) {
38800
39238
  for (const { prefix, provider, stripPrefix } of LEGACY_PREFIX_PATTERNS) {
38801
39239
  if (lowerSpec.startsWith(prefix)) {
38802
39240
  const model = stripPrefix ? modelSpec.slice(prefix.length) : modelSpec;
38803
- if (prefix === "go/")
38804
- warnGoAliasDeprecatedOnce();
38805
39241
  let concurrency;
38806
39242
  let modelName = model;
38807
39243
  if (LOCAL_PROVIDERS.has(provider)) {
@@ -38862,7 +39298,7 @@ function getLegacySyntaxWarning(parsed) {
38862
39298
  return `Deprecation warning: "${parsed.original}" uses legacy prefix syntax.
38863
39299
  ` + ` Consider using: ${newSyntax}`;
38864
39300
  }
38865
- var PROVIDER_SHORTCUTS, _goDeprecationWarned = false, LOCAL_PROVIDERS, NATIVE_MODEL_PATTERNS, LEGACY_PREFIX_PATTERNS, MODEL_CHAIN_SEPARATOR = "+";
39301
+ var PROVIDER_SHORTCUTS, LOCAL_PROVIDERS, NATIVE_MODEL_PATTERNS, LEGACY_PREFIX_PATTERNS, MODEL_CHAIN_SEPARATOR = "+";
38866
39302
  var init_model_parser = __esm(() => {
38867
39303
  init_provider_definitions();
38868
39304
  PROVIDER_SHORTCUTS = getShortcuts();
@@ -39707,9 +40143,9 @@ function showMonthlyBanner() {
39707
40143
  return;
39708
40144
  const profileConfig = loadConfig();
39709
40145
  const consent2 = profileConfig.stats;
39710
- const now = Date.now();
40146
+ const now2 = Date.now();
39711
40147
  const lastPrompt = consent2?.lastMonthlyPrompt ? new Date(consent2.lastMonthlyPrompt).getTime() : 0;
39712
- const timeSincePrompt = now - lastPrompt;
40148
+ const timeSincePrompt = now2 - lastPrompt;
39713
40149
  const isFirstRun = !consent2?.lastMonthlyPrompt;
39714
40150
  const isMonthlyInterval = timeSincePrompt >= MONTHLY_INTERVAL_MS;
39715
40151
  if (!isFirstRun && !isMonthlyInterval)
@@ -39877,7 +40313,7 @@ function statusToErrorType(status) {
39877
40313
  }
39878
40314
  }
39879
40315
  function sanitizeErrorMessage(message, maxLength = MAX_ERROR_MESSAGE_LENGTH) {
39880
- const flattened = String(message ?? "").replace(ANSI_ESCAPE, "").replace(CONTROL_CHARS, " ").replace(/\s+/g, " ").trim();
40316
+ const flattened = String(message ?? "").replace(ANSI_ESCAPE, "").replace(CONTROL_CHARS2, " ").replace(/\s+/g, " ").trim();
39881
40317
  if (flattened.length <= maxLength)
39882
40318
  return flattened;
39883
40319
  return `${flattened.slice(0, maxLength - 1).trimEnd()}\u2026`;
@@ -39973,10 +40409,10 @@ function ensureAnthropicErrorFormat(status, body) {
39973
40409
  const errorType = body?.error?.type || body?.type || body?.code;
39974
40410
  return wrapAnthropicError(status, String(message), errorType);
39975
40411
  }
39976
- var MAX_ERROR_MESSAGE_LENGTH = 600, ANSI_ESCAPE, CONTROL_CHARS;
40412
+ var MAX_ERROR_MESSAGE_LENGTH = 600, ANSI_ESCAPE, CONTROL_CHARS2;
39977
40413
  var init_anthropic_error = __esm(() => {
39978
40414
  ANSI_ESCAPE = /\x1B\[[0-?]*[ -/]*[@-~]|\x1B[@-Z\\-_]/g;
39979
- CONTROL_CHARS = /[\x00-\x1F\x7F]/g;
40415
+ CONTROL_CHARS2 = /[\x00-\x1F\x7F]/g;
39980
40416
  });
39981
40417
 
39982
40418
  // src/handlers/shared/collect-sse-message.ts
@@ -43199,10 +43635,10 @@ class ComposedHandler {
43199
43635
  maybePollPlanUsage(adapter) {
43200
43636
  if (!adapter.poll)
43201
43637
  return;
43202
- const now = Date.now();
43203
- if (now - this.lastPlanPollAt < PLAN_POLL_INTERVAL_MS)
43638
+ const now2 = Date.now();
43639
+ if (now2 - this.lastPlanPollAt < PLAN_POLL_INTERVAL_MS)
43204
43640
  return;
43205
- this.lastPlanPollAt = now;
43641
+ this.lastPlanPollAt = now2;
43206
43642
  adapter.poll({ modelId: this.bareModelName }).then((plan) => {
43207
43643
  if (plan)
43208
43644
  this.tokenTracker.setPlanUsage(plan);
@@ -43223,6 +43659,9 @@ function getRecoveryHint(status, errorText, providerName, transportTerminal429)
43223
43659
  return "Provider overloaded. Retry or use a different model.";
43224
43660
  }
43225
43661
  if (status === 429 && (transportTerminal429 ?? isTerminal429(errorText))) {
43662
+ if (hasPlanLimitWording(errorText)) {
43663
+ return "Plan limit reached \u2014 your allowance is spent for this cycle and refills on the provider's own schedule (see the message below). Wait, upgrade the plan, or switch provider.";
43664
+ }
43226
43665
  return "Out of quota \u2014 check your plan & billing details. This won't recover on retry.";
43227
43666
  }
43228
43667
  if (status === 429 && transportTerminal429 === false) {
@@ -43239,6 +43678,9 @@ function getRecoveryHint(status, errorText, providerName, transportTerminal429)
43239
43678
  return "Model not supported by this provider. Verify model name.";
43240
43679
  }
43241
43680
  if (isQuotaExhaustionError(status, errorText)) {
43681
+ if (hasPlanLimitWording(errorText)) {
43682
+ return "Plan limit reached \u2014 your allowance is spent for this cycle and refills on the provider's own schedule (see the message below). Wait, upgrade the plan, or switch provider.";
43683
+ }
43242
43684
  return "Out of quota \u2014 check your plan & billing details. This won't recover on retry.";
43243
43685
  }
43244
43686
  if (hasActionableLink(errorText)) {
@@ -43354,6 +43796,9 @@ function resolveApiKeyProvenance(envVar, aliases) {
43354
43796
  effectiveSource = configLayerLabel();
43355
43797
  layers[1].isActive = true;
43356
43798
  layers[2].isActive = false;
43799
+ } else if (isKeychainHydratedVar(runtimeVar)) {
43800
+ effectiveSource = "macOS Keychain";
43801
+ layers[2].source = `process.env[${runtimeVar}] (from macOS Keychain)`;
43357
43802
  } else if (isOpHydratedVar(runtimeVar)) {
43358
43803
  effectiveSource = "1Password";
43359
43804
  layers[2].source = `process.env[${runtimeVar}] (from 1Password)`;
@@ -43403,6 +43848,7 @@ function readConfigKey(envVar) {
43403
43848
  }
43404
43849
  var import_dotenv;
43405
43850
  var init_api_key_provenance = __esm(() => {
43851
+ init_keychain_source();
43406
43852
  init_onepassword();
43407
43853
  import_dotenv = __toESM(require_main(), 1);
43408
43854
  });
@@ -44513,16 +44959,15 @@ var init_provider_definitions = __esm(() => {
44513
44959
  apiKeyDescription: "Antigravity (shared OAuth token)",
44514
44960
  apiKeyUrl: "https://antigravity.google/",
44515
44961
  oauthLoginSlug: "antigravity",
44516
- shortcuts: ["ag", "antigravity", "go"],
44962
+ shortcuts: ["ag", "antigravity"],
44517
44963
  shortestPrefix: "ag",
44518
44964
  legacyPrefixes: [
44519
44965
  { prefix: "ag/", stripPrefix: true },
44520
- { prefix: "antigravity/", stripPrefix: true },
44521
- { prefix: "go/", stripPrefix: true }
44966
+ { prefix: "antigravity/", stripPrefix: true }
44522
44967
  ],
44523
44968
  modelDiscovery: { path: "", format: "antigravity" },
44524
44969
  isDirectApi: true,
44525
- description: "Antigravity subscription (ag@; go@ deprecated)"
44970
+ description: "Antigravity subscription (ag@)"
44526
44971
  },
44527
44972
  {
44528
44973
  createHandler: devinHandler,
@@ -45138,7 +45583,7 @@ var init_provider_definitions = __esm(() => {
45138
45583
  legacyPrefixes: [{ prefix: "qp/", stripPrefix: true }],
45139
45584
  modelDiscovery: { path: "/compatible-mode/v1/models", format: "openai-models-list" },
45140
45585
  isDirectApi: true,
45141
- description: "Alibaba Model Studio pay-as-you-go (qp@)"
45586
+ description: "Alibaba Model Studio API, pay-as-you-go (qp@)"
45142
45587
  },
45143
45588
  {
45144
45589
  createHandler: openaiHandler,
@@ -46068,10 +46513,10 @@ var init_predefined_catalog = __esm(() => {
46068
46513
  });
46069
46514
 
46070
46515
  // src/providers/predefined-endpoints.ts
46071
- function warnOnce2(message) {
46072
- if (warnedMessages2.has(message))
46516
+ function warnOnce3(message) {
46517
+ if (warnedMessages3.has(message))
46073
46518
  return;
46074
- warnedMessages2.add(message);
46519
+ warnedMessages3.add(message);
46075
46520
  console.error(message);
46076
46521
  }
46077
46522
  function activeCatalog() {
@@ -46088,7 +46533,7 @@ function readOptOut(config2) {
46088
46533
  if (result.success) {
46089
46534
  parsed = result.data;
46090
46535
  } else {
46091
- warnOnce2("[claudish] config 'predefinedEndpoints' is not valid and was ignored: " + result.error.issues.map((i) => `${i.path.join(".") || "(root)"} ${i.message}`).join(", "));
46536
+ warnOnce3("[claudish] config 'predefinedEndpoints' is not valid and was ignored: " + result.error.issues.map((i) => `${i.path.join(".") || "(root)"} ${i.message}`).join(", "));
46092
46537
  }
46093
46538
  }
46094
46539
  return {
@@ -46135,7 +46580,7 @@ function loadPredefinedEndpoints(config2, opts = {}) {
46135
46580
  const noteStale = (entry, name, reason) => {
46136
46581
  if (!ownRegistrations.has(name) || !runtime.has(entry.name))
46137
46582
  return;
46138
- warnOnce2(`[claudish] predefined endpoint '${entry.name}' is no longer eligible (${reason}) but stays ` + "registered for the rest of this process \u2014 runtime provider registration cannot be " + "undone. Restart claudish to apply the change.");
46583
+ warnOnce3(`[claudish] predefined endpoint '${entry.name}' is no longer eligible (${reason}) but stays ` + "registered for the rest of this process \u2014 runtime provider registration cannot be " + "undone. Restart claudish to apply the change.");
46139
46584
  };
46140
46585
  if (optOut.disabled) {
46141
46586
  for (const entry of catalog) {
@@ -46155,7 +46600,7 @@ function loadPredefinedEndpoints(config2, opts = {}) {
46155
46600
  noteStale(entry, name, reason);
46156
46601
  };
46157
46602
  if (seen.has(name)) {
46158
- warnOnce2(`[claudish] predefined endpoint '${entry.name}' appears more than once in the bundled ` + "catalog. The first row wins; the later one is ignored.");
46603
+ warnOnce3(`[claudish] predefined endpoint '${entry.name}' appears more than once in the bundled ` + "catalog. The first row wins; the later one is ignored.");
46159
46604
  skip("duplicate row");
46160
46605
  continue;
46161
46606
  }
@@ -46163,19 +46608,19 @@ function loadPredefinedEndpoints(config2, opts = {}) {
46163
46608
  const owner = reserved.get(name);
46164
46609
  if (owner) {
46165
46610
  const reason = `'${entry.name}' is already claimed by builtin provider '${owner}' ` + "(as its name, a shortcut, or a legacy prefix)";
46166
- warnOnce2(`[claudish] predefined endpoint '${entry.name}' skipped: ${reason}. ` + "The builtin wins; the bundled entry is inactive.");
46611
+ warnOnce3(`[claudish] predefined endpoint '${entry.name}' skipped: ${reason}. ` + "The builtin wins; the bundled entry is inactive.");
46167
46612
  recordEndpointUnavailable(entry.name, `bundled endpoint was skipped because ${reason}`);
46168
46613
  skip("collides with builtin");
46169
46614
  continue;
46170
46615
  }
46171
46616
  if (runtime.has(entry.name) && !ownRegistrations.has(name)) {
46172
- warnOnce2(`[claudish] predefined endpoint '${entry.name}' skipped: a provider named ` + `'${entry.name}' is already registered for this process.`);
46617
+ warnOnce3(`[claudish] predefined endpoint '${entry.name}' skipped: a provider named ` + `'${entry.name}' is already registered for this process.`);
46173
46618
  skip("already registered");
46174
46619
  continue;
46175
46620
  }
46176
46621
  if (userEndpoints.has(name)) {
46177
46622
  if (hasLocalApiKey({ envVar: entry.apiKeyEnvVar })) {
46178
- warnOnce2(`[claudish] customEndpoints['${entry.name}'] replaces the bundled entry entirely; ` + `${entry.apiKeyEnvVar} no longer applies to it. Add ` + `"apiKey": "\${${entry.apiKeyEnvVar}}" to that entry to keep using it.`);
46623
+ warnOnce3(`[claudish] customEndpoints['${entry.name}'] replaces the bundled entry entirely; ` + `${entry.apiKeyEnvVar} no longer applies to it. Add ` + `"apiKey": "\${${entry.apiKeyEnvVar}}" to that entry to keep using it.`);
46179
46624
  }
46180
46625
  skipStale("replaced by customEndpoints");
46181
46626
  continue;
@@ -46185,7 +46630,7 @@ function loadPredefinedEndpoints(config2, opts = {}) {
46185
46630
  continue;
46186
46631
  }
46187
46632
  const { envVar, aliases } = credentialEnvVars(entry);
46188
- const permitted = optOut.enable.has(name) || hasLocalApiKey({ envVar, aliases });
46633
+ const permitted = optOut.enable.has(name) || hasLocalApiKey({ envVar, aliases }) || keychainHasAnyOf([envVar, ...aliases ?? []]);
46189
46634
  if (!permitted) {
46190
46635
  skipStale("no local credential");
46191
46636
  continue;
@@ -46193,7 +46638,7 @@ function loadPredefinedEndpoints(config2, opts = {}) {
46193
46638
  const resolvedUrl = classifyEndpointBaseUrl(entry.baseUrl, entry.baseUrlEnvVars);
46194
46639
  if (!resolvedUrl.ok) {
46195
46640
  const detail = describeBadBaseUrlOverride(resolvedUrl, entry.baseUrl);
46196
- warnOnce2(`[claudish] predefined endpoint '${entry.name}' skipped: ${detail}`);
46641
+ warnOnce3(`[claudish] predefined endpoint '${entry.name}' skipped: ${detail}`);
46197
46642
  recordEndpointUnavailable(entry.name, detail);
46198
46643
  skipStale("invalid base URL override");
46199
46644
  continue;
@@ -46203,14 +46648,15 @@ function loadPredefinedEndpoints(config2, opts = {}) {
46203
46648
  ownRegistrations.add(name);
46204
46649
  result.registered.push(entry.name);
46205
46650
  } catch (err) {
46206
- warnOnce2(`[claudish] predefined endpoint '${entry.name}' skipped: ` + `${err instanceof Error ? err.message : String(err)}`);
46651
+ warnOnce3(`[claudish] predefined endpoint '${entry.name}' skipped: ` + `${err instanceof Error ? err.message : String(err)}`);
46207
46652
  skip("invalid catalog row");
46208
46653
  }
46209
46654
  }
46210
46655
  return result;
46211
46656
  }
46212
- var KILL_SWITCH_ENV = "CLAUDISH_NO_PREDEFINED_ENDPOINTS", warnedMessages2, ownRegistrations, catalogOverride = null;
46657
+ var KILL_SWITCH_ENV = "CLAUDISH_NO_PREDEFINED_ENDPOINTS", warnedMessages3, ownRegistrations, catalogOverride = null;
46213
46658
  var init_predefined_endpoints = __esm(() => {
46659
+ init_keychain_source();
46214
46660
  init_local_api_key();
46215
46661
  init_config_schema();
46216
46662
  init_custom_endpoints_loader();
@@ -46218,7 +46664,7 @@ var init_predefined_endpoints = __esm(() => {
46218
46664
  init_predefined_catalog();
46219
46665
  init_reserved_namespace();
46220
46666
  init_runtime_providers();
46221
- warnedMessages2 = new Set;
46667
+ warnedMessages3 = new Set;
46222
46668
  ownRegistrations = new Set;
46223
46669
  });
46224
46670
 
@@ -46258,7 +46704,7 @@ function reportCustomEndpoints(result) {
46258
46704
  logStderr(`customEndpoints['${name}'] failed validation: ${message}`);
46259
46705
  }
46260
46706
  for (const { name, reason } of result.refused) {
46261
- warnOnce3(`customEndpoints['${name}'] skipped: ${reason}. The builtin wins. ` + `Rename your entry (e.g. '${name}-custom') to use it.`);
46707
+ warnOnce4(`customEndpoints['${name}'] skipped: ${reason}. The builtin wins. ` + `Rename your entry (e.g. '${name}-custom') to use it.`);
46262
46708
  }
46263
46709
  }
46264
46710
  function warnOnProjectScopedEndpoints() {
@@ -46267,26 +46713,26 @@ function warnOnProjectScopedEndpoints() {
46267
46713
  const names = Object.keys(local?.customEndpoints ?? {});
46268
46714
  if (names.length === 0)
46269
46715
  return;
46270
- warnOnce3(`.claudish.json declares customEndpoints (${names.join(", ")}) but they are ` + "read from the GLOBAL config only, so they are being ignored. Move them to " + "~/.claudish/config.json.");
46716
+ warnOnce4(`.claudish.json declares customEndpoints (${names.join(", ")}) but they are ` + "read from the GLOBAL config only, so they are being ignored. Move them to " + "~/.claudish/config.json.");
46271
46717
  } catch {}
46272
46718
  }
46273
- function warnOnce3(message) {
46274
- if (warnedMessages3.has(message))
46719
+ function warnOnce4(message) {
46720
+ if (warnedMessages4.has(message))
46275
46721
  return;
46276
- warnedMessages3.add(message);
46722
+ warnedMessages4.add(message);
46277
46723
  logStderr(message);
46278
46724
  }
46279
46725
  function invalidateEndpointRegistration() {
46280
46726
  registered = false;
46281
46727
  }
46282
- var registered = false, lastCustomResult, warnedMessages3;
46728
+ var registered = false, lastCustomResult, warnedMessages4;
46283
46729
  var init_endpoint_registration = __esm(() => {
46284
46730
  init_logger();
46285
46731
  init_profile_config();
46286
46732
  init_custom_endpoints_loader();
46287
46733
  init_predefined_endpoints();
46288
46734
  lastCustomResult = { registered: 0, errors: [], refused: [] };
46289
- warnedMessages3 = new Set;
46735
+ warnedMessages4 = new Set;
46290
46736
  });
46291
46737
 
46292
46738
  // src/providers/provider-registry.ts
@@ -48045,8 +48491,8 @@ function readProjectCwd(dirName) {
48045
48491
  }
48046
48492
  return null;
48047
48493
  }
48048
- function isActive(row, now = Date.now()) {
48049
- return now - row.mtimeMs < ACTIVE_WINDOW_MS;
48494
+ function isActive(row, now2 = Date.now()) {
48495
+ return now2 - row.mtimeMs < ACTIVE_WINDOW_MS;
48050
48496
  }
48051
48497
  function discoverWorktreeGroups(repo) {
48052
48498
  const rootSlug = slugForPath(repo.root);
@@ -48661,9 +49107,9 @@ function setupSession(sessionPath, models, input) {
48661
49107
  }
48662
49108
  const ids = models.map((_, i) => String(i + 1).padStart(2, "0"));
48663
49109
  const shuffled = fisherYatesShuffle([...ids]);
48664
- const now = new Date().toISOString();
49110
+ const now2 = new Date().toISOString();
48665
49111
  const manifest = {
48666
- created: now,
49112
+ created: now2,
48667
49113
  models: {},
48668
49114
  shuffleOrder: shuffled
48669
49115
  };
@@ -48671,13 +49117,13 @@ function setupSession(sessionPath, models, input) {
48671
49117
  const anonId = shuffled[i];
48672
49118
  manifest.models[anonId] = {
48673
49119
  model: models[i],
48674
- assignedAt: now
49120
+ assignedAt: now2
48675
49121
  };
48676
49122
  mkdirSync11(join28(sessionPath, "work", anonId), { recursive: true });
48677
49123
  }
48678
49124
  writeFileSync11(join28(sessionPath, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
48679
49125
  const status = {
48680
- startedAt: now,
49126
+ startedAt: now2,
48681
49127
  models: Object.fromEntries(Object.keys(manifest.models).map((id) => [
48682
49128
  id,
48683
49129
  {
@@ -48958,9 +49404,9 @@ async function runModels(sessionPath, opts = {}) {
48958
49404
  return Math.max(0, Date.now() - s.updated_at);
48959
49405
  };
48960
49406
  const graceStartedAt = new Map;
48961
- const graceUsedMs = (id, now) => {
49407
+ const graceUsedMs = (id, now2) => {
48962
49408
  const start = graceStartedAt.get(id);
48963
- return start === undefined ? 0 : Math.max(0, now - start);
49409
+ return start === undefined ? 0 : Math.max(0, now2 - start);
48964
49410
  };
48965
49411
  const runningIds = () => [...processes.keys()].filter((id) => statusCache.models[id]?.state === "RUNNING");
48966
49412
  const timeoutModel = async (id, why) => {
@@ -49013,10 +49459,10 @@ async function runModels(sessionPath, opts = {}) {
49013
49459
  if (running.length === 0)
49014
49460
  return;
49015
49461
  const extended = [];
49016
- const now = Date.now();
49462
+ const now2 = Date.now();
49017
49463
  for (const id of running) {
49018
49464
  const idleMs = idleMsFor(id);
49019
- const usedGrace = graceUsedMs(id, now);
49465
+ const usedGrace = graceUsedMs(id, now2);
49020
49466
  if (!graceEnabled) {
49021
49467
  await timeoutModel(id, "deadline reached (grace extension disabled)");
49022
49468
  } else if (usedGrace >= maxGraceMs) {
@@ -49027,7 +49473,7 @@ async function runModels(sessionPath, opts = {}) {
49027
49473
  await timeoutModel(id, `no measurable progress for ${Math.round(idleMs / 1000)}s`);
49028
49474
  } else {
49029
49475
  if (!graceStartedAt.has(id))
49030
- graceStartedAt.set(id, now);
49476
+ graceStartedAt.set(id, now2);
49031
49477
  extended.push(id);
49032
49478
  }
49033
49479
  }
@@ -50269,6 +50715,9 @@ function extractVersionParts(modelId) {
50269
50715
  break;
50270
50716
  continue;
50271
50717
  }
50718
+ if (!started && /^\d+b$/.test(token) && Number.parseInt(match[0], 10) > 10) {
50719
+ continue;
50720
+ }
50272
50721
  if (!started) {
50273
50722
  started = true;
50274
50723
  for (const part of match[0].split(".")) {
@@ -50789,6 +51238,14 @@ function classifyHttpError(status, body, latencyMs) {
50789
51238
  };
50790
51239
  }
50791
51240
  if (status === 429) {
51241
+ if (hasPlanLimitWording(body)) {
51242
+ return {
51243
+ state: "plan-limit",
51244
+ latencyMs,
51245
+ httpStatus: status,
51246
+ errorMessage: extractErrorMessage(body) || "Plan allowance spent for this cycle"
51247
+ };
51248
+ }
50792
51249
  return {
50793
51250
  state: "rate-limited",
50794
51251
  latencyMs,
@@ -50797,6 +51254,14 @@ function classifyHttpError(status, body, latencyMs) {
50797
51254
  };
50798
51255
  }
50799
51256
  if (upstream === 429 || status === 402) {
51257
+ if (status !== 402 && hasPlanLimitWording(body)) {
51258
+ return {
51259
+ state: "plan-limit",
51260
+ latencyMs,
51261
+ httpStatus: upstream ?? status,
51262
+ errorMessage: extractErrorMessage(body) || "Plan allowance spent for this cycle"
51263
+ };
51264
+ }
50800
51265
  return {
50801
51266
  state: "out-of-credit",
50802
51267
  latencyMs,
@@ -50819,7 +51284,7 @@ function classifyHttpError(status, body, latencyMs) {
50819
51284
  errorMessage: extractErrorMessage(body) || `HTTP ${status}`
50820
51285
  };
50821
51286
  }
50822
- function truncateKeepingLink(text, max = 160) {
51287
+ function truncateKeepingLink(text, max = 400) {
50823
51288
  if (text.length <= max)
50824
51289
  return text;
50825
51290
  const url2 = text.match(/https?:\/\/\S+/i)?.[0];
@@ -51036,6 +51501,8 @@ function describeProbeState(result) {
51036
51501
  return withDetail(`rate limited \xB7 ${result.latencyMs}ms`, result.errorMessage);
51037
51502
  case "out-of-credit":
51038
51503
  return withDetail(`out of credit \xB7 ${status}${latency}`.trim(), result.errorMessage);
51504
+ case "plan-limit":
51505
+ return withDetail(`plan limit reached \xB7 ${status}${latency}`.trim(), result.errorMessage);
51039
51506
  case "server-error":
51040
51507
  return withDetail(`server error \xB7 ${status} \xB7 ${result.latencyMs}ms`, result.errorMessage);
51041
51508
  case "timeout":
@@ -51052,12 +51519,13 @@ function isReadyState(state) {
51052
51519
  return state === "live";
51053
51520
  }
51054
51521
  function isFailureState(state) {
51055
- return state === "auth-failed" || state === "model-not-found" || state === "rate-limited" || state === "out-of-credit" || state === "server-error" || state === "timeout" || state === "network-error" || state === "error";
51522
+ return state === "auth-failed" || state === "model-not-found" || state === "rate-limited" || state === "out-of-credit" || state === "plan-limit" || state === "server-error" || state === "timeout" || state === "network-error" || state === "error";
51056
51523
  }
51057
51524
  var STREAM_MS_FLOOR = 50, OAUTH_PROVIDERS2, PROBE_PROMPT = "Count from one to twenty in words, one per line.", PROBE_MAX_TOKENS = 512, MINIMAL_EFFORT_UNSUPPORTED;
51058
51525
  var init_probe_live = __esm(() => {
51059
51526
  init_anthropic_error();
51060
51527
  init_model_unsupported();
51528
+ init_quota_exhaustion();
51061
51529
  OAUTH_PROVIDERS2 = new Set(["vertex", "antigravity", "devin"]);
51062
51530
  MINIMAL_EFFORT_UNSUPPORTED = new Set(["native-anthropic", "anthropic"]);
51063
51531
  });
@@ -54277,8 +54745,8 @@ class OpenRouterRequestQueue {
54277
54745
  }
54278
54746
  }
54279
54747
  async waitForNextSlot() {
54280
- const now = Date.now();
54281
- const timeSinceLastRequest = now - this.rateLimitState.lastRequestTime;
54748
+ const now2 = Date.now();
54749
+ const timeSinceLastRequest = now2 - this.rateLimitState.lastRequestTime;
54282
54750
  const delayMs = this.calculateDelay();
54283
54751
  this.rateLimitState.currentDelayMs = delayMs;
54284
54752
  if (timeSinceLastRequest < delayMs) {
@@ -54306,8 +54774,8 @@ class OpenRouterRequestQueue {
54306
54774
  }
54307
54775
  }
54308
54776
  if (this.rateLimitState.resetTime !== null && this.rateLimitState.remainingRequests !== null) {
54309
- const now = Date.now() / 1000;
54310
- const timeUntilReset = this.rateLimitState.resetTime - now;
54777
+ const now2 = Date.now() / 1000;
54778
+ const timeUntilReset = this.rateLimitState.resetTime - now2;
54311
54779
  if (timeUntilReset > 0 && this.rateLimitState.remainingRequests > 0) {
54312
54780
  const optimalDelay = timeUntilReset * 1000 / Math.max(this.rateLimitState.remainingRequests, 1);
54313
54781
  delayMs = Math.max(delayMs, Math.min(optimalDelay, this.maxDelayMs));
@@ -56491,11 +56959,15 @@ __export(exports_theme_mode, {
56491
56959
  classifyOscBackground: () => classifyOscBackground,
56492
56960
  detectAndSetThemeMode: () => detectAndSetThemeMode,
56493
56961
  detectAndSetThemeModeSync: () => detectAndSetThemeModeSync,
56962
+ getTerminalBackground: () => getTerminalBackground,
56963
+ getTerminalBackgroundHex: () => getTerminalBackgroundHex,
56494
56964
  getThemeMode: () => getThemeMode,
56495
56965
  onThemeModeChange: () => onThemeModeChange,
56966
+ parseOscBackgroundHex: () => parseOscBackgroundHex,
56496
56967
  queryTerminalThemeMode: () => queryTerminalThemeMode,
56497
56968
  relativeLuminance: () => relativeLuminance,
56498
56969
  resetThemeModeForTests: () => resetThemeModeForTests,
56970
+ setTerminalBackgroundHex: () => setTerminalBackgroundHex,
56499
56971
  setThemeMode: () => setThemeMode,
56500
56972
  themeModeFromColorFgBg: () => themeModeFromColorFgBg,
56501
56973
  themeModeOverride: () => themeModeOverride
@@ -56514,6 +56986,7 @@ function onThemeModeChange(cb) {
56514
56986
  }
56515
56987
  function resetThemeModeForTests() {
56516
56988
  setThemeMode(null);
56989
+ background = null;
56517
56990
  }
56518
56991
  function themeModeOverride(env = process.env) {
56519
56992
  const raw2 = env.CLAUDISH_THEME?.trim().toLowerCase();
@@ -56550,6 +57023,31 @@ function classifyOscBackground(reply) {
56550
57023
  const lum = relativeLuminance(channel(m[1]), channel(m[2]), channel(m[3]));
56551
57024
  return lum >= MID_SRGB_LUMINANCE ? "light" : "dark";
56552
57025
  }
57026
+ function parseOscBackgroundHex(reply) {
57027
+ const m = reply.match(/\]11;rgb:([0-9a-fA-F]{1,4})\/([0-9a-fA-F]{1,4})\/([0-9a-fA-F]{1,4})/);
57028
+ if (!m)
57029
+ return null;
57030
+ const byte = (hex3) => {
57031
+ const max = 16 ** hex3.length - 1;
57032
+ const v = Math.round(Number.parseInt(hex3, 16) / max * 255);
57033
+ return v.toString(16).padStart(2, "0");
57034
+ };
57035
+ return `#${byte(m[1])}${byte(m[2])}${byte(m[3])}`;
57036
+ }
57037
+ function getTerminalBackgroundHex() {
57038
+ return background?.hex ?? null;
57039
+ }
57040
+ function getTerminalBackground() {
57041
+ return background;
57042
+ }
57043
+ function setTerminalBackgroundHex(hex3) {
57044
+ if (!hex3) {
57045
+ background = null;
57046
+ return;
57047
+ }
57048
+ const mode = classifyOscBackground(`]11;rgb:${hex3.slice(1, 3)}/${hex3.slice(3, 5)}/${hex3.slice(5, 7)}`);
57049
+ background = mode ? { hex: hex3, mode } : null;
57050
+ }
56553
57051
  async function queryTerminalThemeMode(timeoutMs = 150) {
56554
57052
  const stdin = process.stdin;
56555
57053
  const stdout = process.stdout;
@@ -56577,7 +57075,10 @@ async function queryTerminalThemeMode(timeoutMs = 150) {
56577
57075
  const onData = (chunk) => {
56578
57076
  buffer += chunk.toString("latin1");
56579
57077
  if (/\]11;[^\x07\x1b]*(\x07|\x1b\\)/.test(buffer)) {
56580
- finish(classifyOscBackground(buffer));
57078
+ const mode = classifyOscBackground(buffer);
57079
+ const hex3 = parseOscBackgroundHex(buffer);
57080
+ background = hex3 && mode ? { hex: hex3, mode } : null;
57081
+ finish(mode);
56581
57082
  }
56582
57083
  };
56583
57084
  const timer = setTimeout(() => finish(null), timeoutMs);
@@ -56595,17 +57096,18 @@ async function queryTerminalThemeMode(timeoutMs = 150) {
56595
57096
  async function detectAndSetThemeMode() {
56596
57097
  const override = themeModeOverride();
56597
57098
  if (override) {
57099
+ setTerminalBackgroundHex(null);
56598
57100
  setThemeMode(override);
56599
57101
  return override;
56600
57102
  }
56601
- const fromEnv = themeModeFromColorFgBg();
56602
- if (fromEnv) {
56603
- setThemeMode(fromEnv);
56604
- return fromEnv;
57103
+ const fromOsc = await queryTerminalThemeMode(150);
57104
+ if (fromOsc) {
57105
+ setThemeMode(fromOsc);
57106
+ return fromOsc;
56605
57107
  }
56606
- const fromOsc = await queryTerminalThemeMode();
56607
- setThemeMode(fromOsc);
56608
- return fromOsc;
57108
+ const fromEnv = themeModeFromColorFgBg();
57109
+ setThemeMode(fromEnv);
57110
+ return fromEnv;
56609
57111
  }
56610
57112
  function detectAndSetThemeModeSync() {
56611
57113
  const mode = themeModeOverride() ?? themeModeFromColorFgBg();
@@ -56613,7 +57115,7 @@ function detectAndSetThemeModeSync() {
56613
57115
  setThemeMode(mode);
56614
57116
  return mode ?? detected;
56615
57117
  }
56616
- var detected = null, listeners, MID_SRGB_LUMINANCE;
57118
+ var detected = null, listeners, MID_SRGB_LUMINANCE, background = null;
56617
57119
  var init_theme_mode = __esm(() => {
56618
57120
  listeners = [];
56619
57121
  MID_SRGB_LUMINANCE = relativeLuminance(0.5, 0.5, 0.5);
@@ -57352,8 +57854,6 @@ function describeSourceSync(p, config3) {
57352
57854
  return "env";
57353
57855
  if (hasCfg)
57354
57856
  return "cfg";
57355
- if (p.publicKeyFallback)
57356
- return "public";
57357
57857
  return null;
57358
57858
  }
57359
57859
  async function describeSource(p, config3) {
@@ -57394,7 +57894,6 @@ function toProviderDef(def) {
57394
57894
  defaultEndpoint: def.baseUrl || undefined,
57395
57895
  aliases: def.apiKeyAliases,
57396
57896
  isLocal: def.isLocal,
57397
- publicKeyFallback: !!def.publicKeyFallback,
57398
57897
  oauthSlug: def.oauthLoginSlug
57399
57898
  };
57400
57899
  }
@@ -68585,7 +69084,7 @@ var require_lib3 = __commonJS(function(exports, module) {
68585
69084
  var trail = encoder.end();
68586
69085
  return trail && trail.length > 0 ? Buffer2.concat([res, trail]) : res;
68587
69086
  };
68588
- iconv.decode = function decode3(buf, encoding, options) {
69087
+ iconv.decode = function decode4(buf, encoding, options) {
68589
69088
  if (typeof buf === "string") {
68590
69089
  if (!iconv.skipDecodeWarning) {
68591
69090
  console.error("Iconv-lite warning: decode()-ing strings is deprecated. Refer to https://github.com/ashtuchkin/iconv-lite/wiki/Use-Buffers-when-decoding");
@@ -69841,6 +70340,420 @@ var init_dist16 = __esm(() => {
69841
70340
  init_dist15();
69842
70341
  });
69843
70342
 
70343
+ // src/keychain-command.ts
70344
+ var exports_keychain_command = {};
70345
+ __export(exports_keychain_command, {
70346
+ keychainCommand: () => keychainCommand
70347
+ });
70348
+ function ok(text) {
70349
+ const c = cliAnsi();
70350
+ return `${c.GREEN}${text}${c.RESET}`;
70351
+ }
70352
+ function warn(text) {
70353
+ const c = cliAnsi();
70354
+ return `${c.YELLOW}${text}${c.RESET}`;
70355
+ }
70356
+ function bad(text) {
70357
+ const c = cliAnsi();
70358
+ return `${c.RED}${text}${c.RESET}`;
70359
+ }
70360
+ function dim3(text) {
70361
+ const c = cliAnsi();
70362
+ return `${c.GRAY}${text}${c.RESET}`;
70363
+ }
70364
+ function strong(text) {
70365
+ const c = cliAnsi();
70366
+ return `${c.BOLD}${text}${c.RESET}`;
70367
+ }
70368
+ function requireKeychain() {
70369
+ const reason = keychainUnavailableReason();
70370
+ if (!reason)
70371
+ return true;
70372
+ console.error(bad(`Keychain unavailable: ${reason}`));
70373
+ console.error(dim3("claudish's keychain backend uses the macOS Security framework via /usr/bin/security."));
70374
+ return false;
70375
+ }
70376
+ function catalogEnvVars() {
70377
+ const names = new Set;
70378
+ for (const p of getAllProviders()) {
70379
+ if (p.apiKeyEnvVar)
70380
+ names.add(p.apiKeyEnvVar);
70381
+ for (const alias of p.apiKeyAliases ?? [])
70382
+ names.add(alias);
70383
+ }
70384
+ return Array.from(names).sort();
70385
+ }
70386
+ function keychainStatus() {
70387
+ const supported = isKeychainSupported();
70388
+ const enabled2 = isKeychainEnabled();
70389
+ console.log(strong("macOS Keychain backend"));
70390
+ console.log(` platform ${supported ? ok("supported") : bad(`unsupported (${process.platform})`)}`);
70391
+ console.log(` backend ${enabled2 ? ok("enabled") : dim3("disabled")}`);
70392
+ console.log(` service ${dim3(KEYCHAIN_SERVICE)}`);
70393
+ if (!supported)
70394
+ return;
70395
+ const listed = enumerateKeychainVars();
70396
+ if (listed.failed) {
70397
+ console.log(` stored keys ${bad("could not read the keychain")}`);
70398
+ console.log();
70399
+ console.log(bad(listed.error ?? "unknown error"));
70400
+ console.log(dim3("The keychain may be locked, or access may have been denied."));
70401
+ process.exitCode = 1;
70402
+ return;
70403
+ }
70404
+ const stored = listed.names;
70405
+ console.log(` stored keys ${stored.length > 0 ? ok(String(stored.length)) : dim3("0")}`);
70406
+ if (!enabled2 && stored.length > 0) {
70407
+ console.log();
70408
+ console.log(warn(`${stored.length} key(s) are in the keychain but the backend is disabled, so claudish will not read them.`));
70409
+ console.log(dim3(" Enable with: claudish keychain enable"));
70410
+ }
70411
+ if (enabled2 && stored.length === 0) {
70412
+ console.log();
70413
+ console.log(dim3("Nothing stored yet. Copy your existing keys in with:"));
70414
+ console.log(dim3(" claudish keychain import"));
70415
+ }
70416
+ console.log();
70417
+ console.log(dim3("Resolution order: env var \u2192 alias \u2192 config.json \u2192 macOS Keychain \u2192 1Password"));
70418
+ }
70419
+ function keychainList() {
70420
+ if (!requireKeychain())
70421
+ return;
70422
+ const listed = enumerateKeychainVars();
70423
+ if (listed.failed) {
70424
+ console.error(bad(`Could not read the keychain: ${listed.error ?? "unknown error"}`));
70425
+ console.error(dim3("The keychain may be locked, or access may have been denied."));
70426
+ process.exitCode = 1;
70427
+ return;
70428
+ }
70429
+ const stored = listed.names;
70430
+ if (stored.length === 0) {
70431
+ console.log(dim3("No keys stored in the keychain."));
70432
+ console.log(dim3("Copy your existing keys in with: claudish keychain import"));
70433
+ return;
70434
+ }
70435
+ const width = Math.max(...stored.map((n) => n.length));
70436
+ for (const name of stored) {
70437
+ let tail;
70438
+ try {
70439
+ const value = readKeychainSecret(name);
70440
+ tail = value ? dim3(valueTail2(value)) : bad("unreadable");
70441
+ } catch (err) {
70442
+ tail = bad(err instanceof KeychainError ? "denied" : "error");
70443
+ }
70444
+ const shadowed = process.env[name] ? warn(" (shadowed by an env var)") : "";
70445
+ console.log(` ${name.padEnd(width)} ${tail}${shadowed}`);
70446
+ }
70447
+ if (!isKeychainEnabled()) {
70448
+ console.log();
70449
+ console.log(warn("The keychain backend is disabled \u2014 claudish will not read these."));
70450
+ console.log(dim3(" Enable with: claudish keychain enable"));
70451
+ }
70452
+ }
70453
+ async function collectImportPlan(opts) {
70454
+ const wanted = opts.only ?? catalogEnvVars();
70455
+ const listed = enumerateKeychainVars();
70456
+ if (listed.failed) {
70457
+ throw new KeychainError(`Cannot read the keychain to plan this import: ${listed.error ?? "unknown error"}. ` + "Refusing to continue \u2014 without knowing what is already stored, an import could " + "silently replace existing keys.");
70458
+ }
70459
+ const stored = new Set(listed.names);
70460
+ const plan = new Map;
70461
+ const classify = (envVar, value, origin) => {
70462
+ const problem = describeUnstorableValue(value) ?? undefined;
70463
+ let action = stored.has(envVar) ? "overwrite" : "new";
70464
+ if (action === "overwrite") {
70465
+ try {
70466
+ if (readKeychainSecret(envVar) === value)
70467
+ action = "unchanged";
70468
+ } catch {}
70469
+ }
70470
+ plan.set(envVar, { envVar, value, origin, action, problem });
70471
+ };
70472
+ if (opts.from === "env" || opts.from === "all") {
70473
+ for (const envVar of wanted) {
70474
+ const value = process.env[envVar];
70475
+ if (value)
70476
+ classify(envVar, value, "environment");
70477
+ }
70478
+ }
70479
+ if ((opts.from === "1password" || opts.from === "all") && hasOpSources()) {
70480
+ const missing = wanted.filter((n) => !plan.has(n));
70481
+ if (missing.length > 0) {
70482
+ const resolved = await resolveOpKeyForEnvVars(new Set(missing), {
70483
+ onAuthFailure: "throw",
70484
+ allowPrompt: true
70485
+ });
70486
+ for (const [envVar, value] of Object.entries(resolved)) {
70487
+ if (value)
70488
+ classify(envVar, value, "1Password");
70489
+ }
70490
+ }
70491
+ }
70492
+ return Array.from(plan.values()).sort((a, b) => a.envVar.localeCompare(b.envVar));
70493
+ }
70494
+ function renderPlan(plan) {
70495
+ const width = Math.max(...plan.map((e) => e.envVar.length));
70496
+ for (const e of plan) {
70497
+ if (e.problem) {
70498
+ console.log(` ${bad("skip")} ${e.envVar.padEnd(width)} ${bad(e.problem)}`);
70499
+ continue;
70500
+ }
70501
+ const label = e.action === "new" ? ok("new") : e.action === "overwrite" ? warn("overwrite") : dim3("unchanged");
70502
+ const pad = " ".repeat(Math.max(0, 10 - (e.action === "new" ? 3 : e.action.length)));
70503
+ console.log(` ${label}${pad} ${e.envVar.padEnd(width)} ${dim3(valueTail2(e.value))} ${dim3(`from ${e.origin}`)}`);
70504
+ }
70505
+ }
70506
+ function writeImportEntries(entries) {
70507
+ let written = 0;
70508
+ const failures = [];
70509
+ for (const entry of entries) {
70510
+ try {
70511
+ writeKeychainSecret(entry.envVar, entry.value);
70512
+ written++;
70513
+ } catch (err) {
70514
+ failures.push(`${entry.envVar}: ${err instanceof Error ? err.message : String(err)}`);
70515
+ }
70516
+ }
70517
+ return { written, failures };
70518
+ }
70519
+ function reportImportResult(written, failures) {
70520
+ if (written > 0 && !isKeychainEnabled()) {
70521
+ setKeychainEnabled(true);
70522
+ console.log(ok("Keychain backend enabled."));
70523
+ }
70524
+ console.log(ok(`Stored ${written} key(s) in the keychain.`));
70525
+ if (failures.length > 0) {
70526
+ console.error(bad(`${failures.length} key(s) failed:`));
70527
+ for (const f of failures)
70528
+ console.error(` ${f}`);
70529
+ process.exitCode = 1;
70530
+ }
70531
+ }
70532
+ async function keychainImport(opts) {
70533
+ if (!requireKeychain())
70534
+ return;
70535
+ let plan;
70536
+ try {
70537
+ plan = await collectImportPlan(opts);
70538
+ } catch (err) {
70539
+ console.error(bad(`Could not build the import plan: ${err instanceof Error ? err.message : String(err)}`));
70540
+ process.exitCode = 1;
70541
+ return;
70542
+ }
70543
+ if (plan.length === 0) {
70544
+ console.log(dim3("Nothing to import \u2014 no matching keys found in the requested sources."));
70545
+ if (opts.from === "1password" && !hasOpSources()) {
70546
+ console.log(dim3("No 1Password source is configured. See: claudish config \u2192 1Password tab"));
70547
+ }
70548
+ return;
70549
+ }
70550
+ const writable = plan.filter((e) => !e.problem && e.action !== "unchanged");
70551
+ const overwrites = writable.filter((e) => e.action === "overwrite");
70552
+ console.log(strong(`Import plan \u2014 ${plan.length} key(s) considered`));
70553
+ renderPlan(plan);
70554
+ console.log();
70555
+ if (writable.length === 0) {
70556
+ console.log(dim3("Everything is already stored with the same value. Nothing to do."));
70557
+ return;
70558
+ }
70559
+ if (overwrites.length > 0) {
70560
+ console.log(warn(`${overwrites.length} existing keychain item(s) will be REPLACED. The previous values cannot be recovered.`));
70561
+ }
70562
+ if (opts.dryRun) {
70563
+ console.log(dim3("--dry-run: nothing was written."));
70564
+ return;
70565
+ }
70566
+ if (!opts.yes) {
70567
+ const { confirm } = await Promise.resolve().then(() => (init_dist16(), exports_dist));
70568
+ const proceed = await confirm({
70569
+ message: `Store ${writable.length} key(s) in the macOS Keychain?`,
70570
+ default: overwrites.length === 0
70571
+ });
70572
+ if (!proceed) {
70573
+ console.log(dim3("Cancelled \u2014 nothing was written."));
70574
+ return;
70575
+ }
70576
+ }
70577
+ const { written, failures } = writeImportEntries(writable);
70578
+ reportImportResult(written, failures);
70579
+ const skipped = plan.filter((e) => e.problem);
70580
+ if (skipped.length > 0) {
70581
+ console.log(warn(`${skipped.length} key(s) skipped \u2014 see the plan above.`));
70582
+ }
70583
+ }
70584
+ async function readSecretInteractively(envVar) {
70585
+ if (!process.stdin.isTTY) {
70586
+ const piped = await new Response(Bun.stdin.stream()).text();
70587
+ const value2 = piped.replace(/\r?\n$/, "");
70588
+ return value2.length > 0 ? value2 : null;
70589
+ }
70590
+ const { password } = await Promise.resolve().then(() => (init_dist16(), exports_dist));
70591
+ const value = await password({ message: `Value for ${envVar}:`, mask: "\u2022" });
70592
+ return value.length > 0 ? value : null;
70593
+ }
70594
+ async function keychainSet(envVar) {
70595
+ if (!requireKeychain())
70596
+ return;
70597
+ if (!envVar) {
70598
+ console.error(bad("Usage: claudish keychain set <ENV_VAR>"));
70599
+ process.exitCode = 1;
70600
+ return;
70601
+ }
70602
+ if (!isValidKeychainVarName(envVar)) {
70603
+ console.error(bad(`"${envVar}" is not a valid environment variable name.`));
70604
+ process.exitCode = 1;
70605
+ return;
70606
+ }
70607
+ const value = await readSecretInteractively(envVar);
70608
+ if (!value) {
70609
+ console.log(dim3("Aborted \u2014 no value given."));
70610
+ return;
70611
+ }
70612
+ try {
70613
+ writeKeychainSecret(envVar, value);
70614
+ } catch (err) {
70615
+ console.error(bad(err instanceof Error ? err.message : String(err)));
70616
+ process.exitCode = 1;
70617
+ return;
70618
+ }
70619
+ if (!isKeychainEnabled())
70620
+ setKeychainEnabled(true);
70621
+ console.log(ok(`Stored ${envVar} ${dim3(valueTail2(value))} in the macOS Keychain.`));
70622
+ if (process.env[envVar]) {
70623
+ console.log(warn(`Note: ${envVar} is also set in this shell, and an env var takes precedence at runtime.`));
70624
+ }
70625
+ }
70626
+ function keychainRemove(envVar) {
70627
+ if (!requireKeychain())
70628
+ return;
70629
+ if (!envVar) {
70630
+ console.error(bad("Usage: claudish keychain rm <ENV_VAR>"));
70631
+ process.exitCode = 1;
70632
+ return;
70633
+ }
70634
+ try {
70635
+ const removed = deleteKeychainSecret(envVar);
70636
+ console.log(removed ? ok(`Removed ${envVar} from the macOS Keychain.`) : dim3(`${envVar} was not in the keychain \u2014 nothing to remove.`));
70637
+ } catch (err) {
70638
+ console.error(bad(err instanceof Error ? err.message : String(err)));
70639
+ process.exitCode = 1;
70640
+ }
70641
+ }
70642
+ function keychainToggle(enabled2) {
70643
+ if (enabled2 && !requireKeychain())
70644
+ return;
70645
+ setKeychainEnabled(enabled2);
70646
+ invalidateKeychainCache();
70647
+ console.log(enabled2 ? ok("Keychain backend enabled \u2014 claudish will now read keys from the macOS Keychain.") : ok("Keychain backend disabled. Stored items were left untouched."));
70648
+ if (!enabled2) {
70649
+ console.log(dim3("Remove the items themselves with: claudish keychain rm <ENV_VAR>"));
70650
+ }
70651
+ }
70652
+ function usage() {
70653
+ console.log(`${strong("claudish keychain")} \u2014 macOS Keychain credential backend
70654
+
70655
+ ${strong("status")} Backend state and how many keys are stored (default)
70656
+ ${strong("list")} Stored variables, with ${dim3("\u2022\u2022\u2022\u20221234")} identification tails
70657
+ ${strong("import")} [options] Copy secrets from env vars / 1Password into the keychain
70658
+ ${strong("set")} <ENV_VAR> Store one secret (prompted, or piped on stdin)
70659
+ ${strong("rm")} <ENV_VAR> Remove one secret
70660
+ ${strong("enable")} | ${strong("disable")} Turn the backend on/off (moves no secrets)
70661
+
70662
+ ${strong("import options")}
70663
+ --from env|1password|all Source to copy from (default: all)
70664
+ --only VAR,VAR Restrict to these variables
70665
+ --dry-run Show the plan and stop
70666
+ --yes Skip the confirmation
70667
+
70668
+ Resolution order: ${dim3("env var \u2192 alias \u2192 config.json \u2192 macOS Keychain \u2192 1Password")}
70669
+ `);
70670
+ }
70671
+ function parseOnly(args) {
70672
+ const idx = args.findIndex((a) => a === "--only" || a.startsWith("--only="));
70673
+ if (idx === -1)
70674
+ return;
70675
+ const raw2 = args[idx].includes("=") ? args[idx].split("=").slice(1).join("=") : args[idx + 1];
70676
+ if (!raw2)
70677
+ return;
70678
+ const names = raw2.split(",").map((n) => n.trim()).filter(Boolean);
70679
+ return names.length > 0 ? names : undefined;
70680
+ }
70681
+ function parseFrom(args) {
70682
+ const idx = args.findIndex((a) => a === "--from" || a.startsWith("--from="));
70683
+ if (idx === -1)
70684
+ return "all";
70685
+ const raw2 = (args[idx].includes("=") ? args[idx].split("=").slice(1).join("=") : args[idx + 1])?.trim().toLowerCase();
70686
+ if (raw2 === "env" || raw2 === "1password" || raw2 === "all")
70687
+ return raw2;
70688
+ if (raw2 === "op")
70689
+ return "1password";
70690
+ return "all";
70691
+ }
70692
+ function positionalArgs(args) {
70693
+ const out = [];
70694
+ for (let i = 0;i < args.length; i++) {
70695
+ const arg = args[i];
70696
+ if (arg.startsWith("-")) {
70697
+ if (VALUE_FLAGS.has(arg))
70698
+ i++;
70699
+ continue;
70700
+ }
70701
+ out.push(arg);
70702
+ }
70703
+ return out;
70704
+ }
70705
+ async function keychainCommand(args) {
70706
+ const positional = positionalArgs(args);
70707
+ const sub = positional[0] ?? "status";
70708
+ switch (sub) {
70709
+ case "status":
70710
+ keychainStatus();
70711
+ return;
70712
+ case "list":
70713
+ case "ls":
70714
+ keychainList();
70715
+ return;
70716
+ case "import":
70717
+ await keychainImport({
70718
+ from: parseFrom(args),
70719
+ only: parseOnly(args),
70720
+ dryRun: args.includes("--dry-run"),
70721
+ yes: args.includes("--yes") || args.includes("-y")
70722
+ });
70723
+ return;
70724
+ case "set":
70725
+ await keychainSet(positional[1]);
70726
+ return;
70727
+ case "rm":
70728
+ case "remove":
70729
+ case "delete":
70730
+ keychainRemove(positional[1]);
70731
+ return;
70732
+ case "enable":
70733
+ keychainToggle(true);
70734
+ return;
70735
+ case "disable":
70736
+ keychainToggle(false);
70737
+ return;
70738
+ case "help":
70739
+ usage();
70740
+ return;
70741
+ default:
70742
+ console.error(bad(`Unknown subcommand: ${sub}`));
70743
+ usage();
70744
+ process.exitCode = 1;
70745
+ }
70746
+ }
70747
+ var VALUE_FLAGS;
70748
+ var init_keychain_command = __esm(() => {
70749
+ init_ansi();
70750
+ init_op_source();
70751
+ init_profile_config();
70752
+ init_keychain();
70753
+ init_provider_definitions();
70754
+ VALUE_FLAGS = new Set(["--config", "--from", "--only"]);
70755
+ });
70756
+
69844
70757
  // src/auth/antigravity-oauth.ts
69845
70758
  import { spawnSync as spawnSync3 } from "child_process";
69846
70759
  import { existsSync as existsSync26, unlinkSync as unlinkSync7 } from "fs";
@@ -69881,16 +70794,16 @@ function defaultRunAgyAuth(agyPath, interactive) {
69881
70794
  spawnSync3(agyPath, ["-p", "hello", "--print-timeout", "3m"], { stdio: "inherit" });
69882
70795
  }
69883
70796
  }
69884
- async function pollForToken(deps) {
69885
- const deadline = deps.now() + deps.timing.graceMs;
70797
+ async function pollForToken(deps2) {
70798
+ const deadline = deps2.now() + deps2.timing.graceMs;
69886
70799
  for (;; ) {
69887
- const tok = deps.readToken();
70800
+ const tok = deps2.readToken();
69888
70801
  if (tok)
69889
70802
  return tok;
69890
- if (deps.now() >= deadline)
70803
+ if (deps2.now() >= deadline)
69891
70804
  return null;
69892
- const remaining = deadline - deps.now();
69893
- await deps.sleep(Math.min(deps.timing.intervalMs, Math.max(0, remaining)));
70805
+ const remaining = deadline - deps2.now();
70806
+ await deps2.sleep(Math.min(deps2.timing.intervalMs, Math.max(0, remaining)));
69894
70807
  }
69895
70808
  }
69896
70809
 
@@ -69904,67 +70817,67 @@ class AntigravityOAuth {
69904
70817
  }
69905
70818
  constructor() {}
69906
70819
  async login(depsOverride = {}) {
69907
- const deps = { ...defaultLoginDeps, ...depsOverride };
70820
+ const deps2 = { ...defaultLoginDeps, ...depsOverride };
69908
70821
  log("[AntigravityOAuth] Starting agy-delegated login");
69909
- if (deps.hasToken()) {
69910
- console.log(`\u2705 Already authenticated with Antigravity. Use: claudish --model ag@${await (deps.suggestModel ?? defaultSuggestModel)()}`);
69911
- return deps.exit(0);
70822
+ if (deps2.hasToken()) {
70823
+ console.log(`\u2705 Already authenticated with Antigravity. Use: claudish --model ag@${await (deps2.suggestModel ?? defaultSuggestModel)()}`);
70824
+ return deps2.exit(0);
69912
70825
  }
69913
- let agyPath = deps.locateAgy();
70826
+ let agyPath = deps2.locateAgy();
69914
70827
  if (!agyPath) {
69915
70828
  console.log("\nThe Antigravity CLI (`agy`) is required to sign in to Antigravity.");
69916
70829
  console.log(`claudish delegates Antigravity sign-in to agy \u2014 agy holds the current OAuth secret
69917
70830
  ` + "and writes the session to the shared keychain store that claudish reads.");
69918
- if (!deps.isInteractive()) {
70831
+ if (!deps2.isInteractive()) {
69919
70832
  printManualInstall();
69920
- return deps.exit(0);
70833
+ return deps2.exit(0);
69921
70834
  }
69922
- const proceed = await deps.confirmInstall();
70835
+ const proceed = await deps2.confirmInstall();
69923
70836
  if (!proceed) {
69924
70837
  printManualInstall();
69925
- return deps.exit(0);
70838
+ return deps2.exit(0);
69926
70839
  }
69927
70840
  console.log(`
69928
70841
  Installing the Antigravity CLI\u2026
69929
70842
  `);
69930
- if (!deps.runInstall()) {
70843
+ if (!deps2.runInstall()) {
69931
70844
  console.log(`
69932
70845
  \u274C Antigravity CLI installation failed.`);
69933
70846
  printManualInstall();
69934
- return deps.exit(0);
70847
+ return deps2.exit(0);
69935
70848
  }
69936
- agyPath = deps.locateAgy();
70849
+ agyPath = deps2.locateAgy();
69937
70850
  if (!agyPath) {
69938
70851
  console.log(`
69939
70852
  \u274C Antigravity CLI still not found after install.`);
69940
70853
  printManualInstall();
69941
- return deps.exit(0);
70854
+ return deps2.exit(0);
69942
70855
  }
69943
70856
  }
69944
70857
  console.log(`
69945
70858
  Launching the Antigravity CLI to sign in \u2014 complete the sign-in in your browser.
69946
70859
  ` + `claudish will detect the session automatically.
69947
70860
  `);
69948
- deps.runAgyAuth(agyPath, false);
69949
- let token = await pollForToken(deps);
70861
+ deps2.runAgyAuth(agyPath, false);
70862
+ let token = await pollForToken(deps2);
69950
70863
  if (!token) {
69951
70864
  console.log(`
69952
70865
  No session detected yet. Starting the Antigravity CLI interactively \u2014
69953
70866
  ` + "sign in, then exit agy (its `/quit` command or Ctrl-C) to return here.\n");
69954
- deps.runAgyAuth(agyPath, true);
69955
- token = await pollForToken(deps);
70867
+ deps2.runAgyAuth(agyPath, true);
70868
+ token = await pollForToken(deps2);
69956
70869
  }
69957
70870
  if (token) {
69958
- deps.onAuthenticated();
70871
+ deps2.onAuthenticated();
69959
70872
  console.log(`
69960
- \u2705 Authenticated with Antigravity. Use: claudish --model ag@${await (deps.suggestModel ?? defaultSuggestModel)()}`);
69961
- return deps.exit(0);
70873
+ \u2705 Authenticated with Antigravity. Use: claudish --model ag@${await (deps2.suggestModel ?? defaultSuggestModel)()}`);
70874
+ return deps2.exit(0);
69962
70875
  }
69963
70876
  console.log("\nNo Antigravity session detected. Run `agy` and sign in, then retry `claudish login antigravity`.");
69964
- return deps.exit(0);
70877
+ return deps2.exit(0);
69965
70878
  }
69966
- async logout(deps) {
69967
- deleteSharedAntigravityToken(deps);
70879
+ async logout(deps2) {
70880
+ deleteSharedAntigravityToken(deps2);
69968
70881
  try {
69969
70882
  const tokenFile = join36(homedir31(), ".gemini", "antigravity-cli", "antigravity-oauth-token");
69970
70883
  if (existsSync26(tokenFile))
@@ -70166,7 +71079,7 @@ async function quotaCommand(provider) {
70166
71079
  `);
70167
71080
  return;
70168
71081
  }
70169
- renderPlan(adapter, plan);
71082
+ renderPlan2(adapter, plan);
70170
71083
  }
70171
71084
  function resolveAdapterFromInput(input) {
70172
71085
  const raw2 = input.toLowerCase().replace(/@+$/, "");
@@ -70196,7 +71109,7 @@ async function promptForAdapter() {
70196
71109
  });
70197
71110
  return select({ message: "Select provider:", choices });
70198
71111
  }
70199
- function renderPlan(adapter, plan) {
71112
+ function renderPlan2(adapter, plan) {
70200
71113
  console.log("");
70201
71114
  boxTop(plan.label);
70202
71115
  boxRow("Provider", adapter.providerId);
@@ -70407,11 +71320,11 @@ function readSlimCacheWithFreshness(reader) {
70407
71320
  const stale = !Number.isFinite(lastUpdatedMs) || ageMs > FIREBASE_CACHE_TTL_MS;
70408
71321
  return { entries: cache3.entries ?? [], stale };
70409
71322
  }
70410
- function createCatalogClient(deps = {}) {
70411
- const _getModelsByProvider = deps.getModelsByProvider ?? getModelsByProvider;
70412
- const _getModelByIdFromFirebase = deps.getModelByIdFromFirebase ?? getModelByIdFromFirebase;
70413
- const _searchModels = deps.searchModels ?? searchModels;
70414
- const _readSlimCache = deps.readSlimCache ?? readAllModelsCache;
71323
+ function createCatalogClient(deps2 = {}) {
71324
+ const _getModelsByProvider = deps2.getModelsByProvider ?? getModelsByProvider;
71325
+ const _getModelByIdFromFirebase = deps2.getModelByIdFromFirebase ?? getModelByIdFromFirebase;
71326
+ const _searchModels = deps2.searchModels ?? searchModels;
71327
+ const _readSlimCache = deps2.readSlimCache ?? readAllModelsCache;
70415
71328
  return {
70416
71329
  async modelsByVendor(vendorSlug) {
70417
71330
  const slug = vendorSlug.toLowerCase();
@@ -70595,6 +71508,8 @@ function resolveDiscoveredContextLength(m) {
70595
71508
  }
70596
71509
  }
70597
71510
  function resolveDiscoveredReleaseDate(m) {
71511
+ if (m.ignoreCatalogReleaseDate)
71512
+ return m.releaseDate;
70598
71513
  try {
70599
71514
  const catalogDate = lookupModel(m.id)?.releaseDate;
70600
71515
  if (catalogDate)
@@ -71408,10 +72323,42 @@ function registerPaletteRefresher(fn) {
71408
72323
  paletteRefreshers.push(fn);
71409
72324
  fn();
71410
72325
  }
72326
+ function hexChannels(hex3) {
72327
+ const m = /^#([0-9a-f]{6})$/i.exec(hex3.trim());
72328
+ if (!m)
72329
+ return null;
72330
+ const n = Number.parseInt(m[1], 16);
72331
+ return [n >> 16 & 255, n >> 8 & 255, n & 255];
72332
+ }
72333
+ function channelsToHex(c) {
72334
+ const hex3 = c.map((v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, "0")).join("");
72335
+ return `#${hex3}`;
72336
+ }
72337
+ function retintSurfaces(palette, pageHex) {
72338
+ const paletteBg = hexChannels(palette.bg);
72339
+ const page = hexChannels(pageHex);
72340
+ if (!paletteBg || !page)
72341
+ return;
72342
+ for (const token of SURFACE_TOKENS) {
72343
+ const original = hexChannels(palette[token]);
72344
+ if (!original)
72345
+ continue;
72346
+ C[token] = channelsToHex([
72347
+ page[0] + (original[0] - paletteBg[0]),
72348
+ page[1] + (original[1] - paletteBg[1]),
72349
+ page[2] + (original[2] - paletteBg[2])
72350
+ ]);
72351
+ }
72352
+ }
71411
72353
  function applyTuiTheme(mode) {
71412
72354
  const palette = mode === "light" ? LIGHT2 : DARK;
71413
72355
  activeLatencyBuckets = mode === "light" ? LATENCY_BUCKETS_LIGHT : LATENCY_BUCKETS_DARK;
71414
72356
  Object.assign(C, palette);
72357
+ const terminalBg = getTerminalBackground();
72358
+ if (terminalBg && terminalBg.mode === mode) {
72359
+ C.bg = terminalBg.hex;
72360
+ retintSurfaces(palette, terminalBg.hex);
72361
+ }
71415
72362
  Object.assign(STAGE_BG, mode === "light" ? STAGE_BG_LIGHT : STAGE_BG_DARK);
71416
72363
  STAGE_BG_ANSI.network = hexToAnsiBg(STAGE_BG.network);
71417
72364
  STAGE_BG_ANSI.server = hexToAnsiBg(STAGE_BG.server);
@@ -71488,7 +72435,7 @@ function tokBarCells(tokensPerSec, maxTokPerSec, tokWidth) {
71488
72435
  const raw2 = Math.round(tokWidth * Math.max(0, tokensPerSec) / denom);
71489
72436
  return Math.min(tokWidth, Math.max(0, raw2));
71490
72437
  }
71491
- var DARK, LIGHT2, C, bold3, A, LATENCY_BUCKETS_DARK, LATENCY_BUCKETS_LIGHT, activeLatencyBuckets, latencyFg = "#ffffff", LATENCY_FG_ANSI = "\x1B[38;2;255;255;255m", ANSI_RESET = "\x1B[0m", STAGE_BG_DARK, STAGE_BG_LIGHT, STAGE_BG, STAGE_FG, STAGE_BG_ANSI, paletteRefreshers;
72438
+ var DARK, LIGHT2, C, bold3, A, LATENCY_BUCKETS_DARK, LATENCY_BUCKETS_LIGHT, activeLatencyBuckets, latencyFg = "#ffffff", LATENCY_FG_ANSI = "\x1B[38;2;255;255;255m", ANSI_RESET = "\x1B[0m", STAGE_BG_DARK, STAGE_BG_LIGHT, STAGE_BG, STAGE_FG, STAGE_BG_ANSI, paletteRefreshers, SURFACE_TOKENS;
71492
72439
  var init_theme2 = __esm(() => {
71493
72440
  init_theme_mode();
71494
72441
  DARK = {
@@ -71598,6 +72545,7 @@ var init_theme2 = __esm(() => {
71598
72545
  streaming: hexToAnsiBg(STAGE_BG.streaming)
71599
72546
  };
71600
72547
  paletteRefreshers = [];
72548
+ SURFACE_TOKENS = ["bgAlt", "border", "tabInactiveBg", "chipKeyBg", "chipLabelBg"];
71601
72549
  onThemeModeChange(applyTuiTheme);
71602
72550
  });
71603
72551
 
@@ -71802,6 +72750,8 @@ function shortStatusLabel(probe2, hasCreds, _hint) {
71802
72750
  return `${pc.red}\u2297 rate-limited${pc.reset}`;
71803
72751
  case "out-of-credit":
71804
72752
  return `${pc.red}\u2297 no credit${pc.reset}`;
72753
+ case "plan-limit":
72754
+ return `${pc.red}\u2297 plan limit${pc.reset}`;
71805
72755
  case "server-error":
71806
72756
  return `${pc.red}\u2297 server ${probe2.httpStatus ?? ""}${pc.reset}`;
71807
72757
  case "timeout":
@@ -75057,7 +76007,7 @@ function printHelp2() {
75057
76007
  const A2 = cliAnsi();
75058
76008
  const c = (esc2) => (s) => useColor && esc2 ? `${esc2}${s}${A2.RESET}` : s;
75059
76009
  const bold4 = c(A2.BOLD);
75060
- const dim3 = c(A2.DIM);
76010
+ const dim4 = c(A2.DIM);
75061
76011
  const cyan = c(A2.CYAN);
75062
76012
  const green2 = c(A2.GREEN);
75063
76013
  const yellow2 = c(A2.YELLOW);
@@ -75065,89 +76015,89 @@ function printHelp2() {
75065
76015
  const blue = c(A2.BLUE);
75066
76016
  const h = (title) => bold4(cyan(`\u258C ${title}`));
75067
76017
  console.log(`
75068
- ${bold4("claudish")} ${dim3("\xB7")} Run Claude Code with any AI model
75069
- ${dim3("OpenRouter \xB7 Gemini \xB7 OpenAI \xB7 xAI \xB7 MiniMax \xB7 Kimi \xB7 GLM \xB7 Z.AI \xB7 Sakana \xB7 Poe \xB7 LiteLLM \xB7 Local")}
76018
+ ${bold4("claudish")} ${dim4("\xB7")} Run Claude Code with any AI model
76019
+ ${dim4("OpenRouter \xB7 Gemini \xB7 OpenAI \xB7 xAI \xB7 MiniMax \xB7 Kimi \xB7 GLM \xB7 Z.AI \xB7 Sakana \xB7 Poe \xB7 LiteLLM \xB7 Local")}
75070
76020
 
75071
76021
  ${h("USAGE")}
75072
- ${green2("claudish")} ${dim3("# Interactive mode (default, model selector)")}
75073
- ${green2("claudish")} ${yellow2("[OPTIONS] <claude-args...>")} ${dim3("# Single-shot mode (requires --model)")}
75074
- ${green2("claudish")} ${green2("--team")} ${yellow2("a,b,c")} ${yellow2('"prompt"')} ${dim3("# Run models in parallel (magmux grid)")}
75075
- ${green2("claudish")} ${green2("--team")} ${yellow2("a,b,c")} ${green2("-f")} ${yellow2("input.md")} ${dim3("# Team mode with file input")}
76022
+ ${green2("claudish")} ${dim4("# Interactive mode (default, model selector)")}
76023
+ ${green2("claudish")} ${yellow2("[OPTIONS] <claude-args...>")} ${dim4("# Single-shot mode (requires --model)")}
76024
+ ${green2("claudish")} ${green2("--team")} ${yellow2("a,b,c")} ${yellow2('"prompt"')} ${dim4("# Run models in parallel (magmux grid)")}
76025
+ ${green2("claudish")} ${green2("--team")} ${yellow2("a,b,c")} ${green2("-f")} ${yellow2("input.md")} ${dim4("# Team mode with file input")}
75076
76026
 
75077
76027
  ${h("MODEL ROUTING")}
75078
76028
  ${bold4("New syntax:")} ${yellow2("provider@model[:concurrency]")}
75079
- ${magenta("google@gemini-3-pro")} ${dim3("Direct Google API (explicit)")}
75080
- ${magenta("openrouter@google/gemini-3-pro")} ${dim3("OpenRouter (explicit)")}
75081
- ${magenta("oai@gpt-5.3")} ${dim3("Direct OpenAI API (shortcut)")}
75082
- ${magenta("ollama@llama3.2:3")} ${dim3("Local Ollama, 3 concurrent requests")}
75083
- ${magenta("ollama@llama3.2:0")} ${dim3("Local Ollama, no limits")}
76029
+ ${magenta("google@gemini-3-pro")} ${dim4("Direct Google API (explicit)")}
76030
+ ${magenta("openrouter@google/gemini-3-pro")} ${dim4("OpenRouter (explicit)")}
76031
+ ${magenta("oai@gpt-5.3")} ${dim4("Direct OpenAI API (shortcut)")}
76032
+ ${magenta("ollama@llama3.2:3")} ${dim4("Local Ollama, 3 concurrent requests")}
76033
+ ${magenta("ollama@llama3.2:0")} ${dim4("Local Ollama, no limits")}
75084
76034
 
75085
76035
  ${bold4("Provider shortcuts:")}
75086
- ${magenta("g, gemini")} ${dim3("->")} Google Gemini ${dim3("google@gemini-3-pro")}
75087
- ${magenta("oai")} ${dim3("->")} OpenAI Direct ${dim3("oai@gpt-5.3")}
75088
- ${magenta("cx, codex")} ${dim3("->")} OpenAI Codex ${dim3("cx@gpt-5.3 (Responses API)")}
75089
- ${magenta("or")} ${dim3("->")} OpenRouter ${dim3("or@openai/gpt-5.3")}
75090
- ${magenta("x-ai, xai, grok")} ${dim3("->")} xAI / Grok ${dim3("x-ai@grok-3")}
75091
- ${magenta("mm, mmax")} ${dim3("->")} MiniMax Direct ${dim3("mm@MiniMax-M2.1")}
75092
- ${magenta("mmc")} ${dim3("->")} MiniMax Coding ${dim3("mmc@MiniMax-M2.1")}
75093
- ${magenta("kimi, moon")} ${dim3("->")} Kimi Direct ${dim3("kimi@kimi-k2-thinking-turbo")}
75094
- ${magenta("kc")} ${dim3("->")} Kimi Coding ${dim3("kc@kimi-k2-thinking-turbo")}
75095
- ${magenta("glm, zhipu")} ${dim3("->")} GLM Direct ${dim3("glm@glm-4.7")}
75096
- ${magenta("gc")} ${dim3("->")} GLM Coding ${dim3("gc@glm-4.7")}
75097
- ${magenta("z-ai, zai")} ${dim3("->")} Z.AI Direct ${dim3("z-ai@glm-4.7")}
75098
- ${magenta("oc, llama, lc, meta")} ${dim3("->")} OllamaCloud ${dim3("oc@llama-3.1")}
75099
- ${magenta("zen")} ${dim3("->")} OpenCode Zen ${dim3("zen@grok-code")}
75100
- ${magenta("zengo, zgo")} ${dim3("->")} OpenCode Zen Go ${dim3("zengo@grok-code")}
75101
- ${magenta("v, vertex")} ${dim3("->")} Vertex AI ${dim3("v@gemini-2.5-flash")}
75102
- ${magenta("poe")} ${dim3("->")} Poe ${dim3("poe@GPT-4o")}
75103
- ${magenta("litellm, ll")} ${dim3("->")} LiteLLM ${dim3("ll@gpt-4o (needs LITELLM_BASE_URL)")}
75104
- ${magenta("ds")} ${dim3("->")} DeepSeek ${dim3("ds@deepseek-chat")}
75105
- ${magenta("sakana, fugu")} ${dim3("->")} Sakana Fugu ${dim3("fugu@fugu-ultra")}
75106
- ${magenta("sc")} ${dim3("->")} Sakana Subscription ${dim3("sc@fugu-ultra")}
75107
- ${magenta("ollama")} ${dim3("->")} Ollama (local) ${dim3("ollama@llama3.2")}
75108
- ${magenta("lms, lmstudio")} ${dim3("->")} LM Studio (local) ${dim3("lms@qwen")}
75109
- ${magenta("vllm")} ${dim3("->")} vLLM (local) ${dim3("vllm@model")}
75110
- ${magenta("mlx")} ${dim3("->")} MLX (local) ${dim3("mlx@model")}
75111
-
75112
- ${bold4("Native auto-detection")} ${dim3("(when no provider specified):")}
75113
- ${yellow2("google/*, gemini-*")} ${dim3("->")} Google API
75114
- ${yellow2("openai/*, gpt-*, o1-*")} ${dim3("->")} OpenAI API
75115
- ${yellow2("x-ai/*, grok-*")} ${dim3("->")} xAI
75116
- ${yellow2("meta-llama/*, llama-*")} ${dim3("->")} OllamaCloud
75117
- ${yellow2("minimax/*, abab-*")} ${dim3("->")} MiniMax API
75118
- ${yellow2("moonshot/*, kimi-*")} ${dim3("->")} Kimi API
75119
- ${yellow2("zhipu/*, glm-*")} ${dim3("->")} GLM API
75120
- ${yellow2("sakana/*, fugu-*")} ${dim3("->")} Sakana Fugu
75121
- ${yellow2("poe:*")} ${dim3("->")} Poe
75122
- ${yellow2("anthropic/*, claude-*")} ${dim3("->")} Native Anthropic
75123
- ${yellow2("(unknown vendor/)")} ${dim3("->")} Error (use openrouter@vendor/model)
75124
-
75125
- ${dim3("A defaultProvider (config / --default-provider) catches bare names that match no rule.")}
76036
+ ${magenta("g, gemini")} ${dim4("->")} Google Gemini ${dim4("google@gemini-3-pro")}
76037
+ ${magenta("oai")} ${dim4("->")} OpenAI Direct ${dim4("oai@gpt-5.3")}
76038
+ ${magenta("cx, codex")} ${dim4("->")} OpenAI Codex ${dim4("cx@gpt-5.3 (Responses API)")}
76039
+ ${magenta("or")} ${dim4("->")} OpenRouter ${dim4("or@openai/gpt-5.3")}
76040
+ ${magenta("x-ai, xai, grok")} ${dim4("->")} xAI / Grok ${dim4("x-ai@grok-3")}
76041
+ ${magenta("mm, mmax")} ${dim4("->")} MiniMax Direct ${dim4("mm@MiniMax-M2.1")}
76042
+ ${magenta("mmc")} ${dim4("->")} MiniMax Coding ${dim4("mmc@MiniMax-M2.1")}
76043
+ ${magenta("kimi, moon")} ${dim4("->")} Kimi Direct ${dim4("kimi@kimi-k2-thinking-turbo")}
76044
+ ${magenta("kc")} ${dim4("->")} Kimi Coding ${dim4("kc@kimi-k2-thinking-turbo")}
76045
+ ${magenta("glm, zhipu")} ${dim4("->")} GLM Direct ${dim4("glm@glm-4.7")}
76046
+ ${magenta("gc")} ${dim4("->")} GLM Coding ${dim4("gc@glm-4.7")}
76047
+ ${magenta("z-ai, zai")} ${dim4("->")} Z.AI Direct ${dim4("z-ai@glm-4.7")}
76048
+ ${magenta("oc, llama, lc, meta")} ${dim4("->")} OllamaCloud ${dim4("oc@llama-3.1")}
76049
+ ${magenta("zen")} ${dim4("->")} OpenCode Zen ${dim4("zen@grok-code")}
76050
+ ${magenta("zengo, zgo")} ${dim4("->")} OpenCode Zen Go ${dim4("zengo@grok-code")}
76051
+ ${magenta("v, vertex")} ${dim4("->")} Vertex AI ${dim4("v@gemini-2.5-flash")}
76052
+ ${magenta("poe")} ${dim4("->")} Poe ${dim4("poe@GPT-4o")}
76053
+ ${magenta("litellm, ll")} ${dim4("->")} LiteLLM ${dim4("ll@gpt-4o (needs LITELLM_BASE_URL)")}
76054
+ ${magenta("ds")} ${dim4("->")} DeepSeek ${dim4("ds@deepseek-chat")}
76055
+ ${magenta("sakana, fugu")} ${dim4("->")} Sakana Fugu ${dim4("fugu@fugu-ultra")}
76056
+ ${magenta("sc")} ${dim4("->")} Sakana Subscription ${dim4("sc@fugu-ultra")}
76057
+ ${magenta("ollama")} ${dim4("->")} Ollama (local) ${dim4("ollama@llama3.2")}
76058
+ ${magenta("lms, lmstudio")} ${dim4("->")} LM Studio (local) ${dim4("lms@qwen")}
76059
+ ${magenta("vllm")} ${dim4("->")} vLLM (local) ${dim4("vllm@model")}
76060
+ ${magenta("mlx")} ${dim4("->")} MLX (local) ${dim4("mlx@model")}
76061
+
76062
+ ${bold4("Native auto-detection")} ${dim4("(when no provider specified):")}
76063
+ ${yellow2("google/*, gemini-*")} ${dim4("->")} Google API
76064
+ ${yellow2("openai/*, gpt-*, o1-*")} ${dim4("->")} OpenAI API
76065
+ ${yellow2("x-ai/*, grok-*")} ${dim4("->")} xAI
76066
+ ${yellow2("meta-llama/*, llama-*")} ${dim4("->")} OllamaCloud
76067
+ ${yellow2("minimax/*, abab-*")} ${dim4("->")} MiniMax API
76068
+ ${yellow2("moonshot/*, kimi-*")} ${dim4("->")} Kimi API
76069
+ ${yellow2("zhipu/*, glm-*")} ${dim4("->")} GLM API
76070
+ ${yellow2("sakana/*, fugu-*")} ${dim4("->")} Sakana Fugu
76071
+ ${yellow2("poe:*")} ${dim4("->")} Poe
76072
+ ${yellow2("anthropic/*, claude-*")} ${dim4("->")} Native Anthropic
76073
+ ${yellow2("(unknown vendor/)")} ${dim4("->")} Error (use openrouter@vendor/model)
76074
+
76075
+ ${dim4("A defaultProvider (config / --default-provider) catches bare names that match no rule.")}
75126
76076
 
75127
76077
  ${h("OPTIONS")}
75128
76078
  ${green2("-i, --interactive")} Run in interactive mode (default when no prompt given)
75129
76079
  ${green2("-m, --model")} ${yellow2("<model>")} Model to use (required for single-shot mode)
75130
76080
  ${green2("--profile")} ${yellow2("<name>")} Use named profile for model mapping (default profile if omitted)
75131
76081
  ${green2("--default-provider")} ${yellow2("<name>")} Fallback provider for bare model names (builtin or customEndpoints key)
75132
- ${dim3("Precedence: this flag > CLAUDISH_DEFAULT_PROVIDER env > config.json")}
76082
+ ${dim4("Precedence: this flag > CLAUDISH_DEFAULT_PROVIDER env > config.json")}
75133
76083
  ${green2("--anthropic-api-billing")} Use your real ANTHROPIC_API_KEY for native Claude models
75134
- ${dim3("(metered API billing). Default: the key is hidden so Claude Code")}
75135
- ${dim3("uses your claude.ai subscription. Env: CLAUDISH_ANTHROPIC_API_BILLING")}
75136
- ${dim3("Config: anthropicApiBilling: true")}
76084
+ ${dim4("(metered API billing). Default: the key is hidden so Claude Code")}
76085
+ ${dim4("uses your claude.ai subscription. Env: CLAUDISH_ANTHROPIC_API_BILLING")}
76086
+ ${dim4("Config: anthropicApiBilling: true")}
75137
76087
  ${green2("--config")} ${yellow2("<file>")} Use THIS config file for the run, fully replacing the machine
75138
- ${dim3("global (~/.claudish/config.json) AND project (.claudish.json).")}
75139
- ${dim3("A file naming no op:// source never touches 1Password (no prompt).")}
75140
- ${dim3("Env vars still resolve first. Env: CLAUDISH_CONFIG")}
76088
+ ${dim4("global (~/.claudish/config.json) AND project (.claudish.json).")}
76089
+ ${dim4("A file naming no op:// source never touches 1Password (no prompt).")}
76090
+ ${dim4("Env vars still resolve first. Env: CLAUDISH_CONFIG")}
75141
76091
  ${green2("--op")} ${yellow2("<op://glob>")} Load API keys from a 1Password item glob (SDK-based, no op CLI)
75142
76092
  ${green2("--op")} ${yellow2("<glob>")} ${green2("--list")} Preview which fields the glob would import (names only, no values)
75143
76093
  ${green2("--op-env")} ${yellow2("<id>")} Load env vars from a 1Password Environment (highest priority)
75144
76094
  ${green2("--port")} ${yellow2("<port>")} Proxy server port (default: random)
75145
76095
  ${green2("-d, --debug-claudish")} Enable claudish debug logging to file (logs/claudish_*.log)
75146
- ${dim3('Always-on: CLAUDISH_DEBUG=1 env var or "debug": true in config.json')}
76096
+ ${dim4('Always-on: CLAUDISH_DEBUG=1 env var or "debug": true in config.json')}
75147
76097
  ${green2("--no-debug-claudish")} Force debug logging off for this run (when globally enabled)
75148
76098
  ${green2("--log-off")} Disable always-on structural logging (~/.claudish/logs/)
75149
76099
  ${green2("--log-diag")} ${yellow2("<mode>")} Diagnostic output: auto (default), logfile, off
75150
- ${dim3('Also: CLAUDISH_DIAG_MODE env var or "diagMode" in config.json')}
76100
+ ${dim4('Also: CLAUDISH_DIAG_MODE env var or "diagMode" in config.json')}
75151
76101
  ${green2("--log-level")} ${yellow2("<level>")} Log verbosity: debug (full), info (truncated), minimal (labels)
75152
76102
  ${green2("-q, --quiet")} Suppress [claudish] log messages (default in single-shot mode)
75153
76103
  ${green2("-v, --verbose")} Show [claudish] log messages (default in interactive mode)
@@ -75171,13 +76121,13 @@ ${h("OPTIONS")}
75171
76121
  ${h("MODEL DISCOVERY")}
75172
76122
  ${green2("--models")} Top 100 ranked (Firebase + local providers)
75173
76123
  ${green2("--models --provider")} ${yellow2("<slug>")} Filter the catalog to one provider
75174
- ${dim3("e.g. --provider opencode-zen, anthropic, openai")}
76124
+ ${dim4("e.g. --provider opencode-zen, anthropic, openai")}
75175
76125
  ${green2("--providers")} Every provider + active-model count
75176
76126
  ${green2("-s, --models-search")} ${yellow2("<query>")} Fuzzy search: id, brand synonyms (chatgpt,
75177
- ${dim3("claude, grok), gateways (zen, oc, codex), caps")}
76127
+ ${dim4("claude, grok), gateways (zen, oc, codex), caps")}
75178
76128
  ${green2("--models-top")} Curated recommended models (flagship + fast)
75179
76129
  ${green2("--probe")} ${yellow2("<models...>")} Probe each provider in the fallback chain with
75180
- ${dim3("a real 1-token request (may incur tiny cost)")}
76130
+ ${dim4("a real 1-token request (may incur tiny cost)")}
75181
76131
  ${green2("--no-probe")} Skip live requests, show static chain only
75182
76132
  ${green2("--probe-timeout")} ${yellow2("<secs>")} Per-link timeout for live probes (default: 40)
75183
76133
  ${green2("--models-refresh")} Force refresh the slim model catalog from Firebase
@@ -75186,11 +76136,11 @@ ${h("MODEL DISCOVERY")}
75186
76136
 
75187
76137
  ${h("TEAM MODE")}
75188
76138
  ${green2("--team")} ${yellow2("<models>")} Run multiple models in parallel (comma-separated)
75189
- ${dim3('Example: --team minimax-m2.5,kimi-k2.5 "prompt"')}
76139
+ ${dim4('Example: --team minimax-m2.5,kimi-k2.5 "prompt"')}
75190
76140
  ${green2("--mode")} ${yellow2("<mode>")} Team mode: default (grid), interactive, json
75191
76141
  ${green2("-f, --file")} ${yellow2("<path>")} Read prompt from file (use with --team or single-shot)
75192
76142
 
75193
- ${h("MODEL MAPPING")} ${dim3("(per-role override)")}
76143
+ ${h("MODEL MAPPING")} ${dim4("(per-role override)")}
75194
76144
  ${green2("--model-opus")} ${yellow2("<model>")} Model for Opus role (planning, complex tasks)
75195
76145
  ${green2("--model-sonnet")} ${yellow2("<model>")} Model for Sonnet role (default coding)
75196
76146
  ${green2("--model-haiku")} ${yellow2("<model>")} Model for Haiku role (fast tasks, background)
@@ -75198,7 +76148,7 @@ ${h("MODEL MAPPING")} ${dim3("(per-role override)")}
75198
76148
 
75199
76149
  ${h("SUBCOMMANDS")}
75200
76150
  ${green2("claudish config")} Open the interactive config TUI (profiles,
75201
- ${dim3("providers, routing, 1Password)")}
76151
+ ${dim4("providers, routing, 1Password)")}
75202
76152
  ${green2("claudish providers")} ${yellow2("[--json]")} Show provider credential status (no key material)
75203
76153
  ${green2("claudish quota")} ${yellow2("[provider]")} Show remaining quota/usage (alias: usage)
75204
76154
  ${green2("claudish serve")} ${yellow2("--port <n> --models <p>")} Run the Claude Desktop redirect gateway
@@ -75212,39 +76162,50 @@ ${h("SUBCOMMANDS")}
75212
76162
  ${green2("claudish profile use")} ${yellow2("[name] [scope]")} Set default profile
75213
76163
  ${green2("claudish profile show")} ${yellow2("[name] [scope]")} Show profile details
75214
76164
  ${green2("claudish profile edit")} ${yellow2("[name] [scope]")} Edit a profile
75215
- ${dim3("scope = --local (.claudish.json) | --global (~/.claudish/config.json) | (prompted)")}
76165
+ ${dim4("scope = --local (.claudish.json) | --global (~/.claudish/config.json) | (prompted)")}
75216
76166
 
75217
76167
  ${bold4("Authentication:")}
75218
76168
  ${green2("claudish login")} ${yellow2("[provider]")} Login to an OAuth provider (interactive if omitted)
75219
76169
  ${green2("claudish logout")} ${yellow2("[provider]")} Clear OAuth credentials
75220
- ${dim3("Providers: gemini, kimi")}
76170
+ ${dim4("Providers: gemini, kimi")}
75221
76171
 
75222
- ${h("1PASSWORD")} ${dim3("(SDK-based \u2014 no op CLI needed for secrets)")}
75223
- ${dim3("Auth via OP_SERVICE_ACCOUNT_TOKEN, or OP_ACCOUNT / onepasswordAccount config (DesktopAuth).")}
76172
+ ${h("1PASSWORD")} ${dim4("(SDK-based \u2014 no op CLI needed for secrets)")}
76173
+ ${dim4("Auth via OP_SERVICE_ACCOUNT_TOKEN, or OP_ACCOUNT / onepasswordAccount config (DesktopAuth).")}
75224
76174
  ${green2("--op")} ${yellow2("<glob> --list")} Preview which fields a glob would import (names only)
75225
76175
  ${green2("--op")} ${yellow2("<glob>")} ${yellow2("[...args]")} Resolve a glob into env vars, then run a session
75226
- ${dim3("Inline op import requires a GLOB (self-names via field labels)")}
75227
- ${dim3('Example: claudish --op "op://Jack/Keys/**" --model gpt-4o "task"')}
76176
+ ${dim4("Inline op import requires a GLOB (self-names via field labels)")}
76177
+ ${dim4('Example: claudish --op "op://Jack/Keys/**" --model gpt-4o "task"')}
75228
76178
  ${green2("--op-env")} ${yellow2("<id>")} Load a 1Password Environment (highest-priority source)
75229
- ${dim3("Persistent setup (single refs, sets, environments, account): claudish config -> 1Password tab")}
76179
+ ${dim4("Persistent setup (single refs, sets, environments, account): claudish config -> 1Password tab")}
76180
+
76181
+ ${h("MACOS KEYCHAIN")} ${dim4("(local, encrypted at rest, no desktop-app handshake)")}
76182
+ ${green2("claudish keychain status")} Backend state and how many keys are stored
76183
+ ${green2("claudish keychain list")} Stored variables, with ${dim4("\u2022\u2022\u2022\u20221234")} identification tails
76184
+ ${green2("claudish keychain import")} Copy keys from env vars / 1Password into the keychain
76185
+ ${dim4("--from env|1password|all --only VAR,VAR --dry-run --yes")}
76186
+ ${green2("claudish keychain set")} ${yellow2("<ENV_VAR>")} Store one key (prompted, or piped on stdin \u2014 never in argv)
76187
+ ${green2("claudish keychain rm")} ${yellow2("<ENV_VAR>")} Remove one key
76188
+ ${green2("claudish keychain enable")}${dim4("|")}${green2("disable")} Turn the backend on/off (moves no secrets)
76189
+ ${dim4("Resolution order: env var -> alias -> config.json -> macOS Keychain -> 1Password")}
76190
+ ${dim4("The config TUI's Providers tab writes to the keychain by default on macOS.")}
75230
76191
 
75231
76192
  ${h("CLAUDE CODE FLAG PASSTHROUGH")}
75232
- ${dim3("Any unrecognized flag is forwarded to Claude Code. Claudish flags can appear in any order.")}
75233
- ${green2("claudish")} --model grok ${yellow2("--agent test")} ${yellow2('"task"')} ${dim3("# --agent passes through")}
75234
- ${green2("claudish")} --model grok ${yellow2("--effort high")} --stdin ${yellow2('"task"')} ${dim3("# --effort passes, --stdin stays")}
75235
- ${green2("claudish")} --model grok ${yellow2("--permission-mode plan")} -i ${dim3("# works in interactive too")}
75236
- ${dim3("Use -- when a Claude Code flag value starts with '-':")}
76193
+ ${dim4("Any unrecognized flag is forwarded to Claude Code. Claudish flags can appear in any order.")}
76194
+ ${green2("claudish")} --model grok ${yellow2("--agent test")} ${yellow2('"task"')} ${dim4("# --agent passes through")}
76195
+ ${green2("claudish")} --model grok ${yellow2("--effort high")} --stdin ${yellow2('"task"')} ${dim4("# --effort passes, --stdin stays")}
76196
+ ${green2("claudish")} --model grok ${yellow2("--permission-mode plan")} -i ${dim4("# works in interactive too")}
76197
+ ${dim4("Use -- when a Claude Code flag value starts with '-':")}
75237
76198
  ${green2("claudish")} --model grok ${green2("--")} ${yellow2('--system-prompt "-verbose mode" "task"')}
75238
76199
 
75239
76200
  ${h("CUSTOM MODELS & ENDPOINTS")}
75240
- ${dim3("Claudish accepts ANY valid model ID from the Firebase catalog, even if not in --models:")}
76201
+ ${dim4("Claudish accepts ANY valid model ID from the Firebase catalog, even if not in --models:")}
75241
76202
  ${green2("claudish")} --model ${yellow2("openrouter@your_provider/custom-model-123")} ${yellow2('"task"')}
75242
- ${dim3("Named custom endpoints live in ~/.claudish/config.json under 'customEndpoints' and route via @:")}
76203
+ ${dim4("Named custom endpoints live in ~/.claudish/config.json under 'customEndpoints' and route via @:")}
75243
76204
  ${green2("claudish")} --model ${yellow2("my-vllm@llama3.1-70b")} ${yellow2('"task"')}
75244
76205
 
75245
76206
  ${h("MODES")}
75246
- ${green2("\u2022")} ${bold4("Interactive")} ${dim3("(default):")} shows model selector, starts a persistent session
75247
- ${green2("\u2022")} ${bold4("Single-shot")} ${dim3("(--model):")} runs one task headless and exits
76207
+ ${green2("\u2022")} ${bold4("Interactive")} ${dim4("(default):")} shows model selector, starts a persistent session
76208
+ ${green2("\u2022")} ${bold4("Single-shot")} ${dim4("(--model):")} runs one task headless and exits
75248
76209
 
75249
76210
  ${h("NOTES")}
75250
76211
  ${yellow2("\u2022")} Permission prompts are ${bold4("ENABLED")} by default (normal Claude Code behavior)
@@ -75253,35 +76214,35 @@ ${h("NOTES")}
75253
76214
  ${yellow2("\u2022")} ${green2("--dangerous")} disables the sandbox \u2014 use with extreme caution
75254
76215
 
75255
76216
  ${h("ENVIRONMENT VARIABLES")}
75256
- ${dim3("Claudish auto-loads a .env file from the current directory.")}
76217
+ ${dim4("Claudish auto-loads a .env file from the current directory.")}
75257
76218
 
75258
76219
  ${bold4("Claude Code installation:")}
75259
76220
  ${blue("CLAUDE_PATH")} Custom path to Claude Code binary
75260
- ${dim3("Search: CLAUDE_PATH -> ~/.claude/local/claude -> PATH")}
76221
+ ${dim4("Search: CLAUDE_PATH -> ~/.claude/local/claude -> PATH")}
75261
76222
 
75262
- ${bold4("API keys")} ${dim3("(at least one required for cloud models):")}
76223
+ ${bold4("API keys")} ${dim4("(at least one required for cloud models):")}
75263
76224
  ${blue("OPENROUTER_API_KEY")} OpenRouter (default backend)
75264
- ${blue("GEMINI_API_KEY")} Google Gemini ${dim3("(g@, gemini@; alias GOOGLE_API_KEY)")}
75265
- ${blue("OPENAI_API_KEY")} OpenAI ${dim3("(oai@)")}
75266
- ${blue("OPENAI_CODEX_API_KEY")} OpenAI Codex / Responses API ${dim3("(cx@, codex@)")}
75267
- ${blue("XAI_API_KEY")} xAI / Grok ${dim3("(x-ai@, grok@)")}
75268
- ${blue("MINIMAX_API_KEY")} MiniMax ${dim3("(mm@, mmax@)")}
75269
- ${blue("MINIMAX_CODING_API_KEY")} MiniMax Coding Plan ${dim3("(mmc@)")}
75270
- ${blue("MOONSHOT_API_KEY")} Kimi / Moonshot ${dim3("(kimi@, moon@; alias KIMI_API_KEY)")}
75271
- ${blue("KIMI_CODING_API_KEY")} Kimi Coding Plan ${dim3("(kc@)")}
75272
- ${blue("ZHIPU_API_KEY")} GLM / Zhipu ${dim3("(glm@, zhipu@; alias GLM_API_KEY)")}
75273
- ${blue("GLM_CODING_API_KEY")} GLM Coding Plan ${dim3("(gc@; alias ZAI_CODING_API_KEY)")}
75274
- ${blue("ZAI_API_KEY")} Z.AI ${dim3("(z-ai@, zai@)")}
75275
- ${blue("DEEPSEEK_API_KEY")} DeepSeek ${dim3("(ds@)")}
75276
- ${blue("SAKANA_API_KEY")} Sakana Fugu ${dim3("(sakana@, fugu@)")}
75277
- ${blue("SAKANA_SUBSCRIPTION_API_KEY")} Sakana Fugu Subscription ${dim3("(sc@; separate subscription key)")}
75278
- ${blue("OLLAMA_API_KEY")} OllamaCloud ${dim3("(oc@, llama@)")}
75279
- ${blue("OPENCODE_API_KEY")} OpenCode Zen ${dim3("(zen@)")}
75280
- ${blue("POE_API_KEY")} Poe ${dim3("(poe@)")}
75281
- ${blue("LITELLM_API_KEY")} LiteLLM ${dim3("(litellm@, ll@; needs LITELLM_BASE_URL)")}
75282
- ${blue("VERTEX_API_KEY")} Vertex AI Express ${dim3("(v@)")}
75283
- ${blue("VERTEX_PROJECT")} Vertex AI project ID ${dim3("(OAuth mode, v@)")}
75284
- ${blue("VERTEX_LOCATION")} Vertex AI region ${dim3("(default: us-central1)")}
76225
+ ${blue("GEMINI_API_KEY")} Google Gemini ${dim4("(g@, gemini@; alias GOOGLE_API_KEY)")}
76226
+ ${blue("OPENAI_API_KEY")} OpenAI ${dim4("(oai@)")}
76227
+ ${blue("OPENAI_CODEX_API_KEY")} OpenAI Codex / Responses API ${dim4("(cx@, codex@)")}
76228
+ ${blue("XAI_API_KEY")} xAI / Grok ${dim4("(x-ai@, grok@)")}
76229
+ ${blue("MINIMAX_API_KEY")} MiniMax ${dim4("(mm@, mmax@)")}
76230
+ ${blue("MINIMAX_CODING_API_KEY")} MiniMax Coding Plan ${dim4("(mmc@)")}
76231
+ ${blue("MOONSHOT_API_KEY")} Kimi / Moonshot ${dim4("(kimi@, moon@; alias KIMI_API_KEY)")}
76232
+ ${blue("KIMI_CODING_API_KEY")} Kimi Coding Plan ${dim4("(kc@)")}
76233
+ ${blue("ZHIPU_API_KEY")} GLM / Zhipu ${dim4("(glm@, zhipu@; alias GLM_API_KEY)")}
76234
+ ${blue("GLM_CODING_API_KEY")} GLM Coding Plan ${dim4("(gc@; alias ZAI_CODING_API_KEY)")}
76235
+ ${blue("ZAI_API_KEY")} Z.AI ${dim4("(z-ai@, zai@)")}
76236
+ ${blue("DEEPSEEK_API_KEY")} DeepSeek ${dim4("(ds@)")}
76237
+ ${blue("SAKANA_API_KEY")} Sakana Fugu ${dim4("(sakana@, fugu@)")}
76238
+ ${blue("SAKANA_SUBSCRIPTION_API_KEY")} Sakana Fugu Subscription ${dim4("(sc@; separate subscription key)")}
76239
+ ${blue("OLLAMA_API_KEY")} OllamaCloud ${dim4("(oc@, llama@)")}
76240
+ ${blue("OPENCODE_API_KEY")} OpenCode Zen ${dim4("(zen@)")}
76241
+ ${blue("POE_API_KEY")} Poe ${dim4("(poe@)")}
76242
+ ${blue("LITELLM_API_KEY")} LiteLLM ${dim4("(litellm@, ll@; needs LITELLM_BASE_URL)")}
76243
+ ${blue("VERTEX_API_KEY")} Vertex AI Express ${dim4("(v@)")}
76244
+ ${blue("VERTEX_PROJECT")} Vertex AI project ID ${dim4("(OAuth mode, v@)")}
76245
+ ${blue("VERTEX_LOCATION")} Vertex AI region ${dim4("(default: us-central1)")}
75285
76246
  ${blue("ANTHROPIC_API_KEY")} Placeholder (prevents Claude Code dialog)
75286
76247
  ${blue("ANTHROPIC_AUTH_TOKEN")} Placeholder (prevents Claude Code login screen)
75287
76248
 
@@ -75289,27 +76250,27 @@ ${h("ENVIRONMENT VARIABLES")}
75289
76250
  ${blue("GEMINI_BASE_URL")} Custom Gemini endpoint
75290
76251
  ${blue("OPENAI_BASE_URL")} Custom OpenAI / Azure endpoint
75291
76252
  ${blue("MINIMAX_BASE_URL")} Custom MiniMax endpoint
75292
- ${blue("MOONSHOT_BASE_URL")} Custom Kimi / Moonshot endpoint ${dim3("(alias KIMI_BASE_URL)")}
75293
- ${blue("ZHIPU_BASE_URL")} Custom GLM / Zhipu endpoint ${dim3("(alias GLM_BASE_URL)")}
75294
- ${blue("SAKANA_BASE_URL")} Custom Sakana endpoint ${dim3("(default: https://api.sakana.ai)")}
75295
- ${blue("LITELLM_BASE_URL")} LiteLLM gateway base URL ${dim3("(required for ll@)")}
75296
- ${blue("OLLAMACLOUD_BASE_URL")} OllamaCloud ${dim3("(default: https://ollama.com)")}
75297
- ${blue("OPENCODE_BASE_URL")} OpenCode Zen ${dim3("(default: https://opencode.ai/zen)")}
76253
+ ${blue("MOONSHOT_BASE_URL")} Custom Kimi / Moonshot endpoint ${dim4("(alias KIMI_BASE_URL)")}
76254
+ ${blue("ZHIPU_BASE_URL")} Custom GLM / Zhipu endpoint ${dim4("(alias GLM_BASE_URL)")}
76255
+ ${blue("SAKANA_BASE_URL")} Custom Sakana endpoint ${dim4("(default: https://api.sakana.ai)")}
76256
+ ${blue("LITELLM_BASE_URL")} LiteLLM gateway base URL ${dim4("(required for ll@)")}
76257
+ ${blue("OLLAMACLOUD_BASE_URL")} OllamaCloud ${dim4("(default: https://ollama.com)")}
76258
+ ${blue("OPENCODE_BASE_URL")} OpenCode Zen ${dim4("(default: https://opencode.ai/zen)")}
75298
76259
 
75299
76260
  ${bold4("Local providers:")}
75300
- ${blue("OLLAMA_BASE_URL")} Ollama server ${dim3("(default: http://localhost:11434; alias OLLAMA_HOST)")}
75301
- ${blue("LMSTUDIO_BASE_URL")} LM Studio server ${dim3("(default: http://localhost:1234)")}
75302
- ${blue("VLLM_BASE_URL")} vLLM server ${dim3("(default: http://localhost:8000)")}
75303
- ${blue("MLX_BASE_URL")} MLX server ${dim3("(default: http://127.0.0.1:8080)")}
76261
+ ${blue("OLLAMA_BASE_URL")} Ollama server ${dim4("(default: http://localhost:11434; alias OLLAMA_HOST)")}
76262
+ ${blue("LMSTUDIO_BASE_URL")} LM Studio server ${dim4("(default: http://localhost:1234)")}
76263
+ ${blue("VLLM_BASE_URL")} vLLM server ${dim4("(default: http://localhost:8000)")}
76264
+ ${blue("MLX_BASE_URL")} MLX server ${dim4("(default: http://127.0.0.1:8080)")}
75304
76265
 
75305
76266
  ${bold4("Claudish settings:")}
75306
- ${blue("CLAUDISH_MODEL")} Default model ${dim3("(default: openai/gpt-5.3)")}
75307
- ${blue("CLAUDISH_DEFAULT_PROVIDER")} Fallback provider for bare names ${dim3("(see --default-provider)")}
76267
+ ${blue("CLAUDISH_MODEL")} Default model ${dim4("(default: openai/gpt-5.3)")}
76268
+ ${blue("CLAUDISH_DEFAULT_PROVIDER")} Fallback provider for bare names ${dim4("(see --default-provider)")}
75308
76269
  ${blue("CLAUDISH_PORT")} Default proxy port
75309
76270
  ${blue("CLAUDISH_CONTEXT_WINDOW")} Override context window size
75310
76271
  ${blue("CLAUDISH_DIAG_MODE")} Diagnostic output: auto / logfile / off
75311
- ${blue("CLAUDISH_DEBUG")} Always enable debug logging: 1 / true ${dim3("(same as -d)")}
75312
- ${blue("CLAUDISH_ANTHROPIC_API_BILLING")} Bill native Claude to your API key ${dim3("(see --anthropic-api-billing)")}
76272
+ ${blue("CLAUDISH_DEBUG")} Always enable debug logging: 1 / true ${dim4("(same as -d)")}
76273
+ ${blue("CLAUDISH_ANTHROPIC_API_BILLING")} Bill native Claude to your API key ${dim4("(see --anthropic-api-billing)")}
75313
76274
  ${blue("CLAUDISH_MCP_TOOLS")} MCP tool gating: all / low-level / agentic / channel
75314
76275
  ${blue("CLAUDISH_MODEL_OPUS")} Override model for Opus role
75315
76276
  ${blue("CLAUDISH_MODEL_SONNET")} Override model for Sonnet role
@@ -75319,39 +76280,39 @@ ${h("ENVIRONMENT VARIABLES")}
75319
76280
 
75320
76281
  ${bold4("1Password auth:")}
75321
76282
  ${blue("OP_SERVICE_ACCOUNT_TOKEN")} Service-account token (preferred for headless)
75322
- ${blue("OP_ACCOUNT")} Account URL for DesktopAuth ${dim3("(e.g. my-team.1password.com)")}
76283
+ ${blue("OP_ACCOUNT")} Account URL for DesktopAuth ${dim4("(e.g. my-team.1password.com)")}
75323
76284
 
75324
76285
  ${h("EXAMPLES")}
75325
- ${dim3("# Interactive (default) - model selector")}
76286
+ ${dim4("# Interactive (default) - model selector")}
75326
76287
  ${green2("claudish")}
75327
- ${green2("claudish")} --free ${dim3("# only FREE models")}
76288
+ ${green2("claudish")} --free ${dim4("# only FREE models")}
75328
76289
 
75329
- ${dim3("# Explicit provider routing")}
76290
+ ${dim4("# Explicit provider routing")}
75330
76291
  ${green2("claudish")} --model ${magenta("google@gemini-3-pro")} ${yellow2('"implement auth"')}
75331
76292
  ${green2("claudish")} --model ${magenta("oai@gpt-5.3")} ${yellow2('"add tests for login"')}
75332
76293
  ${green2("claudish")} --model ${magenta("openrouter@deepseek/deepseek-r1")} ${yellow2('"unknown vendor"')}
75333
76294
 
75334
- ${dim3("# Native auto-detection (provider inferred from model name)")}
76295
+ ${dim4("# Native auto-detection (provider inferred from model name)")}
75335
76296
  ${green2("claudish")} --model ${yellow2("gpt-4o")} ${yellow2('"routes to OpenAI"')}
75336
76297
  ${green2("claudish")} --model ${yellow2("gemini-2.5-pro")} ${yellow2('"routes to Google"')}
75337
76298
 
75338
- ${dim3("# Per-role model mapping")}
76299
+ ${dim4("# Per-role model mapping")}
75339
76300
  ${green2("claudish")} --model-opus ${magenta("oai@gpt-5.3")} --model-sonnet ${magenta("google@gemini-3-pro")}
75340
76301
 
75341
- ${dim3("# stdin for large prompts (diffs, code review)")}
75342
- ${dim3("git diff |")} ${green2("claudish")} --stdin --model ${magenta("oai@gpt-5.3")} ${yellow2('"Review these changes"')}
76302
+ ${dim4("# stdin for large prompts (diffs, code review)")}
76303
+ ${dim4("git diff |")} ${green2("claudish")} --stdin --model ${magenta("oai@gpt-5.3")} ${yellow2('"Review these changes"')}
75343
76304
 
75344
- ${dim3("# Local models with concurrency control")}
76305
+ ${dim4("# Local models with concurrency control")}
75345
76306
  ${green2("claudish")} --model ${magenta("ollama@llama3.2:3")} ${yellow2('"3 concurrent requests"')}
75346
76307
  ${green2("claudish")} --model ${magenta("lms@qwen2.5-coder")} ${yellow2('"LM Studio shortcut"')}
75347
76308
  ${green2("claudish")} --model ${yellow2('"http://localhost:8000/mistral"')} ${yellow2('"any OpenAI-compatible URL"')}
75348
76309
 
75349
- ${dim3("# Autonomous (no prompts, no sandbox) \u2014 use with caution")}
76310
+ ${dim4("# Autonomous (no prompts, no sandbox) \u2014 use with caution")}
75350
76311
  ${green2("claudish")} -y --dangerous ${yellow2('"refactor entire codebase"')}
75351
76312
 
75352
76313
  ${h("MORE INFO")}
75353
- ${dim3("GitHub:")} ${blue("https://github.com/MadAppGang/claude-code")}
75354
- ${dim3("OpenRouter:")} ${blue("https://openrouter.ai")}
76314
+ ${dim4("GitHub:")} ${blue("https://github.com/MadAppGang/claude-code")}
76315
+ ${dim4("OpenRouter:")} ${blue("https://openrouter.ai")}
75355
76316
  `);
75356
76317
  }
75357
76318
  function printAIAgentGuide() {
@@ -76714,6 +77675,8 @@ function Footer({ activeTab, mode, probeMode, providerCaps }) {
76714
77675
  [C.blue, "\u2191\u2193", "navigate"],
76715
77676
  [C.green, "a", "add"],
76716
77677
  [C.cyan, "t", "test"],
77678
+ [C.magenta, "c", "\u2192keychain"],
77679
+ [C.magenta, "C", "all\u2192keychain"],
76717
77680
  [C.green, "o", "account"],
76718
77681
  [C.red, "x", "remove"],
76719
77682
  [C.blue, "Tab", "section"],
@@ -78869,12 +79832,29 @@ var init_ProfilesContent = __esm(() => {
78869
79832
 
78870
79833
  // src/tui/components/ProviderDetail.tsx
78871
79834
  import { jsx as jsx11, jsxs as jsxs10, Fragment as Fragment7 } from "@opentui/react/jsx-runtime";
78872
- function truncateOneLine(text, maxWidth) {
79835
+ function wrapToLines(text, maxWidth, maxLines) {
78873
79836
  const collapsed = text.replace(/\s+/g, " ").trim();
78874
79837
  const limit = Math.max(20, maxWidth);
78875
79838
  if (collapsed.length <= limit)
78876
- return collapsed;
78877
- return `${collapsed.slice(0, limit - 1)}\u2026`;
79839
+ return [collapsed];
79840
+ const lines = [];
79841
+ let rest = collapsed;
79842
+ while (rest.length > 0 && lines.length < maxLines) {
79843
+ if (rest.length <= limit) {
79844
+ lines.push(rest);
79845
+ break;
79846
+ }
79847
+ const slice = rest.slice(0, limit);
79848
+ const cut = slice.lastIndexOf(" ");
79849
+ const at = cut > limit * 0.5 ? cut : limit;
79850
+ lines.push(rest.slice(0, at));
79851
+ rest = rest.slice(at).trimStart();
79852
+ }
79853
+ if (rest.length > 0 && lines.length === maxLines) {
79854
+ const last = lines[maxLines - 1] ?? "";
79855
+ lines[maxLines - 1] = `${last.slice(0, Math.max(1, limit - 1))}\u2026`;
79856
+ }
79857
+ return lines;
78878
79858
  }
78879
79859
  function resolveProviderDetailKeyDisplay(input) {
78880
79860
  if (input.isLocal)
@@ -78885,8 +79865,6 @@ function resolveProviderDetailKeyDisplay(input) {
78885
79865
  return input.envKeyMask;
78886
79866
  if (input.hasCfgKey)
78887
79867
  return input.cfgKeyMask;
78888
- if (input.isPublicKey)
78889
- return "free";
78890
79868
  return "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500";
78891
79869
  }
78892
79870
  function ProviderDetail({
@@ -78900,7 +79878,9 @@ function ProviderDetail({
78900
79878
  hasKey,
78901
79879
  authSource,
78902
79880
  isOpKey,
78903
- isPublicKey,
79881
+ isKcKey,
79882
+ hasKcKey,
79883
+ keySaveTarget,
78904
79884
  cfgKeyMask,
78905
79885
  envKeyMask,
78906
79886
  activeEndpoint,
@@ -78914,7 +79894,6 @@ function ProviderDetail({
78914
79894
  authSource,
78915
79895
  hasEnvKey,
78916
79896
  hasCfgKey,
78917
- isPublicKey,
78918
79897
  envKeyMask,
78919
79898
  cfgKeyMask
78920
79899
  });
@@ -78924,7 +79903,7 @@ function ProviderDetail({
78924
79903
  border: true,
78925
79904
  borderStyle: "single",
78926
79905
  borderColor: C.focusBorder,
78927
- title: ` Set ${mode === "input_key" ? "API Key" : "Endpoint"} \u2014 ${selectedProvider.displayName} `,
79906
+ title: mode === "input_key" ? ` Set API Key \u2014 ${selectedProvider.displayName} \u2192 ${keySaveTarget} ` : ` Set Endpoint \u2014 ${selectedProvider.displayName} `,
78928
79907
  backgroundColor: C.bg,
78929
79908
  flexDirection: "column",
78930
79909
  paddingX: 1,
@@ -78985,6 +79964,7 @@ function ProviderDetail({
78985
79964
  });
78986
79965
  }
78987
79966
  const tr = testResults[selectedProvider.name];
79967
+ const failureText = tr && (tr.status === "failed" || tr.status === "unavailable") ? tr.providerMessage ?? tr.error : undefined;
78988
79968
  return /* @__PURE__ */ jsxs10("box", {
78989
79969
  height: DETAIL_H,
78990
79970
  border: true,
@@ -79058,7 +80038,7 @@ function ProviderDetail({
79058
80038
  })
79059
80039
  ]
79060
80040
  }),
79061
- hasKey && !selectedProvider.isLocal && isPublicKey && /* @__PURE__ */ jsxs10(Fragment7, {
80041
+ hasKey && !selectedProvider.isLocal && isOAuth && /* @__PURE__ */ jsxs10(Fragment7, {
79062
80042
  children: [
79063
80043
  /* @__PURE__ */ jsx11("span", {
79064
80044
  fg: C.dim,
@@ -79070,9 +80050,13 @@ function ProviderDetail({
79070
80050
  children: "From: "
79071
80051
  }),
79072
80052
  /* @__PURE__ */ jsx11("span", {
79073
- fg: C.green,
80053
+ fg: C.cyan,
79074
80054
  attributes: A.bold,
79075
- children: "public key (free)"
80055
+ children: "oauth"
80056
+ }),
80057
+ /* @__PURE__ */ jsx11("span", {
80058
+ fg: C.fgMuted,
80059
+ children: " (used)"
79076
80060
  })
79077
80061
  ]
79078
80062
  }),
@@ -79094,7 +80078,7 @@ function ProviderDetail({
79094
80078
  })
79095
80079
  ]
79096
80080
  }),
79097
- hasKey && !selectedProvider.isLocal && !isPublicKey && !isOAuth && /* @__PURE__ */ jsxs10(Fragment7, {
80081
+ hasKey && !selectedProvider.isLocal && !isOAuth && /* @__PURE__ */ jsxs10(Fragment7, {
79098
80082
  children: [
79099
80083
  /* @__PURE__ */ jsx11("span", {
79100
80084
  fg: C.dim,
@@ -79108,7 +80092,7 @@ function ProviderDetail({
79108
80092
  hasEnvKey && /* @__PURE__ */ jsx11("span", {
79109
80093
  fg: C.green,
79110
80094
  attributes: A.bold,
79111
- children: isOpKey ? "1Password" : "env"
80095
+ children: isKcKey ? "keychain" : isOpKey ? "1Password" : "env"
79112
80096
  }),
79113
80097
  hasEnvKey && hasCfgKey && /* @__PURE__ */ jsx11("span", {
79114
80098
  fg: C.fgMuted,
@@ -79126,6 +80110,18 @@ function ProviderDetail({
79126
80110
  hasCfgKey && /* @__PURE__ */ jsx11("span", {
79127
80111
  fg: C.fgMuted,
79128
80112
  children: hasEnvKey ? " (shadowed)" : " (used)"
80113
+ }),
80114
+ hasKcKey && !isKcKey && /* @__PURE__ */ jsxs10(Fragment7, {
80115
+ children: [
80116
+ /* @__PURE__ */ jsx11("span", {
80117
+ fg: C.fgMuted,
80118
+ children: " + "
80119
+ }),
80120
+ /* @__PURE__ */ jsx11("span", {
80121
+ fg: C.fgMuted,
80122
+ children: "keychain (shadowed)"
80123
+ })
80124
+ ]
79129
80125
  })
79130
80126
  ]
79131
80127
  })
@@ -79147,7 +80143,7 @@ function ProviderDetail({
79147
80143
  })
79148
80144
  ]
79149
80145
  }),
79150
- /* @__PURE__ */ jsxs10("text", {
80146
+ !failureText && /* @__PURE__ */ jsxs10("text", {
79151
80147
  children: [
79152
80148
  /* @__PURE__ */ jsxs10("span", {
79153
80149
  fg: C.blue,
@@ -79163,7 +80159,7 @@ function ProviderDetail({
79163
80159
  })
79164
80160
  ]
79165
80161
  }),
79166
- selectedProvider.keyUrl && /* @__PURE__ */ jsxs10("text", {
80162
+ selectedProvider.keyUrl && !failureText && /* @__PURE__ */ jsxs10("text", {
79167
80163
  children: [
79168
80164
  /* @__PURE__ */ jsxs10("span", {
79169
80165
  fg: C.blue,
@@ -79208,34 +80204,24 @@ function ProviderDetail({
79208
80204
  })
79209
80205
  ]
79210
80206
  }),
79211
- tr.status === "failed" && /* @__PURE__ */ jsxs10(Fragment7, {
79212
- children: [
79213
- /* @__PURE__ */ jsx11("span", {
79214
- fg: C.red,
79215
- attributes: A.bold,
79216
- children: "\u2717 failed"
79217
- }),
79218
- tr.error && /* @__PURE__ */ jsx11("span", {
79219
- fg: C.red,
79220
- children: ` ${truncateOneLine(tr.error, width - 16)}`
79221
- })
79222
- ]
80207
+ tr.status === "failed" && /* @__PURE__ */ jsx11("span", {
80208
+ fg: C.red,
80209
+ attributes: A.bold,
80210
+ children: "\u2717 failed"
79223
80211
  }),
79224
- tr.status === "unavailable" && /* @__PURE__ */ jsxs10(Fragment7, {
79225
- children: [
79226
- /* @__PURE__ */ jsx11("span", {
79227
- fg: C.yellow,
79228
- attributes: A.bold,
79229
- children: "\u25CB unavailable"
79230
- }),
79231
- tr.error && /* @__PURE__ */ jsx11("span", {
79232
- fg: C.yellow,
79233
- children: ` ${truncateOneLine(tr.error, width - 16)}`
79234
- })
79235
- ]
80212
+ tr.status === "unavailable" && /* @__PURE__ */ jsx11("span", {
80213
+ fg: C.yellow,
80214
+ attributes: A.bold,
80215
+ children: "\u25CB unavailable"
79236
80216
  })
79237
80217
  ]
79238
- })
80218
+ }),
80219
+ failureText && wrapToLines(failureText, width - 4, 2).map((line, i) => /* @__PURE__ */ jsx11("text", {
80220
+ children: /* @__PURE__ */ jsx11("span", {
80221
+ fg: tr?.status === "unavailable" ? C.yellow : C.red,
80222
+ children: line
80223
+ })
80224
+ }, i))
79239
80225
  ]
79240
80226
  });
79241
80227
  }
@@ -79302,8 +80288,6 @@ function ProvidersContent({
79302
80288
  keyDisplay = "local";
79303
80289
  } else if (isOauthOnly) {
79304
80290
  keyDisplay = "oauth\xB7\xB7\xB7";
79305
- } else if (auth === "public") {
79306
- keyDisplay = "free";
79307
80291
  } else if (auth === "cfg") {
79308
80292
  keyDisplay = maskKey2(config3.apiKeys?.[p.apiKeyEnvVar]);
79309
80293
  } else if (auth === "env" || auth === "e+c") {
@@ -80734,18 +81718,18 @@ function useProfileWizard(args) {
80734
81718
  setEditProfileValue("");
80735
81719
  return;
80736
81720
  }
80737
- const now = new Date().toISOString();
81721
+ const now2 = new Date().toISOString();
80738
81722
  if (profileScope === "project") {
80739
81723
  const localCfg = loadLocalConfig() ?? {
80740
81724
  version: "1.0.0",
80741
81725
  defaultProfile: "",
80742
81726
  profiles: {}
80743
81727
  };
80744
- localCfg.profiles[name] = { name, models: {}, createdAt: now, updatedAt: now };
81728
+ localCfg.profiles[name] = { name, models: {}, createdAt: now2, updatedAt: now2 };
80745
81729
  saveLocalConfig(localCfg);
80746
81730
  } else {
80747
81731
  const cfg = loadConfig();
80748
- cfg.profiles[name] = { name, models: {}, createdAt: now, updatedAt: now };
81732
+ cfg.profiles[name] = { name, models: {}, createdAt: now2, updatedAt: now2 };
80749
81733
  saveConfig(cfg);
80750
81734
  }
80751
81735
  refreshConfig();
@@ -81100,20 +82084,20 @@ function useRouteProbe(config3) {
81100
82084
  errorMessage: String(e instanceof Error ? e.message : e)
81101
82085
  }));
81102
82086
  const ms = Date.now() - startMs;
81103
- const ok = result.state === "live";
82087
+ const ok2 = result.state === "live";
81104
82088
  setProbeResults((prev) => prev.map((e, idx) => {
81105
82089
  if (idx === i)
81106
82090
  return {
81107
82091
  ...e,
81108
- status: ok ? "success" : "failed",
81109
- error: ok ? undefined : describeProbeState(result),
82092
+ status: ok2 ? "success" : "failed",
82093
+ error: ok2 ? undefined : describeProbeState(result),
81110
82094
  ms
81111
82095
  };
81112
- if (idx > i && ok && e.status !== "no_key")
82096
+ if (idx > i && ok2 && e.status !== "no_key")
81113
82097
  return { ...e, status: "skipped" };
81114
82098
  return e;
81115
82099
  }));
81116
- if (ok)
82100
+ if (ok2)
81117
82101
  break;
81118
82102
  }
81119
82103
  setProbeMode("done");
@@ -81153,6 +82137,50 @@ var init_useRouteProbe = __esm(() => {
81153
82137
  import { useKeyboard as useKeyboard2, useRenderer, useTerminalDimensions as useTerminalDimensions2 } from "@opentui/react";
81154
82138
  import { useCallback as useCallback3, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef4, useState as useState5 } from "react";
81155
82139
  import { jsx as jsx16, jsxs as jsxs15, Fragment as Fragment10 } from "@opentui/react/jsx-runtime";
82140
+ async function resolveOpEntrySecrets(entries, auth) {
82141
+ const secrets = {};
82142
+ for (const entry of entries) {
82143
+ if (entry.kind === "environment") {
82144
+ Object.assign(secrets, await withSdkRetry(() => readEnvironment(entry.value, { auth }), "tui:copy-keychain"));
82145
+ } else if (entry.kind === "glob" || isGlobImport(entry.value)) {
82146
+ Object.assign(secrets, await withSdkRetry(() => resolveGlobImport(entry.value, { auth }), "tui:copy-keychain"));
82147
+ } else {
82148
+ const r = await withSdkRetry(() => resolveSecrets({ T: entry.value }, { auth }), "tui:copy-keychain");
82149
+ const name = envNameFromOpRef(entry.value);
82150
+ if (name && r.T)
82151
+ secrets[name] = r.T;
82152
+ }
82153
+ }
82154
+ return secrets;
82155
+ }
82156
+ function writeSecretsToKeychain(secrets, alreadyStored) {
82157
+ const outcome = { created: 0, replaced: 0, skipped: [] };
82158
+ for (const [name, value] of Object.entries(secrets)) {
82159
+ if (!value)
82160
+ continue;
82161
+ try {
82162
+ writeKeychainSecret(name, value);
82163
+ if (alreadyStored.has(name))
82164
+ outcome.replaced++;
82165
+ else
82166
+ outcome.created++;
82167
+ } catch {
82168
+ outcome.skipped.push(name);
82169
+ }
82170
+ }
82171
+ return outcome;
82172
+ }
82173
+ function describeCopyOutcome({ created, replaced, skipped }) {
82174
+ if (created + replaced === 0) {
82175
+ return `Nothing copied to the Keychain${skipped.length > 0 ? ` \u2014 ${skipped.length} could not be stored` : ""}.`;
82176
+ }
82177
+ const parts = [`${created} new`];
82178
+ if (replaced > 0)
82179
+ parts.push(`${replaced} replaced`);
82180
+ if (skipped.length > 0)
82181
+ parts.push(`${skipped.length} skipped (${skipped.join(", ")})`);
82182
+ return `Copied to macOS Keychain: ${parts.join(" \xB7 ")}.`;
82183
+ }
81156
82184
  function App({ requestLogin } = {}) {
81157
82185
  const renderer = useRenderer();
81158
82186
  const { width, height: height2 } = useTerminalDimensions2();
@@ -81162,6 +82190,9 @@ function App({ requestLogin } = {}) {
81162
82190
  const [activeTab, setActiveTab] = useState5("providers");
81163
82191
  const [mode, setMode] = useState5("browse");
81164
82192
  const [inputValue, setInputValue] = useState5("");
82193
+ const [keychainVars, setKeychainVars] = useState5(new Set);
82194
+ const keychainSupported = useMemo2(() => isKeychainSupported(), []);
82195
+ const inputTargetRef = useRef4(null);
81165
82196
  const [routingPattern, setRoutingPattern] = useState5("");
81166
82197
  const [chainSelected, setChainSelected] = useState5(new Set);
81167
82198
  const [chainOrder, setChainOrder] = useState5([]);
@@ -81249,6 +82280,36 @@ function App({ requestLogin } = {}) {
81249
82280
  setBufStats(getBufferStats());
81250
82281
  setOpTick((t) => t + 1);
81251
82282
  }, []);
82283
+ const refreshKeychainVars = useCallback3(() => {
82284
+ if (!hasKeychainSource()) {
82285
+ setKeychainVars(new Set);
82286
+ return;
82287
+ }
82288
+ try {
82289
+ setKeychainVars(new Set(listKeychainVars()));
82290
+ } catch {
82291
+ setKeychainVars(new Set);
82292
+ }
82293
+ }, []);
82294
+ useEffect4(() => {
82295
+ if (!keychainSupported)
82296
+ return;
82297
+ let cancelled = false;
82298
+ (async () => {
82299
+ let hydrated = 0;
82300
+ try {
82301
+ hydrated = await hydrateKeychainIntoEnv();
82302
+ } catch {}
82303
+ if (cancelled)
82304
+ return;
82305
+ refreshKeychainVars();
82306
+ if (hydrated > 0)
82307
+ refreshConfig();
82308
+ })();
82309
+ return () => {
82310
+ cancelled = true;
82311
+ };
82312
+ }, [keychainSupported, refreshKeychainVars, refreshConfig]);
81252
82313
  const clearTestResult = useCallback3((provName) => {
81253
82314
  setTestResults((prev) => {
81254
82315
  if (!(provName in prev))
@@ -81264,11 +82325,12 @@ function App({ requestLogin } = {}) {
81264
82325
  const { editProfileValue, profileScope, suggestions, suggestionIndex, providerPickerIndex } = wizard;
81265
82326
  const hasCfgKey = !!config3.apiKeys?.[selectedProvider.apiKeyEnvVar];
81266
82327
  const hasEnvKey = !!process.env[selectedProvider.apiKeyEnvVar];
82328
+ const providerKeychainVars = useMemo2(() => [selectedProvider.apiKeyEnvVar, ...selectedProvider.aliases ?? []].filter((n) => !!n && keychainVars.has(n)), [selectedProvider, keychainVars]);
82329
+ const hasKcKey = providerKeychainVars.length > 0;
81267
82330
  const selectedAuthSource = providerAuthSource(selectedProvider, config3);
81268
- const selectedLocalRunning = selectedProviderIsLocal && localLiveness[selectedProvider.catalogName] === "running";
81269
- const hasKey = selectedAuthSource !== null || selectedLocalRunning;
81270
- const selectedPublicKey = selectedAuthSource === "public";
82331
+ const hasKey = providerIsReadyForDisplay(selectedProvider, config3, localLiveness);
81271
82332
  const isOpKey = hasEnvKey && isOpHydratedVar(selectedProvider.apiKeyEnvVar);
82333
+ const isKcKey = hasEnvKey && isKeychainHydratedVar(selectedProvider.apiKeyEnvVar);
81272
82334
  const cfgKeyMask = maskKey2(config3.apiKeys?.[selectedProvider.apiKeyEnvVar]);
81273
82335
  const envKeyMask = maskKey2(process.env[selectedProvider.apiKeyEnvVar]);
81274
82336
  const activeEndpointEnvVar = selectedProvider.endpointEnvVar;
@@ -81445,6 +82507,36 @@ function App({ requestLogin } = {}) {
81445
82507
  setOpBusy(false);
81446
82508
  }
81447
82509
  }, [acquireOpAuth]);
82510
+ const copyOpToKeychain = useCallback3(async (entries, label) => {
82511
+ if (!keychainSupported) {
82512
+ setStatusMsg("macOS Keychain is only available on macOS.");
82513
+ return;
82514
+ }
82515
+ const copyable = entries.filter((e) => e.kind !== "account");
82516
+ if (copyable.length === 0) {
82517
+ setStatusMsg("Nothing to copy \u2014 the account entry holds no secret.");
82518
+ return;
82519
+ }
82520
+ setOpBusy(true);
82521
+ setStatusMsg(`Resolving ${label} from 1Password\u2026`);
82522
+ try {
82523
+ const auth = await acquireOpAuth();
82524
+ const secrets = await resolveOpEntrySecrets(copyable, auth);
82525
+ const outcome = writeSecretsToKeychain(secrets, new Set(keychainVars));
82526
+ if (outcome.created + outcome.replaced > 0) {
82527
+ setKeychainEnabled(true);
82528
+ refreshKeychainVars();
82529
+ credentials.invalidate();
82530
+ invalidateProbeProxyHandlers();
82531
+ refreshConfig();
82532
+ }
82533
+ setStatusMsg(describeCopyOutcome(outcome));
82534
+ } catch (err) {
82535
+ setStatusMsg(err instanceof Error ? err.message : String(err));
82536
+ } finally {
82537
+ setOpBusy(false);
82538
+ }
82539
+ }, [acquireOpAuth, keychainSupported, keychainVars, refreshKeychainVars, refreshConfig]);
81448
82540
  const resetOpWizard = useCallback3(() => {
81449
82541
  setInputValue("");
81450
82542
  setOpPendingValue("");
@@ -81715,7 +82807,7 @@ function App({ requestLogin } = {}) {
81715
82807
  const error46 = tried.size > 1 ? `${baseError} (tried ${tried.size} models)` : baseError;
81716
82808
  setTestResults((prev) => ({
81717
82809
  ...prev,
81718
- [provName]: { status: "failed", error: error46, ms }
82810
+ [provName]: { status: "failed", error: error46, providerMessage: result.errorMessage, ms }
81719
82811
  }));
81720
82812
  }
81721
82813
  } catch (err) {
@@ -81767,26 +82859,49 @@ function App({ requestLogin } = {}) {
81767
82859
  setMode("browse");
81768
82860
  return;
81769
82861
  }
82862
+ const target = inputTargetRef.current ?? selectedProvider;
81770
82863
  if (mode === "input_key") {
81771
- if (!selectedProvider.apiKeyEnvVar) {
81772
- setStatusMsg(`${selectedProvider.displayName} has no apiKeyEnvVar \u2014 cannot save key.`);
82864
+ if (!target.apiKeyEnvVar) {
82865
+ setStatusMsg(`${target.displayName} has no apiKeyEnvVar \u2014 cannot save key.`);
81773
82866
  } else {
81774
- setApiKey(selectedProvider.apiKeyEnvVar, val);
81775
- process.env[selectedProvider.apiKeyEnvVar] = val;
81776
- setStatusMsg(`Key saved for ${selectedProvider.displayName} (${selectedProvider.apiKeyEnvVar}).`);
82867
+ const envVar = target.apiKeyEnvVar;
82868
+ let saved = false;
82869
+ if (keychainSupported) {
82870
+ try {
82871
+ writeKeychainSecret(envVar, val);
82872
+ setKeychainEnabled(true);
82873
+ process.env[envVar] = val;
82874
+ recordKeychainHydratedVar(envVar);
82875
+ refreshKeychainVars();
82876
+ setStatusMsg(`Key saved to macOS Keychain for ${target.displayName} (${envVar}).`);
82877
+ saved = true;
82878
+ } catch (err) {
82879
+ setStatusMsg(`Keychain write failed (${err instanceof Error ? err.message : String(err)}) \u2014 saved to config.json instead.`);
82880
+ }
82881
+ }
82882
+ if (!saved) {
82883
+ setApiKey(envVar, val);
82884
+ process.env[envVar] = val;
82885
+ if (!keychainSupported) {
82886
+ setStatusMsg(`Key saved for ${target.displayName} (${envVar}).`);
82887
+ }
82888
+ }
82889
+ credentials.invalidate(target.catalogName);
82890
+ invalidateProbeProxyHandlers(target.catalogName);
82891
+ clearTestResult(target.name);
81777
82892
  }
81778
82893
  } else {
81779
- if (!selectedProvider.endpointEnvVar) {
81780
- setStatusMsg(`${selectedProvider.displayName} has no endpointEnvVar \u2014 cannot save URL.`);
82894
+ if (!target.endpointEnvVar) {
82895
+ setStatusMsg(`${target.displayName} has no endpointEnvVar \u2014 cannot save URL.`);
81781
82896
  } else {
81782
- setEndpoint(selectedProvider.endpointEnvVar, val);
81783
- process.env[selectedProvider.endpointEnvVar] = val;
81784
- setStatusMsg(`URL saved for ${selectedProvider.displayName} (${selectedProvider.endpointEnvVar}=${val}).`);
82897
+ setEndpoint(target.endpointEnvVar, val);
82898
+ process.env[target.endpointEnvVar] = val;
82899
+ setStatusMsg(`URL saved for ${target.displayName} (${target.endpointEnvVar}=${val}).`);
81785
82900
  }
81786
82901
  }
81787
- invalidateProbeProxyHandlers(selectedProvider.catalogName);
81788
- invalidateProbeDiscovery(selectedProvider.catalogName);
81789
- clearTestResult(selectedProvider.name);
82902
+ invalidateProbeProxyHandlers(target.catalogName);
82903
+ invalidateProbeDiscovery(target.catalogName);
82904
+ clearTestResult(target.name);
81790
82905
  refreshConfig();
81791
82906
  setInputValue("");
81792
82907
  setMode("browse");
@@ -82255,10 +83370,12 @@ function App({ requestLogin } = {}) {
82255
83370
  setStatusMsg(null);
82256
83371
  } else if (key.name === "s") {
82257
83372
  if (selectedProvider.apiKeyEnvVar) {
83373
+ inputTargetRef.current = selectedProvider;
82258
83374
  setInputValue("");
82259
83375
  setStatusMsg(null);
82260
83376
  setMode("input_key");
82261
83377
  } else if (selectedProvider.endpointEnvVar) {
83378
+ inputTargetRef.current = selectedProvider;
82262
83379
  setInputValue(activeEndpoint);
82263
83380
  setStatusMsg(null);
82264
83381
  setMode("input_endpoint");
@@ -82276,6 +83393,7 @@ function App({ requestLogin } = {}) {
82276
83393
  }
82277
83394
  refreshConfig();
82278
83395
  } else if (selectedProvider.endpointEnvVar) {
83396
+ inputTargetRef.current = selectedProvider;
82279
83397
  setInputValue(activeEndpoint);
82280
83398
  setStatusMsg(null);
82281
83399
  setMode("input_endpoint");
@@ -82284,6 +83402,7 @@ function App({ requestLogin } = {}) {
82284
83402
  }
82285
83403
  } else if (key.name === "u") {
82286
83404
  if (selectedProvider.endpointEnvVar) {
83405
+ inputTargetRef.current = selectedProvider;
82287
83406
  setInputValue(activeEndpoint);
82288
83407
  setStatusMsg(null);
82289
83408
  setMode("input_endpoint");
@@ -82292,24 +83411,43 @@ function App({ requestLogin } = {}) {
82292
83411
  }
82293
83412
  } else if (key.name === "x") {
82294
83413
  let changed = false;
83414
+ const removedFrom = [];
83415
+ let failureMsg = null;
82295
83416
  if (hasCfgKey) {
82296
83417
  removeApiKey(selectedProvider.apiKeyEnvVar);
83418
+ removedFrom.push("config.json");
82297
83419
  changed = true;
82298
83420
  }
83421
+ if (keychainSupported) {
83422
+ for (const name of providerKeychainVars) {
83423
+ try {
83424
+ if (deleteKeychainSecret(name)) {
83425
+ removedFrom.push(`macOS Keychain (${name})`);
83426
+ changed = true;
83427
+ }
83428
+ } catch (err) {
83429
+ failureMsg = `Keychain delete failed for ${name}: ${err instanceof Error ? err.message : String(err)}`;
83430
+ }
83431
+ }
83432
+ if (providerKeychainVars.length > 0)
83433
+ refreshKeychainVars();
83434
+ }
83435
+ if (changed && (isKcKey || isOpKey)) {
83436
+ delete process.env[selectedProvider.apiKeyEnvVar];
83437
+ }
82299
83438
  if (activeEndpointEnvVar && config3.endpoints?.[activeEndpointEnvVar]) {
82300
83439
  removeEndpoint(activeEndpointEnvVar);
82301
83440
  delete process.env[activeEndpointEnvVar];
82302
83441
  changed = true;
82303
83442
  }
82304
83443
  if (changed) {
83444
+ credentials.invalidate(selectedProvider.catalogName);
82305
83445
  invalidateProbeProxyHandlers(selectedProvider.catalogName);
82306
83446
  invalidateProbeDiscovery(selectedProvider.catalogName);
82307
83447
  clearTestResult(selectedProvider.name);
82308
83448
  refreshConfig();
82309
- setStatusMsg(`Stored config removed for ${selectedProvider.displayName}.`);
82310
- } else {
82311
- setStatusMsg("No stored config to remove.");
82312
83449
  }
83450
+ setStatusMsg(failureMsg ?? (changed ? removedFrom.length > 0 ? `Removed ${selectedProvider.displayName} key from ${removedFrom.join(" + ")}.` : `Stored config removed for ${selectedProvider.displayName}.` : "No stored config to remove."));
82313
83451
  } else if (key.name === "l") {
82314
83452
  const slug = selectedProvider.oauthSlug;
82315
83453
  if (!slug) {
@@ -82534,6 +83672,18 @@ function App({ requestLogin } = {}) {
82534
83672
  } else if (selectedOpEntry) {
82535
83673
  testOpEntry(selectedOpEntry);
82536
83674
  }
83675
+ } else if (key.raw === "C") {
83676
+ if (opEntries.length === 0) {
83677
+ setStatusMsg("No 1Password entries to copy.");
83678
+ } else {
83679
+ copyOpToKeychain(opEntries, `all ${opEntries.length} entries`);
83680
+ }
83681
+ } else if (key.name === "c") {
83682
+ if (opEntries.length === 0 || !selectedOpEntry) {
83683
+ setStatusMsg("No 1Password entry to copy.");
83684
+ } else {
83685
+ copyOpToKeychain([selectedOpEntry], selectedOpEntry.envName ?? selectedOpEntry.value);
83686
+ }
82537
83687
  } else if (key.name === "x") {
82538
83688
  if (opEntries.length === 0 || !selectedOpEntry) {
82539
83689
  setStatusMsg("No 1Password entry to remove.");
@@ -82670,7 +83820,9 @@ function App({ requestLogin } = {}) {
82670
83820
  hasKey,
82671
83821
  authSource: selectedAuthSource,
82672
83822
  isOpKey,
82673
- isPublicKey: selectedPublicKey,
83823
+ isKcKey,
83824
+ hasKcKey,
83825
+ keySaveTarget: keychainSupported ? "macOS Keychain" : "config.json",
82674
83826
  cfgKeyMask,
82675
83827
  envKeyMask,
82676
83828
  activeEndpoint,
@@ -82798,10 +83950,13 @@ function App({ requestLogin } = {}) {
82798
83950
  });
82799
83951
  }
82800
83952
  var init_App = __esm(() => {
83953
+ init_authority();
83954
+ init_keychain_source();
82801
83955
  init_op_source();
82802
83956
  init_profile_config();
82803
83957
  init_default_routing_rules();
82804
83958
  init_endpoint_registration();
83959
+ init_keychain();
82805
83960
  init_local_liveness();
82806
83961
  init_onepassword_config();
82807
83962
  init_onepassword();
@@ -83038,10 +84193,10 @@ function shouldHideIncidentalAnthropicKey(config3, env = process.env) {
83038
84193
  return false;
83039
84194
  return !wantsAnthropicApiBilling(config3, env);
83040
84195
  }
83041
- function hasResolvableAnthropicAuth(deps = {}) {
83042
- const env = deps.env ?? process.env;
83043
- const fileExists = deps.fileExists ?? existsSync30;
83044
- const keychainProbe = deps.keychainProbe ?? defaultKeychainAnthropicProbe;
84196
+ function hasResolvableAnthropicAuth(deps2 = {}) {
84197
+ const env = deps2.env ?? process.env;
84198
+ const fileExists = deps2.fileExists ?? existsSync30;
84199
+ const keychainProbe = deps2.keychainProbe ?? defaultKeychainAnthropicProbe;
83045
84200
  if (env.ANTHROPIC_API_KEY || env.ANTHROPIC_AUTH_TOKEN)
83046
84201
  return true;
83047
84202
  if (fileExists(join40(homedir35(), ".claude", ".credentials.json")))
@@ -83233,7 +84388,7 @@ function initializeTokenFile(tokenFilePath) {
83233
84388
  log(`[claude-runner] Could not initialize token file ${tokenFilePath}: ${e}`);
83234
84389
  }
83235
84390
  }
83236
- function cleanupStaleTokenFiles(dir, now = Date.now(), maxAgeMs = STALE_TOKEN_FILE_MS) {
84391
+ function cleanupStaleTokenFiles(dir, now2 = Date.now(), maxAgeMs = STALE_TOKEN_FILE_MS) {
83237
84392
  let removed = 0;
83238
84393
  let entries;
83239
84394
  try {
@@ -83241,7 +84396,7 @@ function cleanupStaleTokenFiles(dir, now = Date.now(), maxAgeMs = STALE_TOKEN_FI
83241
84396
  } catch {
83242
84397
  return 0;
83243
84398
  }
83244
- const cutoff = now - maxAgeMs;
84399
+ const cutoff = now2 - maxAgeMs;
83245
84400
  let scanned = 0;
83246
84401
  for (const name of entries) {
83247
84402
  if (scanned >= MAX_TOKEN_FILES_SCANNED)
@@ -83855,7 +85010,7 @@ function shouldWarmCatalog(args) {
83855
85010
  }
83856
85011
  return true;
83857
85012
  }
83858
- function classifyCatalogState(cache3, ttlHours, now) {
85013
+ function classifyCatalogState(cache3, ttlHours, now2) {
83859
85014
  if (cache3 === null)
83860
85015
  return "missing";
83861
85016
  if (cache3.entries.length === 0 && cache3.models.length === 0)
@@ -83863,7 +85018,7 @@ function classifyCatalogState(cache3, ttlHours, now) {
83863
85018
  const lastUpdatedMs = Date.parse(cache3.lastUpdated);
83864
85019
  if (Number.isNaN(lastUpdatedMs))
83865
85020
  return "missing";
83866
- const ageMs = now.getTime() - lastUpdatedMs;
85021
+ const ageMs = now2.getTime() - lastUpdatedMs;
83867
85022
  const ttlMs = ttlHours * 3600000;
83868
85023
  return ageMs < ttlMs ? "fresh" : "stale";
83869
85024
  }
@@ -83920,9 +85075,9 @@ async function warmCatalogIfNeeded(config3, opts) {
83920
85075
  }
83921
85076
  const ttlHoursRaw = opts?.ttlHours ?? Number.parseFloat(process.env.CLAUDISH_CATALOG_TTL_HOURS ?? "24");
83922
85077
  const ttlHours = Number.isFinite(ttlHoursRaw) && ttlHoursRaw > 0 ? ttlHoursRaw : 24;
83923
- const now = opts?.now ?? new Date;
85078
+ const now2 = opts?.now ?? new Date;
83924
85079
  const cache3 = readAllModelsCache();
83925
- const state = classifyCatalogState(cache3, ttlHours, now);
85080
+ const state = classifyCatalogState(cache3, ttlHours, now2);
83926
85081
  if (state === "fresh" && !config3.forceUpdate) {
83927
85082
  return "ok";
83928
85083
  }
@@ -83941,14 +85096,14 @@ async function warmCatalogIfNeeded(config3, opts) {
83941
85096
  return "ok";
83942
85097
  }
83943
85098
  if (state === "stale") {
83944
- const ageMs = now.getTime() - Date.parse(cache3.lastUpdated);
85099
+ const ageMs = now2.getTime() - Date.parse(cache3.lastUpdated);
83945
85100
  const ageStr = humanizeAge(ageMs);
83946
85101
  process.stderr.write(`WARNING: Catalog stale (${ageStr}). Using cached version. Run \`claudish --models-refresh\` to retry.
83947
85102
  `);
83948
85103
  return "warned";
83949
85104
  }
83950
85105
  if (state === "fresh") {
83951
- const ageMs = now.getTime() - Date.parse(cache3.lastUpdated);
85106
+ const ageMs = now2.getTime() - Date.parse(cache3.lastUpdated);
83952
85107
  const ageStr = humanizeAge(ageMs);
83953
85108
  process.stderr.write(`WARNING: Catalog refresh failed (cache age ${ageStr}). Using cached version.
83954
85109
  `);
@@ -85410,10 +86565,10 @@ function ResumePicker({ groups, onDone }) {
85410
86565
  const { fresh, stale, visibleGroups } = useMemo4(() => {
85411
86566
  const withSessions = groups.filter((g) => (listed.get(g.name)?.length ?? 0) > 0);
85412
86567
  const matching = filter ? withSessions.filter((g) => fuzzy(filter, g.name)) : withSessions;
85413
- const now = Date.now();
86568
+ const now2 = Date.now();
85414
86569
  const byRecency = (a, b) => b.lastActiveMs - a.lastActiveMs;
85415
- const f = matching.filter((g) => g.current || now - g.lastActiveMs < STALE_MS).sort((a, b) => a.current !== b.current ? a.current ? -1 : 1 : byRecency(a, b));
85416
- const st = matching.filter((g) => !g.current && now - g.lastActiveMs >= STALE_MS).sort(byRecency);
86570
+ const f = matching.filter((g) => g.current || now2 - g.lastActiveMs < STALE_MS).sort((a, b) => a.current !== b.current ? a.current ? -1 : 1 : byRecency(a, b));
86571
+ const st = matching.filter((g) => !g.current && now2 - g.lastActiveMs >= STALE_MS).sort(byRecency);
85417
86572
  return { fresh: f, stale: st, visibleGroups: [...f, ...st] };
85418
86573
  }, [groups, filter, listed]);
85419
86574
  const group = visibleGroups[Math.min(wtCursor, visibleGroups.length - 1)];
@@ -86342,7 +87497,7 @@ function renderSessionSummary(input) {
86342
87497
  const W2 = cardWidth();
86343
87498
  const inner = W2 - CHROME;
86344
87499
  const out = [];
86345
- const dim3 = (s) => paint(s, tokens.subtle);
87500
+ const dim4 = (s) => paint(s, tokens.subtle);
86346
87501
  const body = (s) => paint(s, tokens.text);
86347
87502
  const titleText = exitCode === 0 ? " session " : " session \xB7 failed ";
86348
87503
  const titleHex = exitCode === 0 ? tokens.accent : tokens.error;
@@ -86364,26 +87519,26 @@ function renderSessionSummary(input) {
86364
87519
  const gap = Math.max(1, inner - visibleWidth(left) - visibleWidth(right));
86365
87520
  row(left + " ".repeat(gap) + right);
86366
87521
  if (stats.providerName)
86367
- row(dim3(truncate3(stats.providerName, inner)));
87522
+ row(dim4(truncate3(stats.providerName, inner)));
86368
87523
  blank();
86369
87524
  const VALUE_W = 24;
86370
87525
  const barW = Math.max(12, inner - LABEL_W - VALUE_W);
86371
87526
  const dataRow = (label, bar, values) => {
86372
- row(dim3(padTo(label, LABEL_W)) + bar + padVisible2(values, VALUE_W, "right"));
87527
+ row(dim4(padTo(label, LABEL_W)) + bar + padVisible2(values, VALUE_W, "right"));
86373
87528
  };
86374
87529
  if (stats.contextUsed !== null && stats.contextWindow) {
86375
87530
  const pct = stats.contextUsed * 100;
86376
- dataRow("context", meter(pct, barW, ramps.load), body(padStartTo(`${Math.round(pct)}%`, 4)) + dim3(` ${compact(stats.inputTokens)}/${compact(stats.contextWindow)}`));
87531
+ dataRow("context", meter(pct, barW, ramps.load), body(padStartTo(`${Math.round(pct)}%`, 4)) + dim4(` ${compact(stats.inputTokens)}/${compact(stats.contextWindow)}`));
86377
87532
  }
86378
87533
  dataRow("tokens", stackedBar([
86379
87534
  { value: stats.inputTokens, color: C.blue },
86380
87535
  { value: stats.outputTokens, color: C.cyan }
86381
- ], barW), dim3("in ") + body(compact(stats.inputTokens)) + dim3(" out ") + body(compact(stats.outputTokens)));
87536
+ ], barW), dim4("in ") + body(compact(stats.inputTokens)) + dim4(" out ") + body(compact(stats.outputTokens)));
86382
87537
  if (!stats.isFree && stats.inputCostUsd + stats.outputCostUsd > 0) {
86383
87538
  dataRow("spend", stackedBar([
86384
87539
  { value: stats.inputCostUsd, color: C.blue },
86385
87540
  { value: stats.outputCostUsd, color: C.cyan }
86386
- ], barW), dim3("in ") + body(usd(stats.inputCostUsd)) + dim3(" out ") + body(usd(stats.outputCostUsd)));
87541
+ ], barW), dim4("in ") + body(usd(stats.inputCostUsd)) + dim4(" out ") + body(usd(stats.outputCostUsd)));
86387
87542
  }
86388
87543
  if (stats.toolCallTotal > 0) {
86389
87544
  const toolCols = toolColors();
@@ -86393,27 +87548,27 @@ function renderSessionSummary(input) {
86393
87548
  const segs = shown.map((t, i) => ({ value: t.count, color: toolCols[i] }));
86394
87549
  if (rest > 0)
86395
87550
  segs.push({ value: rest, color: other });
86396
- dataRow("tools", stackedBar(segs, barW), body(padStartTo(String(stats.toolCallTotal), 4)) + dim3(" calls"));
87551
+ dataRow("tools", stackedBar(segs, barW), body(padStartTo(String(stats.toolCallTotal), 4)) + dim4(" calls"));
86397
87552
  const legend = shown.map((t, i) => paint(`${t.name} ${t.count}`, toolCols[i])).concat(rest > 0 ? [paint(`other ${rest}`, other)] : []);
86398
- for (const line of wrapStyled(legend, dim3(" \xB7 "), inner - LABEL_W)) {
87553
+ for (const line of wrapStyled(legend, dim4(" \xB7 "), inner - LABEL_W)) {
86399
87554
  row(" ".repeat(LABEL_W) + line);
86400
87555
  }
86401
87556
  }
86402
87557
  blank();
86403
- row(dim3(padTo("cost", LABEL_W)) + paint(stats.isFree ? "free" : usd(stats.costUsd), stats.isFree ? tokens.success : tokens.text, true) + (stats.isEstimated && !stats.isFree ? dim3(" estimated") : ""));
87558
+ row(dim4(padTo("cost", LABEL_W)) + paint(stats.isFree ? "free" : usd(stats.costUsd), stats.isFree ? tokens.success : tokens.text, true) + (stats.isEstimated && !stats.isFree ? dim4(" estimated") : ""));
86404
87559
  for (const s of stats.savings) {
86405
87560
  const label = padTo(`vs ${s.label}`, LABEL_W);
86406
87561
  if (s.savedUsd >= 0) {
86407
87562
  const pct = s.baselineUsd > 0 ? s.savedUsd / s.baselineUsd * 100 : 0;
86408
- dataRow(label, meter(pct, barW, ramps.savings), paint(padStartTo(`${Math.round(pct)}%`, 4), tokens.success) + dim3(" saved ") + paint(usd(s.savedUsd), tokens.success));
87563
+ dataRow(label, meter(pct, barW, ramps.savings), paint(padStartTo(`${Math.round(pct)}%`, 4), tokens.success) + dim4(" saved ") + paint(usd(s.savedUsd), tokens.success));
86409
87564
  } else {
86410
- dataRow(label, meter(0, barW, ramps.savings), dim3("over by ") + paint(usd(-s.savedUsd), tokens.error));
87565
+ dataRow(label, meter(0, barW, ramps.savings), dim4("over by ") + paint(usd(-s.savedUsd), tokens.error));
86411
87566
  }
86412
87567
  }
86413
87568
  out.push(paint(`\u2570${"\u2500".repeat(W2 - 2)}\u256F`, tokens.border));
86414
87569
  if (resumeId) {
86415
87570
  out.push("");
86416
- out.push(dim3("Resume this session with:"));
87571
+ out.push(dim4("Resume this session with:"));
86417
87572
  const modelFlag = resumeModelSpec ? `--model ${resumeModelSpec} ` : "";
86418
87573
  out.push(`claudish ${modelFlag}--resume ${resumeId}`);
86419
87574
  }
@@ -86471,6 +87626,7 @@ function classifyStartupKind() {
86471
87626
  "telemetry",
86472
87627
  "stats",
86473
87628
  "providers",
87629
+ "keychain",
86474
87630
  "login",
86475
87631
  "logout",
86476
87632
  "quota",
@@ -86590,6 +87746,7 @@ var isStatsCommand = firstPositional === "stats";
86590
87746
  var isConfigCommand = firstPositional === "config";
86591
87747
  var isServeCommand = firstPositional === "serve";
86592
87748
  var isProvidersCommand = firstPositional === "providers";
87749
+ var isKeychainCommand = firstPositional === "keychain";
86593
87750
  var isBehaviorCommand = firstPositional === "behavior";
86594
87751
  var isTeamCommand = firstPositional === "team";
86595
87752
  var isLoginCommand = firstPositional === "login";
@@ -86629,6 +87786,12 @@ if (isMcpMode) {
86629
87786
  console.error(`[claudish providers] ${e instanceof Error ? e.message : String(e)}`);
86630
87787
  process.exit(1);
86631
87788
  }));
87789
+ } else if (isKeychainCommand) {
87790
+ const keychainArgIndex = args.indexOf("keychain");
87791
+ Promise.resolve().then(() => (init_keychain_command(), exports_keychain_command)).then((m) => m.keychainCommand(args.slice(keychainArgIndex + 1)).catch((e) => {
87792
+ console.error(`[claudish keychain] ${e instanceof Error ? e.message : String(e)}`);
87793
+ process.exit(1);
87794
+ }));
86632
87795
  } else if (isLoginCommand) {
86633
87796
  const loginProviderArg = args.find((a, i) => i > args.indexOf("login") && !a.startsWith("-"));
86634
87797
  Promise.resolve().then(() => (init_auth_commands(), exports_auth_commands)).then((m) => m.loginCommand(loginProviderArg).catch(handlePromptExit));
@@ -86666,6 +87829,10 @@ if (isMcpMode) {
86666
87829
  });
86667
87830
  } else if (isConfigCommand) {
86668
87831
  traceSpan("startup:tui-import", () => Promise.resolve().then(() => (init_tui(), exports_tui))).then(async (m) => {
87832
+ await traceSpan("startup:theme-detect", async () => {
87833
+ const { detectAndSetThemeMode: detectAndSetThemeMode2 } = await Promise.resolve().then(() => (init_theme_mode(), exports_theme_mode));
87834
+ await detectAndSetThemeMode2();
87835
+ });
86669
87836
  const { credentials: credentials2 } = await Promise.resolve().then(() => (init_authority(), exports_authority));
86670
87837
  const { ensureEndpointsRegistered: ensureEndpointsRegistered2 } = await Promise.resolve().then(() => (init_endpoint_registration(), exports_endpoint_registration));
86671
87838
  ensureEndpointsRegistered2();