claudish 9.0.9 → 9.2.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 +383 -203
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -715,7 +715,7 @@ var init_onepassword_config = __esm(() => {
715
715
  });
716
716
 
717
717
  // src/version.ts
718
- var VERSION = "9.0.9";
718
+ var VERSION = "9.2.0";
719
719
 
720
720
  // src/logger.ts
721
721
  import { appendFile, existsSync as existsSync2, mkdirSync, readdirSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
@@ -40410,6 +40410,18 @@ function teamSlotActivity(teamSessionId) {
40410
40410
  }
40411
40411
  return out;
40412
40412
  }
40413
+ function teamSlotLiveBytes(teamSessionId) {
40414
+ const run = liveTeamRuns.get(teamSessionId);
40415
+ if (!run)
40416
+ return null;
40417
+ const out = {};
40418
+ for (const slotId of run.processes.keys()) {
40419
+ const bytes = run.liveBytesFor(slotId);
40420
+ if (bytes !== null)
40421
+ out[slotId] = bytes;
40422
+ }
40423
+ return out;
40424
+ }
40413
40425
  async function cancelTeamRun(teamSessionId, slotId) {
40414
40426
  const run = liveTeamRuns.get(teamSessionId);
40415
40427
  if (!run)
@@ -40862,6 +40874,7 @@ async function startModels(sessionPath, opts = {}) {
40862
40874
  processes,
40863
40875
  idleMsFor: (slotId) => runtimes.get(slotId)?.getIdleMs() ?? null,
40864
40876
  activityFor: (slotId) => runtimes.get(slotId)?.getActivity() ?? null,
40877
+ liveBytesFor: (slotId) => runtimes.get(slotId)?.getByteCount() ?? null,
40865
40878
  cancelledSlots
40866
40879
  });
