claudish 7.65.0 → 7.66.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1467 -344
  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.0";
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;
@@ -35277,6 +35342,9 @@ var init_gemini_queue = __esm(() => {
35277
35342
 
35278
35343
  // src/providers/transport/antigravity.ts
35279
35344
  import { randomUUID } from "crypto";
35345
+ function antigravityEndpoint() {
35346
+ return `${antigravityHost()}/v1internal:streamGenerateContent?alt=sse`;
35347
+ }
35280
35348
  function rankReasoningSuffix(suffix) {
35281
35349
  const rank = REASONING_TIER_RANK[suffix.toLowerCase()];
35282
35350
  return rank === undefined ? Number.MAX_SAFE_INTEGER : rank;
@@ -35343,7 +35411,12 @@ function classify429(responseBody) {
35343
35411
  return { terminal: false, retryDelayMs: retryDelayMs ?? 60000, reason };
35344
35412
  }
35345
35413
  }
35346
- return { terminal: false, retryDelayMs, reason };
35414
+ return {
35415
+ terminal: false,
35416
+ retryDelayMs,
35417
+ reason,
35418
+ unattributed: reason === undefined && retryDelayMs === undefined
35419
+ };
35347
35420
  } catch {
35348
35421
  return null;
35349
35422
  }
@@ -35389,7 +35462,7 @@ class AntigravityProviderTransport {
35389
35462
  return this._activeModelName;
35390
35463
  }
35391
35464
  getEndpoint() {
35392
- return ANTIGRAVITY_ENDPOINT;
35465
+ return antigravityEndpoint();
35393
35466
  }
35394
35467
  async getHeaders() {
35395
35468
  if (this.cachedAuth)
@@ -35477,8 +35550,8 @@ class AntigravityProviderTransport {
35477
35550
  return await this.explainTerminalQuota(response, bodyText, classification.reason);
35478
35551
  }
35479
35552
  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})`);
35553
+ const delay = classification.retryDelayMs ?? (classification.unattributed ? UNATTRIBUTED_RETRY_DELAY_MS : DEFAULT_RATE_LIMIT_DELAY_MS);
35554
+ 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
35555
  if (attempt === 1) {
35483
35556
  await this.logQuotaInfo();
35484
35557
  }
@@ -35665,7 +35738,7 @@ ${lines.join(`
35665
35738
  }
35666
35739
  }