40867
40880
  const done = (async () => {
@@ -47107,6 +47120,28 @@ function buildChildClaudeFlags(agent, claudeFlags) {
47107
47120
  const flags = [...named ? ["--agent", named] : [], ...cleaned];
47108
47121
  return flags.length > 0 ? flags : undefined;
47109
47122
  }
47123
+ function teamStatusNote(opts) {
47124
+ if (!opts.anyRunning)
47125
+ return;
47126
+ const trap = "outputSize is the size of the FINAL answer and is written once, when a slot " + "finishes. Every RUNNING slot therefore reports 0 however much work it has done: " + "it is NOT a progress signal, and 0 there does not mean the slot produced nothing.";
47127
+ if (!opts.live) {
47128
+ return `${trap} This run is not live in this process \u2014 it was started elsewhere, or this ` + "server restarted \u2014 so there are no liveness fields for it and the states above are " + "whatever was last written to status.json.";
47129
+ }
47130
+ return `${trap} Judge a running slot on three fields read TOGETHER: ` + "live_output_bytes_by_slot (answer bytes so far, the number outputSize will become), " + "idle_seconds_by_slot (seconds of silence) and activity_by_slot (what it is doing). " + "Silence in tool_executing is a build or test suite running, and is not a failure " + 'signal. Nothing cancels on your behalf \u2014 use mode:"cancel" if you decide to.';
47131
+ }
47132
+ function buildTeamStatusPayload(opts) {
47133
+ const { status, sessionPath, idle, activity, liveBytes } = opts;
47134
+ const anyRunning = Object.values(status.models).some((m) => m.state === "RUNNING");
47135
+ const note = teamStatusNote({ anyRunning, live: idle !== null });
47136
+ return {
47137
+ ...status,
47138
+ live_output_bytes_by_slot: liveBytes,
47139
+ idle_seconds_by_slot: idle,
47140
+ activity_by_slot: activity,
47141
+ ...note ? { note } : {},
47142
+ ...anyRunning ? {} : { summary: formatTeamResult(status, sessionPath) }
47143
+ };
47144
+ }
47110
47145
  function formatTeamResult(status, sessionPath) {
47111
47146
  const entries = Object.entries(status.models).sort(([a], [b]) => a.localeCompare(b));
47112
47147
  const failed = entries.filter(([, m]) => m.state === "FAILED" || m.state === "TIMEOUT" || m.state === "EMPTY");
@@ -47592,14 +47627,14 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
47592
47627
  });
47593
47628
  tools.push({
47594
47629
  name: "team",
47595
- description: "Run AI models on a task with anonymized outputs and optional blind judging. " + "Modes: 'run' (START the models and return a slot map immediately \u2014 it does NOT " + "wait), 'status' (per-slot state, plus how long each slot has been silent), " + "'cancel' (stop one slot or the whole run), 'judge' (blind-vote on existing " + "outputs), 'run-and-judge' (the blocking pipeline). " + "NO SLOT IS EVER KILLED ON A TIMER. A team slot is a full Claude Code session and " + "may work for a long time; a slot inside a build or test suite emits nothing for " + "minutes and is working, not stuck. Poll 'status', judge the silence against the " + "task you set, and use 'cancel' if you decide a slot is wedged.",
47630
+ description: "Run AI models on a task with anonymized outputs and optional blind judging. " + "Modes: 'run' (START the models and return a slot map immediately \u2014 it does NOT " + "wait), 'status' (per-slot state, plus live answer bytes, seconds of silence and " + "current activity per slot), " + "'cancel' (stop one slot or the whole run), 'judge' (blind-vote on existing " + "outputs), 'run-and-judge' (the blocking pipeline). " + "NO SLOT IS EVER KILLED ON A TIMER. A team slot is a full Claude Code session and " + "may work for a long time; a slot inside a build or test suite emits nothing for " + "minutes and is working, not stuck. Poll 'status', judge the silence against the " + "task you set, and use 'cancel' if you decide a slot is wedged. " + "JUDGING A RUNNING SLOT: read live_output_bytes_by_slot, idle_seconds_by_slot and " + "activity_by_slot. Do NOT use 'outputSize' \u2014 it is the size of the final answer, " + "written only when a slot finishes, so it is 0 for every running slot no matter how " + "much that slot has produced.",
47596
47631
  inputSchema: {
47597
47632
  type: "object",
47598
47633
  properties: {
47599
47634
  mode: {
47600
47635
  type: "string",
47601
47636
  enum: ["run", "judge", "run-and-judge", "status", "cancel"],
47602
- description: "Operation mode. 'run' STARTS the models and returns immediately with a " + "slot map \u2014 it does not wait. Poll 'status' for progress, then 'judge' once " + "the slots have finished. 'run-and-judge' is the blocking pipeline and holds " + "the call open for the whole run. 'cancel' stops one slot or the whole run."
47637
+ description: "Operation mode. 'run' STARTS the models and returns immediately with a " + "slot map \u2014 it does not wait. Poll 'status' for progress (read it from " + "live_output_bytes_by_slot and activity_by_slot, never from outputSize, " + "which stays 0 until a slot finishes), then 'judge' once the slots have " + "finished. 'run-and-judge' is the blocking pipeline and holds the call open " + "for the whole run. 'cancel' stops one slot or the whole run."
47603
47638
  },
47604
47639
  slot: {
47605
47640
  type: "string",
@@ -47750,21 +47785,17 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
47750
47785
  case "status": {
47751
47786
  const status = getStatus(resolved);
47752
47787
  const teamSessionId = resolved.split("/").filter(Boolean).pop() ?? "team";
47753
- const idle = teamSlotIdleSeconds(teamSessionId);
47754
- const settled = !Object.values(status.models).some((m) => m.state === "RUNNING");
47755
47788
  return {
47756
47789
  content: [
47757
47790
  {
47758
47791
  type: "text",
47759
- text: JSON.stringify({
47760
- ...status,
47761
- idle_seconds_by_slot: idle,
47762
- activity_by_slot: teamSlotActivity(teamSessionId),
47763
- ...idle ? {
47764
- note: "idle_seconds_by_slot is how long each slot has been silent; " + "read it against activity_by_slot. Silence in tool_executing " + "is a build or test suite running, and is not a failure " + 'signal. Nothing cancels on your behalf \u2014 use mode:"cancel" ' + "if you decide to."
47765
- } : {},
47766
- ...settled ? { summary: formatTeamResult(status, resolved) } : {}
47767
- }, null, 2)
47792
+ text: JSON.stringify(buildTeamStatusPayload({
47793
+ status,
47794
+ sessionPath: resolved,
47795
+ idle: teamSlotIdleSeconds(teamSessionId),
47796
+ activity: teamSlotActivity(teamSessionId),
47797
+ liveBytes: teamSlotLiveBytes(teamSessionId)
47798
+ }), null, 2)
47768
47799
  }
47769
47800
  ]
47770
47801
  };
@@ -56013,25 +56044,214 @@ var init_api_key_map = __esm(() => {
56013
56044
  };
56014
56045
  });
56015
56046
 
56047
+ // src/branding.ts
56048
+ function paint(line) {
56049
+ const { RESET, BOLD, CYAN, BLUE, DIM } = cliAnsi();
56050
+ if (!CYAN && !BLUE)
56051
+ return line;
56052
+ return line.replace(/\u2588+/g, (run) => `${BOLD}${CYAN}${run}${RESET}`).replace(/[\u2557\u255D\u2551\u2550\u2554\u255A]+/g, (run) => `${DIM}${BLUE}${run}${RESET}`);
56053
+ }
56054
+ function fitsWordmark(stream) {
56055
+ if (process.env.TERM === "dumb")
56056
+ return false;
56057
+ const columns = stream.columns ?? 0;
56058
+ return columns >= MIN_LOGO_WIDTH;
56059
+ }
56060
+ function wordmarkLines() {
56061
+ return WORDMARK.map((line) => ` ${paint(line)}`);
56062
+ }
56063
+ function compactLogo() {
56064
+ const { RESET, BOLD, CYAN, DIM } = cliAnsi();
56065
+ return ` ${BOLD}${CYAN}claudish${RESET} ${DIM}\xB7${RESET} ${TAGLINE}`;
56066
+ }
56067
+ function printLogo(stream, options = {}) {
56068
+ const { version, trailingBlankLine = true } = options;
56069
+ const { RESET, BOLD, DIM, GRAY } = cliAnsi();
56070
+ stream.write(`
56071
+ `);
56072
+ if (fitsWordmark(stream)) {
56073
+ for (const line of wordmarkLines())
56074
+ stream.write(`${line}
56075
+ `);
56076
+ stream.write(`
56077
+ ${GRAY}${TAGLINE}${RESET}
56078
+ `);
56079
+ } else {
56080
+ stream.write(`${compactLogo()}
56081
+ `);
56082
+ }
56083
+ if (version) {
56084
+ stream.write(` ${DIM}claudish version${RESET} ${BOLD}${version}${RESET}
56085
+ `);
56086
+ }
56087
+ if (trailingBlankLine)
56088
+ stream.write(`
56089
+ `);
56090
+ }
56091
+ var MIN_LOGO_WIDTH = 64, TAGLINE = "Claude Code. Any Model.", WORDMARK;
56092
+ var init_branding = __esm(() => {
56093
+ init_ansi();
56094
+ WORDMARK = [
56095
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557",
56096
+ "\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551",
56097
+ "\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551",
56098
+ "\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551",
56099
+ "\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551",
56100
+ " \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D"
56101
+ ];
56102
+ });
56103
+
56104
+ // src/update-checker.ts
56105
+ import { existsSync as existsSync28, mkdirSync as mkdirSync16, readFileSync as readFileSync28, unlinkSync as unlinkSync7, writeFileSync as writeFileSync17 } from "fs";
56106
+ import { homedir as homedir33, platform as platform2, tmpdir } from "os";
56107
+ import { join as join38 } from "path";
56108
+ function getCacheFilePath() {
56109
+ let cacheDir;
56110
+ if (isWindows) {
56111
+ const localAppData = process.env.LOCALAPPDATA || join38(homedir33(), "AppData", "Local");
56112
+ cacheDir = join38(localAppData, "claudish");
56113
+ } else {
56114
+ cacheDir = join38(homedir33(), ".cache", "claudish");
56115
+ }
56116
+ try {
56117
+ if (!existsSync28(cacheDir)) {
56118
+ mkdirSync16(cacheDir, { recursive: true });
56119
+ }
56120
+ return join38(cacheDir, "update-check.json");
56121
+ } catch {
56122
+ return join38(tmpdir(), "claudish-update-check.json");
56123
+ }
56124
+ }
56125
+ function readCache() {
56126
+ try {
56127
+ const cachePath = getCacheFilePath();
56128
+ if (!existsSync28(cachePath)) {
56129
+ return null;
56130
+ }
56131
+ const data = JSON.parse(readFileSync28(cachePath, "utf-8"));
56132
+ return data;
56133
+ } catch {
56134
+ return null;
56135
+ }
56136
+ }
56137
+ function writeCache(latestVersion) {
56138
+ try {
56139
+ const cachePath = getCacheFilePath();
56140
+ const data = {
56141
+ lastCheck: Date.now(),
56142
+ latestVersion
56143
+ };
56144
+ writeFileSync17(cachePath, JSON.stringify(data), "utf-8");
56145
+ } catch {}
56146
+ }
56147
+ function isCacheValid(cache) {
56148
+ const age = Date.now() - cache.lastCheck;
56149
+ return age < CACHE_MAX_AGE_MS;
56150
+ }
56151
+ function clearCache() {
56152
+ try {
56153
+ const cachePath = getCacheFilePath();
56154
+ if (existsSync28(cachePath)) {
56155
+ unlinkSync7(cachePath);
56156
+ }
56157
+ } catch {}
56158
+ }
56159
+ function compareVersions(v1, v2) {
56160
+ const parts1 = v1.replace(/^v/, "").split(".").map(Number);
56161
+ const parts2 = v2.replace(/^v/, "").split(".").map(Number);
56162
+ for (let i = 0;i < Math.max(parts1.length, parts2.length); i++) {
56163
+ const p1 = parts1[i] || 0;
56164
+ const p2 = parts2[i] || 0;
56165
+ if (p1 > p2)
56166
+ return 1;
56167
+ if (p1 < p2)
56168
+ return -1;
56169
+ }
56170
+ return 0;
56171
+ }
56172
+ async function fetchLatestVersionOrThrow(options = {}) {
56173
+ const { timeoutMs = 5000, retries = 0 } = options;
56174
+ let lastError = new Error("unknown error");
56175
+ for (let attempt = 0;attempt <= retries; attempt++) {
56176
+ const controller = new AbortController;
56177
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
56178
+ try {
56179
+ const response = await fetch(NPM_REGISTRY_URL, {
56180
+ signal: controller.signal,
56181
+ headers: { Accept: "application/json" }
56182
+ });
56183
+ if (!response.ok) {
56184
+ throw new Error(`npm registry returned HTTP ${response.status}`);
56185
+ }
56186
+ const data = await response.json();
56187
+ if (!data.version) {
56188
+ throw new Error("npm registry response contained no version field");
56189
+ }
56190
+ return data.version;
56191
+ } catch (error) {
56192
+ lastError = error instanceof Error && error.name === "AbortError" ? new Error(`request timed out after ${timeoutMs}ms`) : error instanceof Error ? error : new Error(String(error));
56193
+ if (attempt < retries) {
56194
+ await new Promise((resolve) => setTimeout(resolve, 300 * (attempt + 1)));
56195
+ }
56196
+ } finally {
56197
+ clearTimeout(timeout);
56198
+ }
56199
+ }
56200
+ throw lastError;
56201
+ }
56202
+ async function fetchLatestVersion(options = {}) {
56203
+ try {
56204
+ return await fetchLatestVersionOrThrow(options);
56205
+ } catch {
56206
+ return null;
56207
+ }
56208
+ }
56209
+ async function getLatestVersionCached(options = {}) {
56210
+ const cache = readCache();
56211
+ if (cache && isCacheValid(cache)) {
56212
+ return cache.latestVersion;
56213
+ }
56214
+ const latestVersion = await fetchLatestVersion(options);
56215
+ writeCache(latestVersion);
56216
+ return latestVersion;
56217
+ }
56218
+ function isUpgrade(latestVersion, currentVersion) {
56219
+ if (!latestVersion)
56220
+ return false;
56221
+ return compareVersions(latestVersion, currentVersion) > 0;
56222
+ }
56223
+ function formatUpdateNotice(currentVersion, latestVersion, options = {}) {
56224
+ const { hint = true } = options;
56225
+ const { RESET, BOLD, GREEN, CYAN, DIM } = cliAnsi();
56226
+ const runHint = hint ? ` ${DIM}Run:${RESET} ${BOLD}${CYAN}claudish update${RESET}` : "";
56227
+ return ` ${CYAN}\u250C${RESET} ${BOLD}Update available:${RESET} ${currentVersion} ${DIM}\u2192${RESET} ${GREEN}${latestVersion}${RESET}${runHint}`;
56228
+ }
56229
+ var isWindows, NPM_REGISTRY_URL = "https://registry.npmjs.org/claudish/latest", CACHE_MAX_AGE_MS;
56230
+ var init_update_checker = __esm(() => {
56231
+ init_ansi();
56232
+ isWindows = platform2() === "win32";
56233
+ CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
56234
+ });
56235
+
56016
56236
  // src/cli.ts
56017
56237
  import {
56018
56238
  copyFileSync as copyFileSync2,
56019
- existsSync as existsSync28,
56020
- mkdirSync as mkdirSync16,
56021
- readFileSync as readFileSync28,
56239
+ existsSync as existsSync29,
56240
+ mkdirSync as mkdirSync17,
56241
+ readFileSync as readFileSync29,
56022
56242
  readdirSync as readdirSync7,
56023
- unlinkSync as unlinkSync7,
56024
- writeFileSync as writeFileSync17
56243
+ unlinkSync as unlinkSync8,
56244
+ writeFileSync as writeFileSync18
56025
56245
  } from "fs";
56026
- import { homedir as homedir33 } from "os";
56027
- import { dirname as dirname11, join as join38 } from "path";
56246
+ import { homedir as homedir34 } from "os";
56247
+ import { dirname as dirname11, join as join39 } from "path";
56028
56248
  import { fileURLToPath as fileURLToPath3 } from "url";
56029
56249
  function getVersion3() {
56030
56250
  return VERSION;
56031
56251
  }
56032
56252
  function clearAllModelCaches() {
56033
- const cacheDir = join38(homedir33(), ".claudish");
56034
- if (!existsSync28(cacheDir))
56253
+ const cacheDir = join39(homedir34(), ".claudish");
56254
+ if (!existsSync29(cacheDir))
56035
56255
  return;
56036
56256
  const cachePatterns = ["pricing-cache.json", "recommended-models-cache.json"];
56037
56257
  let cleared = 0;
@@ -56039,7 +56259,7 @@ function clearAllModelCaches() {
56039
56259
  const files = readdirSync7(cacheDir);
56040
56260
  for (const file of files) {
56041
56261
  if (cachePatterns.includes(file)) {
56042
- unlinkSync7(join38(cacheDir, file));
56262
+ unlinkSync8(join39(cacheDir, file));
56043
56263
  cleared++;
56044
56264
  }
56045
56265
  }
@@ -56310,7 +56530,7 @@ async function parseArgs(args) {
56310
56530
  } else if (arg === "--cost-reset") {
56311
56531
  config.resetCosts = true;
56312
56532
  } else if (arg === "--version") {
56313
- printVersion();
56533
+ await printVersion();
56314
56534
  process.exit(0);
56315
56535
  } else if (arg === "--help" || arg === "-h") {
56316
56536
  printHelp2();
@@ -56496,15 +56716,15 @@ Usage: claudish --models --provider <slug>`);
56496
56716
  });
56497
56717
  config.resolvedDefaultProvider = resolved;
56498
56718
  if (resolved.legacyAutoPromoted && !config.quiet) {
56499
- const markerFile = join38(homedir33(), ".claudish", ".legacy-litellm-hint-shown");
56500
- if (!existsSync28(markerFile)) {
56719
+ const markerFile = join39(homedir34(), ".claudish", ".legacy-litellm-hint-shown");
56720
+ if (!existsSync29(markerFile)) {
56501
56721
  const hint = buildLegacyHint(resolved);
56502
56722
  if (hint) {
56503
56723
  console.error(hint);
56504
56724
  }
56505
56725
  try {
56506
- mkdirSync16(dirname11(markerFile), { recursive: true });
56507
- writeFileSync17(markerFile, new Date().toISOString(), "utf-8");
56726
+ mkdirSync17(dirname11(markerFile), { recursive: true });
56727
+ writeFileSync18(markerFile, new Date().toISOString(), "utf-8");
56508
56728
  } catch {}
56509
56729
  }
56510
56730
  }
@@ -56797,8 +57017,20 @@ Recommended Models (last updated: ${lastUpdated}):
56797
57017
  console.log(" claudish --models-top --models-refresh (refresh from Firebase)");
56798
57018
  console.log("");
56799
57019
  }
56800
- function printVersion() {
56801
- console.log(`claudish version ${VERSION}`);
57020
+ async function printVersion() {
57021
+ if (!process.stdout.isTTY) {
57022
+ console.log(`claudish version ${VERSION}`);
57023
+ return;
57024
+ }
57025
+ await Promise.resolve().then(() => init_branding());
57026
+ printLogo(process.stdout, { version: VERSION, trailingBlankLine: false });
57027
+ await Promise.resolve().then(() => init_update_checker());
57028
+ const latestVersion = await getLatestVersionCached({ timeoutMs: 1500 });
57029
+ if (latestVersion && isUpgrade(latestVersion, VERSION)) {
57030
+ console.log("");
57031
+ console.log(formatUpdateNotice(VERSION, latestVersion));
57032
+ }
57033
+ console.log("");
56802
57034
  }
56803
57035
  async function probeModelRouting(models, jsonOutput, options = { live: true, timeoutMs: 40000 }) {
56804
57036
  const userRoutingKeys = new Set([
@@ -57629,8 +57861,8 @@ ${h("MORE INFO")}
57629
57861
  }
57630
57862
  function printAIAgentGuide() {
57631
57863
  try {
57632
- const guidePath = join38(__dirname3, "../AI_AGENT_GUIDE.md");
57633
- const guideContent = readFileSync28(guidePath, "utf-8");
57864
+ const guidePath = join39(__dirname3, "../AI_AGENT_GUIDE.md");
57865
+ const guideContent = readFileSync29(guidePath, "utf-8");
57634
57866
  console.log(guideContent);
57635
57867
  } catch (error) {
57636
57868
  console.error("Error reading AI Agent Guide:");
@@ -57646,19 +57878,19 @@ async function initializeClaudishSkill() {
57646
57878
  console.log(`\uD83D\uDD27 Initializing Claudish skill in current project...
57647
57879
  `);
57648
57880
  const cwd = process.cwd();
57649
- const claudeDir = join38(cwd, ".claude");
57650
- const skillsDir = join38(claudeDir, "skills");
57651
- const claudishSkillDir = join38(skillsDir, "claudish-usage");
57652
- const skillFile = join38(claudishSkillDir, "SKILL.md");
57653
- if (existsSync28(skillFile)) {
57881
+ const claudeDir = join39(cwd, ".claude");
57882
+ const skillsDir = join39(claudeDir, "skills");
57883
+ const claudishSkillDir = join39(skillsDir, "claudish-usage");
57884
+ const skillFile = join39(claudishSkillDir, "SKILL.md");
57885
+ if (existsSync29(skillFile)) {
57654
57886
  console.log("\u2705 Claudish skill already installed at:");
57655
57887
  console.log(` ${skillFile}
57656
57888
  `);
57657
57889
  console.log("\uD83D\uDCA1 To reinstall, delete the file and run 'claudish --init' again.");
57658
57890
  return;
57659
57891
  }
57660
- const sourceSkillPath = join38(__dirname3, "../skills/claudish-usage/SKILL.md");
57661
- if (!existsSync28(sourceSkillPath)) {
57892
+ const sourceSkillPath = join39(__dirname3, "../skills/claudish-usage/SKILL.md");
57893
+ if (!existsSync29(sourceSkillPath)) {
57662
57894
  console.error("\u274C Error: Claudish skill file not found in installation.");
57663
57895
  console.error(` Expected at: ${sourceSkillPath}`);
57664
57896
  console.error(`
@@ -57667,16 +57899,16 @@ async function initializeClaudishSkill() {
57667
57899
  process.exit(1);
57668
57900
  }
57669
57901
  try {
57670
- if (!existsSync28(claudeDir)) {
57671
- mkdirSync16(claudeDir, { recursive: true });
57902
+ if (!existsSync29(claudeDir)) {
57903
+ mkdirSync17(claudeDir, { recursive: true });
57672
57904
  console.log("\uD83D\uDCC1 Created .claude/ directory");
57673
57905
  }
57674
- if (!existsSync28(skillsDir)) {
57675
- mkdirSync16(skillsDir, { recursive: true });
57906
+ if (!existsSync29(skillsDir)) {
57907
+ mkdirSync17(skillsDir, { recursive: true });
57676
57908
  console.log("\uD83D\uDCC1 Created .claude/skills/ directory");
57677
57909
  }
57678
- if (!existsSync28(claudishSkillDir)) {
57679
- mkdirSync16(claudishSkillDir, { recursive: true });
57910
+ if (!existsSync29(claudishSkillDir)) {
57911
+ mkdirSync17(claudishSkillDir, { recursive: true });
57680
57912
  console.log("\uD83D\uDCC1 Created .claude/skills/claudish-usage/ directory");
57681
57913
  }
57682
57914
  copyFileSync2(sourceSkillPath, skillFile);
@@ -57756,141 +57988,6 @@ var init_cli = __esm(() => {
57756
57988
  __dirname3 = dirname11(__filename3);
57757
57989
  });
57758
57990
 
57759
- // src/update-checker.ts
57760
- import { existsSync as existsSync29, mkdirSync as mkdirSync17, readFileSync as readFileSync29, unlinkSync as unlinkSync8, writeFileSync as writeFileSync18 } from "fs";
57761
- import { homedir as homedir34, platform as platform2, tmpdir } from "os";
57762
- import { join as join39 } from "path";
57763
- function getCacheFilePath() {
57764
- let cacheDir;
57765
- if (isWindows) {
57766
- const localAppData = process.env.LOCALAPPDATA || join39(homedir34(), "AppData", "Local");
57767
- cacheDir = join39(localAppData, "claudish");
57768
- } else {
57769
- cacheDir = join39(homedir34(), ".cache", "claudish");
57770
- }
57771
- try {
57772
- if (!existsSync29(cacheDir)) {
57773
- mkdirSync17(cacheDir, { recursive: true });
57774
- }
57775
- return join39(cacheDir, "update-check.json");
57776
- } catch {
57777
- return join39(tmpdir(), "claudish-update-check.json");
57778
- }
57779
- }
57780
- function readCache() {
57781
- try {
57782
- const cachePath = getCacheFilePath();
57783
- if (!existsSync29(cachePath)) {
57784
- return null;
57785
- }
57786
- const data = JSON.parse(readFileSync29(cachePath, "utf-8"));
57787
- return data;
57788
- } catch {
57789
- return null;
57790
- }
57791
- }
57792
- function writeCache(latestVersion) {
57793
- try {
57794
- const cachePath = getCacheFilePath();
57795
- const data = {
57796
- lastCheck: Date.now(),
57797
- latestVersion
57798
- };
57799
- writeFileSync18(cachePath, JSON.stringify(data), "utf-8");
57800
- } catch {}
57801
- }
57802
- function isCacheValid(cache) {
57803
- const age = Date.now() - cache.lastCheck;
57804
- return age < CACHE_MAX_AGE_MS;
57805
- }
57806
- function clearCache() {
57807
- try {
57808
- const cachePath = getCacheFilePath();
57809
- if (existsSync29(cachePath)) {
57810
- unlinkSync8(cachePath);
57811
- }
57812
- } catch {}
57813
- }
57814
- function compareVersions(v1, v2) {
57815
- const parts1 = v1.replace(/^v/, "").split(".").map(Number);
57816
- const parts2 = v2.replace(/^v/, "").split(".").map(Number);
57817
- for (let i = 0;i < Math.max(parts1.length, parts2.length); i++) {
57818
- const p1 = parts1[i] || 0;
57819
- const p2 = parts2[i] || 0;
57820
- if (p1 > p2)
57821
- return 1;
57822
- if (p1 < p2)
57823
- return -1;
57824
- }
57825
- return 0;
57826
- }
57827
- async function fetchLatestVersionOrThrow(options = {}) {
57828
- const { timeoutMs = 5000, retries = 0 } = options;
57829
- let lastError = new Error("unknown error");
57830
- for (let attempt = 0;attempt <= retries; attempt++) {
57831
- const controller = new AbortController;
57832
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
57833
- try {
57834
- const response = await fetch(NPM_REGISTRY_URL, {
57835
- signal: controller.signal,
57836
- headers: { Accept: "application/json" }
57837
- });
57838
- if (!response.ok) {
57839
- throw new Error(`npm registry returned HTTP ${response.status}`);
57840
- }
57841
- const data = await response.json();
57842
- if (!data.version) {
57843
- throw new Error("npm registry response contained no version field");
57844
- }
57845
- return data.version;
57846
- } catch (error) {
57847
- lastError = error instanceof Error && error.name === "AbortError" ? new Error(`request timed out after ${timeoutMs}ms`) : error instanceof Error ? error : new Error(String(error));
57848
- if (attempt < retries) {
57849
- await new Promise((resolve) => setTimeout(resolve, 300 * (attempt + 1)));
57850
- }
57851
- } finally {
57852
- clearTimeout(timeout);
57853
- }
57854
- }
57855
- throw lastError;
57856
- }
57857
- async function fetchLatestVersion(options = {}) {
57858
- try {
57859
- return await fetchLatestVersionOrThrow(options);
57860
- } catch {
57861
- return null;
57862
- }
57863
- }
57864
- async function checkForUpdates(currentVersion, options = {}) {
57865
- const { quiet = false } = options;
57866
- let latestVersion = null;
57867
- const cache = readCache();
57868
- if (cache && isCacheValid(cache)) {
57869
- latestVersion = cache.latestVersion;
57870
- } else {
57871
- latestVersion = await fetchLatestVersion();
57872
- writeCache(latestVersion);
57873
- }
57874
- if (!latestVersion) {
57875
- return;
57876
- }
57877
- if (compareVersions(latestVersion, currentVersion) <= 0) {
57878
- return;
57879
- }
57880
- if (!quiet) {
57881
- const { RESET, BOLD, GREEN, CYAN, DIM } = cliAnsi();
57882
- console.error("");
57883
- console.error(` ${CYAN}\u250C${RESET} ${BOLD}Update available:${RESET} ${currentVersion} ${DIM}\u2192${RESET} ${GREEN}${latestVersion}${RESET} ${DIM}Run:${RESET} ${BOLD}${CYAN}claudish update${RESET}`);
57884
- console.error("");
57885
- }
57886
- }
57887
- var isWindows, NPM_REGISTRY_URL = "https://registry.npmjs.org/claudish/latest", CACHE_MAX_AGE_MS;
57888
- var init_update_checker = __esm(() => {
57889
- init_ansi();
57890
- isWindows = platform2() === "win32";
57891
- CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
57892
- });
57893
-
57894
57991
  // src/update-command.ts
57895
57992
  import { execSync as execSync2 } from "child_process";
57896
57993
  function refreshAnsi2() {
@@ -58083,6 +58180,24 @@ async function resolveLatestVersion() {
58083
58180
  return { version: viaNpm };
58084
58181
  return { error: fetchError };
58085
58182
  }
58183
+ async function performUpdate() {
58184
+ refreshAnsi2();
58185
+ const installInfo = detectInstallationMethod();
58186
+ if (installInfo.method === "unknown") {
58187
+ printManualInstructions();
58188
+ return { status: "unsupported" };
58189
+ }
58190
+ const command = getUpdateCommand(installInfo.method);
58191
+ console.log(`
58192
+ ${DIM}Updating...${RESET}
58193
+ `);
58194
+ const success = await executeUpdate(command);
58195
+ if (success) {
58196
+ clearCache();
58197
+ return { status: "updated", method: installInfo.method };
58198
+ }
58199
+ return { status: "failed", method: installInfo.method };
58200
+ }
58086
58201
  async function updateCommand() {
58087
58202
  refreshAnsi2();
58088
58203
  const currentVersion = getVersion3();
@@ -58111,19 +58226,13 @@ async function updateCommand() {
58111
58226
  process.exit(0);
58112
58227
  }
58113
58228
  console.log(` ${BOLD}claudish${RESET} ${YELLOW}v${currentVersion}${RESET} ${DIM}\u2192${RESET} ${GREEN}v${latestVersion}${RESET} ${DIM}(${installInfo.method})${RESET}`);
58114
- if (installInfo.method === "unknown") {
58115
- printManualInstructions();
58229
+ const outcome = await performUpdate();
58230
+ if (outcome.status === "unsupported") {
58116
58231
  process.exit(1);
58117
58232
  }
58118
- const command = getUpdateCommand(installInfo.method);
58119
- console.log(`
58120
- ${DIM}Updating...${RESET}
58121
- `);
58122
- const success = await executeUpdate(command);
58123
- if (success) {
58233
+ if (outcome.status === "updated") {
58124
58234
  console.log(`
58125
58235
  ${GREEN}\u2713${RESET} ${BOLD}Updated successfully${RESET}`);
58126
- clearCache();
58127
58236
  const changelog = await fetchChangelog(currentVersion, latestVersion);
58128
58237
  displayChangelog(changelog);
58129
58238
  console.log("");
@@ -66293,6 +66402,67 @@ function createDiagOutput(options) {
66293
66402
  }
66294
66403
  var init_diag_output = () => {};
66295
66404
 
66405
+ // src/update-prompt.ts
66406
+ async function confirm(question) {
66407
+ const { createInterface } = await import("readline");
66408
+ return new Promise((resolve) => {
66409
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
66410
+ let answered = false;
66411
+ rl.question(question, (value) => {
66412
+ answered = true;
66413
+ rl.close();
66414
+ const normalized = value.trim().toLowerCase();
66415
+ resolve(!(normalized === "n" || normalized === "no"));
66416
+ });
66417
+ rl.on("close", () => {
66418
+ if (!answered)
66419
+ resolve(false);
66420
+ });
66421
+ });
66422
+ }
66423
+ async function checkForUpdatesInteractive(currentVersion, options = {}) {
66424
+ const { quiet = false, canPrompt = true } = options;
66425
+ const latestVersion = await getLatestVersionCached();
66426
+ if (!latestVersion || !isUpgrade(latestVersion, currentVersion)) {
66427
+ return "continue";
66428
+ }
66429
+ if (quiet) {
66430
+ return "continue";
66431
+ }
66432
+ const { RESET, BOLD, GREEN, CYAN, DIM, YELLOW } = cliAnsi();
66433
+ if (!canPrompt || !process.stdin.isTTY) {
66434
+ console.error("");
66435
+ console.error(formatUpdateNotice(currentVersion, latestVersion));
66436
+ console.error("");
66437
+ return "continue";
66438
+ }
66439
+ console.error("");
66440
+ console.error(formatUpdateNotice(currentVersion, latestVersion, { hint: false }));
66441
+ console.error("");
66442
+ const accepted = await confirm(` ${BOLD}Update now?${RESET} ${DIM}[Y/n]${RESET} `);
66443
+ if (!accepted) {
66444
+ console.error(` ${DIM}Skipped. Run${RESET} ${BOLD}${CYAN}claudish update${RESET} ${DIM}when you are ready.${RESET}
66445
+ `);
66446
+ return "continue";
66447
+ }
66448
+ const outcome = await performUpdate();
66449
+ if (outcome.status === "updated") {
66450
+ console.error("");
66451
+ console.error(` ${GREEN}\u2713${RESET} ${BOLD}Updated to ${latestVersion}.${RESET} ${DIM}This process is still running ${currentVersion}.${RESET}`);
66452
+ console.error(` ${DIM}Run${RESET} ${BOLD}${CYAN}claudish${RESET} ${DIM}again to start.${RESET}`);
66453
+ console.error("");
66454
+ return "restart-required";
66455
+ }
66456
+ console.error(` ${YELLOW}Continuing on ${currentVersion}.${RESET}
66457
+ `);
66458
+ return "continue";
66459
+ }
66460
+ var init_update_prompt = __esm(() => {
66461
+ init_ansi();
66462
+ init_update_checker();
66463
+ init_update_command();
66464
+ });
66465
+
66296
66466
  // src/launcher/catalog-warm.ts
66297
66467
  function shouldWarmCatalog(args) {
66298
66468
  if (args.skipModelsUpdate)
@@ -68647,7 +68817,7 @@ function bg(hex) {
68647
68817
  const [r, g, b] = rgb(hex);
68648
68818
  return `\x1B[48;2;${r};${g};${b}m`;
68649
68819
  }
68650
- function paint(text, hex, bold = false) {
68820
+ function paint2(text, hex, bold = false) {
68651
68821
  return `${bold ? BOLD4 : ""}${fg(hex)}${text}${RESET3}`;
68652
68822
  }
68653
68823
  function meter(pct, width, ramp = ramps.load) {
@@ -68655,7 +68825,7 @@ function meter(pct, width, ramp = ramps.load) {
68655
68825
  if (!Number.isFinite(cells) || cells <= 0)
68656
68826
  return "";
68657
68827
  if (Number.isNaN(pct))
68658
- return paint(NODATA2.repeat(cells), tokens.dead);
68828
+ return paint2(NODATA2.repeat(cells), tokens.dead);
68659
68829
  const cols = rampFor(cells, ramp);
68660
68830
  const filled = fillCells(pct, cells);
68661
68831
  let out = "";
@@ -68676,7 +68846,7 @@ function stackedBar(segments, width) {
68676
68846
  return "";
68677
68847
  const split = splitCells(segments.map((s) => s.value), cells);
68678
68848
  if (split.reduce((a, b) => a + b, 0) === 0)
68679
- return paint(TRACK2.repeat(cells), tokens.border);
68849
+ return paint2(TRACK2.repeat(cells), tokens.border);
68680
68850
  let out = "";
68681
68851
  for (let i = 0;i < split.length; i++) {
68682
68852
  const n = split[i];
@@ -68801,14 +68971,14 @@ function renderSessionSummary(input) {
68801
68971
  const W = cardWidth();
68802
68972
  const inner = W - CHROME;
68803
68973
  const out = [];
68804
- const dim = (s) => paint(s, tokens.subtle);
68805
- const body = (s) => paint(s, tokens.text);
68974
+ const dim = (s) => paint2(s, tokens.subtle);
68975
+ const body = (s) => paint2(s, tokens.text);
68806
68976
  const titleText = exitCode === 0 ? " session " : " session \xB7 failed ";
68807
68977
  const titleHex = exitCode === 0 ? tokens.accent : tokens.error;
68808
68978
  const rule = "\u2500".repeat(Math.max(0, W - 2 - visibleWidth(titleText) - 1));
68809
- out.push(paint("\u256D\u2500", tokens.border) + paint(titleText, titleHex) + paint(rule + "\u256E", tokens.border));
68979
+ out.push(paint2("\u256D\u2500", tokens.border) + paint2(titleText, titleHex) + paint2(rule + "\u256E", tokens.border));
68810
68980
  const row = (s) => {
68811
- out.push(`${paint("\u2502", tokens.border)} ${padVisible2(s, inner)} ${paint("\u2502", tokens.border)}`);
68981
+ out.push(`${paint2("\u2502", tokens.border)} ${padVisible2(s, inner)} ${paint2("\u2502", tokens.border)}`);
68812
68982
  };
68813
68983
  const blank = () => row("");
68814
68984
  const chips = [badge(truncate3(modelSpec, 34), tokens.accent)];
@@ -68858,13 +69028,13 @@ function renderSessionSummary(input) {
68858
69028
  if (rest > 0)
68859
69029
  segs.push({ value: rest, color: area(other) });
68860
69030
  dataRow("tools", stackedBar(segs, barW), body(padStartTo(String(stats.toolCallTotal), 4)) + dim(" calls"));
68861
- const legend = shown.map((t, i) => paint(`${t.name} ${t.count}`, toolCols[i])).concat(rest > 0 ? [paint(`other ${rest}`, other)] : []);
69031
+ const legend = shown.map((t, i) => paint2(`${t.name} ${t.count}`, toolCols[i])).concat(rest > 0 ? [paint2(`other ${rest}`, other)] : []);
68862
69032
  for (const line of wrapStyled(legend, dim(" \xB7 "), inner - LABEL_W)) {
68863
69033
  row(" ".repeat(LABEL_W) + line);
68864
69034
  }
68865
69035
  }
68866
69036
  blank();
68867
- row(dim(padTo("cost", LABEL_W)) + paint(stats.isFree ? "free" : usd(stats.costUsd), tokens.text, true) + (stats.isEstimated && !stats.isFree ? dim(" estimated") : ""));
69037
+ row(dim(padTo("cost", LABEL_W)) + paint2(stats.isFree ? "free" : usd(stats.costUsd), tokens.text, true) + (stats.isEstimated && !stats.isFree ? dim(" estimated") : ""));
68868
69038
  const dearest = Math.max(0, ...stats.savings.map((s) => s.baselineUsd));
68869
69039
  for (const s of stats.savings) {
68870
69040
  const label = padTo(`vs ${s.label}`, LABEL_W);
@@ -68873,10 +69043,10 @@ function renderSessionSummary(input) {
68873
69043
  const share = dearest > 0 ? s.savedUsd / dearest * 100 : 0;
68874
69044
  dataRow(label, meter(share, barW, areaRamp(ramps.volume)), dim(`${padStartTo(`${Math.round(pct)}%`, 4)} saved `) + body(usd(s.savedUsd)));
68875
69045
  } else {
68876
- dataRow(label, meter(0, barW, areaRamp(ramps.volume)), dim("over by ") + paint(usd(-s.savedUsd), tokens.error));
69046
+ dataRow(label, meter(0, barW, areaRamp(ramps.volume)), dim("over by ") + paint2(usd(-s.savedUsd), tokens.error));
68877
69047
  }
68878
69048
  }
68879
- out.push(paint(`\u2570${"\u2500".repeat(W - 2)}\u256F`, tokens.border));
69049
+ out.push(paint2(`\u2570${"\u2500".repeat(W - 2)}\u256F`, tokens.border));
68880
69050
  if (resumeId) {
68881
69051
  out.push("");
68882
69052
  out.push(dim("Resume this session with:"));
@@ -69170,7 +69340,8 @@ async function runCli() {
69170
69340
  await Promise.resolve().then(() => init_diag_output());
69171
69341
  await Promise.resolve().then(() => init_port_manager());
69172
69342
  await Promise.resolve().then(() => init_proxy_server());
69173
- await Promise.resolve().then(() => init_update_checker());
69343
+ await Promise.resolve().then(() => init_update_prompt());
69344
+ await Promise.resolve().then(() => init_branding());
69174
69345
  await Promise.resolve().then(() => init_catalog_warm());
69175
69346
  endImports();
69176
69347
  async function readStdin() {
@@ -69241,6 +69412,9 @@ Team Status`);
69241
69412
  }
69242
69413
  process.exit(0);
69243
69414
  }
69415
+ if (cliConfig.interactive && !cliConfig.quiet && process.stderr.isTTY) {
69416
+ printLogo(process.stderr, { version: getVersion3() });
69417
+ }
69244
69418
  const rawArgs = process.argv.slice(2);
69245
69419
  const explicitNoAutoApprove = rawArgs.includes("--no-auto-approve");
69246
69420
  if (cliConfig.autoApprove && !explicitNoAutoApprove && !cliConfig.stdin && cliConfig.interactive) {
@@ -69295,7 +69469,13 @@ Team Status`);
69295
69469
  }
69296
69470
  }
69297
69471
  if (cliConfig.interactive && !cliConfig.jsonOutput) {
69298
- await traceSpan("startup:update-check", () => checkForUpdates(getVersion3(), { quiet: cliConfig.quiet }));
69472
+ const updateStep = await traceSpan("startup:update-check", () => checkForUpdatesInteractive(getVersion3(), {
69473
+ quiet: cliConfig.quiet,
69474
+ canPrompt: !cliConfig.stdin
69475
+ }), { mayIncludeUserPrompt: true });
69476
+ if (updateStep === "restart-required") {
69477
+ process.exit(0);
69478
+ }
69299
69479
  }
69300
69480
  if (!await traceSpan("startup:claude-detect", () => checkClaudeInstalled())) {
69301
69481
  console.error("Error: Claude Code CLI not found");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "9.0.9",
3
+ "version": "9.2.0",
4
4
  "description": "Run Claude Code with any model - OpenRouter, Ollama, LM Studio & local models",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -60,10 +60,10 @@
60
60
  "ai"
61
61
  ],
62
62
  "optionalDependencies": {
63
- "@claudish/magmux-darwin-arm64": "9.0.9",
64
- "@claudish/magmux-darwin-x64": "9.0.9",
65
- "@claudish/magmux-linux-arm64": "9.0.9",
66
- "@claudish/magmux-linux-x64": "9.0.9"
63
+ "@claudish/magmux-darwin-arm64": "9.2.0",
64
+ "@claudish/magmux-darwin-x64": "9.2.0",
65
+ "@claudish/magmux-linux-arm64": "9.2.0",
66
+ "@claudish/magmux-linux-x64": "9.2.0"
67
67
  },
68
68
  "author": "Jack Rudenko <i@madappgang.com>",
69
69
  "license": "MIT",