35667
35740
  }
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;
35741
+ 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
35742
  var init_antigravity2 = __esm(() => {
35670
35743
  init_model_catalog();
35671
35744
  init_antigravity_token();
@@ -35673,7 +35746,6 @@ var init_antigravity2 = __esm(() => {
35673
35746
  init_authority();
35674
35747
  init_gemini_queue();
35675
35748
  init_logger();
35676
- ANTIGRAVITY_ENDPOINT = `${ANTIGRAVITY_BASE}/v1internal:streamGenerateContent?alt=sse`;
35677
35749
  REASONING_TIER_RANK = {
35678
35750
  high: 0,
35679
35751
  medium: 1,
@@ -35731,6 +35803,257 @@ var init_antigravity_credential = __esm(() => {
35731
35803
  init_antigravity_user();
35732
35804
  });
35733
35805
 
35806
+ // src/providers/keychain.ts
35807
+ function syncRun(args, stdin) {
35808
+ try {
35809
+ const proc = Bun.spawnSync([SECURITY_BIN, ...args], {
35810
+ stdin: stdin === undefined ? "ignore" : new TextEncoder().encode(stdin),
35811
+ stdout: "pipe",
35812
+ stderr: "pipe",
35813
+ timeout: SPAWN_TIMEOUT_MS
35814
+ });
35815
+ return normalizeResult(proc.exitCode, proc.signalCode, decode3(proc.stdout), decode3(proc.stderr));
35816
+ } catch (err) {
35817
+ return { code: 1, stdout: "", stderr: err instanceof Error ? err.message : String(err) };
35818
+ }
35819
+ }
35820
+ async function asyncRun(args, stdin) {
35821
+ try {
35822
+ const proc = Bun.spawn([SECURITY_BIN, ...args], {
35823
+ stdin: stdin === undefined ? "ignore" : new TextEncoder().encode(stdin),
35824
+ stdout: "pipe",
35825
+ stderr: "pipe",
35826
+ timeout: SPAWN_TIMEOUT_MS
35827
+ });
35828
+ const [stdout, stderr, exitCode] = await Promise.all([
35829
+ new Response(proc.stdout).text(),
35830
+ new Response(proc.stderr).text(),
35831
+ proc.exited
35832
+ ]);
35833
+ return normalizeResult(exitCode, proc.signalCode, stdout, stderr);
35834
+ } catch (err) {
35835
+ return { code: 1, stdout: "", stderr: err instanceof Error ? err.message : String(err) };
35836
+ }
35837
+ }
35838
+ function decode3(buf) {
35839
+ return buf ? new TextDecoder().decode(buf) : "";
35840
+ }
35841
+ function normalizeResult(exitCode, signalCode, stdout, stderr) {
35842
+ if (typeof exitCode === "number")
35843
+ return { code: exitCode, stdout, stderr };
35844
+ const detail = signalCode ? `killed by ${signalCode}` : "terminated without an exit code";
35845
+ return { code: -1, stdout, stderr: stderr.trim() || `security ${detail}` };
35846
+ }
35847
+ function invalidateKeychainCache() {
35848
+ listMemo = null;
35849
+ valueMemo.clear();
35850
+ }
35851
+ function now() {
35852
+ return Date.now();
35853
+ }
35854
+ function isValidKeychainVarName(name) {
35855
+ return ENV_VAR_NAME.test(name);
35856
+ }
35857
+ function describeUnstorableValue(value) {
35858
+ if (value.length === 0)
35859
+ return "value is empty";
35860
+ if (CONTROL_CHARS.test(value)) {
35861
+ return "value contains control characters (tab/newline/etc), which the keychain read path cannot represent unambiguously";
35862
+ }
35863
+ return null;
35864
+ }
35865
+ function isKeychainSupported() {
35866
+ return deps.platform() === "darwin";
35867
+ }
35868
+ function keychainUnavailableReason() {
35869
+ if (!isKeychainSupported()) {
35870
+ return `the macOS Keychain is only available on macOS (this is ${deps.platform()})`;
35871
+ }
35872
+ return null;
35873
+ }
35874
+ function stripOneTrailingNewline(out) {
35875
+ return out.endsWith(`
35876
+ `) ? out.slice(0, -1) : out;
35877
+ }
35878
+ function keychainFileArgs() {
35879
+ const file2 = process.env.CLAUDISH_KEYCHAIN_FILE;
35880
+ return file2 ? [file2] : [];
35881
+ }
35882
+ function findArgs(envVar) {
35883
+ return [
35884
+ "find-generic-password",
35885
+ "-s",
35886
+ KEYCHAIN_SERVICE,
35887
+ "-a",
35888
+ envVar,
35889
+ "-w",
35890
+ ...keychainFileArgs()
35891
+ ];
35892
+ }
35893
+ function interpretRead(envVar, res) {
35894
+ if (res.code === 0) {
35895
+ const value = stripOneTrailingNewline(res.stdout);
35896
+ return value.length > 0 ? value : null;
35897
+ }
35898
+ if (res.code === EXIT_ITEM_NOT_FOUND)
35899
+ return null;
35900
+ throw new KeychainError(`Keychain lookup for ${envVar} failed: ${res.stderr.trim() || `security exited ${res.code}`}`, res.code);
35901
+ }
35902
+ function readKeychainSecret(envVar) {
35903
+ if (!isKeychainSupported())
35904
+ return null;
35905
+ const cached2 = valueMemo.get(envVar);
35906
+ const at = now();
35907
+ if (cached2 && at - cached2.at < MEMO_TTL_MS)
35908
+ return cached2.value;
35909
+ const value = interpretRead(envVar, deps.run(findArgs(envVar)));
35910
+ valueMemo.set(envVar, { at, value });
35911
+ return value;
35912
+ }
35913
+ async function readKeychainSecretAsync(envVar) {
35914
+ if (!isKeychainSupported())
35915
+ return null;
35916
+ const cached2 = valueMemo.get(envVar);
35917
+ const at = now();
35918
+ if (cached2 && at - cached2.at < MEMO_TTL_MS)
35919
+ return cached2.value;
35920
+ const value = interpretRead(envVar, await deps.runAsync(findArgs(envVar)));
35921
+ valueMemo.set(envVar, { at, value });
35922
+ return value;
35923
+ }
35924
+ function enumerateKeychainVars() {
35925
+ if (!isKeychainSupported())
35926
+ return { names: [], failed: false };
35927
+ const at = now();
35928
+ if (listMemo && at - listMemo.at < MEMO_TTL_MS)
35929
+ return listMemo.value;
35930
+ const res = deps.run(["dump-keychain", ...keychainFileArgs()]);
35931
+ const value = res.code === 0 ? { names: parseDumpAccounts(res.stdout), failed: false } : {
35932
+ names: [],
35933
+ failed: true,
35934
+ error: res.stderr.trim() || `security exited ${res.code}`
35935
+ };
35936
+ listMemo = { at, value };
35937
+ return value;
35938
+ }
35939
+ function listKeychainVars() {
35940
+ return enumerateKeychainVars().names;
35941
+ }
35942
+ function parseDumpAccounts(dump) {
35943
+ const found = new Set;
35944
+ for (const block of dump.split(/\nkeychain: /)) {
35945
+ if (!block.includes(SVCE_MATCH))
35946
+ continue;
35947
+ const m = block.match(ACCT_ATTR);
35948
+ if (m?.[1] && isValidKeychainVarName(m[1]))
35949
+ found.add(m[1]);
35950
+ }
35951
+ return Array.from(found).sort();
35952
+ }
35953
+ function lookupKeychainVar(envVar) {
35954
+ if (!isKeychainSupported())
35955
+ return { present: false, failed: false };
35956
+ const cached2 = valueMemo.get(envVar);
35957
+ if (cached2 && now() - cached2.at < MEMO_TTL_MS && cached2.value !== null) {
35958
+ return { present: true, failed: false };
35959
+ }
35960
+ const listed = enumerateKeychainVars();
35961
+ return { present: listed.names.includes(envVar), failed: listed.failed };
35962
+ }
35963
+ function toHex(value) {
35964
+ return Buffer.from(value, "utf8").toString("hex");
35965
+ }
35966
+ function quoteForStdin(value) {
35967
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
35968
+ }
35969
+ function writeKeychainSecret(envVar, value) {
35970
+ const unsupported2 = keychainUnavailableReason();
35971
+ if (unsupported2)
35972
+ throw new KeychainError(`Cannot write ${envVar}: ${unsupported2}`);
35973
+ if (!isValidKeychainVarName(envVar)) {
35974
+ throw new KeychainError(`Cannot write "${envVar}": not a valid environment variable name`);
35975
+ }
35976
+ const unstorable = describeUnstorableValue(value);
35977
+ if (unstorable)
35978
+ throw new KeychainError(`Cannot store ${envVar}: ${unstorable}`);
35979
+ invalidateKeychainCache();
35980
+ const cmd = [
35981
+ "add-generic-password",
35982
+ "-s",
35983
+ quoteForStdin(KEYCHAIN_SERVICE),
35984
+ "-a",
35985
+ quoteForStdin(envVar),
35986
+ "-l",
35987
+ quoteForStdin(`${KEYCHAIN_SERVICE}: ${envVar}`),
35988
+ "-D",
35989
+ quoteForStdin("application password"),
35990
+ "-j",
35991
+ quoteForStdin("Stored by claudish"),
35992
+ "-X",
35993
+ quoteForStdin(toHex(value)),
35994
+ "-U",
35995
+ "-T",
35996
+ quoteForStdin(SECURITY_BIN),
35997
+ ...keychainFileArgs().map(quoteForStdin)
35998
+ ].join(" ");
35999
+ const res = deps.run(["-i"], `${cmd}
36000
+ `);
36001
+ if (res.code !== 0) {
36002
+ throw new KeychainError(`Failed to store ${envVar} in the keychain: ${res.stderr.trim() || `security exited ${res.code}`}`, res.code);
36003
+ }
36004
+ invalidateKeychainCache();
36005
+ const readBack = readKeychainSecret(envVar);
36006
+ if (readBack !== value) {
36007
+ 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.");
36008
+ }
36009
+ }
36010
+ function deleteKeychainSecret(envVar) {
36011
+ if (!isKeychainSupported())
36012
+ return false;
36013
+ invalidateKeychainCache();
36014
+ const res = deps.run([
36015
+ "delete-generic-password",
36016
+ "-s",
36017
+ KEYCHAIN_SERVICE,
36018
+ "-a",
36019
+ envVar,
36020
+ ...keychainFileArgs()
36021
+ ]);
36022
+ invalidateKeychainCache();
36023
+ if (res.code === 0)
36024
+ return true;
36025
+ if (res.code === EXIT_ITEM_NOT_FOUND)
36026
+ return false;
36027
+ throw new KeychainError(`Failed to delete ${envVar} from the keychain: ${res.stderr.trim() || `security exited ${res.code}`}`, res.code);
36028
+ }
36029
+ function valueTail2(value) {
36030
+ if (value.length <= 6)
36031
+ return "\u2022\u2022\u2022\u2022";
36032
+ return `\u2022\u2022\u2022\u2022${value.slice(-4)}`;
36033
+ }
36034
+ 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;
36035
+ var init_keychain = __esm(() => {
36036
+ KeychainError = class KeychainError extends Error {
36037
+ exitCode;
36038
+ constructor(message, exitCode) {
36039
+ super(message);
36040
+ this.exitCode = exitCode;
36041
+ this.name = "KeychainError";
36042
+ }
36043
+ };
36044
+ defaultDeps2 = {
36045
+ platform: () => process.platform,
36046
+ run: syncRun,
36047
+ runAsync: asyncRun
36048
+ };
36049
+ deps = defaultDeps2;
36050
+ valueMemo = new Map;
36051
+ CONTROL_CHARS = /[\u0000-\u001F\u007F]/;
36052
+ ENV_VAR_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
36053
+ ACCT_ATTR = /"acct"<blob>=(?:0x[0-9A-Fa-f]*\s+)?"([^"]*)"/;
36054
+ SVCE_MATCH = `"svce"<blob>="${KEYCHAIN_SERVICE}"`;
36055
+ });
36056
+
35734
36057
  // src/auth/credentials/local-api-key.ts
35735
36058
  function resolveLocalApiKey(q) {
35736
36059
  return realValue(process.env[q.envVar]) || (q.aliases ?? []).map((a) => realValue(process.env[a])).find((v) => !!v) || realValue(getApiKey(q.envVar));
@@ -35747,6 +36070,108 @@ var init_local_api_key = __esm(() => {
35747
36070
  init_profile_config();
35748
36071
  });
35749
36072
 
36073
+ // src/auth/credentials/keychain-source.ts
36074
+ function warnOnce2(message) {
36075
+ if (warnedMessages2.has(message))
36076
+ return;
36077
+ warnedMessages2.add(message);
36078
+ console.error(message);
36079
+ }
36080
+ function recordKeychainHydratedVar(envVar) {
36081
+ hydratedVars.add(envVar);
36082
+ }
36083
+ function isKeychainHydratedVar(envVar) {
36084
+ return hydratedVars.has(envVar);
36085
+ }
36086
+ function hasKeychainSource() {
36087
+ if (process.env.CLAUDISH_DISABLE_KEYCHAIN === "1")
36088
+ return false;
36089
+ if (!isKeychainSupported())
36090
+ return false;
36091
+ return isKeychainEnabled();
36092
+ }
36093
+ function resolveKeychainKeyForEnvVars(wanted) {
36094
+ if (!hasKeychainSource())
36095
+ return { failed: false };
36096
+ let failed = false;
36097
+ try {
36098
+ for (const name of wanted) {
36099
+ if (!name)
36100
+ continue;
36101
+ const { present, failed: lookupFailed } = lookupKeychainVar(name);
36102
+ if (lookupFailed)
36103
+ failed = true;
36104
+ if (!present)
36105
+ continue;
36106
+ const value = readKeychainSecret(name);
36107
+ if (value)
36108
+ return { value, failed: false };
36109
+ }
36110
+ } catch (err) {
36111
+ warnOnce2(`[claudish] macOS Keychain lookup skipped: ${err instanceof KeychainError ? err.message : String(err)}`);
36112
+ return { failed: true };
36113
+ }
36114
+ if (failed) {
36115
+ warnOnce2("[claudish] macOS Keychain could not be enumerated \u2014 treating this provider as unresolved rather than uncredentialed.");
36116
+ }
36117
+ return { failed };
36118
+ }
36119
+ async function hydrateKeychainIntoEnv() {
36120
+ if (!hasKeychainSource())
36121
+ return 0;
36122
+ let names;
36123
+ try {
36124
+ const listed = enumerateKeychainVars();
36125
+ if (listed.failed) {
36126
+ warnOnce2(`[claudish] macOS Keychain enumeration skipped: ${listed.error ?? "unknown error"}`);
36127
+ return 0;
36128
+ }
36129
+ names = listed.names;
36130
+ } catch (err) {
36131
+ warnOnce2(`[claudish] macOS Keychain enumeration skipped: ${String(err)}`);
36132
+ return 0;
36133
+ }
36134
+ const missing = names.filter((n) => !resolveLocalApiKey({ envVar: n }));
36135
+ if (missing.length === 0)
36136
+ return 0;
36137
+ const results = await Promise.all(missing.map(async (name) => {
36138
+ try {
36139
+ return { name, value: await readKeychainSecretAsync(name) };
36140
+ } catch (err) {
36141
+ warnOnce2(`[claudish] macOS Keychain read for ${name} skipped: ${err instanceof KeychainError ? err.message : String(err)}`);
36142
+ return { name, value: null };
36143
+ }
36144
+ }));
36145
+ let hydrated = 0;
36146
+ for (const { name, value } of results) {
36147
+ if (!value || resolveLocalApiKey({ envVar: name }))
36148
+ continue;
36149
+ process.env[name] = value;
36150
+ recordKeychainHydratedVar(name);
36151
+ hydrated++;
36152
+ }
36153
+ return hydrated;
36154
+ }
36155
+ function keychainHasAnyOf(names) {
36156
+ if (!hasKeychainSource())
36157
+ return false;
36158
+ try {
36159
+ for (const name of names) {
36160
+ if (name && lookupKeychainVar(name).present)
36161
+ return true;
36162
+ }
36163
+ } catch {}
36164
+ return false;
36165
+ }
36166
+ var warnedMessages2, hydratedVars;
36167
+ var init_keychain_source = __esm(() => {
36168
+ init_profile_config();
36169
+ init_keychain();
36170
+ init_local_api_key();
36171
+ warnedMessages2 = new Set;
36172
+ hydratedVars = new Set;
36173
+ });
36174
+
35750
36175
  // src/auth/credentials/api-key-credential.ts
35751
36176
  import { existsSync as existsSync12 } from "fs";
35752
36177
  import { homedir as homedir18 } from "os";
@@ -35758,7 +36183,6 @@ class ApiKeyCredentialProvider {
35758
36183
  aliases;
35759
36184
  authScheme;
35760
36185
  staticHeaders;
35761
- publicKeyFallback;
35762
36186
  oauthFallback;
35763
36187
  declaredKey;
35764
36188
  cachedKey;
@@ -35769,7 +36193,6 @@ class ApiKeyCredentialProvider {
35769
36193
  this.aliases = descriptor.aliases ?? [];
35770
36194
  this.authScheme = descriptor.authScheme ?? "bearer";
35771
36195
  this.staticHeaders = descriptor.staticHeaders ?? {};
35772
- this.publicKeyFallback = descriptor.publicKeyFallback;
35773
36196
  this.oauthFallback = descriptor.oauthFallback;
35774
36197
  this.declaredKey = descriptor.declaredKey;
35775
36198
  }
@@ -35803,6 +36226,17 @@ class ApiKeyCredentialProvider {
35803
36226
  this.cachedKey = local;
35804
36227
  return local;
35805
36228
  }
36229
+ let keychainFailed = false;
36230
+ if (hasKeychainSource()) {
36231
+ const kc = resolveKeychainKeyForEnvVars([this.envVar, ...this.aliases]);
36232
+ if (kc.value) {
36233
+ process.env[this.envVar] = kc.value;
36234
+ recordKeychainHydratedVar(this.envVar);
36235
+ this.cachedKey = kc.value;
36236
+ return kc.value;
36237
+ }
36238
+ keychainFailed = kc.failed;
36239
+ }
35806
36240
  if (hasOpSources()) {
35807
36241
  const wanted = new Set([this.envVar, ...this.aliases]);
35808
36242
  const resolved = await resolveOpKeyForEnvVars(wanted, {
@@ -35817,7 +36251,8 @@ class ApiKeyCredentialProvider {
35817
36251
  }
35818
36252
  return "";
35819
36253
  }
35820
- this.cachedKey = "";
36254
+ if (!keychainFailed)
36255
+ this.cachedKey = "";
35821
36256
  return "";
35822
36257
  })();
35823
36258
  try {
@@ -35829,8 +36264,6 @@ class ApiKeyCredentialProvider {
35829
36264
  async isAvailable(opts) {
35830
36265
  if (this.authScheme === "none")
35831
36266
  return true;
35832
- if (this.publicKeyFallback)
35833
- return true;
35834
36267
  if (this.resolveFromEnvConfig())
35835
36268
  return true;
35836
36269
  if (this.hasOauthFallbackFile())
@@ -35846,7 +36279,7 @@ class ApiKeyCredentialProvider {
35846
36279
  if (this.authScheme === "none") {
35847
36280
  return { headers: { ...this.staticHeaders } };
35848
36281
  }
35849
- const key = await this.resolveKey({ allowOpPrompt: ctx.allowOpPrompt }) || this.publicKeyFallback || "";
36282
+ const key = await this.resolveKey({ allowOpPrompt: ctx.allowOpPrompt });
35850
36283
  let headers;
35851
36284
  if (this.authScheme === "x-api-key") {
35852
36285
  headers = { "x-api-key": key, ...this.staticHeaders };
@@ -35860,6 +36293,7 @@ class ApiKeyCredentialProvider {
35860
36293
  }
35861
36294
  var init_api_key_credential = __esm(() => {
35862
36295
  init_env_placeholder();
36296
+ init_keychain_source();
35863
36297
  init_local_api_key();
35864
36298
  init_op_source();
35865
36299
  });
@@ -37112,7 +37546,6 @@ class CredentialAuthority {
37112
37546
  envVar: def.apiKeyEnvVar,
37113
37547
  aliases: def.apiKeyAliases,
37114
37548
  authScheme: normalizeAuthScheme(def.authScheme),
37115
- publicKeyFallback: def.publicKeyFallback,
37116
37549
  oauthFallback: def.oauthFallback
37117
37550
  }), [def.name, ...RUNTIME_NAME_ALIASES[def.name] ?? []]);
37118
37551
  }
@@ -37528,8 +37961,8 @@ async function fetchDevinAllowedUids(apiKey) {
37528
37961
  return uids;
37529
37962
  }
37530
37963
  async function getServedDevinModels(opts) {
37531
- const now = Date.now();
37532
- if (!opts?.force && rosterCache && now - rosterCacheAt < ROSTER_TTL_MS)
37964
+ const now2 = Date.now();
37965
+ if (!opts?.force && rosterCache && now2 - rosterCacheAt < ROSTER_TTL_MS)
37533
37966
  return rosterCache;
37534
37967
  const apiKey = opts?.apiKey ?? readDevinApiKey();
37535
37968
  if (!apiKey)
@@ -37547,7 +37980,7 @@ async function getServedDevinModels(opts) {
37547
37980
  log("[Devin] entitlement unknown \u2014 using the full config list (superset)");
37548
37981
  }
37549
37982
  rosterCache = served;
37550
- rosterCacheAt = now;
37983
+ rosterCacheAt = now2;
37551
37984
  return served;
37552
37985
  } catch (err) {
37553
37986
  log(`[Devin] served-model discovery error: ${err}`);
@@ -37575,12 +38008,12 @@ function nonEmpty(value) {
37575
38008
  function groupKeyOf(entry) {
37576
38009
  return nonEmpty(entry.groupLabel) ?? nonEmpty(entry.family) ?? entry.wireId;
37577
38010
  }
37578
- function offerIsLive(offer, now = Date.now()) {
38011
+ function offerIsLive(offer, now2 = Date.now()) {
37579
38012
  if (!offer)
37580
38013
  return false;
37581
38014
  if (offer.expiresAt === undefined)
37582
38015
  return true;
37583
- return offer.expiresAt * 1000 > now;
38016
+ return offer.expiresAt * 1000 > now2;
37584
38017
  }
37585
38018
 
37586
38019
  // src/providers/model-resolvers/devin.ts
@@ -37815,6 +38248,9 @@ async function fetchDevinRoster() {
37815
38248
  return { id: wireId, ...rest };
37816
38249
  });
37817
38250
  }
38251
+ function isUndeclaredEditorInternal(id) {
38252
+ return id.startsWith("tab_");
38253
+ }
37818
38254
  async function fetchAntigravityRoster() {
37819
38255
  const { getValidAntigravityAccessToken: getValidAntigravityAccessToken2 } = await Promise.resolve().then(() => (init_antigravity_token(), exports_antigravity_token));
37820
38256
  const { setupAntigravityUser: setupAntigravityUser2, getServedAntigravityModels: getServedAntigravityModels2 } = await Promise.resolve().then(() => (init_antigravity_user(), exports_antigravity_user));
@@ -37822,10 +38258,12 @@ async function fetchAntigravityRoster() {
37822
38258
  if (!token)
37823
38259
  return [];
37824
38260
  const { projectId } = await setupAntigravityUser2(token);
37825
- const { servedIds, meta: meta3 } = await getServedAntigravityModels2(token, projectId);
37826
- return servedIds.map((id) => {
38261
+ const { servedIds, meta: meta3, excludedIds } = await getServedAntigravityModels2(token, projectId);
38262
+ const declaredExcluded = excludedIds ?? new Set;
38263
+ const selectable = servedIds.filter((id) => !declaredExcluded.has(id) && !isUndeclaredEditorInternal(id));
38264
+ return selectable.map((id) => {
37827
38265
  const m = meta3[id];
37828
- return m?.contextWindow ? { id, contextWindow: m.contextWindow } : { id };
38266
+ return m?.contextWindow ? { id, contextWindow: m.contextWindow, ignoreCatalogReleaseDate: true } : { id, ignoreCatalogReleaseDate: true };
37829
38267
  });
37830
38268
  }
37831
38269
  async function fetchOllamaRoster() {
@@ -38752,13 +39190,6 @@ var init_vision_proxy = __esm(() => {
38752
39190
  });
38753
39191
 
38754
39192
  // 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
39193
  function parseModelChain(modelSpec) {
38763
39194
  const parts = modelSpec.split(MODEL_CHAIN_SEPARATOR).map((s) => s.trim()).filter(Boolean);
38764
39195
  return parts.length > 0 ? parts : [modelSpec];
@@ -38785,8 +39216,6 @@ function parseModelSpec(modelSpec) {
38785
39216
  concurrency = Number.parseInt(concurrencyMatch[2], 10);
38786
39217
  }
38787
39218
  const provider = PROVIDER_SHORTCUTS[providerPart] || providerPart;
38788
- if (providerPart === "go")
38789
- warnGoAliasDeprecatedOnce();
38790
39219
  return {
38791
39220
  provider,
38792
39221
  model: modelPart,
@@ -38800,8 +39229,6 @@ function parseModelSpec(modelSpec) {
38800
39229
  for (const { prefix, provider, stripPrefix } of LEGACY_PREFIX_PATTERNS) {
38801
39230
  if (lowerSpec.startsWith(prefix)) {
38802
39231
  const model = stripPrefix ? modelSpec.slice(prefix.length) : modelSpec;
38803
- if (prefix === "go/")
38804
- warnGoAliasDeprecatedOnce();
38805
39232
  let concurrency;
38806
39233
  let modelName = model;
38807
39234
  if (LOCAL_PROVIDERS.has(provider)) {
@@ -38862,7 +39289,7 @@ function getLegacySyntaxWarning(parsed) {
38862
39289
  return `Deprecation warning: "${parsed.original}" uses legacy prefix syntax.
38863
39290
  ` + ` Consider using: ${newSyntax}`;
38864
39291
  }
38865
- var PROVIDER_SHORTCUTS, _goDeprecationWarned = false, LOCAL_PROVIDERS, NATIVE_MODEL_PATTERNS, LEGACY_PREFIX_PATTERNS, MODEL_CHAIN_SEPARATOR = "+";
39292
+ var PROVIDER_SHORTCUTS, LOCAL_PROVIDERS, NATIVE_MODEL_PATTERNS, LEGACY_PREFIX_PATTERNS, MODEL_CHAIN_SEPARATOR = "+";
38866
39293
  var init_model_parser = __esm(() => {
38867
39294
  init_provider_definitions();
38868
39295
  PROVIDER_SHORTCUTS = getShortcuts();
@@ -39707,9 +40134,9 @@ function showMonthlyBanner() {
39707
40134
  return;
39708
40135
  const profileConfig = loadConfig();
39709
40136
  const consent2 = profileConfig.stats;
39710
- const now = Date.now();
40137
+ const now2 = Date.now();
39711
40138
  const lastPrompt = consent2?.lastMonthlyPrompt ? new Date(consent2.lastMonthlyPrompt).getTime() : 0;
39712
- const timeSincePrompt = now - lastPrompt;
40139
+ const timeSincePrompt = now2 - lastPrompt;
39713
40140
  const isFirstRun = !consent2?.lastMonthlyPrompt;
39714
40141
  const isMonthlyInterval = timeSincePrompt >= MONTHLY_INTERVAL_MS;
39715
40142
  if (!isFirstRun && !isMonthlyInterval)
@@ -39877,7 +40304,7 @@ function statusToErrorType(status) {
39877
40304
  }
39878
40305
  }
39879
40306
  function sanitizeErrorMessage(message, maxLength = MAX_ERROR_MESSAGE_LENGTH) {
39880
- const flattened = String(message ?? "").replace(ANSI_ESCAPE, "").replace(CONTROL_CHARS, " ").replace(/\s+/g, " ").trim();
40307
+ const flattened = String(message ?? "").replace(ANSI_ESCAPE, "").replace(CONTROL_CHARS2, " ").replace(/\s+/g, " ").trim();
39881
40308
  if (flattened.length <= maxLength)
39882
40309
  return flattened;
39883
40310
  return `${flattened.slice(0, maxLength - 1).trimEnd()}\u2026`;
@@ -39973,10 +40400,10 @@ function ensureAnthropicErrorFormat(status, body) {
39973
40400
  const errorType = body?.error?.type || body?.type || body?.code;
39974
40401
  return wrapAnthropicError(status, String(message), errorType);
39975
40402
  }
39976
- var MAX_ERROR_MESSAGE_LENGTH = 600, ANSI_ESCAPE, CONTROL_CHARS;
40403
+ var MAX_ERROR_MESSAGE_LENGTH = 600, ANSI_ESCAPE, CONTROL_CHARS2;
39977
40404
  var init_anthropic_error = __esm(() => {
39978
40405
  ANSI_ESCAPE = /\x1B\[[0-?]*[ -/]*[@-~]|\x1B[@-Z\\-_]/g;
39979
- CONTROL_CHARS = /[\x00-\x1F\x7F]/g;
40406
+ CONTROL_CHARS2 = /[\x00-\x1F\x7F]/g;
39980
40407
  });
39981
40408
 
39982
40409
  // src/handlers/shared/collect-sse-message.ts
@@ -43199,10 +43626,10 @@ class ComposedHandler {
43199
43626
  maybePollPlanUsage(adapter) {
43200
43627
  if (!adapter.poll)
43201
43628
  return;
43202
- const now = Date.now();
43203
- if (now - this.lastPlanPollAt < PLAN_POLL_INTERVAL_MS)
43629
+ const now2 = Date.now();
43630
+ if (now2 - this.lastPlanPollAt < PLAN_POLL_INTERVAL_MS)
43204
43631
  return;
43205
- this.lastPlanPollAt = now;
43632
+ this.lastPlanPollAt = now2;
43206
43633
  adapter.poll({ modelId: this.bareModelName }).then((plan) => {
43207
43634
  if (plan)
43208
43635
  this.tokenTracker.setPlanUsage(plan);
@@ -43354,6 +43781,9 @@ function resolveApiKeyProvenance(envVar, aliases) {
43354
43781
  effectiveSource = configLayerLabel();
43355
43782
  layers[1].isActive = true;
43356
43783
  layers[2].isActive = false;
43784
+ } else if (isKeychainHydratedVar(runtimeVar)) {
43785
+ effectiveSource = "macOS Keychain";
43786
+ layers[2].source = `process.env[${runtimeVar}] (from macOS Keychain)`;
43357
43787
  } else if (isOpHydratedVar(runtimeVar)) {
43358
43788
  effectiveSource = "1Password";
43359
43789
  layers[2].source = `process.env[${runtimeVar}] (from 1Password)`;
@@ -43403,6 +43833,7 @@ function readConfigKey(envVar) {
43403
43833
  }
43404
43834
  var import_dotenv;
43405
43835
  var init_api_key_provenance = __esm(() => {
43836
+ init_keychain_source();
43406
43837
  init_onepassword();
43407
43838
  import_dotenv = __toESM(require_main(), 1);
43408
43839
  });
@@ -44513,16 +44944,15 @@ var init_provider_definitions = __esm(() => {
44513
44944
  apiKeyDescription: "Antigravity (shared OAuth token)",
44514
44945
  apiKeyUrl: "https://antigravity.google/",
44515
44946
  oauthLoginSlug: "antigravity",
44516
- shortcuts: ["ag", "antigravity", "go"],
44947
+ shortcuts: ["ag", "antigravity"],
44517
44948
  shortestPrefix: "ag",
44518
44949
  legacyPrefixes: [
44519
44950
  { prefix: "ag/", stripPrefix: true },
44520
- { prefix: "antigravity/", stripPrefix: true },
44521
- { prefix: "go/", stripPrefix: true }
44951
+ { prefix: "antigravity/", stripPrefix: true }
44522
44952
  ],
44523
44953
  modelDiscovery: { path: "", format: "antigravity" },
44524
44954
  isDirectApi: true,
44525
- description: "Antigravity subscription (ag@; go@ deprecated)"
44955
+ description: "Antigravity subscription (ag@)"
44526
44956
  },
44527
44957
  {
44528
44958
  createHandler: devinHandler,
@@ -45138,7 +45568,7 @@ var init_provider_definitions = __esm(() => {
45138
45568
  legacyPrefixes: [{ prefix: "qp/", stripPrefix: true }],
45139
45569
  modelDiscovery: { path: "/compatible-mode/v1/models", format: "openai-models-list" },
45140
45570
  isDirectApi: true,
45141
- description: "Alibaba Model Studio pay-as-you-go (qp@)"
45571
+ description: "Alibaba Model Studio API, pay-as-you-go (qp@)"
45142
45572
  },
45143
45573
  {
45144
45574
  createHandler: openaiHandler,
@@ -46068,10 +46498,10 @@ var init_predefined_catalog = __esm(() => {
46068
46498
  });
46069
46499
 
46070
46500
  // src/providers/predefined-endpoints.ts
46071
- function warnOnce2(message) {
46072
- if (warnedMessages2.has(message))
46501
+ function warnOnce3(message) {
46502
+ if (warnedMessages3.has(message))
46073
46503
  return;
46074
- warnedMessages2.add(message);
46504
+ warnedMessages3.add(message);
46075
46505
  console.error(message);
46076
46506
  }
46077
46507
  function activeCatalog() {
@@ -46088,7 +46518,7 @@ function readOptOut(config2) {
46088
46518
  if (result.success) {
46089
46519
  parsed = result.data;
46090
46520
  } else {
46091
- warnOnce2("[claudish] config 'predefinedEndpoints' is not valid and was ignored: " + result.error.issues.map((i) => `${i.path.join(".") || "(root)"} ${i.message}`).join(", "));
46521
+ warnOnce3("[claudish] config 'predefinedEndpoints' is not valid and was ignored: " + result.error.issues.map((i) => `${i.path.join(".") || "(root)"} ${i.message}`).join(", "));
46092
46522
  }
46093
46523
  }
46094
46524
  return {
@@ -46135,7 +46565,7 @@ function loadPredefinedEndpoints(config2, opts = {}) {
46135
46565
  const noteStale = (entry, name, reason) => {
46136
46566
  if (!ownRegistrations.has(name) || !runtime.has(entry.name))
46137
46567
  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.");
46568
+ 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
46569
  };
46140
46570
  if (optOut.disabled) {
46141
46571
  for (const entry of catalog) {
@@ -46155,7 +46585,7 @@ function loadPredefinedEndpoints(config2, opts = {}) {
46155
46585
  noteStale(entry, name, reason);
46156
46586
  };
46157
46587
  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.");
46588
+ warnOnce3(`[claudish] predefined endpoint '${entry.name}' appears more than once in the bundled ` + "catalog. The first row wins; the later one is ignored.");
46159
46589
  skip("duplicate row");
46160
46590
  continue;
46161
46591
  }
@@ -46163,19 +46593,19 @@ function loadPredefinedEndpoints(config2, opts = {}) {
46163
46593
  const owner = reserved.get(name);
46164
46594
  if (owner) {
46165
46595
  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.");
46596
+ warnOnce3(`[claudish] predefined endpoint '${entry.name}' skipped: ${reason}. ` + "The builtin wins; the bundled entry is inactive.");
46167
46597
  recordEndpointUnavailable(entry.name, `bundled endpoint was skipped because ${reason}`);
46168
46598
  skip("collides with builtin");
46169
46599
  continue;
46170
46600
  }
46171
46601
  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.`);
46602
+ warnOnce3(`[claudish] predefined endpoint '${entry.name}' skipped: a provider named ` + `'${entry.name}' is already registered for this process.`);
46173
46603
  skip("already registered");
46174
46604
  continue;
46175
46605
  }
46176
46606
  if (userEndpoints.has(name)) {
46177
46607
  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.`);
46608
+ 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
46609
  }
46180
46610
  skipStale("replaced by customEndpoints");
46181
46611
  continue;
@@ -46185,7 +46615,7 @@ function loadPredefinedEndpoints(config2, opts = {}) {
46185
46615
  continue;
46186
46616
  }
46187
46617
  const { envVar, aliases } = credentialEnvVars(entry);
46188
- const permitted = optOut.enable.has(name) || hasLocalApiKey({ envVar, aliases });
46618
+ const permitted = optOut.enable.has(name) || hasLocalApiKey({ envVar, aliases }) || keychainHasAnyOf([envVar, ...aliases ?? []]);
46189
46619
  if (!permitted) {
46190
46620
  skipStale("no local credential");
46191
46621
  continue;
@@ -46193,7 +46623,7 @@ function loadPredefinedEndpoints(config2, opts = {}) {
46193
46623
  const resolvedUrl = classifyEndpointBaseUrl(entry.baseUrl, entry.baseUrlEnvVars);
46194
46624
  if (!resolvedUrl.ok) {
46195
46625
  const detail = describeBadBaseUrlOverride(resolvedUrl, entry.baseUrl);
46196
- warnOnce2(`[claudish] predefined endpoint '${entry.name}' skipped: ${detail}`);
46626
+ warnOnce3(`[claudish] predefined endpoint '${entry.name}' skipped: ${detail}`);
46197
46627
  recordEndpointUnavailable(entry.name, detail);
46198
46628
  skipStale("invalid base URL override");
46199
46629
  continue;
@@ -46203,14 +46633,15 @@ function loadPredefinedEndpoints(config2, opts = {}) {
46203
46633
  ownRegistrations.add(name);
46204
46634
  result.registered.push(entry.name);
46205
46635
  } catch (err) {
46206
- warnOnce2(`[claudish] predefined endpoint '${entry.name}' skipped: ` + `${err instanceof Error ? err.message : String(err)}`);
46636
+ warnOnce3(`[claudish] predefined endpoint '${entry.name}' skipped: ` + `${err instanceof Error ? err.message : String(err)}`);
46207
46637
  skip("invalid catalog row");
46208
46638
  }
46209
46639
  }
46210
46640
  return result;
46211
46641
  }
46212
- var KILL_SWITCH_ENV = "CLAUDISH_NO_PREDEFINED_ENDPOINTS", warnedMessages2, ownRegistrations, catalogOverride = null;
46642
+ var KILL_SWITCH_ENV = "CLAUDISH_NO_PREDEFINED_ENDPOINTS", warnedMessages3, ownRegistrations, catalogOverride = null;
46213
46643
  var init_predefined_endpoints = __esm(() => {
46644
+ init_keychain_source();
46214
46645
  init_local_api_key();
46215
46646
  init_config_schema();
46216
46647
  init_custom_endpoints_loader();
@@ -46218,7 +46649,7 @@ var init_predefined_endpoints = __esm(() => {
46218
46649
  init_predefined_catalog();
46219
46650
  init_reserved_namespace();
46220
46651
  init_runtime_providers();
46221
- warnedMessages2 = new Set;
46652
+ warnedMessages3 = new Set;
46222
46653
  ownRegistrations = new Set;
46223
46654
  });
46224
46655
 
@@ -46258,7 +46689,7 @@ function reportCustomEndpoints(result) {
46258
46689
  logStderr(`customEndpoints['${name}'] failed validation: ${message}`);
46259
46690
  }
46260
46691
  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.`);
46692
+ warnOnce4(`customEndpoints['${name}'] skipped: ${reason}. The builtin wins. ` + `Rename your entry (e.g. '${name}-custom') to use it.`);
46262
46693
  }
46263
46694
  }
46264
46695
  function warnOnProjectScopedEndpoints() {
@@ -46267,26 +46698,26 @@ function warnOnProjectScopedEndpoints() {
46267
46698
  const names = Object.keys(local?.customEndpoints ?? {});
46268
46699
  if (names.length === 0)
46269
46700
  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.");
46701
+ 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
46702
  } catch {}
46272
46703
  }
46273
- function warnOnce3(message) {
46274
- if (warnedMessages3.has(message))
46704
+ function warnOnce4(message) {
46705
+ if (warnedMessages4.has(message))
46275
46706
  return;
46276
- warnedMessages3.add(message);
46707
+ warnedMessages4.add(message);
46277
46708
  logStderr(message);
46278
46709
  }
46279
46710
  function invalidateEndpointRegistration() {
46280
46711
  registered = false;
46281
46712
  }
46282
- var registered = false, lastCustomResult, warnedMessages3;
46713
+ var registered = false, lastCustomResult, warnedMessages4;
46283
46714
  var init_endpoint_registration = __esm(() => {
46284
46715
  init_logger();
46285
46716
  init_profile_config();
46286
46717
  init_custom_endpoints_loader();
46287
46718
  init_predefined_endpoints();
46288
46719
  lastCustomResult = { registered: 0, errors: [], refused: [] };
46289
- warnedMessages3 = new Set;
46720
+ warnedMessages4 = new Set;
46290
46721
  });
46291
46722
 
46292
46723
  // src/providers/provider-registry.ts
@@ -48045,8 +48476,8 @@ function readProjectCwd(dirName) {
48045
48476
  }
48046
48477
  return null;
48047
48478
  }
48048
- function isActive(row, now = Date.now()) {
48049
- return now - row.mtimeMs < ACTIVE_WINDOW_MS;
48479
+ function isActive(row, now2 = Date.now()) {
48480
+ return now2 - row.mtimeMs < ACTIVE_WINDOW_MS;
48050
48481
  }
48051
48482
  function discoverWorktreeGroups(repo) {
48052
48483
  const rootSlug = slugForPath(repo.root);
@@ -48661,9 +49092,9 @@ function setupSession(sessionPath, models, input) {
48661
49092
  }
48662
49093
  const ids = models.map((_, i) => String(i + 1).padStart(2, "0"));
48663
49094
  const shuffled = fisherYatesShuffle([...ids]);
48664
- const now = new Date().toISOString();
49095
+ const now2 = new Date().toISOString();
48665
49096
  const manifest = {
48666
- created: now,
49097
+ created: now2,
48667
49098
  models: {},
48668
49099
  shuffleOrder: shuffled
48669
49100
  };
@@ -48671,13 +49102,13 @@ function setupSession(sessionPath, models, input) {
48671
49102
  const anonId = shuffled[i];
48672
49103
  manifest.models[anonId] = {
48673
49104
  model: models[i],
48674
- assignedAt: now
49105
+ assignedAt: now2
48675
49106
  };
48676
49107
  mkdirSync11(join28(sessionPath, "work", anonId), { recursive: true });
48677
49108
  }
48678
49109
  writeFileSync11(join28(sessionPath, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
48679
49110
  const status = {
48680
- startedAt: now,
49111
+ startedAt: now2,
48681
49112
  models: Object.fromEntries(Object.keys(manifest.models).map((id) => [
48682
49113
  id,
48683
49114
  {
@@ -48958,9 +49389,9 @@ async function runModels(sessionPath, opts = {}) {
48958
49389
  return Math.max(0, Date.now() - s.updated_at);
48959
49390
  };
48960
49391
  const graceStartedAt = new Map;
48961
- const graceUsedMs = (id, now) => {
49392
+ const graceUsedMs = (id, now2) => {
48962
49393
  const start = graceStartedAt.get(id);
48963
- return start === undefined ? 0 : Math.max(0, now - start);
49394
+ return start === undefined ? 0 : Math.max(0, now2 - start);
48964
49395
  };
48965
49396
  const runningIds = () => [...processes.keys()].filter((id) => statusCache.models[id]?.state === "RUNNING");
48966
49397
  const timeoutModel = async (id, why) => {
@@ -49013,10 +49444,10 @@ async function runModels(sessionPath, opts = {}) {
49013
49444
  if (running.length === 0)
49014
49445
  return;
49015
49446
  const extended = [];
49016
- const now = Date.now();
49447
+ const now2 = Date.now();
49017
49448
  for (const id of running) {
49018
49449
  const idleMs = idleMsFor(id);
49019
- const usedGrace = graceUsedMs(id, now);
49450
+ const usedGrace = graceUsedMs(id, now2);
49020
49451
  if (!graceEnabled) {
49021
49452
  await timeoutModel(id, "deadline reached (grace extension disabled)");
49022
49453
  } else if (usedGrace >= maxGraceMs) {
@@ -49027,7 +49458,7 @@ async function runModels(sessionPath, opts = {}) {
49027
49458
  await timeoutModel(id, `no measurable progress for ${Math.round(idleMs / 1000)}s`);
49028
49459
  } else {
49029
49460
  if (!graceStartedAt.has(id))
49030
- graceStartedAt.set(id, now);
49461
+ graceStartedAt.set(id, now2);
49031
49462
  extended.push(id);
49032
49463
  }
49033
49464
  }
@@ -50269,6 +50700,9 @@ function extractVersionParts(modelId) {
50269
50700
  break;
50270
50701
  continue;
50271
50702
  }
50703
+ if (!started && /^\d+b$/.test(token) && Number.parseInt(match[0], 10) > 10) {
50704
+ continue;
50705
+ }
50272
50706
  if (!started) {
50273
50707
  started = true;
50274
50708
  for (const part of match[0].split(".")) {
@@ -54277,8 +54711,8 @@ class OpenRouterRequestQueue {
54277
54711
  }
54278
54712
  }
54279
54713
  async waitForNextSlot() {
54280
- const now = Date.now();
54281
- const timeSinceLastRequest = now - this.rateLimitState.lastRequestTime;
54714
+ const now2 = Date.now();
54715
+ const timeSinceLastRequest = now2 - this.rateLimitState.lastRequestTime;
54282
54716
  const delayMs = this.calculateDelay();
54283
54717
  this.rateLimitState.currentDelayMs = delayMs;
54284
54718
  if (timeSinceLastRequest < delayMs) {
@@ -54306,8 +54740,8 @@ class OpenRouterRequestQueue {
54306
54740
  }
54307
54741
  }
54308
54742
  if (this.rateLimitState.resetTime !== null && this.rateLimitState.remainingRequests !== null) {
54309
- const now = Date.now() / 1000;
54310
- const timeUntilReset = this.rateLimitState.resetTime - now;
54743
+ const now2 = Date.now() / 1000;
54744
+ const timeUntilReset = this.rateLimitState.resetTime - now2;
54311
54745
  if (timeUntilReset > 0 && this.rateLimitState.remainingRequests > 0) {
54312
54746
  const optimalDelay = timeUntilReset * 1000 / Math.max(this.rateLimitState.remainingRequests, 1);
54313
54747
  delayMs = Math.max(delayMs, Math.min(optimalDelay, this.maxDelayMs));
@@ -56491,11 +56925,15 @@ __export(exports_theme_mode, {
56491
56925
  classifyOscBackground: () => classifyOscBackground,
56492
56926
  detectAndSetThemeMode: () => detectAndSetThemeMode,
56493
56927
  detectAndSetThemeModeSync: () => detectAndSetThemeModeSync,
56928
+ getTerminalBackground: () => getTerminalBackground,
56929
+ getTerminalBackgroundHex: () => getTerminalBackgroundHex,
56494
56930
  getThemeMode: () => getThemeMode,
56495
56931
  onThemeModeChange: () => onThemeModeChange,
56932
+ parseOscBackgroundHex: () => parseOscBackgroundHex,
56496
56933
  queryTerminalThemeMode: () => queryTerminalThemeMode,
56497
56934
  relativeLuminance: () => relativeLuminance,
56498
56935
  resetThemeModeForTests: () => resetThemeModeForTests,
56936
+ setTerminalBackgroundHex: () => setTerminalBackgroundHex,
56499
56937
  setThemeMode: () => setThemeMode,
56500
56938
  themeModeFromColorFgBg: () => themeModeFromColorFgBg,
56501
56939
  themeModeOverride: () => themeModeOverride
@@ -56514,6 +56952,7 @@ function onThemeModeChange(cb) {
56514
56952
  }
56515
56953
  function resetThemeModeForTests() {
56516
56954
  setThemeMode(null);
56955
+ background = null;
56517
56956
  }
56518
56957
  function themeModeOverride(env = process.env) {
56519
56958
  const raw2 = env.CLAUDISH_THEME?.trim().toLowerCase();
@@ -56550,6 +56989,31 @@ function classifyOscBackground(reply) {
56550
56989
  const lum = relativeLuminance(channel(m[1]), channel(m[2]), channel(m[3]));
56551
56990
  return lum >= MID_SRGB_LUMINANCE ? "light" : "dark";
56552
56991
  }
56992
+ function parseOscBackgroundHex(reply) {
56993
+ const m = reply.match(/\]11;rgb:([0-9a-fA-F]{1,4})\/([0-9a-fA-F]{1,4})\/([0-9a-fA-F]{1,4})/);
56994
+ if (!m)
56995
+ return null;
56996
+ const byte = (hex3) => {
56997
+ const max = 16 ** hex3.length - 1;
56998
+ const v = Math.round(Number.parseInt(hex3, 16) / max * 255);
56999
+ return v.toString(16).padStart(2, "0");
57000
+ };
57001
+ return `#${byte(m[1])}${byte(m[2])}${byte(m[3])}`;
57002
+ }
57003
+ function getTerminalBackgroundHex() {
57004
+ return background?.hex ?? null;
57005
+ }
57006
+ function getTerminalBackground() {
57007
+ return background;
57008
+ }
57009
+ function setTerminalBackgroundHex(hex3) {
57010
+ if (!hex3) {
57011
+ background = null;
57012
+ return;
57013
+ }
57014
+ const mode = classifyOscBackground(`]11;rgb:${hex3.slice(1, 3)}/${hex3.slice(3, 5)}/${hex3.slice(5, 7)}`);
57015
+ background = mode ? { hex: hex3, mode } : null;
57016
+ }
56553
57017
  async function queryTerminalThemeMode(timeoutMs = 150) {
56554
57018
  const stdin = process.stdin;
56555
57019
  const stdout = process.stdout;
@@ -56577,7 +57041,10 @@ async function queryTerminalThemeMode(timeoutMs = 150) {
56577
57041
  const onData = (chunk) => {
56578
57042
  buffer += chunk.toString("latin1");
56579
57043
  if (/\]11;[^\x07\x1b]*(\x07|\x1b\\)/.test(buffer)) {
56580
- finish(classifyOscBackground(buffer));
57044
+ const mode = classifyOscBackground(buffer);
57045
+ const hex3 = parseOscBackgroundHex(buffer);
57046
+ background = hex3 && mode ? { hex: hex3, mode } : null;
57047
+ finish(mode);
56581
57048
  }
56582
57049
  };
56583
57050
  const timer = setTimeout(() => finish(null), timeoutMs);
@@ -56595,17 +57062,18 @@ async function queryTerminalThemeMode(timeoutMs = 150) {
56595
57062
  async function detectAndSetThemeMode() {
56596
57063
  const override = themeModeOverride();
56597
57064
  if (override) {
57065
+ setTerminalBackgroundHex(null);
56598
57066
  setThemeMode(override);
56599
57067
  return override;
56600
57068
  }
56601
- const fromEnv = themeModeFromColorFgBg();
56602
- if (fromEnv) {
56603
- setThemeMode(fromEnv);
56604
- return fromEnv;
57069
+ const fromOsc = await queryTerminalThemeMode(150);
57070
+ if (fromOsc) {
57071
+ setThemeMode(fromOsc);
57072
+ return fromOsc;
56605
57073
  }
56606
- const fromOsc = await queryTerminalThemeMode();
56607
- setThemeMode(fromOsc);
56608
- return fromOsc;
57074
+ const fromEnv = themeModeFromColorFgBg();
57075
+ setThemeMode(fromEnv);
57076
+ return fromEnv;
56609
57077
  }
56610
57078
  function detectAndSetThemeModeSync() {
56611
57079
  const mode = themeModeOverride() ?? themeModeFromColorFgBg();
@@ -56613,7 +57081,7 @@ function detectAndSetThemeModeSync() {
56613
57081
  setThemeMode(mode);
56614
57082
  return mode ?? detected;
56615
57083
  }
56616
- var detected = null, listeners, MID_SRGB_LUMINANCE;
57084
+ var detected = null, listeners, MID_SRGB_LUMINANCE, background = null;
56617
57085
  var init_theme_mode = __esm(() => {
56618
57086
  listeners = [];
56619
57087
  MID_SRGB_LUMINANCE = relativeLuminance(0.5, 0.5, 0.5);
@@ -57352,8 +57820,6 @@ function describeSourceSync(p, config3) {
57352
57820
  return "env";
57353
57821
  if (hasCfg)
57354
57822
  return "cfg";
57355
- if (p.publicKeyFallback)
57356
- return "public";
57357
57823
  return null;
57358
57824
  }
57359
57825
  async function describeSource(p, config3) {
@@ -57394,7 +57860,6 @@ function toProviderDef(def) {
57394
57860
  defaultEndpoint: def.baseUrl || undefined,
57395
57861
  aliases: def.apiKeyAliases,
57396
57862
  isLocal: def.isLocal,
57397
- publicKeyFallback: !!def.publicKeyFallback,
57398
57863
  oauthSlug: def.oauthLoginSlug
57399
57864
  };
57400
57865
  }
@@ -68585,7 +69050,7 @@ var require_lib3 = __commonJS(function(exports, module) {
68585
69050
  var trail = encoder.end();
68586
69051
  return trail && trail.length > 0 ? Buffer2.concat([res, trail]) : res;
68587
69052
  };
68588
- iconv.decode = function decode3(buf, encoding, options) {
69053
+ iconv.decode = function decode4(buf, encoding, options) {
68589
69054
  if (typeof buf === "string") {
68590
69055
  if (!iconv.skipDecodeWarning) {
68591
69056
  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 +70306,420 @@ var init_dist16 = __esm(() => {
69841
70306
  init_dist15();
69842
70307
  });
69843
70308
 
70309
+ // src/keychain-command.ts
70310
+ var exports_keychain_command = {};
70311
+ __export(exports_keychain_command, {
70312
+ keychainCommand: () => keychainCommand
70313
+ });
70314
+ function ok(text) {
70315
+ const c = cliAnsi();
70316
+ return `${c.GREEN}${text}${c.RESET}`;
70317
+ }
70318
+ function warn(text) {
70319
+ const c = cliAnsi();
70320
+ return `${c.YELLOW}${text}${c.RESET}`;
70321
+ }
70322
+ function bad(text) {
70323
+ const c = cliAnsi();
70324
+ return `${c.RED}${text}${c.RESET}`;
70325
+ }
70326
+ function dim3(text) {
70327
+ const c = cliAnsi();
70328
+ return `${c.GRAY}${text}${c.RESET}`;
70329
+ }
70330
+ function strong(text) {
70331
+ const c = cliAnsi();
70332
+ return `${c.BOLD}${text}${c.RESET}`;
70333
+ }
70334
+ function requireKeychain() {
70335
+ const reason = keychainUnavailableReason();
70336
+ if (!reason)
70337
+ return true;
70338
+ console.error(bad(`Keychain unavailable: ${reason}`));
70339
+ console.error(dim3("claudish's keychain backend uses the macOS Security framework via /usr/bin/security."));
70340
+ return false;
70341
+ }
70342
+ function catalogEnvVars() {
70343
+ const names = new Set;
70344
+ for (const p of getAllProviders()) {
70345
+ if (p.apiKeyEnvVar)
70346
+ names.add(p.apiKeyEnvVar);
70347
+ for (const alias of p.apiKeyAliases ?? [])
70348
+ names.add(alias);
70349
+ }
70350
+ return Array.from(names).sort();
70351
+ }
70352
+ function keychainStatus() {
70353
+ const supported = isKeychainSupported();
70354
+ const enabled2 = isKeychainEnabled();
70355
+ console.log(strong("macOS Keychain backend"));
70356
+ console.log(` platform ${supported ? ok("supported") : bad(`unsupported (${process.platform})`)}`);
70357
+ console.log(` backend ${enabled2 ? ok("enabled") : dim3("disabled")}`);
70358
+ console.log(` service ${dim3(KEYCHAIN_SERVICE)}`);
70359
+ if (!supported)
70360
+ return;
70361
+ const listed = enumerateKeychainVars();
70362
+ if (listed.failed) {
70363
+ console.log(` stored keys ${bad("could not read the keychain")}`);
70364
+ console.log();
70365
+ console.log(bad(listed.error ?? "unknown error"));
70366
+ console.log(dim3("The keychain may be locked, or access may have been denied."));
70367
+ process.exitCode = 1;
70368
+ return;
70369
+ }
70370
+ const stored = listed.names;
70371
+ console.log(` stored keys ${stored.length > 0 ? ok(String(stored.length)) : dim3("0")}`);
70372
+ if (!enabled2 && stored.length > 0) {
70373
+ console.log();
70374
+ console.log(warn(`${stored.length} key(s) are in the keychain but the backend is disabled, so claudish will not read them.`));
70375
+ console.log(dim3(" Enable with: claudish keychain enable"));
70376
+ }
70377
+ if (enabled2 && stored.length === 0) {
70378
+ console.log();
70379
+ console.log(dim3("Nothing stored yet. Copy your existing keys in with:"));
70380
+ console.log(dim3(" claudish keychain import"));
70381
+ }
70382
+ console.log();
70383
+ console.log(dim3("Resolution order: env var \u2192 alias \u2192 config.json \u2192 macOS Keychain \u2192 1Password"));
70384
+ }
70385
+ function keychainList() {
70386
+ if (!requireKeychain())
70387
+ return;
70388
+ const listed = enumerateKeychainVars();
70389
+ if (listed.failed) {
70390
+ console.error(bad(`Could not read the keychain: ${listed.error ?? "unknown error"}`));
70391
+ console.error(dim3("The keychain may be locked, or access may have been denied."));
70392
+ process.exitCode = 1;
70393
+ return;
70394
+ }
70395
+ const stored = listed.names;
70396
+ if (stored.length === 0) {
70397
+ console.log(dim3("No keys stored in the keychain."));
70398
+ console.log(dim3("Copy your existing keys in with: claudish keychain import"));
70399
+ return;
70400
+ }
70401
+ const width = Math.max(...stored.map((n) => n.length));
70402
+ for (const name of stored) {
70403
+ let tail;
70404
+ try {
70405
+ const value = readKeychainSecret(name);
70406
+ tail = value ? dim3(valueTail2(value)) : bad("unreadable");
70407
+ } catch (err) {
70408
+ tail = bad(err instanceof KeychainError ? "denied" : "error");
70409
+ }
70410
+ const shadowed = process.env[name] ? warn(" (shadowed by an env var)") : "";
70411
+ console.log(` ${name.padEnd(width)} ${tail}${shadowed}`);
70412
+ }
70413
+ if (!isKeychainEnabled()) {
70414
+ console.log();
70415
+ console.log(warn("The keychain backend is disabled \u2014 claudish will not read these."));
70416
+ console.log(dim3(" Enable with: claudish keychain enable"));
70417
+ }
70418
+ }
70419
+ async function collectImportPlan(opts) {
70420
+ const wanted = opts.only ?? catalogEnvVars();
70421
+ const listed = enumerateKeychainVars();
70422
+ if (listed.failed) {
70423
+ 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.");
70424
+ }
70425
+ const stored = new Set(listed.names);
70426
+ const plan = new Map;
70427
+ const classify = (envVar, value, origin) => {
70428
+ const problem = describeUnstorableValue(value) ?? undefined;
70429
+ let action = stored.has(envVar) ? "overwrite" : "new";
70430
+ if (action === "overwrite") {
70431
+ try {
70432
+ if (readKeychainSecret(envVar) === value)
70433
+ action = "unchanged";
70434
+ } catch {}
70435
+ }
70436
+ plan.set(envVar, { envVar, value, origin, action, problem });
70437
+ };
70438
+ if (opts.from === "env" || opts.from === "all") {
70439
+ for (const envVar of wanted) {
70440
+ const value = process.env[envVar];
70441
+ if (value)
70442
+ classify(envVar, value, "environment");
70443
+ }
70444
+ }
70445
+ if ((opts.from === "1password" || opts.from === "all") && hasOpSources()) {
70446
+ const missing = wanted.filter((n) => !plan.has(n));
70447
+ if (missing.length > 0) {
70448
+ const resolved = await resolveOpKeyForEnvVars(new Set(missing), {
70449
+ onAuthFailure: "throw",
70450
+ allowPrompt: true
70451
+ });
70452
+ for (const [envVar, value] of Object.entries(resolved)) {
70453
+ if (value)
70454
+ classify(envVar, value, "1Password");
70455
+ }
70456
+ }
70457
+ }
70458
+ return Array.from(plan.values()).sort((a, b) => a.envVar.localeCompare(b.envVar));
70459
+ }
70460
+ function renderPlan(plan) {
70461
+ const width = Math.max(...plan.map((e) => e.envVar.length));
70462
+ for (const e of plan) {
70463
+ if (e.problem) {
70464
+ console.log(` ${bad("skip")} ${e.envVar.padEnd(width)} ${bad(e.problem)}`);
70465
+ continue;
70466
+ }
70467
+ const label = e.action === "new" ? ok("new") : e.action === "overwrite" ? warn("overwrite") : dim3("unchanged");
70468
+ const pad = " ".repeat(Math.max(0, 10 - (e.action === "new" ? 3 : e.action.length)));
70469
+ console.log(` ${label}${pad} ${e.envVar.padEnd(width)} ${dim3(valueTail2(e.value))} ${dim3(`from ${e.origin}`)}`);
70470
+ }
70471
+ }
70472
+ function writeImportEntries(entries) {
70473
+ let written = 0;
70474
+ const failures = [];
70475
+ for (const entry of entries) {
70476
+ try {
70477
+ writeKeychainSecret(entry.envVar, entry.value);
70478
+ written++;
70479
+ } catch (err) {
70480
+ failures.push(`${entry.envVar}: ${err instanceof Error ? err.message : String(err)}`);
70481
+ }
70482
+ }
70483
+ return { written, failures };
70484
+ }
70485
+ function reportImportResult(written, failures) {
70486
+ if (written > 0 && !isKeychainEnabled()) {
70487
+ setKeychainEnabled(true);
70488
+ console.log(ok("Keychain backend enabled."));
70489
+ }
70490
+ console.log(ok(`Stored ${written} key(s) in the keychain.`));
70491
+ if (failures.length > 0) {
70492
+ console.error(bad(`${failures.length} key(s) failed:`));
70493
+ for (const f of failures)
70494
+ console.error(` ${f}`);
70495
+ process.exitCode = 1;
70496
+ }
70497
+ }
70498
+ async function keychainImport(opts) {
70499
+ if (!requireKeychain())
70500
+ return;
70501
+ let plan;
70502
+ try {
70503
+ plan = await collectImportPlan(opts);
70504
+ } catch (err) {
70505
+ console.error(bad(`Could not build the import plan: ${err instanceof Error ? err.message : String(err)}`));
70506
+ process.exitCode = 1;
70507
+ return;
70508
+ }
70509
+ if (plan.length === 0) {
70510
+ console.log(dim3("Nothing to import \u2014 no matching keys found in the requested sources."));
70511
+ if (opts.from === "1password" && !hasOpSources()) {
70512
+ console.log(dim3("No 1Password source is configured. See: claudish config \u2192 1Password tab"));
70513
+ }
70514
+ return;
70515
+ }
70516
+ const writable = plan.filter((e) => !e.problem && e.action !== "unchanged");
70517
+ const overwrites = writable.filter((e) => e.action === "overwrite");
70518
+ console.log(strong(`Import plan \u2014 ${plan.length} key(s) considered`));
70519
+ renderPlan(plan);
70520
+ console.log();
70521
+ if (writable.length === 0) {
70522
+ console.log(dim3("Everything is already stored with the same value. Nothing to do."));
70523
+ return;
70524
+ }
70525
+ if (overwrites.length > 0) {
70526
+ console.log(warn(`${overwrites.length} existing keychain item(s) will be REPLACED. The previous values cannot be recovered.`));
70527
+ }
70528
+ if (opts.dryRun) {
70529
+ console.log(dim3("--dry-run: nothing was written."));
70530
+ return;
70531
+ }
70532
+ if (!opts.yes) {
70533
+ const { confirm } = await Promise.resolve().then(() => (init_dist16(), exports_dist));
70534
+ const proceed = await confirm({
70535
+ message: `Store ${writable.length} key(s) in the macOS Keychain?`,
70536
+ default: overwrites.length === 0
70537
+ });
70538
+ if (!proceed) {
70539
+ console.log(dim3("Cancelled \u2014 nothing was written."));
70540
+ return;
70541
+ }
70542
+ }
70543
+ const { written, failures } = writeImportEntries(writable);
70544
+ reportImportResult(written, failures);
70545
+ const skipped = plan.filter((e) => e.problem);
70546
+ if (skipped.length > 0) {
70547
+ console.log(warn(`${skipped.length} key(s) skipped \u2014 see the plan above.`));
70548
+ }
70549
+ }
70550
+ async function readSecretInteractively(envVar) {
70551
+ if (!process.stdin.isTTY) {
70552
+ const piped = await new Response(Bun.stdin.stream()).text();
70553
+ const value2 = piped.replace(/\r?\n$/, "");
70554
+ return value2.length > 0 ? value2 : null;
70555
+ }
70556
+ const { password } = await Promise.resolve().then(() => (init_dist16(), exports_dist));
70557
+ const value = await password({ message: `Value for ${envVar}:`, mask: "\u2022" });
70558
+ return value.length > 0 ? value : null;
70559
+ }
70560
+ async function keychainSet(envVar) {
70561
+ if (!requireKeychain())
70562
+ return;
70563
+ if (!envVar) {
70564
+ console.error(bad("Usage: claudish keychain set <ENV_VAR>"));
70565
+ process.exitCode = 1;
70566
+ return;
70567
+ }
70568
+ if (!isValidKeychainVarName(envVar)) {
70569
+ console.error(bad(`"${envVar}" is not a valid environment variable name.`));
70570
+ process.exitCode = 1;
70571
+ return;
70572
+ }
70573
+ const value = await readSecretInteractively(envVar);
70574
+ if (!value) {
70575
+ console.log(dim3("Aborted \u2014 no value given."));
70576
+ return;
70577
+ }
70578
+ try {
70579
+ writeKeychainSecret(envVar, value);
70580
+ } catch (err) {
70581
+ console.error(bad(err instanceof Error ? err.message : String(err)));
70582
+ process.exitCode = 1;
70583
+ return;
70584
+ }
70585
+ if (!isKeychainEnabled())
70586
+ setKeychainEnabled(true);
70587
+ console.log(ok(`Stored ${envVar} ${dim3(valueTail2(value))} in the macOS Keychain.`));
70588
+ if (process.env[envVar]) {
70589
+ console.log(warn(`Note: ${envVar} is also set in this shell, and an env var takes precedence at runtime.`));
70590
+ }
70591
+ }
70592
+ function keychainRemove(envVar) {
70593
+ if (!requireKeychain())
70594
+ return;
70595
+ if (!envVar) {
70596
+ console.error(bad("Usage: claudish keychain rm <ENV_VAR>"));
70597
+ process.exitCode = 1;
70598
+ return;
70599
+ }
70600
+ try {
70601
+ const removed = deleteKeychainSecret(envVar);
70602
+ console.log(removed ? ok(`Removed ${envVar} from the macOS Keychain.`) : dim3(`${envVar} was not in the keychain \u2014 nothing to remove.`));
70603
+ } catch (err) {
70604
+ console.error(bad(err instanceof Error ? err.message : String(err)));
70605
+ process.exitCode = 1;
70606
+ }
70607
+ }
70608
+ function keychainToggle(enabled2) {
70609
+ if (enabled2 && !requireKeychain())
70610
+ return;
70611
+ setKeychainEnabled(enabled2);
70612
+ invalidateKeychainCache();
70613
+ 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."));
70614
+ if (!enabled2) {
70615
+ console.log(dim3("Remove the items themselves with: claudish keychain rm <ENV_VAR>"));
70616
+ }
70617
+ }
70618
+ function usage() {
70619
+ console.log(`${strong("claudish keychain")} \u2014 macOS Keychain credential backend
70620
+
70621
+ ${strong("status")} Backend state and how many keys are stored (default)
70622
+ ${strong("list")} Stored variables, with ${dim3("\u2022\u2022\u2022\u20221234")} identification tails
70623
+ ${strong("import")} [options] Copy secrets from env vars / 1Password into the keychain
70624
+ ${strong("set")} <ENV_VAR> Store one secret (prompted, or piped on stdin)
70625
+ ${strong("rm")} <ENV_VAR> Remove one secret
70626
+ ${strong("enable")} | ${strong("disable")} Turn the backend on/off (moves no secrets)
70627
+
70628
+ ${strong("import options")}
70629
+ --from env|1password|all Source to copy from (default: all)
70630
+ --only VAR,VAR Restrict to these variables
70631
+ --dry-run Show the plan and stop
70632
+ --yes Skip the confirmation
70633
+
70634
+ Resolution order: ${dim3("env var \u2192 alias \u2192 config.json \u2192 macOS Keychain \u2192 1Password")}
70635
+ `);
70636
+ }
70637
+ function parseOnly(args) {
70638
+ const idx = args.findIndex((a) => a === "--only" || a.startsWith("--only="));
70639
+ if (idx === -1)
70640
+ return;
70641
+ const raw2 = args[idx].includes("=") ? args[idx].split("=").slice(1).join("=") : args[idx + 1];
70642
+ if (!raw2)
70643
+ return;
70644
+ const names = raw2.split(",").map((n) => n.trim()).filter(Boolean);
70645
+ return names.length > 0 ? names : undefined;
70646
+ }
70647
+ function parseFrom(args) {
70648
+ const idx = args.findIndex((a) => a === "--from" || a.startsWith("--from="));
70649
+ if (idx === -1)
70650
+ return "all";
70651
+ const raw2 = (args[idx].includes("=") ? args[idx].split("=").slice(1).join("=") : args[idx + 1])?.trim().toLowerCase();
70652
+ if (raw2 === "env" || raw2 === "1password" || raw2 === "all")
70653
+ return raw2;
70654
+ if (raw2 === "op")
70655
+ return "1password";
70656
+ return "all";
70657
+ }
70658
+ function positionalArgs(args) {
70659
+ const out = [];
70660
+ for (let i = 0;i < args.length; i++) {
70661
+ const arg = args[i];
70662
+ if (arg.startsWith("-")) {
70663
+ if (VALUE_FLAGS.has(arg))
70664
+ i++;
70665
+ continue;
70666
+ }
70667
+ out.push(arg);
70668
+ }
70669
+ return out;
70670
+ }
70671
+ async function keychainCommand(args) {
70672
+ const positional = positionalArgs(args);
70673
+ const sub = positional[0] ?? "status";
70674
+ switch (sub) {
70675
+ case "status":
70676
+ keychainStatus();
70677
+ return;
70678
+ case "list":
70679
+ case "ls":
70680
+ keychainList();
70681
+ return;
70682
+ case "import":
70683
+ await keychainImport({
70684
+ from: parseFrom(args),
70685
+ only: parseOnly(args),
70686
+ dryRun: args.includes("--dry-run"),
70687
+ yes: args.includes("--yes") || args.includes("-y")
70688
+ });
70689
+ return;
70690
+ case "set":
70691
+ await keychainSet(positional[1]);
70692
+ return;
70693
+ case "rm":
70694
+ case "remove":
70695
+ case "delete":
70696
+ keychainRemove(positional[1]);
70697
+ return;
70698
+ case "enable":
70699
+ keychainToggle(true);
70700
+ return;
70701
+ case "disable":
70702
+ keychainToggle(false);
70703
+ return;
70704
+ case "help":
70705
+ usage();
70706
+ return;
70707
+ default:
70708
+ console.error(bad(`Unknown subcommand: ${sub}`));
70709
+ usage();
70710
+ process.exitCode = 1;
70711
+ }
70712
+ }
70713
+ var VALUE_FLAGS;
70714
+ var init_keychain_command = __esm(() => {
70715
+ init_ansi();
70716
+ init_op_source();
70717
+ init_profile_config();
70718
+ init_keychain();
70719
+ init_provider_definitions();
70720
+ VALUE_FLAGS = new Set(["--config", "--from", "--only"]);
70721
+ });
70722
+
69844
70723
  // src/auth/antigravity-oauth.ts
69845
70724
  import { spawnSync as spawnSync3 } from "child_process";
69846
70725
  import { existsSync as existsSync26, unlinkSync as unlinkSync7 } from "fs";
@@ -69881,16 +70760,16 @@ function defaultRunAgyAuth(agyPath, interactive) {
69881
70760
  spawnSync3(agyPath, ["-p", "hello", "--print-timeout", "3m"], { stdio: "inherit" });
69882
70761
  }
69883
70762
  }
69884
- async function pollForToken(deps) {
69885
- const deadline = deps.now() + deps.timing.graceMs;
70763
+ async function pollForToken(deps2) {
70764
+ const deadline = deps2.now() + deps2.timing.graceMs;
69886
70765
  for (;; ) {
69887
- const tok = deps.readToken();
70766
+ const tok = deps2.readToken();
69888
70767
  if (tok)
69889
70768
  return tok;
69890
- if (deps.now() >= deadline)
70769
+ if (deps2.now() >= deadline)
69891
70770
  return null;
69892
- const remaining = deadline - deps.now();
69893
- await deps.sleep(Math.min(deps.timing.intervalMs, Math.max(0, remaining)));
70771
+ const remaining = deadline - deps2.now();
70772
+ await deps2.sleep(Math.min(deps2.timing.intervalMs, Math.max(0, remaining)));
69894
70773
  }
69895
70774
  }
69896
70775
 
@@ -69904,67 +70783,67 @@ class AntigravityOAuth {
69904
70783
  }
69905
70784
  constructor() {}
69906
70785
  async login(depsOverride = {}) {
69907
- const deps = { ...defaultLoginDeps, ...depsOverride };
70786
+ const deps2 = { ...defaultLoginDeps, ...depsOverride };
69908
70787
  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);
70788
+ if (deps2.hasToken()) {
70789
+ console.log(`\u2705 Already authenticated with Antigravity. Use: claudish --model ag@${await (deps2.suggestModel ?? defaultSuggestModel)()}`);
70790
+ return deps2.exit(0);
69912
70791
  }
69913
- let agyPath = deps.locateAgy();
70792
+ let agyPath = deps2.locateAgy();
69914
70793
  if (!agyPath) {
69915
70794
  console.log("\nThe Antigravity CLI (`agy`) is required to sign in to Antigravity.");
69916
70795
  console.log(`claudish delegates Antigravity sign-in to agy \u2014 agy holds the current OAuth secret
69917
70796
  ` + "and writes the session to the shared keychain store that claudish reads.");
69918
- if (!deps.isInteractive()) {
70797
+ if (!deps2.isInteractive()) {
69919
70798
  printManualInstall();
69920
- return deps.exit(0);
70799
+ return deps2.exit(0);
69921
70800
  }
69922
- const proceed = await deps.confirmInstall();
70801
+ const proceed = await deps2.confirmInstall();
69923
70802
  if (!proceed) {
69924
70803
  printManualInstall();
69925
- return deps.exit(0);
70804
+ return deps2.exit(0);
69926
70805
  }
69927
70806
  console.log(`
69928
70807
  Installing the Antigravity CLI\u2026
69929
70808
  `);
69930
- if (!deps.runInstall()) {
70809
+ if (!deps2.runInstall()) {
69931
70810
  console.log(`
69932
70811
  \u274C Antigravity CLI installation failed.`);
69933
70812
  printManualInstall();
69934
- return deps.exit(0);
70813
+ return deps2.exit(0);
69935
70814
  }
69936
- agyPath = deps.locateAgy();
70815
+ agyPath = deps2.locateAgy();
69937
70816
  if (!agyPath) {
69938
70817
  console.log(`
69939
70818
  \u274C Antigravity CLI still not found after install.`);
69940
70819
  printManualInstall();
69941
- return deps.exit(0);
70820
+ return deps2.exit(0);
69942
70821
  }
69943
70822
  }
69944
70823
  console.log(`
69945
70824
  Launching the Antigravity CLI to sign in \u2014 complete the sign-in in your browser.
69946
70825
  ` + `claudish will detect the session automatically.
69947
70826
  `);
69948
- deps.runAgyAuth(agyPath, false);
69949
- let token = await pollForToken(deps);
70827
+ deps2.runAgyAuth(agyPath, false);
70828
+ let token = await pollForToken(deps2);
69950
70829
  if (!token) {
69951
70830
  console.log(`
69952
70831
  No session detected yet. Starting the Antigravity CLI interactively \u2014
69953
70832
  ` + "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);
70833
+ deps2.runAgyAuth(agyPath, true);
70834
+ token = await pollForToken(deps2);
69956
70835
  }
69957
70836
  if (token) {
69958
- deps.onAuthenticated();
70837
+ deps2.onAuthenticated();
69959
70838
  console.log(`
69960
- \u2705 Authenticated with Antigravity. Use: claudish --model ag@${await (deps.suggestModel ?? defaultSuggestModel)()}`);
69961
- return deps.exit(0);
70839
+ \u2705 Authenticated with Antigravity. Use: claudish --model ag@${await (deps2.suggestModel ?? defaultSuggestModel)()}`);
70840
+ return deps2.exit(0);
69962
70841
  }
69963
70842
  console.log("\nNo Antigravity session detected. Run `agy` and sign in, then retry `claudish login antigravity`.");
69964
- return deps.exit(0);
70843
+ return deps2.exit(0);
69965
70844
  }
69966
- async logout(deps) {
69967
- deleteSharedAntigravityToken(deps);
70845
+ async logout(deps2) {
70846
+ deleteSharedAntigravityToken(deps2);
69968
70847
  try {
69969
70848
  const tokenFile = join36(homedir31(), ".gemini", "antigravity-cli", "antigravity-oauth-token");
69970
70849
  if (existsSync26(tokenFile))
@@ -70166,7 +71045,7 @@ async function quotaCommand(provider) {
70166
71045
  `);
70167
71046
  return;
70168
71047
  }
70169
- renderPlan(adapter, plan);
71048
+ renderPlan2(adapter, plan);
70170
71049
  }
70171
71050
  function resolveAdapterFromInput(input) {
70172
71051
  const raw2 = input.toLowerCase().replace(/@+$/, "");
@@ -70196,7 +71075,7 @@ async function promptForAdapter() {
70196
71075
  });
70197
71076
  return select({ message: "Select provider:", choices });
70198
71077
  }
70199
- function renderPlan(adapter, plan) {
71078
+ function renderPlan2(adapter, plan) {
70200
71079
  console.log("");
70201
71080
  boxTop(plan.label);
70202
71081
  boxRow("Provider", adapter.providerId);
@@ -70407,11 +71286,11 @@ function readSlimCacheWithFreshness(reader) {
70407
71286
  const stale = !Number.isFinite(lastUpdatedMs) || ageMs > FIREBASE_CACHE_TTL_MS;
70408
71287
  return { entries: cache3.entries ?? [], stale };
70409
71288
  }
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;
71289
+ function createCatalogClient(deps2 = {}) {
71290
+ const _getModelsByProvider = deps2.getModelsByProvider ?? getModelsByProvider;
71291
+ const _getModelByIdFromFirebase = deps2.getModelByIdFromFirebase ?? getModelByIdFromFirebase;
71292
+ const _searchModels = deps2.searchModels ?? searchModels;
71293
+ const _readSlimCache = deps2.readSlimCache ?? readAllModelsCache;
70415
71294
  return {
70416
71295
  async modelsByVendor(vendorSlug) {
70417
71296
  const slug = vendorSlug.toLowerCase();
@@ -70595,6 +71474,8 @@ function resolveDiscoveredContextLength(m) {
70595
71474
  }
70596
71475
  }
70597
71476
  function resolveDiscoveredReleaseDate(m) {
71477
+ if (m.ignoreCatalogReleaseDate)
71478
+ return m.releaseDate;
70598
71479
  try {
70599
71480
  const catalogDate = lookupModel(m.id)?.releaseDate;
70600
71481
  if (catalogDate)
@@ -71408,10 +72289,42 @@ function registerPaletteRefresher(fn) {
71408
72289
  paletteRefreshers.push(fn);
71409
72290
  fn();
71410
72291
  }
72292
+ function hexChannels(hex3) {
72293
+ const m = /^#([0-9a-f]{6})$/i.exec(hex3.trim());
72294
+ if (!m)
72295
+ return null;
72296
+ const n = Number.parseInt(m[1], 16);
72297
+ return [n >> 16 & 255, n >> 8 & 255, n & 255];
72298
+ }
72299
+ function channelsToHex(c) {
72300
+ const hex3 = c.map((v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, "0")).join("");
72301
+ return `#${hex3}`;
72302
+ }
72303
+ function retintSurfaces(palette, pageHex) {
72304
+ const paletteBg = hexChannels(palette.bg);
72305
+ const page = hexChannels(pageHex);
72306
+ if (!paletteBg || !page)
72307
+ return;
72308
+ for (const token of SURFACE_TOKENS) {
72309
+ const original = hexChannels(palette[token]);
72310
+ if (!original)
72311
+ continue;
72312
+ C[token] = channelsToHex([
72313
+ page[0] + (original[0] - paletteBg[0]),
72314
+ page[1] + (original[1] - paletteBg[1]),
72315
+ page[2] + (original[2] - paletteBg[2])
72316
+ ]);
72317
+ }
72318
+ }
71411
72319
  function applyTuiTheme(mode) {
71412
72320
  const palette = mode === "light" ? LIGHT2 : DARK;
71413
72321
  activeLatencyBuckets = mode === "light" ? LATENCY_BUCKETS_LIGHT : LATENCY_BUCKETS_DARK;
71414
72322
  Object.assign(C, palette);
72323
+ const terminalBg = getTerminalBackground();
72324
+ if (terminalBg && terminalBg.mode === mode) {
72325
+ C.bg = terminalBg.hex;
72326
+ retintSurfaces(palette, terminalBg.hex);
72327
+ }
71415
72328
  Object.assign(STAGE_BG, mode === "light" ? STAGE_BG_LIGHT : STAGE_BG_DARK);
71416
72329
  STAGE_BG_ANSI.network = hexToAnsiBg(STAGE_BG.network);
71417
72330
  STAGE_BG_ANSI.server = hexToAnsiBg(STAGE_BG.server);
@@ -71488,7 +72401,7 @@ function tokBarCells(tokensPerSec, maxTokPerSec, tokWidth) {
71488
72401
  const raw2 = Math.round(tokWidth * Math.max(0, tokensPerSec) / denom);
71489
72402
  return Math.min(tokWidth, Math.max(0, raw2));
71490
72403
  }
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;
72404
+ 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
72405
  var init_theme2 = __esm(() => {
71493
72406
  init_theme_mode();
71494
72407
  DARK = {
@@ -71598,6 +72511,7 @@ var init_theme2 = __esm(() => {
71598
72511
  streaming: hexToAnsiBg(STAGE_BG.streaming)
71599
72512
  };
71600
72513
  paletteRefreshers = [];
72514
+ SURFACE_TOKENS = ["bgAlt", "border", "tabInactiveBg", "chipKeyBg", "chipLabelBg"];
71601
72515
  onThemeModeChange(applyTuiTheme);
71602
72516
  });
71603
72517
 
@@ -75057,7 +75971,7 @@ function printHelp2() {
75057
75971
  const A2 = cliAnsi();
75058
75972
  const c = (esc2) => (s) => useColor && esc2 ? `${esc2}${s}${A2.RESET}` : s;
75059
75973
  const bold4 = c(A2.BOLD);
75060
- const dim3 = c(A2.DIM);
75974
+ const dim4 = c(A2.DIM);
75061
75975
  const cyan = c(A2.CYAN);
75062
75976
  const green2 = c(A2.GREEN);
75063
75977
  const yellow2 = c(A2.YELLOW);
@@ -75065,89 +75979,89 @@ function printHelp2() {
75065
75979
  const blue = c(A2.BLUE);
75066
75980
  const h = (title) => bold4(cyan(`\u258C ${title}`));
75067
75981
  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")}
75982
+ ${bold4("claudish")} ${dim4("\xB7")} Run Claude Code with any AI model
75983
+ ${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
75984
 
75071
75985
  ${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")}
75986
+ ${green2("claudish")} ${dim4("# Interactive mode (default, model selector)")}
75987
+ ${green2("claudish")} ${yellow2("[OPTIONS] <claude-args...>")} ${dim4("# Single-shot mode (requires --model)")}
75988
+ ${green2("claudish")} ${green2("--team")} ${yellow2("a,b,c")} ${yellow2('"prompt"')} ${dim4("# Run models in parallel (magmux grid)")}
75989
+ ${green2("claudish")} ${green2("--team")} ${yellow2("a,b,c")} ${green2("-f")} ${yellow2("input.md")} ${dim4("# Team mode with file input")}
75076
75990
 
75077
75991
  ${h("MODEL ROUTING")}
75078
75992
  ${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")}
75993
+ ${magenta("google@gemini-3-pro")} ${dim4("Direct Google API (explicit)")}
75994
+ ${magenta("openrouter@google/gemini-3-pro")} ${dim4("OpenRouter (explicit)")}
75995
+ ${magenta("oai@gpt-5.3")} ${dim4("Direct OpenAI API (shortcut)")}
75996
+ ${magenta("ollama@llama3.2:3")} ${dim4("Local Ollama, 3 concurrent requests")}
75997
+ ${magenta("ollama@llama3.2:0")} ${dim4("Local Ollama, no limits")}
75084
75998
 
75085
75999
  ${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.")}
76000
+ ${magenta("g, gemini")} ${dim4("->")} Google Gemini ${dim4("google@gemini-3-pro")}
76001
+ ${magenta("oai")} ${dim4("->")} OpenAI Direct ${dim4("oai@gpt-5.3")}
76002
+ ${magenta("cx, codex")} ${dim4("->")} OpenAI Codex ${dim4("cx@gpt-5.3 (Responses API)")}
76003
+ ${magenta("or")} ${dim4("->")} OpenRouter ${dim4("or@openai/gpt-5.3")}
76004
+ ${magenta("x-ai, xai, grok")} ${dim4("->")} xAI / Grok ${dim4("x-ai@grok-3")}
76005
+ ${magenta("mm, mmax")} ${dim4("->")} MiniMax Direct ${dim4("mm@MiniMax-M2.1")}
76006
+ ${magenta("mmc")} ${dim4("->")} MiniMax Coding ${dim4("mmc@MiniMax-M2.1")}
76007
+ ${magenta("kimi, moon")} ${dim4("->")} Kimi Direct ${dim4("kimi@kimi-k2-thinking-turbo")}
76008
+ ${magenta("kc")} ${dim4("->")} Kimi Coding ${dim4("kc@kimi-k2-thinking-turbo")}
76009
+ ${magenta("glm, zhipu")} ${dim4("->")} GLM Direct ${dim4("glm@glm-4.7")}
76010
+ ${magenta("gc")} ${dim4("->")} GLM Coding ${dim4("gc@glm-4.7")}
76011
+ ${magenta("z-ai, zai")} ${dim4("->")} Z.AI Direct ${dim4("z-ai@glm-4.7")}
76012
+ ${magenta("oc, llama, lc, meta")} ${dim4("->")} OllamaCloud ${dim4("oc@llama-3.1")}
76013
+ ${magenta("zen")} ${dim4("->")} OpenCode Zen ${dim4("zen@grok-code")}
76014
+ ${magenta("zengo, zgo")} ${dim4("->")} OpenCode Zen Go ${dim4("zengo@grok-code")}
76015
+ ${magenta("v, vertex")} ${dim4("->")} Vertex AI ${dim4("v@gemini-2.5-flash")}
76016
+ ${magenta("poe")} ${dim4("->")} Poe ${dim4("poe@GPT-4o")}
76017
+ ${magenta("litellm, ll")} ${dim4("->")} LiteLLM ${dim4("ll@gpt-4o (needs LITELLM_BASE_URL)")}
76018
+ ${magenta("ds")} ${dim4("->")} DeepSeek ${dim4("ds@deepseek-chat")}
76019
+ ${magenta("sakana, fugu")} ${dim4("->")} Sakana Fugu ${dim4("fugu@fugu-ultra")}
76020
+ ${magenta("sc")} ${dim4("->")} Sakana Subscription ${dim4("sc@fugu-ultra")}
76021
+ ${magenta("ollama")} ${dim4("->")} Ollama (local) ${dim4("ollama@llama3.2")}
76022
+ ${magenta("lms, lmstudio")} ${dim4("->")} LM Studio (local) ${dim4("lms@qwen")}
76023
+ ${magenta("vllm")} ${dim4("->")} vLLM (local) ${dim4("vllm@model")}
76024
+ ${magenta("mlx")} ${dim4("->")} MLX (local) ${dim4("mlx@model")}
76025
+
76026
+ ${bold4("Native auto-detection")} ${dim4("(when no provider specified):")}
76027
+ ${yellow2("google/*, gemini-*")} ${dim4("->")} Google API
76028
+ ${yellow2("openai/*, gpt-*, o1-*")} ${dim4("->")} OpenAI API
76029
+ ${yellow2("x-ai/*, grok-*")} ${dim4("->")} xAI
76030
+ ${yellow2("meta-llama/*, llama-*")} ${dim4("->")} OllamaCloud
76031
+ ${yellow2("minimax/*, abab-*")} ${dim4("->")} MiniMax API
76032
+ ${yellow2("moonshot/*, kimi-*")} ${dim4("->")} Kimi API
76033
+ ${yellow2("zhipu/*, glm-*")} ${dim4("->")} GLM API
76034
+ ${yellow2("sakana/*, fugu-*")} ${dim4("->")} Sakana Fugu
76035
+ ${yellow2("poe:*")} ${dim4("->")} Poe
76036
+ ${yellow2("anthropic/*, claude-*")} ${dim4("->")} Native Anthropic
76037
+ ${yellow2("(unknown vendor/)")} ${dim4("->")} Error (use openrouter@vendor/model)
76038
+
76039
+ ${dim4("A defaultProvider (config / --default-provider) catches bare names that match no rule.")}
75126
76040
 
75127
76041
  ${h("OPTIONS")}
75128
76042
  ${green2("-i, --interactive")} Run in interactive mode (default when no prompt given)
75129
76043
  ${green2("-m, --model")} ${yellow2("<model>")} Model to use (required for single-shot mode)
75130
76044
  ${green2("--profile")} ${yellow2("<name>")} Use named profile for model mapping (default profile if omitted)
75131
76045
  ${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")}
76046
+ ${dim4("Precedence: this flag > CLAUDISH_DEFAULT_PROVIDER env > config.json")}
75133
76047
  ${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")}
76048
+ ${dim4("(metered API billing). Default: the key is hidden so Claude Code")}
76049
+ ${dim4("uses your claude.ai subscription. Env: CLAUDISH_ANTHROPIC_API_BILLING")}
76050
+ ${dim4("Config: anthropicApiBilling: true")}
75137
76051
  ${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")}
76052
+ ${dim4("global (~/.claudish/config.json) AND project (.claudish.json).")}
76053
+ ${dim4("A file naming no op:// source never touches 1Password (no prompt).")}
76054
+ ${dim4("Env vars still resolve first. Env: CLAUDISH_CONFIG")}
75141
76055
  ${green2("--op")} ${yellow2("<op://glob>")} Load API keys from a 1Password item glob (SDK-based, no op CLI)
75142
76056
  ${green2("--op")} ${yellow2("<glob>")} ${green2("--list")} Preview which fields the glob would import (names only, no values)
75143
76057
  ${green2("--op-env")} ${yellow2("<id>")} Load env vars from a 1Password Environment (highest priority)
75144
76058
  ${green2("--port")} ${yellow2("<port>")} Proxy server port (default: random)
75145
76059
  ${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')}
76060
+ ${dim4('Always-on: CLAUDISH_DEBUG=1 env var or "debug": true in config.json')}
75147
76061
  ${green2("--no-debug-claudish")} Force debug logging off for this run (when globally enabled)
75148
76062
  ${green2("--log-off")} Disable always-on structural logging (~/.claudish/logs/)
75149
76063
  ${green2("--log-diag")} ${yellow2("<mode>")} Diagnostic output: auto (default), logfile, off
75150
- ${dim3('Also: CLAUDISH_DIAG_MODE env var or "diagMode" in config.json')}
76064
+ ${dim4('Also: CLAUDISH_DIAG_MODE env var or "diagMode" in config.json')}
75151
76065
  ${green2("--log-level")} ${yellow2("<level>")} Log verbosity: debug (full), info (truncated), minimal (labels)
75152
76066
  ${green2("-q, --quiet")} Suppress [claudish] log messages (default in single-shot mode)
75153
76067
  ${green2("-v, --verbose")} Show [claudish] log messages (default in interactive mode)
@@ -75171,13 +76085,13 @@ ${h("OPTIONS")}
75171
76085
  ${h("MODEL DISCOVERY")}
75172
76086
  ${green2("--models")} Top 100 ranked (Firebase + local providers)
75173
76087
  ${green2("--models --provider")} ${yellow2("<slug>")} Filter the catalog to one provider
75174
- ${dim3("e.g. --provider opencode-zen, anthropic, openai")}
76088
+ ${dim4("e.g. --provider opencode-zen, anthropic, openai")}
75175
76089
  ${green2("--providers")} Every provider + active-model count
75176
76090
  ${green2("-s, --models-search")} ${yellow2("<query>")} Fuzzy search: id, brand synonyms (chatgpt,
75177
- ${dim3("claude, grok), gateways (zen, oc, codex), caps")}
76091
+ ${dim4("claude, grok), gateways (zen, oc, codex), caps")}
75178
76092
  ${green2("--models-top")} Curated recommended models (flagship + fast)
75179
76093
  ${green2("--probe")} ${yellow2("<models...>")} Probe each provider in the fallback chain with
75180
- ${dim3("a real 1-token request (may incur tiny cost)")}
76094
+ ${dim4("a real 1-token request (may incur tiny cost)")}
75181
76095
  ${green2("--no-probe")} Skip live requests, show static chain only
75182
76096
  ${green2("--probe-timeout")} ${yellow2("<secs>")} Per-link timeout for live probes (default: 40)
75183
76097
  ${green2("--models-refresh")} Force refresh the slim model catalog from Firebase
@@ -75186,11 +76100,11 @@ ${h("MODEL DISCOVERY")}
75186
76100
 
75187
76101
  ${h("TEAM MODE")}
75188
76102
  ${green2("--team")} ${yellow2("<models>")} Run multiple models in parallel (comma-separated)
75189
- ${dim3('Example: --team minimax-m2.5,kimi-k2.5 "prompt"')}
76103
+ ${dim4('Example: --team minimax-m2.5,kimi-k2.5 "prompt"')}
75190
76104
  ${green2("--mode")} ${yellow2("<mode>")} Team mode: default (grid), interactive, json
75191
76105
  ${green2("-f, --file")} ${yellow2("<path>")} Read prompt from file (use with --team or single-shot)
75192
76106
 
75193
- ${h("MODEL MAPPING")} ${dim3("(per-role override)")}
76107
+ ${h("MODEL MAPPING")} ${dim4("(per-role override)")}
75194
76108
  ${green2("--model-opus")} ${yellow2("<model>")} Model for Opus role (planning, complex tasks)
75195
76109
  ${green2("--model-sonnet")} ${yellow2("<model>")} Model for Sonnet role (default coding)
75196
76110
  ${green2("--model-haiku")} ${yellow2("<model>")} Model for Haiku role (fast tasks, background)
@@ -75198,7 +76112,7 @@ ${h("MODEL MAPPING")} ${dim3("(per-role override)")}
75198
76112
 
75199
76113
  ${h("SUBCOMMANDS")}
75200
76114
  ${green2("claudish config")} Open the interactive config TUI (profiles,
75201
- ${dim3("providers, routing, 1Password)")}
76115
+ ${dim4("providers, routing, 1Password)")}
75202
76116
  ${green2("claudish providers")} ${yellow2("[--json]")} Show provider credential status (no key material)
75203
76117
  ${green2("claudish quota")} ${yellow2("[provider]")} Show remaining quota/usage (alias: usage)
75204
76118
  ${green2("claudish serve")} ${yellow2("--port <n> --models <p>")} Run the Claude Desktop redirect gateway
@@ -75212,39 +76126,50 @@ ${h("SUBCOMMANDS")}
75212
76126
  ${green2("claudish profile use")} ${yellow2("[name] [scope]")} Set default profile
75213
76127
  ${green2("claudish profile show")} ${yellow2("[name] [scope]")} Show profile details
75214
76128
  ${green2("claudish profile edit")} ${yellow2("[name] [scope]")} Edit a profile
75215
- ${dim3("scope = --local (.claudish.json) | --global (~/.claudish/config.json) | (prompted)")}
76129
+ ${dim4("scope = --local (.claudish.json) | --global (~/.claudish/config.json) | (prompted)")}
75216
76130
 
75217
76131
  ${bold4("Authentication:")}
75218
76132
  ${green2("claudish login")} ${yellow2("[provider]")} Login to an OAuth provider (interactive if omitted)
75219
76133
  ${green2("claudish logout")} ${yellow2("[provider]")} Clear OAuth credentials
75220
- ${dim3("Providers: gemini, kimi")}
76134
+ ${dim4("Providers: gemini, kimi")}
75221
76135
 
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).")}
76136
+ ${h("1PASSWORD")} ${dim4("(SDK-based \u2014 no op CLI needed for secrets)")}
76137
+ ${dim4("Auth via OP_SERVICE_ACCOUNT_TOKEN, or OP_ACCOUNT / onepasswordAccount config (DesktopAuth).")}
75224
76138
  ${green2("--op")} ${yellow2("<glob> --list")} Preview which fields a glob would import (names only)
75225
76139
  ${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"')}
76140
+ ${dim4("Inline op import requires a GLOB (self-names via field labels)")}
76141
+ ${dim4('Example: claudish --op "op://Jack/Keys/**" --model gpt-4o "task"')}
75228
76142
  ${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")}
76143
+ ${dim4("Persistent setup (single refs, sets, environments, account): claudish config -> 1Password tab")}
76144
+
76145
+ ${h("MACOS KEYCHAIN")} ${dim4("(local, encrypted at rest, no desktop-app handshake)")}
76146
+ ${green2("claudish keychain status")} Backend state and how many keys are stored
76147
+ ${green2("claudish keychain list")} Stored variables, with ${dim4("\u2022\u2022\u2022\u20221234")} identification tails
76148
+ ${green2("claudish keychain import")} Copy keys from env vars / 1Password into the keychain
76149
+ ${dim4("--from env|1password|all --only VAR,VAR --dry-run --yes")}
76150
+ ${green2("claudish keychain set")} ${yellow2("<ENV_VAR>")} Store one key (prompted, or piped on stdin \u2014 never in argv)
76151
+ ${green2("claudish keychain rm")} ${yellow2("<ENV_VAR>")} Remove one key
76152
+ ${green2("claudish keychain enable")}${dim4("|")}${green2("disable")} Turn the backend on/off (moves no secrets)
76153
+ ${dim4("Resolution order: env var -> alias -> config.json -> macOS Keychain -> 1Password")}
76154
+ ${dim4("The config TUI's Providers tab writes to the keychain by default on macOS.")}
75230
76155
 
75231
76156
  ${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 '-':")}
76157
+ ${dim4("Any unrecognized flag is forwarded to Claude Code. Claudish flags can appear in any order.")}
76158
+ ${green2("claudish")} --model grok ${yellow2("--agent test")} ${yellow2('"task"')} ${dim4("# --agent passes through")}
76159
+ ${green2("claudish")} --model grok ${yellow2("--effort high")} --stdin ${yellow2('"task"')} ${dim4("# --effort passes, --stdin stays")}
76160
+ ${green2("claudish")} --model grok ${yellow2("--permission-mode plan")} -i ${dim4("# works in interactive too")}
76161
+ ${dim4("Use -- when a Claude Code flag value starts with '-':")}
75237
76162
  ${green2("claudish")} --model grok ${green2("--")} ${yellow2('--system-prompt "-verbose mode" "task"')}
75238
76163
 
75239
76164
  ${h("CUSTOM MODELS & ENDPOINTS")}
75240
- ${dim3("Claudish accepts ANY valid model ID from the Firebase catalog, even if not in --models:")}
76165
+ ${dim4("Claudish accepts ANY valid model ID from the Firebase catalog, even if not in --models:")}
75241
76166
  ${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 @:")}
76167
+ ${dim4("Named custom endpoints live in ~/.claudish/config.json under 'customEndpoints' and route via @:")}
75243
76168
  ${green2("claudish")} --model ${yellow2("my-vllm@llama3.1-70b")} ${yellow2('"task"')}
75244
76169
 
75245
76170
  ${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
76171
+ ${green2("\u2022")} ${bold4("Interactive")} ${dim4("(default):")} shows model selector, starts a persistent session
76172
+ ${green2("\u2022")} ${bold4("Single-shot")} ${dim4("(--model):")} runs one task headless and exits
75248
76173
 
75249
76174
  ${h("NOTES")}
75250
76175
  ${yellow2("\u2022")} Permission prompts are ${bold4("ENABLED")} by default (normal Claude Code behavior)
@@ -75253,35 +76178,35 @@ ${h("NOTES")}
75253
76178
  ${yellow2("\u2022")} ${green2("--dangerous")} disables the sandbox \u2014 use with extreme caution
75254
76179
 
75255
76180
  ${h("ENVIRONMENT VARIABLES")}
75256
- ${dim3("Claudish auto-loads a .env file from the current directory.")}
76181
+ ${dim4("Claudish auto-loads a .env file from the current directory.")}
75257
76182
 
75258
76183
  ${bold4("Claude Code installation:")}
75259
76184
  ${blue("CLAUDE_PATH")} Custom path to Claude Code binary
75260
- ${dim3("Search: CLAUDE_PATH -> ~/.claude/local/claude -> PATH")}
76185
+ ${dim4("Search: CLAUDE_PATH -> ~/.claude/local/claude -> PATH")}
75261
76186
 
75262
- ${bold4("API keys")} ${dim3("(at least one required for cloud models):")}
76187
+ ${bold4("API keys")} ${dim4("(at least one required for cloud models):")}
75263
76188
  ${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)")}
76189
+ ${blue("GEMINI_API_KEY")} Google Gemini ${dim4("(g@, gemini@; alias GOOGLE_API_KEY)")}
76190
+ ${blue("OPENAI_API_KEY")} OpenAI ${dim4("(oai@)")}
76191
+ ${blue("OPENAI_CODEX_API_KEY")} OpenAI Codex / Responses API ${dim4("(cx@, codex@)")}
76192
+ ${blue("XAI_API_KEY")} xAI / Grok ${dim4("(x-ai@, grok@)")}
76193
+ ${blue("MINIMAX_API_KEY")} MiniMax ${dim4("(mm@, mmax@)")}
76194
+ ${blue("MINIMAX_CODING_API_KEY")} MiniMax Coding Plan ${dim4("(mmc@)")}
76195
+ ${blue("MOONSHOT_API_KEY")} Kimi / Moonshot ${dim4("(kimi@, moon@; alias KIMI_API_KEY)")}
76196
+ ${blue("KIMI_CODING_API_KEY")} Kimi Coding Plan ${dim4("(kc@)")}
76197
+ ${blue("ZHIPU_API_KEY")} GLM / Zhipu ${dim4("(glm@, zhipu@; alias GLM_API_KEY)")}
76198
+ ${blue("GLM_CODING_API_KEY")} GLM Coding Plan ${dim4("(gc@; alias ZAI_CODING_API_KEY)")}
76199
+ ${blue("ZAI_API_KEY")} Z.AI ${dim4("(z-ai@, zai@)")}
76200
+ ${blue("DEEPSEEK_API_KEY")} DeepSeek ${dim4("(ds@)")}
76201
+ ${blue("SAKANA_API_KEY")} Sakana Fugu ${dim4("(sakana@, fugu@)")}
76202
+ ${blue("SAKANA_SUBSCRIPTION_API_KEY")} Sakana Fugu Subscription ${dim4("(sc@; separate subscription key)")}
76203
+ ${blue("OLLAMA_API_KEY")} OllamaCloud ${dim4("(oc@, llama@)")}
76204
+ ${blue("OPENCODE_API_KEY")} OpenCode Zen ${dim4("(zen@)")}
76205
+ ${blue("POE_API_KEY")} Poe ${dim4("(poe@)")}
76206
+ ${blue("LITELLM_API_KEY")} LiteLLM ${dim4("(litellm@, ll@; needs LITELLM_BASE_URL)")}
76207
+ ${blue("VERTEX_API_KEY")} Vertex AI Express ${dim4("(v@)")}
76208
+ ${blue("VERTEX_PROJECT")} Vertex AI project ID ${dim4("(OAuth mode, v@)")}
76209
+ ${blue("VERTEX_LOCATION")} Vertex AI region ${dim4("(default: us-central1)")}
75285
76210
  ${blue("ANTHROPIC_API_KEY")} Placeholder (prevents Claude Code dialog)
75286
76211
  ${blue("ANTHROPIC_AUTH_TOKEN")} Placeholder (prevents Claude Code login screen)
75287
76212
 
@@ -75289,27 +76214,27 @@ ${h("ENVIRONMENT VARIABLES")}
75289
76214
  ${blue("GEMINI_BASE_URL")} Custom Gemini endpoint
75290
76215
  ${blue("OPENAI_BASE_URL")} Custom OpenAI / Azure endpoint
75291
76216
  ${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)")}
76217
+ ${blue("MOONSHOT_BASE_URL")} Custom Kimi / Moonshot endpoint ${dim4("(alias KIMI_BASE_URL)")}
76218
+ ${blue("ZHIPU_BASE_URL")} Custom GLM / Zhipu endpoint ${dim4("(alias GLM_BASE_URL)")}
76219
+ ${blue("SAKANA_BASE_URL")} Custom Sakana endpoint ${dim4("(default: https://api.sakana.ai)")}
76220
+ ${blue("LITELLM_BASE_URL")} LiteLLM gateway base URL ${dim4("(required for ll@)")}
76221
+ ${blue("OLLAMACLOUD_BASE_URL")} OllamaCloud ${dim4("(default: https://ollama.com)")}
76222
+ ${blue("OPENCODE_BASE_URL")} OpenCode Zen ${dim4("(default: https://opencode.ai/zen)")}
75298
76223
 
75299
76224
  ${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)")}
76225
+ ${blue("OLLAMA_BASE_URL")} Ollama server ${dim4("(default: http://localhost:11434; alias OLLAMA_HOST)")}
76226
+ ${blue("LMSTUDIO_BASE_URL")} LM Studio server ${dim4("(default: http://localhost:1234)")}
76227
+ ${blue("VLLM_BASE_URL")} vLLM server ${dim4("(default: http://localhost:8000)")}
76228
+ ${blue("MLX_BASE_URL")} MLX server ${dim4("(default: http://127.0.0.1:8080)")}
75304
76229
 
75305
76230
  ${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)")}
76231
+ ${blue("CLAUDISH_MODEL")} Default model ${dim4("(default: openai/gpt-5.3)")}
76232
+ ${blue("CLAUDISH_DEFAULT_PROVIDER")} Fallback provider for bare names ${dim4("(see --default-provider)")}
75308
76233
  ${blue("CLAUDISH_PORT")} Default proxy port
75309
76234
  ${blue("CLAUDISH_CONTEXT_WINDOW")} Override context window size
75310
76235
  ${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)")}
76236
+ ${blue("CLAUDISH_DEBUG")} Always enable debug logging: 1 / true ${dim4("(same as -d)")}
76237
+ ${blue("CLAUDISH_ANTHROPIC_API_BILLING")} Bill native Claude to your API key ${dim4("(see --anthropic-api-billing)")}
75313
76238
  ${blue("CLAUDISH_MCP_TOOLS")} MCP tool gating: all / low-level / agentic / channel
75314
76239
  ${blue("CLAUDISH_MODEL_OPUS")} Override model for Opus role
75315
76240
  ${blue("CLAUDISH_MODEL_SONNET")} Override model for Sonnet role
@@ -75319,39 +76244,39 @@ ${h("ENVIRONMENT VARIABLES")}
75319
76244
 
75320
76245
  ${bold4("1Password auth:")}
75321
76246
  ${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)")}
76247
+ ${blue("OP_ACCOUNT")} Account URL for DesktopAuth ${dim4("(e.g. my-team.1password.com)")}
75323
76248
 
75324
76249
  ${h("EXAMPLES")}
75325
- ${dim3("# Interactive (default) - model selector")}
76250
+ ${dim4("# Interactive (default) - model selector")}
75326
76251
  ${green2("claudish")}
75327
- ${green2("claudish")} --free ${dim3("# only FREE models")}
76252
+ ${green2("claudish")} --free ${dim4("# only FREE models")}
75328
76253
 
75329
- ${dim3("# Explicit provider routing")}
76254
+ ${dim4("# Explicit provider routing")}
75330
76255
  ${green2("claudish")} --model ${magenta("google@gemini-3-pro")} ${yellow2('"implement auth"')}
75331
76256
  ${green2("claudish")} --model ${magenta("oai@gpt-5.3")} ${yellow2('"add tests for login"')}
75332
76257
  ${green2("claudish")} --model ${magenta("openrouter@deepseek/deepseek-r1")} ${yellow2('"unknown vendor"')}
75333
76258
 
75334
- ${dim3("# Native auto-detection (provider inferred from model name)")}
76259
+ ${dim4("# Native auto-detection (provider inferred from model name)")}
75335
76260
  ${green2("claudish")} --model ${yellow2("gpt-4o")} ${yellow2('"routes to OpenAI"')}
75336
76261
  ${green2("claudish")} --model ${yellow2("gemini-2.5-pro")} ${yellow2('"routes to Google"')}
75337
76262
 
75338
- ${dim3("# Per-role model mapping")}
76263
+ ${dim4("# Per-role model mapping")}
75339
76264
  ${green2("claudish")} --model-opus ${magenta("oai@gpt-5.3")} --model-sonnet ${magenta("google@gemini-3-pro")}
75340
76265
 
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"')}
76266
+ ${dim4("# stdin for large prompts (diffs, code review)")}
76267
+ ${dim4("git diff |")} ${green2("claudish")} --stdin --model ${magenta("oai@gpt-5.3")} ${yellow2('"Review these changes"')}
75343
76268
 
75344
- ${dim3("# Local models with concurrency control")}
76269
+ ${dim4("# Local models with concurrency control")}
75345
76270
  ${green2("claudish")} --model ${magenta("ollama@llama3.2:3")} ${yellow2('"3 concurrent requests"')}
75346
76271
  ${green2("claudish")} --model ${magenta("lms@qwen2.5-coder")} ${yellow2('"LM Studio shortcut"')}
75347
76272
  ${green2("claudish")} --model ${yellow2('"http://localhost:8000/mistral"')} ${yellow2('"any OpenAI-compatible URL"')}
75348
76273
 
75349
- ${dim3("# Autonomous (no prompts, no sandbox) \u2014 use with caution")}
76274
+ ${dim4("# Autonomous (no prompts, no sandbox) \u2014 use with caution")}
75350
76275
  ${green2("claudish")} -y --dangerous ${yellow2('"refactor entire codebase"')}
75351
76276
 
75352
76277
  ${h("MORE INFO")}
75353
- ${dim3("GitHub:")} ${blue("https://github.com/MadAppGang/claude-code")}
75354
- ${dim3("OpenRouter:")} ${blue("https://openrouter.ai")}
76278
+ ${dim4("GitHub:")} ${blue("https://github.com/MadAppGang/claude-code")}
76279
+ ${dim4("OpenRouter:")} ${blue("https://openrouter.ai")}
75355
76280
  `);
75356
76281
  }
75357
76282
  function printAIAgentGuide() {
@@ -76714,6 +77639,8 @@ function Footer({ activeTab, mode, probeMode, providerCaps }) {
76714
77639
  [C.blue, "\u2191\u2193", "navigate"],
76715
77640
  [C.green, "a", "add"],
76716
77641
  [C.cyan, "t", "test"],
77642
+ [C.magenta, "c", "\u2192keychain"],
77643
+ [C.magenta, "C", "all\u2192keychain"],
76717
77644
  [C.green, "o", "account"],
76718
77645
  [C.red, "x", "remove"],
76719
77646
  [C.blue, "Tab", "section"],
@@ -78885,8 +79812,6 @@ function resolveProviderDetailKeyDisplay(input) {
78885
79812
  return input.envKeyMask;
78886
79813
  if (input.hasCfgKey)
78887
79814
  return input.cfgKeyMask;
78888
- if (input.isPublicKey)
78889
- return "free";
78890
79815
  return "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500";
78891
79816
  }
78892
79817
  function ProviderDetail({
@@ -78900,7 +79825,9 @@ function ProviderDetail({
78900
79825
  hasKey,
78901
79826
  authSource,
78902
79827
  isOpKey,
78903
- isPublicKey,
79828
+ isKcKey,
79829
+ hasKcKey,
79830
+ keySaveTarget,
78904
79831
  cfgKeyMask,
78905
79832
  envKeyMask,
78906
79833
  activeEndpoint,
@@ -78914,7 +79841,6 @@ function ProviderDetail({
78914
79841
  authSource,
78915
79842
  hasEnvKey,
78916
79843
  hasCfgKey,
78917
- isPublicKey,
78918
79844
  envKeyMask,
78919
79845
  cfgKeyMask
78920
79846
  });
@@ -78924,7 +79850,7 @@ function ProviderDetail({
78924
79850
  border: true,
78925
79851
  borderStyle: "single",
78926
79852
  borderColor: C.focusBorder,
78927
- title: ` Set ${mode === "input_key" ? "API Key" : "Endpoint"} \u2014 ${selectedProvider.displayName} `,
79853
+ title: mode === "input_key" ? ` Set API Key \u2014 ${selectedProvider.displayName} \u2192 ${keySaveTarget} ` : ` Set Endpoint \u2014 ${selectedProvider.displayName} `,
78928
79854
  backgroundColor: C.bg,
78929
79855
  flexDirection: "column",
78930
79856
  paddingX: 1,
@@ -79058,7 +79984,7 @@ function ProviderDetail({
79058
79984
  })
79059
79985
  ]
79060
79986
  }),
79061
- hasKey && !selectedProvider.isLocal && isPublicKey && /* @__PURE__ */ jsxs10(Fragment7, {
79987
+ hasKey && !selectedProvider.isLocal && isOAuth && /* @__PURE__ */ jsxs10(Fragment7, {
79062
79988
  children: [
79063
79989
  /* @__PURE__ */ jsx11("span", {
79064
79990
  fg: C.dim,
@@ -79070,9 +79996,13 @@ function ProviderDetail({
79070
79996
  children: "From: "
79071
79997
  }),
79072
79998
  /* @__PURE__ */ jsx11("span", {
79073
- fg: C.green,
79999
+ fg: C.cyan,
79074
80000
  attributes: A.bold,
79075
- children: "public key (free)"
80001
+ children: "oauth"
80002
+ }),
80003
+ /* @__PURE__ */ jsx11("span", {
80004
+ fg: C.fgMuted,
80005
+ children: " (used)"
79076
80006
  })
79077
80007
  ]
79078
80008
  }),
@@ -79094,7 +80024,7 @@ function ProviderDetail({
79094
80024
  })
79095
80025
  ]
79096
80026
  }),
79097
- hasKey && !selectedProvider.isLocal && !isPublicKey && !isOAuth && /* @__PURE__ */ jsxs10(Fragment7, {
80027
+ hasKey && !selectedProvider.isLocal && !isOAuth && /* @__PURE__ */ jsxs10(Fragment7, {
79098
80028
  children: [
79099
80029
  /* @__PURE__ */ jsx11("span", {
79100
80030
  fg: C.dim,
@@ -79108,7 +80038,7 @@ function ProviderDetail({
79108
80038
  hasEnvKey && /* @__PURE__ */ jsx11("span", {
79109
80039
  fg: C.green,
79110
80040
  attributes: A.bold,
79111
- children: isOpKey ? "1Password" : "env"
80041
+ children: isKcKey ? "keychain" : isOpKey ? "1Password" : "env"
79112
80042
  }),
79113
80043
  hasEnvKey && hasCfgKey && /* @__PURE__ */ jsx11("span", {
79114
80044
  fg: C.fgMuted,
@@ -79126,6 +80056,18 @@ function ProviderDetail({
79126
80056
  hasCfgKey && /* @__PURE__ */ jsx11("span", {
79127
80057
  fg: C.fgMuted,
79128
80058
  children: hasEnvKey ? " (shadowed)" : " (used)"
80059
+ }),
80060
+ hasKcKey && !isKcKey && /* @__PURE__ */ jsxs10(Fragment7, {
80061
+ children: [
80062
+ /* @__PURE__ */ jsx11("span", {
80063
+ fg: C.fgMuted,
80064
+ children: " + "
80065
+ }),
80066
+ /* @__PURE__ */ jsx11("span", {
80067
+ fg: C.fgMuted,
80068
+ children: "keychain (shadowed)"
80069
+ })
80070
+ ]
79129
80071
  })
79130
80072
  ]
79131
80073
  })
@@ -79302,8 +80244,6 @@ function ProvidersContent({
79302
80244
  keyDisplay = "local";
79303
80245
  } else if (isOauthOnly) {
79304
80246
  keyDisplay = "oauth\xB7\xB7\xB7";
79305
- } else if (auth === "public") {
79306
- keyDisplay = "free";
79307
80247
  } else if (auth === "cfg") {
79308
80248
  keyDisplay = maskKey2(config3.apiKeys?.[p.apiKeyEnvVar]);
79309
80249
  } else if (auth === "env" || auth === "e+c") {
@@ -80734,18 +81674,18 @@ function useProfileWizard(args) {
80734
81674
  setEditProfileValue("");
80735
81675
  return;
80736
81676
  }
80737
- const now = new Date().toISOString();
81677
+ const now2 = new Date().toISOString();
80738
81678
  if (profileScope === "project") {
80739
81679
  const localCfg = loadLocalConfig() ?? {
80740
81680
  version: "1.0.0",
80741
81681
  defaultProfile: "",
80742
81682
  profiles: {}
80743
81683
  };
80744
- localCfg.profiles[name] = { name, models: {}, createdAt: now, updatedAt: now };
81684
+ localCfg.profiles[name] = { name, models: {}, createdAt: now2, updatedAt: now2 };
80745
81685
  saveLocalConfig(localCfg);
80746
81686
  } else {
80747
81687
  const cfg = loadConfig();
80748
- cfg.profiles[name] = { name, models: {}, createdAt: now, updatedAt: now };
81688
+ cfg.profiles[name] = { name, models: {}, createdAt: now2, updatedAt: now2 };
80749
81689
  saveConfig(cfg);
80750
81690
  }
80751
81691
  refreshConfig();
@@ -81100,20 +82040,20 @@ function useRouteProbe(config3) {
81100
82040
  errorMessage: String(e instanceof Error ? e.message : e)
81101
82041
  }));
81102
82042
  const ms = Date.now() - startMs;
81103
- const ok = result.state === "live";
82043
+ const ok2 = result.state === "live";
81104
82044
  setProbeResults((prev) => prev.map((e, idx) => {
81105
82045
  if (idx === i)
81106
82046
  return {
81107
82047
  ...e,
81108
- status: ok ? "success" : "failed",
81109
- error: ok ? undefined : describeProbeState(result),
82048
+ status: ok2 ? "success" : "failed",
82049
+ error: ok2 ? undefined : describeProbeState(result),
81110
82050
  ms
81111
82051
  };
81112
- if (idx > i && ok && e.status !== "no_key")
82052
+ if (idx > i && ok2 && e.status !== "no_key")
81113
82053
  return { ...e, status: "skipped" };
81114
82054
  return e;
81115
82055
  }));
81116
- if (ok)
82056
+ if (ok2)
81117
82057
  break;
81118
82058
  }
81119
82059
  setProbeMode("done");
@@ -81153,6 +82093,50 @@ var init_useRouteProbe = __esm(() => {
81153
82093
  import { useKeyboard as useKeyboard2, useRenderer, useTerminalDimensions as useTerminalDimensions2 } from "@opentui/react";
81154
82094
  import { useCallback as useCallback3, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef4, useState as useState5 } from "react";
81155
82095
  import { jsx as jsx16, jsxs as jsxs15, Fragment as Fragment10 } from "@opentui/react/jsx-runtime";
82096
+ async function resolveOpEntrySecrets(entries, auth) {
82097
+ const secrets = {};
82098
+ for (const entry of entries) {
82099
+ if (entry.kind === "environment") {
82100
+ Object.assign(secrets, await withSdkRetry(() => readEnvironment(entry.value, { auth }), "tui:copy-keychain"));
82101
+ } else if (entry.kind === "glob" || isGlobImport(entry.value)) {
82102
+ Object.assign(secrets, await withSdkRetry(() => resolveGlobImport(entry.value, { auth }), "tui:copy-keychain"));
82103
+ } else {
82104
+ const r = await withSdkRetry(() => resolveSecrets({ T: entry.value }, { auth }), "tui:copy-keychain");
82105
+ const name = envNameFromOpRef(entry.value);
82106
+ if (name && r.T)
82107
+ secrets[name] = r.T;
82108
+ }
82109
+ }
82110
+ return secrets;
82111
+ }
82112
+ function writeSecretsToKeychain(secrets, alreadyStored) {
82113
+ const outcome = { created: 0, replaced: 0, skipped: [] };
82114
+ for (const [name, value] of Object.entries(secrets)) {
82115
+ if (!value)
82116
+ continue;
82117
+ try {
82118
+ writeKeychainSecret(name, value);
82119
+ if (alreadyStored.has(name))
82120
+ outcome.replaced++;
82121
+ else
82122
+ outcome.created++;
82123
+ } catch {
82124
+ outcome.skipped.push(name);
82125
+ }
82126
+ }
82127
+ return outcome;
82128
+ }
82129
+ function describeCopyOutcome({ created, replaced, skipped }) {
82130
+ if (created + replaced === 0) {
82131
+ return `Nothing copied to the Keychain${skipped.length > 0 ? ` \u2014 ${skipped.length} could not be stored` : ""}.`;
82132
+ }
82133
+ const parts = [`${created} new`];
82134
+ if (replaced > 0)
82135
+ parts.push(`${replaced} replaced`);
82136
+ if (skipped.length > 0)
82137
+ parts.push(`${skipped.length} skipped (${skipped.join(", ")})`);
82138
+ return `Copied to macOS Keychain: ${parts.join(" \xB7 ")}.`;
82139
+ }
81156
82140
  function App({ requestLogin } = {}) {
81157
82141
  const renderer = useRenderer();
81158
82142
  const { width, height: height2 } = useTerminalDimensions2();
@@ -81162,6 +82146,9 @@ function App({ requestLogin } = {}) {
81162
82146
  const [activeTab, setActiveTab] = useState5("providers");
81163
82147
  const [mode, setMode] = useState5("browse");
81164
82148
  const [inputValue, setInputValue] = useState5("");
82149
+ const [keychainVars, setKeychainVars] = useState5(new Set);
82150
+ const keychainSupported = useMemo2(() => isKeychainSupported(), []);
82151
+ const inputTargetRef = useRef4(null);
81165
82152
  const [routingPattern, setRoutingPattern] = useState5("");
81166
82153
  const [chainSelected, setChainSelected] = useState5(new Set);
81167
82154
  const [chainOrder, setChainOrder] = useState5([]);
@@ -81249,6 +82236,36 @@ function App({ requestLogin } = {}) {
81249
82236
  setBufStats(getBufferStats());
81250
82237
  setOpTick((t) => t + 1);
81251
82238
  }, []);
82239
+ const refreshKeychainVars = useCallback3(() => {
82240
+ if (!hasKeychainSource()) {
82241
+ setKeychainVars(new Set);
82242
+ return;
82243
+ }
82244
+ try {
82245
+ setKeychainVars(new Set(listKeychainVars()));
82246
+ } catch {
82247
+ setKeychainVars(new Set);
82248
+ }
82249
+ }, []);
82250
+ useEffect4(() => {
82251
+ if (!keychainSupported)
82252
+ return;
82253
+ let cancelled = false;
82254
+ (async () => {
82255
+ let hydrated = 0;
82256
+ try {
82257
+ hydrated = await hydrateKeychainIntoEnv();
82258
+ } catch {}
82259
+ if (cancelled)
82260
+ return;
82261
+ refreshKeychainVars();
82262
+ if (hydrated > 0)
82263
+ refreshConfig();
82264
+ })();
82265
+ return () => {
82266
+ cancelled = true;
82267
+ };
82268
+ }, [keychainSupported, refreshKeychainVars, refreshConfig]);
81252
82269
  const clearTestResult = useCallback3((provName) => {
81253
82270
  setTestResults((prev) => {
81254
82271
  if (!(provName in prev))
@@ -81264,11 +82281,12 @@ function App({ requestLogin } = {}) {
81264
82281
  const { editProfileValue, profileScope, suggestions, suggestionIndex, providerPickerIndex } = wizard;
81265
82282
  const hasCfgKey = !!config3.apiKeys?.[selectedProvider.apiKeyEnvVar];
81266
82283
  const hasEnvKey = !!process.env[selectedProvider.apiKeyEnvVar];
82284
+ const providerKeychainVars = useMemo2(() => [selectedProvider.apiKeyEnvVar, ...selectedProvider.aliases ?? []].filter((n) => !!n && keychainVars.has(n)), [selectedProvider, keychainVars]);
82285
+ const hasKcKey = providerKeychainVars.length > 0;
81267
82286
  const selectedAuthSource = providerAuthSource(selectedProvider, config3);
81268
- const selectedLocalRunning = selectedProviderIsLocal && localLiveness[selectedProvider.catalogName] === "running";
81269
- const hasKey = selectedAuthSource !== null || selectedLocalRunning;
81270
- const selectedPublicKey = selectedAuthSource === "public";
82287
+ const hasKey = providerIsReadyForDisplay(selectedProvider, config3, localLiveness);
81271
82288
  const isOpKey = hasEnvKey && isOpHydratedVar(selectedProvider.apiKeyEnvVar);
82289
+ const isKcKey = hasEnvKey && isKeychainHydratedVar(selectedProvider.apiKeyEnvVar);
81272
82290
  const cfgKeyMask = maskKey2(config3.apiKeys?.[selectedProvider.apiKeyEnvVar]);
81273
82291
  const envKeyMask = maskKey2(process.env[selectedProvider.apiKeyEnvVar]);
81274
82292
  const activeEndpointEnvVar = selectedProvider.endpointEnvVar;
@@ -81445,6 +82463,36 @@ function App({ requestLogin } = {}) {
81445
82463
  setOpBusy(false);
81446
82464
  }
81447
82465
  }, [acquireOpAuth]);
82466
+ const copyOpToKeychain = useCallback3(async (entries, label) => {
82467
+ if (!keychainSupported) {
82468
+ setStatusMsg("macOS Keychain is only available on macOS.");
82469
+ return;
82470
+ }
82471
+ const copyable = entries.filter((e) => e.kind !== "account");
82472
+ if (copyable.length === 0) {
82473
+ setStatusMsg("Nothing to copy \u2014 the account entry holds no secret.");
82474
+ return;
82475
+ }
82476
+ setOpBusy(true);
82477
+ setStatusMsg(`Resolving ${label} from 1Password\u2026`);
82478
+ try {
82479
+ const auth = await acquireOpAuth();
82480
+ const secrets = await resolveOpEntrySecrets(copyable, auth);
82481
+ const outcome = writeSecretsToKeychain(secrets, new Set(keychainVars));
82482
+ if (outcome.created + outcome.replaced > 0) {
82483
+ setKeychainEnabled(true);
82484
+ refreshKeychainVars();
82485
+ credentials.invalidate();
82486
+ invalidateProbeProxyHandlers();
82487
+ refreshConfig();
82488
+ }
82489
+ setStatusMsg(describeCopyOutcome(outcome));
82490
+ } catch (err) {
82491
+ setStatusMsg(err instanceof Error ? err.message : String(err));
82492
+ } finally {
82493
+ setOpBusy(false);
82494
+ }
82495
+ }, [acquireOpAuth, keychainSupported, keychainVars, refreshKeychainVars, refreshConfig]);
81448
82496
  const resetOpWizard = useCallback3(() => {
81449
82497
  setInputValue("");
81450
82498
  setOpPendingValue("");
@@ -81767,26 +82815,49 @@ function App({ requestLogin } = {}) {
81767
82815
  setMode("browse");
81768
82816
  return;
81769
82817
  }
82818
+ const target = inputTargetRef.current ?? selectedProvider;
81770
82819
  if (mode === "input_key") {
81771
- if (!selectedProvider.apiKeyEnvVar) {
81772
- setStatusMsg(`${selectedProvider.displayName} has no apiKeyEnvVar \u2014 cannot save key.`);
82820
+ if (!target.apiKeyEnvVar) {
82821
+ setStatusMsg(`${target.displayName} has no apiKeyEnvVar \u2014 cannot save key.`);
81773
82822
  } else {
81774
- setApiKey(selectedProvider.apiKeyEnvVar, val);
81775
- process.env[selectedProvider.apiKeyEnvVar] = val;
81776
- setStatusMsg(`Key saved for ${selectedProvider.displayName} (${selectedProvider.apiKeyEnvVar}).`);
82823
+ const envVar = target.apiKeyEnvVar;
82824
+ let saved = false;
82825
+ if (keychainSupported) {
82826
+ try {
82827
+ writeKeychainSecret(envVar, val);
82828
+ setKeychainEnabled(true);
82829
+ process.env[envVar] = val;
82830
+ recordKeychainHydratedVar(envVar);
82831
+ refreshKeychainVars();
82832
+ setStatusMsg(`Key saved to macOS Keychain for ${target.displayName} (${envVar}).`);
82833
+ saved = true;
82834
+ } catch (err) {
82835
+ setStatusMsg(`Keychain write failed (${err instanceof Error ? err.message : String(err)}) \u2014 saved to config.json instead.`);
82836
+ }
82837
+ }
82838
+ if (!saved) {
82839
+ setApiKey(envVar, val);
82840
+ process.env[envVar] = val;
82841
+ if (!keychainSupported) {
82842
+ setStatusMsg(`Key saved for ${target.displayName} (${envVar}).`);
82843
+ }
82844
+ }
82845
+ credentials.invalidate(target.catalogName);
82846
+ invalidateProbeProxyHandlers(target.catalogName);
82847
+ clearTestResult(target.name);
81777
82848
  }
81778
82849
  } else {
81779
- if (!selectedProvider.endpointEnvVar) {
81780
- setStatusMsg(`${selectedProvider.displayName} has no endpointEnvVar \u2014 cannot save URL.`);
82850
+ if (!target.endpointEnvVar) {
82851
+ setStatusMsg(`${target.displayName} has no endpointEnvVar \u2014 cannot save URL.`);
81781
82852
  } else {
81782
- setEndpoint(selectedProvider.endpointEnvVar, val);
81783
- process.env[selectedProvider.endpointEnvVar] = val;
81784
- setStatusMsg(`URL saved for ${selectedProvider.displayName} (${selectedProvider.endpointEnvVar}=${val}).`);
82853
+ setEndpoint(target.endpointEnvVar, val);
82854
+ process.env[target.endpointEnvVar] = val;
82855
+ setStatusMsg(`URL saved for ${target.displayName} (${target.endpointEnvVar}=${val}).`);
81785
82856
  }
81786
82857
  }
81787
- invalidateProbeProxyHandlers(selectedProvider.catalogName);
81788
- invalidateProbeDiscovery(selectedProvider.catalogName);
81789
- clearTestResult(selectedProvider.name);
82858
+ invalidateProbeProxyHandlers(target.catalogName);
82859
+ invalidateProbeDiscovery(target.catalogName);
82860
+ clearTestResult(target.name);
81790
82861
  refreshConfig();
81791
82862
  setInputValue("");
81792
82863
  setMode("browse");
@@ -82255,10 +83326,12 @@ function App({ requestLogin } = {}) {
82255
83326
  setStatusMsg(null);
82256
83327
  } else if (key.name === "s") {
82257
83328
  if (selectedProvider.apiKeyEnvVar) {
83329
+ inputTargetRef.current = selectedProvider;
82258
83330
  setInputValue("");
82259
83331
  setStatusMsg(null);
82260
83332
  setMode("input_key");
82261
83333
  } else if (selectedProvider.endpointEnvVar) {
83334
+ inputTargetRef.current = selectedProvider;
82262
83335
  setInputValue(activeEndpoint);
82263
83336
  setStatusMsg(null);
82264
83337
  setMode("input_endpoint");
@@ -82276,6 +83349,7 @@ function App({ requestLogin } = {}) {
82276
83349
  }
82277
83350
  refreshConfig();
82278
83351
  } else if (selectedProvider.endpointEnvVar) {
83352
+ inputTargetRef.current = selectedProvider;
82279
83353
  setInputValue(activeEndpoint);
82280
83354
  setStatusMsg(null);
82281
83355
  setMode("input_endpoint");
@@ -82284,6 +83358,7 @@ function App({ requestLogin } = {}) {
82284
83358
  }
82285
83359
  } else if (key.name === "u") {
82286
83360
  if (selectedProvider.endpointEnvVar) {
83361
+ inputTargetRef.current = selectedProvider;
82287
83362
  setInputValue(activeEndpoint);
82288
83363
  setStatusMsg(null);
82289
83364
  setMode("input_endpoint");
@@ -82292,24 +83367,43 @@ function App({ requestLogin } = {}) {
82292
83367
  }
82293
83368
  } else if (key.name === "x") {
82294
83369
  let changed = false;
83370
+ const removedFrom = [];
83371
+ let failureMsg = null;
82295
83372
  if (hasCfgKey) {
82296
83373
  removeApiKey(selectedProvider.apiKeyEnvVar);
83374
+ removedFrom.push("config.json");
82297
83375
  changed = true;
82298
83376
  }
83377
+ if (keychainSupported) {
83378
+ for (const name of providerKeychainVars) {
83379
+ try {
83380
+ if (deleteKeychainSecret(name)) {
83381
+ removedFrom.push(`macOS Keychain (${name})`);
83382
+ changed = true;
83383
+ }
83384
+ } catch (err) {
83385
+ failureMsg = `Keychain delete failed for ${name}: ${err instanceof Error ? err.message : String(err)}`;
83386
+ }
83387
+ }
83388
+ if (providerKeychainVars.length > 0)
83389
+ refreshKeychainVars();
83390
+ }
83391
+ if (changed && (isKcKey || isOpKey)) {
83392
+ delete process.env[selectedProvider.apiKeyEnvVar];
83393
+ }
82299
83394
  if (activeEndpointEnvVar && config3.endpoints?.[activeEndpointEnvVar]) {
82300
83395
  removeEndpoint(activeEndpointEnvVar);
82301
83396
  delete process.env[activeEndpointEnvVar];
82302
83397
  changed = true;
82303
83398
  }
82304
83399
  if (changed) {
83400
+ credentials.invalidate(selectedProvider.catalogName);
82305
83401
  invalidateProbeProxyHandlers(selectedProvider.catalogName);
82306
83402
  invalidateProbeDiscovery(selectedProvider.catalogName);
82307
83403
  clearTestResult(selectedProvider.name);
82308
83404
  refreshConfig();
82309
- setStatusMsg(`Stored config removed for ${selectedProvider.displayName}.`);
82310
- } else {
82311
- setStatusMsg("No stored config to remove.");
82312
83405
  }
83406
+ 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
83407
  } else if (key.name === "l") {
82314
83408
  const slug = selectedProvider.oauthSlug;
82315
83409
  if (!slug) {
@@ -82534,6 +83628,18 @@ function App({ requestLogin } = {}) {
82534
83628
  } else if (selectedOpEntry) {
82535
83629
  testOpEntry(selectedOpEntry);
82536
83630
  }
83631
+ } else if (key.raw === "C") {
83632
+ if (opEntries.length === 0) {
83633
+ setStatusMsg("No 1Password entries to copy.");
83634
+ } else {
83635
+ copyOpToKeychain(opEntries, `all ${opEntries.length} entries`);
83636
+ }
83637
+ } else if (key.name === "c") {
83638
+ if (opEntries.length === 0 || !selectedOpEntry) {
83639
+ setStatusMsg("No 1Password entry to copy.");
83640
+ } else {
83641
+ copyOpToKeychain([selectedOpEntry], selectedOpEntry.envName ?? selectedOpEntry.value);
83642
+ }
82537
83643
  } else if (key.name === "x") {
82538
83644
  if (opEntries.length === 0 || !selectedOpEntry) {
82539
83645
  setStatusMsg("No 1Password entry to remove.");
@@ -82670,7 +83776,9 @@ function App({ requestLogin } = {}) {
82670
83776
  hasKey,
82671
83777
  authSource: selectedAuthSource,
82672
83778
  isOpKey,
82673
- isPublicKey: selectedPublicKey,
83779
+ isKcKey,
83780
+ hasKcKey,
83781
+ keySaveTarget: keychainSupported ? "macOS Keychain" : "config.json",
82674
83782
  cfgKeyMask,
82675
83783
  envKeyMask,
82676
83784
  activeEndpoint,
@@ -82798,10 +83906,13 @@ function App({ requestLogin } = {}) {
82798
83906
  });
82799
83907
  }
82800
83908
  var init_App = __esm(() => {
83909
+ init_authority();
83910
+ init_keychain_source();
82801
83911
  init_op_source();
82802
83912
  init_profile_config();
82803
83913
  init_default_routing_rules();
82804
83914
  init_endpoint_registration();
83915
+ init_keychain();
82805
83916
  init_local_liveness();
82806
83917
  init_onepassword_config();
82807
83918
  init_onepassword();
@@ -83038,10 +84149,10 @@ function shouldHideIncidentalAnthropicKey(config3, env = process.env) {
83038
84149
  return false;
83039
84150
  return !wantsAnthropicApiBilling(config3, env);
83040
84151
  }
83041
- function hasResolvableAnthropicAuth(deps = {}) {
83042
- const env = deps.env ?? process.env;
83043
- const fileExists = deps.fileExists ?? existsSync30;
83044
- const keychainProbe = deps.keychainProbe ?? defaultKeychainAnthropicProbe;
84152
+ function hasResolvableAnthropicAuth(deps2 = {}) {
84153
+ const env = deps2.env ?? process.env;
84154
+ const fileExists = deps2.fileExists ?? existsSync30;
84155
+ const keychainProbe = deps2.keychainProbe ?? defaultKeychainAnthropicProbe;
83045
84156
  if (env.ANTHROPIC_API_KEY || env.ANTHROPIC_AUTH_TOKEN)
83046
84157
  return true;
83047
84158
  if (fileExists(join40(homedir35(), ".claude", ".credentials.json")))
@@ -83233,7 +84344,7 @@ function initializeTokenFile(tokenFilePath) {
83233
84344
  log(`[claude-runner] Could not initialize token file ${tokenFilePath}: ${e}`);
83234
84345
  }
83235
84346
  }
83236
- function cleanupStaleTokenFiles(dir, now = Date.now(), maxAgeMs = STALE_TOKEN_FILE_MS) {
84347
+ function cleanupStaleTokenFiles(dir, now2 = Date.now(), maxAgeMs = STALE_TOKEN_FILE_MS) {
83237
84348
  let removed = 0;
83238
84349
  let entries;
83239
84350
  try {
@@ -83241,7 +84352,7 @@ function cleanupStaleTokenFiles(dir, now = Date.now(), maxAgeMs = STALE_TOKEN_FI
83241
84352
  } catch {
83242
84353
  return 0;
83243
84354
  }
83244
- const cutoff = now - maxAgeMs;
84355
+ const cutoff = now2 - maxAgeMs;
83245
84356
  let scanned = 0;
83246
84357
  for (const name of entries) {
83247
84358
  if (scanned >= MAX_TOKEN_FILES_SCANNED)
@@ -83855,7 +84966,7 @@ function shouldWarmCatalog(args) {
83855
84966
  }
83856
84967
  return true;
83857
84968
  }
83858
- function classifyCatalogState(cache3, ttlHours, now) {
84969
+ function classifyCatalogState(cache3, ttlHours, now2) {
83859
84970
  if (cache3 === null)
83860
84971
  return "missing";
83861
84972
  if (cache3.entries.length === 0 && cache3.models.length === 0)
@@ -83863,7 +84974,7 @@ function classifyCatalogState(cache3, ttlHours, now) {
83863
84974
  const lastUpdatedMs = Date.parse(cache3.lastUpdated);
83864
84975
  if (Number.isNaN(lastUpdatedMs))
83865
84976
  return "missing";
83866
- const ageMs = now.getTime() - lastUpdatedMs;
84977
+ const ageMs = now2.getTime() - lastUpdatedMs;
83867
84978
  const ttlMs = ttlHours * 3600000;
83868
84979
  return ageMs < ttlMs ? "fresh" : "stale";
83869
84980
  }
@@ -83920,9 +85031,9 @@ async function warmCatalogIfNeeded(config3, opts) {
83920
85031
  }
83921
85032
  const ttlHoursRaw = opts?.ttlHours ?? Number.parseFloat(process.env.CLAUDISH_CATALOG_TTL_HOURS ?? "24");
83922
85033
  const ttlHours = Number.isFinite(ttlHoursRaw) && ttlHoursRaw > 0 ? ttlHoursRaw : 24;
83923
- const now = opts?.now ?? new Date;
85034
+ const now2 = opts?.now ?? new Date;
83924
85035
  const cache3 = readAllModelsCache();
83925
- const state = classifyCatalogState(cache3, ttlHours, now);
85036
+ const state = classifyCatalogState(cache3, ttlHours, now2);
83926
85037
  if (state === "fresh" && !config3.forceUpdate) {
83927
85038
  return "ok";
83928
85039
  }
@@ -83941,14 +85052,14 @@ async function warmCatalogIfNeeded(config3, opts) {
83941
85052
  return "ok";
83942
85053
  }
83943
85054
  if (state === "stale") {
83944
- const ageMs = now.getTime() - Date.parse(cache3.lastUpdated);
85055
+ const ageMs = now2.getTime() - Date.parse(cache3.lastUpdated);
83945
85056
  const ageStr = humanizeAge(ageMs);
83946
85057
  process.stderr.write(`WARNING: Catalog stale (${ageStr}). Using cached version. Run \`claudish --models-refresh\` to retry.
83947
85058
  `);
83948
85059
  return "warned";
83949
85060
  }
83950
85061
  if (state === "fresh") {
83951
- const ageMs = now.getTime() - Date.parse(cache3.lastUpdated);
85062
+ const ageMs = now2.getTime() - Date.parse(cache3.lastUpdated);
83952
85063
  const ageStr = humanizeAge(ageMs);
83953
85064
  process.stderr.write(`WARNING: Catalog refresh failed (cache age ${ageStr}). Using cached version.
83954
85065
  `);
@@ -85410,10 +86521,10 @@ function ResumePicker({ groups, onDone }) {
85410
86521
  const { fresh, stale, visibleGroups } = useMemo4(() => {
85411
86522
  const withSessions = groups.filter((g) => (listed.get(g.name)?.length ?? 0) > 0);
85412
86523
  const matching = filter ? withSessions.filter((g) => fuzzy(filter, g.name)) : withSessions;
85413
- const now = Date.now();
86524
+ const now2 = Date.now();
85414
86525
  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);
86526
+ 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));
86527
+ const st = matching.filter((g) => !g.current && now2 - g.lastActiveMs >= STALE_MS).sort(byRecency);
85417
86528
  return { fresh: f, stale: st, visibleGroups: [...f, ...st] };
85418
86529
  }, [groups, filter, listed]);
85419
86530
  const group = visibleGroups[Math.min(wtCursor, visibleGroups.length - 1)];
@@ -86342,7 +87453,7 @@ function renderSessionSummary(input) {
86342
87453
  const W2 = cardWidth();
86343
87454
  const inner = W2 - CHROME;
86344
87455
  const out = [];
86345
- const dim3 = (s) => paint(s, tokens.subtle);
87456
+ const dim4 = (s) => paint(s, tokens.subtle);
86346
87457
  const body = (s) => paint(s, tokens.text);
86347
87458
  const titleText = exitCode === 0 ? " session " : " session \xB7 failed ";
86348
87459
  const titleHex = exitCode === 0 ? tokens.accent : tokens.error;
@@ -86364,26 +87475,26 @@ function renderSessionSummary(input) {
86364
87475
  const gap = Math.max(1, inner - visibleWidth(left) - visibleWidth(right));
86365
87476
  row(left + " ".repeat(gap) + right);
86366
87477
  if (stats.providerName)
86367
- row(dim3(truncate3(stats.providerName, inner)));
87478
+ row(dim4(truncate3(stats.providerName, inner)));
86368
87479
  blank();
86369
87480
  const VALUE_W = 24;
86370
87481
  const barW = Math.max(12, inner - LABEL_W - VALUE_W);
86371
87482
  const dataRow = (label, bar, values) => {
86372
- row(dim3(padTo(label, LABEL_W)) + bar + padVisible2(values, VALUE_W, "right"));
87483
+ row(dim4(padTo(label, LABEL_W)) + bar + padVisible2(values, VALUE_W, "right"));
86373
87484
  };
86374
87485
  if (stats.contextUsed !== null && stats.contextWindow) {
86375
87486
  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)}`));
87487
+ dataRow("context", meter(pct, barW, ramps.load), body(padStartTo(`${Math.round(pct)}%`, 4)) + dim4(` ${compact(stats.inputTokens)}/${compact(stats.contextWindow)}`));
86377
87488
  }
86378
87489
  dataRow("tokens", stackedBar([
86379
87490
  { value: stats.inputTokens, color: C.blue },
86380
87491
  { value: stats.outputTokens, color: C.cyan }
86381
- ], barW), dim3("in ") + body(compact(stats.inputTokens)) + dim3(" out ") + body(compact(stats.outputTokens)));
87492
+ ], barW), dim4("in ") + body(compact(stats.inputTokens)) + dim4(" out ") + body(compact(stats.outputTokens)));
86382
87493
  if (!stats.isFree && stats.inputCostUsd + stats.outputCostUsd > 0) {
86383
87494
  dataRow("spend", stackedBar([
86384
87495
  { value: stats.inputCostUsd, color: C.blue },
86385
87496
  { value: stats.outputCostUsd, color: C.cyan }
86386
- ], barW), dim3("in ") + body(usd(stats.inputCostUsd)) + dim3(" out ") + body(usd(stats.outputCostUsd)));
87497
+ ], barW), dim4("in ") + body(usd(stats.inputCostUsd)) + dim4(" out ") + body(usd(stats.outputCostUsd)));
86387
87498
  }
86388
87499
  if (stats.toolCallTotal > 0) {
86389
87500
  const toolCols = toolColors();
@@ -86393,27 +87504,27 @@ function renderSessionSummary(input) {
86393
87504
  const segs = shown.map((t, i) => ({ value: t.count, color: toolCols[i] }));
86394
87505
  if (rest > 0)
86395
87506
  segs.push({ value: rest, color: other });
86396
- dataRow("tools", stackedBar(segs, barW), body(padStartTo(String(stats.toolCallTotal), 4)) + dim3(" calls"));
87507
+ dataRow("tools", stackedBar(segs, barW), body(padStartTo(String(stats.toolCallTotal), 4)) + dim4(" calls"));
86397
87508
  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)) {
87509
+ for (const line of wrapStyled(legend, dim4(" \xB7 "), inner - LABEL_W)) {
86399
87510
  row(" ".repeat(LABEL_W) + line);
86400
87511
  }
86401
87512
  }
86402
87513
  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") : ""));
87514
+ 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
87515
  for (const s of stats.savings) {
86405
87516
  const label = padTo(`vs ${s.label}`, LABEL_W);
86406
87517
  if (s.savedUsd >= 0) {
86407
87518
  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));
87519
+ dataRow(label, meter(pct, barW, ramps.savings), paint(padStartTo(`${Math.round(pct)}%`, 4), tokens.success) + dim4(" saved ") + paint(usd(s.savedUsd), tokens.success));
86409
87520
  } else {
86410
- dataRow(label, meter(0, barW, ramps.savings), dim3("over by ") + paint(usd(-s.savedUsd), tokens.error));
87521
+ dataRow(label, meter(0, barW, ramps.savings), dim4("over by ") + paint(usd(-s.savedUsd), tokens.error));
86411
87522
  }
86412
87523
  }
86413
87524
  out.push(paint(`\u2570${"\u2500".repeat(W2 - 2)}\u256F`, tokens.border));
86414
87525
  if (resumeId) {
86415
87526
  out.push("");
86416
- out.push(dim3("Resume this session with:"));
87527
+ out.push(dim4("Resume this session with:"));
86417
87528
  const modelFlag = resumeModelSpec ? `--model ${resumeModelSpec} ` : "";
86418
87529
  out.push(`claudish ${modelFlag}--resume ${resumeId}`);
86419
87530
  }
@@ -86471,6 +87582,7 @@ function classifyStartupKind() {
86471
87582
  "telemetry",
86472
87583
  "stats",
86473
87584
  "providers",
87585
+ "keychain",
86474
87586
  "login",
86475
87587
  "logout",
86476
87588
  "quota",
@@ -86590,6 +87702,7 @@ var isStatsCommand = firstPositional === "stats";
86590
87702
  var isConfigCommand = firstPositional === "config";
86591
87703
  var isServeCommand = firstPositional === "serve";
86592
87704
  var isProvidersCommand = firstPositional === "providers";
87705
+ var isKeychainCommand = firstPositional === "keychain";
86593
87706
  var isBehaviorCommand = firstPositional === "behavior";
86594
87707
  var isTeamCommand = firstPositional === "team";
86595
87708
  var isLoginCommand = firstPositional === "login";
@@ -86629,6 +87742,12 @@ if (isMcpMode) {
86629
87742
  console.error(`[claudish providers] ${e instanceof Error ? e.message : String(e)}`);
86630
87743
  process.exit(1);
86631
87744
  }));
87745
+ } else if (isKeychainCommand) {
87746
+ const keychainArgIndex = args.indexOf("keychain");
87747
+ Promise.resolve().then(() => (init_keychain_command(), exports_keychain_command)).then((m) => m.keychainCommand(args.slice(keychainArgIndex + 1)).catch((e) => {
87748
+ console.error(`[claudish keychain] ${e instanceof Error ? e.message : String(e)}`);
87749
+ process.exit(1);
87750
+ }));
86632
87751
  } else if (isLoginCommand) {
86633
87752
  const loginProviderArg = args.find((a, i) => i > args.indexOf("login") && !a.startsWith("-"));
86634
87753
  Promise.resolve().then(() => (init_auth_commands(), exports_auth_commands)).then((m) => m.loginCommand(loginProviderArg).catch(handlePromptExit));
@@ -86666,6 +87785,10 @@ if (isMcpMode) {
86666
87785
  });
86667
87786
  } else if (isConfigCommand) {
86668
87787
  traceSpan("startup:tui-import", () => Promise.resolve().then(() => (init_tui(), exports_tui))).then(async (m) => {
87788
+ await traceSpan("startup:theme-detect", async () => {
87789
+ const { detectAndSetThemeMode: detectAndSetThemeMode2 } = await Promise.resolve().then(() => (init_theme_mode(), exports_theme_mode));
87790
+ await detectAndSetThemeMode2();
87791
+ });
86669
87792
  const { credentials: credentials2 } = await Promise.resolve().then(() => (init_authority(), exports_authority));
86670
87793
  const { ensureEndpointsRegistered: ensureEndpointsRegistered2 } = await Promise.resolve().then(() => (init_endpoint_registration(), exports_endpoint_registration));
86671
87794
  ensureEndpointsRegistered2();