@spinalfluid/ultrac 0.1.0 → 0.1.5

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 (3) hide show
  1. package/README.md +20 -4
  2. package/dist/cli.js +424 -160
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -35,7 +35,7 @@ For local testing before a registry release, build and install the verified tarb
35
35
  pnpm check
36
36
  pnpm verify:install
37
37
  npm pack
38
- npm install -g ./spinalfluid-ultrac-0.1.0.tgz
38
+ npm install -g ./spinalfluid-ultrac-0.1.5.tgz
39
39
  ultrac login
40
40
  ```
41
41
 
@@ -53,7 +53,17 @@ ultrac logout # drop local credentials and revoke the minted key
53
53
 
54
54
  For interactive use, `ultrac login` opens `https://ultrac.io/cli/login` (or the same path on a local
55
55
  site). Approve the CLI using your existing website account; the site mints a key and the CLI stores
56
- it in `~/.ultrac/auth.json` with `0600` permissions. You do not need to create or paste a key.
56
+ it in `~/.ultrac/auth.json`. The browser return is bound to the initiating CLI with loopback,
57
+ state, and PKCE checks. Confirm that the browser and terminal show the same reference code; you do
58
+ not enter that code during the normal flow. If the browser cannot reach the loopback callback, the
59
+ CLI accepts the short-lived, one-time completion code from the failed
60
+ localhost callback URL and exchanges it with the same PKCE verifier. This recovery path never asks
61
+ for a permanent API key. `--no-browser` prints the authorization URL instead of opening it.
62
+
63
+ Credentials use an owner-only `0600` file because the package intentionally avoids native keychain
64
+ dependencies. On filesystems that do not enforce Unix permission bits, protect the account and home
65
+ directory with the operating system's access controls. You do not need to create or paste a key for
66
+ normal browser sign-in.
57
67
 
58
68
  For scripts and CI, do not run the browser sign-in flow. Create a dedicated API key in the account's
59
69
  `/keys` page, store it in your CI secret manager, and expose it to the command as `ULTRAC_API_KEY`.
@@ -62,8 +72,10 @@ for an existing key, and `ultrac login --key <key>` verifies and stores one; use
62
72
  intentionally need a local saved credential, not as the default CI setup. Avoid putting secrets in
63
73
  shell history, command arguments, project config, or source control. An approved Ultrac account is required for model access.
64
74
 
65
- The minted key is checked with `GET <base>/models` before it is saved: a 401/403 is refused and
66
- nothing is written; if the API cannot be reached the key is saved anyway with a warning.
75
+ Every key is checked with `GET <base>/models` before it is saved. A browser-issued key is stored only
76
+ after the API accepts it; a 401/403 or unavailable verification writes nothing. For compatibility,
77
+ an explicitly supplied `--key` or `--manual` key may still be saved with a warning when verification
78
+ is unavailable, but a rejected key is never saved.
67
79
 
68
80
  ### Cosmic 1, in a repo
69
81
 
@@ -129,6 +141,10 @@ ultrac "Find the main entry points and summarize the architecture"
129
141
  Inside a session:
130
142
 
131
143
  - Type to talk to Ultrac; it gathers repository context first and can call tools.
144
+ - You can type another message while Ultrac is working. It is queued and inserted after the current
145
+ tool batch or completed model response, before the next model request. Slash commands wait for
146
+ the main prompt instead of entering model context.
147
+ - Ctrl-C stops active work at a safe boundary. At an idle prompt, Ctrl-C exits and saves the session.
132
148
  - Gated tools (file writes, shell, network) prompt for approval: `once` / `always` / `no`.
133
149
  - Coding commands: `/plan [task]`, `/coding`, `/review [focus]`, `/diff [--staged]`,
134
150
  `/test [instructions]`, `/init`, `/max-steps [number]`.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import chalk14 from "chalk";
4
+ import chalk15 from "chalk";
5
5
  import { Command } from "commander";
6
6
 
7
7
  // src/commands/ask.ts
@@ -11186,16 +11186,16 @@ function normalizeFilter(filter, workspace) {
11186
11186
  function tokenizeCommand(command) {
11187
11187
  const tokens = [];
11188
11188
  let current = "";
11189
- let quote = null;
11189
+ let quote2 = null;
11190
11190
  let started = false;
11191
11191
  for (const character of command) {
11192
- if (quote) {
11193
- if (character === quote) quote = null;
11192
+ if (quote2) {
11193
+ if (character === quote2) quote2 = null;
11194
11194
  else current += character;
11195
11195
  continue;
11196
11196
  }
11197
11197
  if (character === '"' || character === "'") {
11198
- quote = character;
11198
+ quote2 = character;
11199
11199
  started = true;
11200
11200
  continue;
11201
11201
  }
@@ -12900,6 +12900,7 @@ import { stdin as stdin3 } from "process";
12900
12900
 
12901
12901
  // src/auth/browser-login.ts
12902
12902
  import { hostname as osHostname } from "os";
12903
+ import { randomInt } from "crypto";
12903
12904
 
12904
12905
  // src/auth/loopback.ts
12905
12906
  import http from "http";
@@ -13097,6 +13098,7 @@ function errorMessage(error) {
13097
13098
 
13098
13099
  // src/auth/browser-login.ts
13099
13100
  var BROWSER_LOGIN_REQUEST_TIMEOUT_MS = 15e3;
13101
+ var BROWSER_LOGIN_CALLBACK_TIMEOUT_MS = 2 * 60 * 1e3;
13100
13102
  var BrowserLoginError = class extends Error {
13101
13103
  code;
13102
13104
  constructor(code, message) {
@@ -13110,16 +13112,20 @@ async function loginWithBrowser(input) {
13110
13112
  const hostname = (input.hostname ?? osHostname()).trim();
13111
13113
  const fetchImpl = input.fetchImpl ?? ((url, init) => fetch(url, init));
13112
13114
  const requestTimeoutMs = input.requestTimeoutMs ?? BROWSER_LOGIN_REQUEST_TIMEOUT_MS;
13115
+ const callbackTimeoutMs = input.timeoutMs ?? BROWSER_LOGIN_CALLBACK_TIMEOUT_MS;
13113
13116
  const pkce = generatePkce();
13114
13117
  const state = generateOAuthState();
13115
13118
  const log = input.onInfo ?? (() => void 0);
13119
+ const startLoopback = input.startLoopback ?? startLoopbackServer;
13116
13120
  let loopback;
13121
+ let redirectUri;
13117
13122
  try {
13118
- loopback = await startLoopbackServer({ expectedState: state, timeoutMs: input.timeoutMs });
13123
+ loopback = await startLoopback({ expectedState: state, timeoutMs: callbackTimeoutMs });
13124
+ redirectUri = loopback.redirectUri;
13119
13125
  } catch (error) {
13120
- throw new BrowserLoginError(
13121
- "loopback_bind",
13122
- `Could not bind a local callback server (${error instanceof Error ? error.message : String(error)}). Use \`ultrac login --manual\` to paste a key.`
13126
+ redirectUri = `http://127.0.0.1:${randomInt(49152, 65536)}/callback`;
13127
+ log(
13128
+ `Could not bind a local callback server (${error instanceof Error ? error.message : String(error)}).`
13123
13129
  );
13124
13130
  }
13125
13131
  try {
@@ -13129,7 +13135,7 @@ async function loginWithBrowser(input) {
13129
13135
  {
13130
13136
  client: "ultrac",
13131
13137
  hostname,
13132
- redirect_uri: loopback.redirectUri,
13138
+ redirect_uri: redirectUri,
13133
13139
  state,
13134
13140
  code_challenge: pkce.challenge,
13135
13141
  code_challenge_method: pkce.method
@@ -13153,26 +13159,27 @@ async function loginWithBrowser(input) {
13153
13159
  if (input.openBrowser(verificationUrl) === false) {
13154
13160
  log("If a browser did not open, paste the URL above.");
13155
13161
  }
13156
- const callback = await loopback.wait;
13157
- if (!callback.ok) {
13158
- if (callback.error === "access_denied") {
13159
- throw new BrowserLoginError("access_denied", "Sign-in was denied in the browser.");
13160
- }
13161
- if (callback.error === "expired_token") {
13162
- throw new BrowserLoginError("expired_token", "Timed out waiting for the browser. Run ultrac login again.");
13162
+ let code;
13163
+ if (loopback) {
13164
+ const callback = await loopback.wait;
13165
+ if (callback.ok) {
13166
+ code = callback.code;
13167
+ } else {
13168
+ if (callback.error === "access_denied") {
13169
+ throw new BrowserLoginError("access_denied", "Sign-in was denied in the browser.");
13170
+ }
13171
+ code = await requestRecoveryCode(input, log, callback.description);
13163
13172
  }
13164
- throw new BrowserLoginError(
13165
- callback.error,
13166
- callback.description ?? `Browser login failed (${callback.error}).`
13167
- );
13173
+ } else {
13174
+ code = await requestRecoveryCode(input, log);
13168
13175
  }
13169
13176
  const exchanged = await postJson(
13170
13177
  fetchImpl,
13171
13178
  `${origin}/api/cli/login/exchange`,
13172
13179
  {
13173
- code: callback.code,
13180
+ code,
13174
13181
  code_verifier: pkce.verifier,
13175
- redirect_uri: loopback.redirectUri
13182
+ redirect_uri: redirectUri
13176
13183
  },
13177
13184
  requestTimeoutMs
13178
13185
  );
@@ -13193,7 +13200,32 @@ async function loginWithBrowser(input) {
13193
13200
  label: exchanged.body.label
13194
13201
  };
13195
13202
  } finally {
13196
- await loopback.close().catch(() => void 0);
13203
+ await loopback?.close().catch(() => void 0);
13204
+ }
13205
+ }
13206
+ async function requestRecoveryCode(input, log, reason) {
13207
+ if (!input.requestCompletionCode) {
13208
+ throw new BrowserLoginError(
13209
+ "callback_failed",
13210
+ `${reason ? `${reason} ` : ""}Browser completion did not reach this terminal. Run ultrac login again.`
13211
+ );
13212
+ }
13213
+ log("Browser completion did not reach this terminal.");
13214
+ log("Copy the short-lived completion code (or the failed localhost callback URL) from the browser.");
13215
+ const supplied = (await input.requestCompletionCode()).trim();
13216
+ const code = completionCodeFromInput(supplied);
13217
+ if (!code) {
13218
+ throw new BrowserLoginError("missing_code", "No completion code was provided. Nothing was saved.");
13219
+ }
13220
+ return code;
13221
+ }
13222
+ function completionCodeFromInput(value) {
13223
+ if (!value) return "";
13224
+ try {
13225
+ const url = new URL(value);
13226
+ return url.searchParams.get("code")?.trim() ?? "";
13227
+ } catch {
13228
+ return value;
13197
13229
  }
13198
13230
  }
13199
13231
  async function postJson(fetchImpl, url, body, timeoutMs) {
@@ -13376,7 +13408,8 @@ async function browserLogin(baseUrl, shouldOpen) {
13376
13408
  baseUrl,
13377
13409
  hostname: osHostname2(),
13378
13410
  openBrowser: (url) => shouldOpen ? openBrowser(url) : false,
13379
- onInfo: (line) => console.log(line)
13411
+ onInfo: (line) => console.log(line),
13412
+ requestCompletionCode: () => promptSecret("Paste the one-time completion code: ")
13380
13413
  });
13381
13414
  } catch (error) {
13382
13415
  if (error instanceof BrowserLoginError) throw new Error(error.message);
@@ -13400,7 +13433,10 @@ async function verifyAndStore(input) {
13400
13433
  } else {
13401
13434
  console.log(chalk4.yellow("unverified"));
13402
13435
  console.log(chalk4.yellow(` Could not verify the key (${check.message}).`));
13403
- console.log(chalk4.yellow(" Saving it anyway; run `ultrac doctor` once the API is reachable."));
13436
+ if (!input.pasted) {
13437
+ throw new Error("Nothing was saved. Browser sign-in keys are stored only after the API accepts them.");
13438
+ }
13439
+ console.log(chalk4.yellow(" Saving the explicitly supplied key; run `ultrac doctor` once the API is reachable."));
13404
13440
  }
13405
13441
  const path = await writeCredentials({
13406
13442
  apiKey: input.apiKey,
@@ -14529,7 +14565,7 @@ async function readStdin() {
14529
14565
  // src/commands/repl.ts
14530
14566
  import { createInterface as createInterface3 } from "readline";
14531
14567
  import { stdin as stdin5, stdout as stdout5 } from "process";
14532
- import chalk9 from "chalk";
14568
+ import chalk10 from "chalk";
14533
14569
 
14534
14570
  // src/config/budget.ts
14535
14571
  function applyBudgetFlags(flags, config) {
@@ -14929,6 +14965,21 @@ Acknowledge that you have the context and wait for the user's first request.`
14929
14965
  this.fileProvenance.set(contextMessage, unknownContextProvenance());
14930
14966
  this.started = true;
14931
14967
  }
14968
+ appendQueuedMessages(handlers) {
14969
+ const queued = handlers.drainQueuedMessages?.() ?? [];
14970
+ let appended = 0;
14971
+ for (const raw of queued) {
14972
+ const content = raw.trim();
14973
+ if (!content) continue;
14974
+ const message = { role: "user", content };
14975
+ this.messages.push(message);
14976
+ this.pinnedMessages.push(message);
14977
+ this.userConstraints.push({ kind: "message", content });
14978
+ handlers.onQueuedMessage?.(content);
14979
+ appended += 1;
14980
+ }
14981
+ return appended;
14982
+ }
14932
14983
  async ask(input, handlers = {}) {
14933
14984
  if (!this.started) await this.start();
14934
14985
  if (!this.firstTask) this.firstTask = input;
@@ -14947,6 +14998,7 @@ Acknowledge that you have the context and wait for the user's first request.`
14947
14998
  handlers.onInterrupted?.();
14948
14999
  break;
14949
15000
  }
15001
+ this.appendQueuedMessages(handlers);
14950
15002
  const system = buildSystemPrompt(
14951
15003
  this.advertisedTools(),
14952
15004
  getEffectiveModePermissions(this.activeMode, this.permissions),
@@ -15029,7 +15081,8 @@ Acknowledge that you have the context and wait for the user's first request.`
15029
15081
  handlers.onInterrupted?.();
15030
15082
  break;
15031
15083
  }
15032
- if (parsed.done && parsed.toolCalls.length === 0) {
15084
+ const queuedMessages = this.appendQueuedMessages(handlers);
15085
+ if (parsed.done && parsed.toolCalls.length === 0 && queuedMessages === 0) {
15033
15086
  stopReason = "completed";
15034
15087
  break;
15035
15088
  }
@@ -15395,6 +15448,21 @@ ${context}`
15395
15448
  this.fileProvenance.set(contextMessage, unknownContextProvenance());
15396
15449
  this.started = true;
15397
15450
  }
15451
+ appendQueuedMessages(handlers) {
15452
+ const queued = handlers.drainQueuedMessages?.() ?? [];
15453
+ let appended = 0;
15454
+ for (const raw of queued) {
15455
+ const content = raw.trim();
15456
+ if (!content) continue;
15457
+ const message = { role: "user", content };
15458
+ this.messages.push(message);
15459
+ this.pinnedMessages.push(message);
15460
+ this.userConstraints.push({ kind: "message", content });
15461
+ handlers.onQueuedMessage?.(content);
15462
+ appended += 1;
15463
+ }
15464
+ return appended;
15465
+ }
15398
15466
  async ask(input, handlers = {}) {
15399
15467
  if (!this.started) await this.start();
15400
15468
  if (!this.firstTask) this.firstTask = input;
@@ -15412,6 +15480,7 @@ ${context}`
15412
15480
  handlers.onInterrupted?.();
15413
15481
  break;
15414
15482
  }
15483
+ this.appendQueuedMessages(handlers);
15415
15484
  this.refreshSystemPrompt();
15416
15485
  const tools = toToolSchemas(this.advertisedTools());
15417
15486
  const turn = this.steps.length + 1;
@@ -15462,9 +15531,17 @@ ${context}`
15462
15531
  });
15463
15532
  }
15464
15533
  if (!response.toolCalls.length) {
15465
- stopReason = "completed";
15466
15534
  this.steps.push(step);
15467
- break;
15535
+ if (handlers.signal?.aborted) {
15536
+ stopReason = "aborted";
15537
+ handlers.onInterrupted?.();
15538
+ break;
15539
+ }
15540
+ if (this.appendQueuedMessages(handlers) === 0) {
15541
+ stopReason = "completed";
15542
+ break;
15543
+ }
15544
+ continue;
15468
15545
  }
15469
15546
  for (const call of response.toolCalls) {
15470
15547
  if (handlers.signal?.aborted) break;
@@ -15508,6 +15585,7 @@ ${context}`
15508
15585
  handlers.onInterrupted?.();
15509
15586
  break;
15510
15587
  }
15588
+ this.appendQueuedMessages(handlers);
15511
15589
  }
15512
15590
  } catch (error) {
15513
15591
  stopReason = "error";
@@ -16212,6 +16290,149 @@ var Spinner = class {
16212
16290
  }
16213
16291
  };
16214
16292
 
16293
+ // src/ui/tool-activity.ts
16294
+ var VERBS = {
16295
+ read_file: "Read",
16296
+ read_many_files: "Read",
16297
+ write_file: "Edit",
16298
+ replace_in_file: "Edit",
16299
+ multi_edit: "Edit",
16300
+ apply_patch: "Apply patch",
16301
+ search_files: "Search",
16302
+ list_files: "List files",
16303
+ inspect_project: "Inspect project",
16304
+ repo_map: "Map repository",
16305
+ context_bundle: "Gather context",
16306
+ run_shell: "Run",
16307
+ run_package_script: "Run package script",
16308
+ run_tests: "Run tests",
16309
+ diagnostics: "Check diagnostics",
16310
+ task: "Delegate",
16311
+ fetch_url: "Fetch",
16312
+ git_status: "Check Git status",
16313
+ git_diff: "Read Git diff",
16314
+ git_log: "Read Git log",
16315
+ git_show: "Read Git object",
16316
+ git_blame: "Read Git blame",
16317
+ git_commit: "Commit changes",
16318
+ git_push: "Push changes"
16319
+ };
16320
+ function toolActivityLabel(call) {
16321
+ const verb = VERBS[call.tool] ?? humanize(call.tool);
16322
+ const target = toolTarget(call);
16323
+ return target ? `${verb} ${target}` : verb;
16324
+ }
16325
+ function toolFailurePreview(result) {
16326
+ if (result.ok) return void 0;
16327
+ const detail = result.content.split("\n").map((line) => line.trim()).filter(Boolean).slice(0, 2).join(" \xB7 ");
16328
+ return detail ? truncate2(detail, 240) : `${humanize(result.tool)} failed`;
16329
+ }
16330
+ function toolTarget(call) {
16331
+ const input = call.input;
16332
+ const path = stringValue(input.path);
16333
+ if (path) return compact2(path);
16334
+ const paths = Array.isArray(input.paths) ? input.paths.filter((value) => typeof value === "string") : [];
16335
+ if (paths.length === 1) return compact2(paths[0]);
16336
+ if (paths.length > 1) return `${paths.length} files`;
16337
+ if (call.tool === "run_shell") return compact2(stringValue(input.command), 72);
16338
+ if (call.tool === "run_package_script") return compact2(stringValue(input.script), 50);
16339
+ if (call.tool === "search_files") return quote(stringValue(input.query) || stringValue(input.pattern));
16340
+ if (call.tool === "fetch_url") return compact2(stringValue(input.url), 72);
16341
+ if (call.tool === "task") return compact2(stringValue(input.description) || stringValue(input.task), 64);
16342
+ if (call.tool === "git_show") return compact2(stringValue(input.ref), 40);
16343
+ if (call.tool === "git_commit") return quote(stringValue(input.message), 56);
16344
+ return "";
16345
+ }
16346
+ function stringValue(value) {
16347
+ return typeof value === "string" ? value.trim() : "";
16348
+ }
16349
+ function compact2(value, max = 64) {
16350
+ if (!value) return "";
16351
+ return truncate2(value.replace(/\s+/g, " "), max);
16352
+ }
16353
+ function quote(value, max = 48) {
16354
+ return value ? `\u201C${compact2(value, max)}\u201D` : "";
16355
+ }
16356
+ function truncate2(value, max) {
16357
+ return value.length > max ? `${value.slice(0, Math.max(1, max - 1))}\u2026` : value;
16358
+ }
16359
+ function humanize(value) {
16360
+ const words = value.replace(/[_-]+/g, " ").trim();
16361
+ return words ? `${words[0].toUpperCase()}${words.slice(1)}` : "Tool";
16362
+ }
16363
+
16364
+ // src/ui/welcome.ts
16365
+ import chalk9, { Chalk } from "chalk";
16366
+ var LOGO = [
16367
+ "\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557",
16368
+ "\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D",
16369
+ "\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 ",
16370
+ "\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 ",
16371
+ "\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
16372
+ " \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D"
16373
+ ];
16374
+ function renderWelcome(info, options = {}) {
16375
+ const columns = normalizeColumns(options.columns);
16376
+ const paint = new Chalk({ level: options.color === false ? 0 : chalk9.level });
16377
+ const cardWidth = Math.min(88, Math.max(42, columns));
16378
+ const innerWidth = cardWidth - 4;
16379
+ const wide = columns >= 64;
16380
+ const lines = [];
16381
+ if (wide) {
16382
+ lines.push("");
16383
+ for (let index = 0; index < LOGO.length; index += 1) {
16384
+ const color = index < 2 ? paint.hex("#38BDF8") : index < 4 ? paint.hex("#22D3EE") : paint.hex("#A78BFA");
16385
+ lines.push(` ${color(LOGO[index])}`);
16386
+ }
16387
+ } else {
16388
+ lines.push("", ` ${paint.bold.hex("#22D3EE")("ULTRAC")} ${paint.gray(`v${info.version}`)}`);
16389
+ }
16390
+ const topLabel = wide ? ` ultrac v${info.version} ` : " session ";
16391
+ const topFill = Math.max(0, cardWidth - topLabel.length - 2);
16392
+ lines.push(paint.cyan(`\u256D${topLabel}${"\u2500".repeat(topFill)}\u256E`));
16393
+ lines.push(cardLine(`${info.model} via ${info.providerName}`, innerWidth, paint.bold));
16394
+ lines.push(cardLine(`${info.mode} \xB7 ${info.protocol}`, innerWidth, paint.white));
16395
+ lines.push(cardLine(shortenMiddle(info.workspace, innerWidth), innerWidth, paint.gray));
16396
+ lines.push(paint.cyan(`\u251C${"\u2500".repeat(cardWidth - 2)}\u2524`));
16397
+ lines.push(cardLine(info.budget, innerWidth, paint.gray));
16398
+ lines.push(cardLine(`logs ${shortenMiddle(info.diagnosticPath, Math.max(8, innerWidth - 5))}`, innerWidth, paint.gray));
16399
+ lines.push(paint.cyan(`\u2570${"\u2500".repeat(cardWidth - 2)}\u256F`));
16400
+ lines.push(
16401
+ wide ? `${paint.gray(" /help commands")} ${paint.dim("\xB7")} ${paint.gray("/exit quit")} ${paint.dim("\xB7")} ${paint.gray("Ctrl-C interrupt")}` : paint.gray(" /help \xB7 /exit \xB7 Ctrl-C")
16402
+ );
16403
+ return lines.join("\n");
16404
+ }
16405
+ function renderComposer(columns = 80, color = true) {
16406
+ const paint = new Chalk({ level: color ? chalk9.level : 0 });
16407
+ const width = Math.min(88, Math.max(28, normalizeColumns(columns)));
16408
+ const label = " message ";
16409
+ const fill = Math.max(1, width - label.length - 2);
16410
+ return {
16411
+ frame: `
16412
+ ${paint.cyan(`\u256D\u2500${label}${"\u2500".repeat(fill)}`)}
16413
+ ${paint.cyan("\u2502")}`,
16414
+ prompt: paint.bold.hex("#22D3EE")("\u2570\u2500\u276F ")
16415
+ };
16416
+ }
16417
+ function cardLine(value, width, style) {
16418
+ const clipped = truncate3(value, width);
16419
+ return `\u2502 ${style(clipped)}${" ".repeat(Math.max(0, width - clipped.length))} \u2502`;
16420
+ }
16421
+ function normalizeColumns(columns) {
16422
+ return Number.isFinite(columns) ? Math.max(28, Math.floor(columns)) : 80;
16423
+ }
16424
+ function truncate3(value, width) {
16425
+ if (value.length <= width) return value;
16426
+ if (width <= 1) return "\u2026";
16427
+ return `${value.slice(0, width - 1)}\u2026`;
16428
+ }
16429
+ function shortenMiddle(value, width) {
16430
+ if (value.length <= width) return value;
16431
+ if (width < 9) return truncate3(value, width);
16432
+ const side = Math.floor((width - 1) / 2);
16433
+ return `${value.slice(0, side)}\u2026${value.slice(value.length - (width - side - 1))}`;
16434
+ }
16435
+
16215
16436
  // src/update/check.ts
16216
16437
  import { spawn as spawn4 } from "child_process";
16217
16438
  import { randomBytes as randomBytes4 } from "crypto";
@@ -16438,14 +16659,14 @@ async function runRepl(options, initialTask = "") {
16438
16659
  if (loaded.needsApproval && await offerWorkspaceTrust(workspace, loaded.withheld, loaded.trustSnapshot, loaded.trustStatus)) {
16439
16660
  loaded = await loadConfigWithTrust(workspace);
16440
16661
  if (loaded.needsApproval) {
16441
- console.log(chalk9.yellow(`
16662
+ console.log(chalk10.yellow(`
16442
16663
  ${withheldNotice(workspace, loaded.withheld, loaded.trustStatus)}
16443
16664
  `));
16444
16665
  }
16445
16666
  }
16446
16667
  const config = loaded.config;
16447
16668
  const toolSelection = applyBudgetFlags(options, config);
16448
- setContextBudgetNotice((message) => console.log(chalk9.yellow(`
16669
+ setContextBudgetNotice((message) => console.log(chalk10.yellow(`
16449
16670
  ! ${message}`)));
16450
16671
  const providerName = options.provider ?? config.provider;
16451
16672
  if (!isProviderName(providerName)) {
@@ -16461,8 +16682,8 @@ async function runRepl(options, initialTask = "") {
16461
16682
  }
16462
16683
  const hasFallbackChain = Boolean(config.fallback?.chain?.length);
16463
16684
  if (providerName === "ultrac" && !hasFallbackChain && !await resolveUltracCredentials()) {
16464
- console.log(chalk9.yellow("You are not signed in to Ultrac."));
16465
- console.log(`Run ${chalk9.cyan("ultrac login")} first, or start with ${chalk9.cyan("--provider echo")}.`);
16685
+ console.log(chalk10.yellow("You are not signed in to Ultrac."));
16686
+ console.log(`Run ${chalk10.cyan("ultrac login")} first, or start with ${chalk10.cyan("--provider echo")}.`);
16466
16687
  process.exitCode = EXIT_REPL_NOT_STARTED;
16467
16688
  return;
16468
16689
  }
@@ -16474,26 +16695,26 @@ async function runRepl(options, initialTask = "") {
16474
16695
  model,
16475
16696
  fallback: config.fallback,
16476
16697
  onFailover: (event) => {
16477
- console.log(chalk9.yellow(formatFailoverNotice(event)));
16698
+ console.log(chalk10.yellow(formatFailoverNotice(event)));
16478
16699
  if (event.to) {
16479
- console.log(chalk9.gray(` now answering: provider ${event.to.provider} \xB7 model ${event.to.model}`));
16700
+ console.log(chalk10.gray(` now answering: provider ${event.to.provider} \xB7 model ${event.to.model}`));
16480
16701
  }
16481
16702
  },
16482
16703
  // A tool-calling turn served by an entry without native tool calling still
16483
16704
  // answers, but over the JSON tool protocol. Say so rather than letting the
16484
16705
  // banner's "native tools" claim go stale.
16485
- onDowngrade: (event) => console.log(chalk9.yellow(formatDowngradeNotice(event))),
16706
+ onDowngrade: (event) => console.log(chalk10.yellow(formatDowngradeNotice(event))),
16486
16707
  onRetry: (event) => {
16487
16708
  diagnosticEvent("provider.retry", event);
16488
16709
  console.log(
16489
- chalk9.yellow(
16710
+ chalk10.yellow(
16490
16711
  ` retrying ${event.target.provider} (${event.target.model}) in ${event.delayMs}ms after ${event.classification.reason}`
16491
16712
  )
16492
16713
  );
16493
16714
  }
16494
16715
  });
16495
16716
  } catch (error) {
16496
- console.log(chalk9.red(error instanceof Error ? error.message : String(error)));
16717
+ console.log(chalk10.red(error instanceof Error ? error.message : String(error)));
16497
16718
  process.exitCode = EXIT_REPL_NOT_STARTED;
16498
16719
  return;
16499
16720
  }
@@ -16525,7 +16746,7 @@ async function runRepl(options, initialTask = "") {
16525
16746
  permissions: basePermissions
16526
16747
  });
16527
16748
  const writeCheckpoint = createSessionCheckpointWriter(workspace, (message) => {
16528
- console.error(chalk9.yellow(message));
16749
+ console.error(chalk10.yellow(message));
16529
16750
  });
16530
16751
  const persistSession = async (target = session) => {
16531
16752
  const snapshot = target.toSession();
@@ -16543,7 +16764,7 @@ async function runRepl(options, initialTask = "") {
16543
16764
  diagnosticPath2
16544
16765
  );
16545
16766
  if (mcpRuntime && mcpRuntime.statuses.length > 0) {
16546
- console.log(chalk9.bold("\nMCP servers"));
16767
+ console.log(chalk10.bold("\nMCP servers"));
16547
16768
  console.log(renderMcpStatuses(mcpRuntime.statuses));
16548
16769
  }
16549
16770
  try {
@@ -16553,24 +16774,25 @@ async function runRepl(options, initialTask = "") {
16553
16774
  }
16554
16775
  const update = await updateCheck;
16555
16776
  if (update?.updateAvailable && update.latestVersion) {
16556
- console.log(chalk9.yellow(`
16777
+ console.log(chalk10.yellow(`
16557
16778
  \u2191 Update available: ${update.currentVersion} \u2192 ${update.latestVersion}`));
16558
- console.log(chalk9.gray(" Run `ultrac update` after this session."));
16779
+ console.log(chalk10.gray(" Run `ultrac update` after this session."));
16559
16780
  }
16560
16781
  const rl = createInterface3({ input: stdin5, output: stdout5 });
16561
16782
  const reader = new LineReader(rl);
16562
16783
  const question = async (prompt, signal) => {
16563
16784
  stdout5.write(prompt);
16564
- return reader.next(signal);
16785
+ return reader.nextFresh(signal);
16565
16786
  };
16787
+ const drainQueuedMessages = () => reader.drainQueuedMessages();
16566
16788
  setAskUserRuntime(async (text, options2, signal) => {
16567
- console.log(chalk9.cyan(`
16789
+ console.log(chalk10.cyan(`
16568
16790
  ? ${text}`));
16569
16791
  if (options2?.length) {
16570
- options2.forEach((option, index) => console.log(chalk9.gray(` ${index + 1}. ${option}`)));
16571
- console.log(chalk9.gray(" (type a number, or answer in your own words)"));
16792
+ options2.forEach((option, index) => console.log(chalk10.gray(` ${index + 1}. ${option}`)));
16793
+ console.log(chalk10.gray(" (type a number, or answer in your own words)"));
16572
16794
  }
16573
- const rawAnswer = await question(chalk9.cyan(" \u203A "), signal);
16795
+ const rawAnswer = await question(chalk10.cyan(" \u203A "), signal);
16574
16796
  if (signal?.aborted) return null;
16575
16797
  const answer = (rawAnswer ?? "").trim();
16576
16798
  const picked = Number.parseInt(answer, 10);
@@ -16582,11 +16804,10 @@ async function runRepl(options, initialTask = "") {
16582
16804
  let running = true;
16583
16805
  let turnAbort;
16584
16806
  rl.on("SIGINT", () => {
16585
- if (turnAbort && !turnAbort.signal.aborted) {
16586
- turnAbort.abort();
16587
- return;
16588
- }
16589
- console.log("\nUse /exit to end the session.");
16807
+ if (handleReplInterrupt(turnAbort) === "abort-turn") return;
16808
+ running = false;
16809
+ console.log(chalk10.gray("\nExiting ultrac\u2026"));
16810
+ rl.close();
16590
16811
  });
16591
16812
  const terminate = () => {
16592
16813
  running = false;
@@ -16604,12 +16825,18 @@ async function runRepl(options, initialTask = "") {
16604
16825
  };
16605
16826
  try {
16606
16827
  if (initialTask) {
16607
- console.log(chalk9.cyan(`
16828
+ console.log(chalk10.cyan(`
16608
16829
  \u203A ${initialTask}`));
16609
- await withInterrupt((signal) => runAndCheckpointTurn(session, initialTask, question, signal, persistSession));
16830
+ await withInterrupt((signal) => runAndCheckpointTurn(session, initialTask, question, signal, persistSession, drainQueuedMessages));
16610
16831
  }
16611
16832
  while (running) {
16612
- stdout5.write(chalk9.cyan("\n\u203A "));
16833
+ if (stdout5.isTTY) {
16834
+ const composer = renderComposer(stdout5.columns, true);
16835
+ console.log(composer.frame);
16836
+ stdout5.write(composer.prompt);
16837
+ } else {
16838
+ stdout5.write(chalk10.cyan("\n\u203A "));
16839
+ }
16613
16840
  const line = await reader.next();
16614
16841
  if (line === null) break;
16615
16842
  const raw = line.trim();
@@ -16625,9 +16852,9 @@ async function runRepl(options, initialTask = "") {
16625
16852
  permissions: session.currentPermissions
16626
16853
  });
16627
16854
  resetContextBudgetNotices();
16628
- console.log(chalk9.gray("Started a fresh conversation."));
16855
+ console.log(chalk10.gray("Started a fresh conversation."));
16629
16856
  }, async (prompt) => {
16630
- await runAndCheckpointTurn(session, prompt, question, signal, persistSession);
16857
+ await runAndCheckpointTurn(session, prompt, question, signal, persistSession, drainQueuedMessages);
16631
16858
  }, signal, async (id) => {
16632
16859
  const lineage = await loadSessionLineage(workspace, id);
16633
16860
  const restored = createSession({ model: session.model, mode: session.mode, permissions: session.currentPermissions });
@@ -16656,7 +16883,7 @@ async function runRepl(options, initialTask = "") {
16656
16883
  }
16657
16884
  continue;
16658
16885
  }
16659
- await withInterrupt((signal) => runAndCheckpointTurn(session, raw, question, signal, persistSession));
16886
+ await withInterrupt((signal) => runAndCheckpointTurn(session, raw, question, signal, persistSession, drainQueuedMessages));
16660
16887
  }
16661
16888
  } finally {
16662
16889
  process.off("SIGTERM", terminate);
@@ -16665,9 +16892,9 @@ async function runRepl(options, initialTask = "") {
16665
16892
  await saveTranscript(session, writeCheckpoint);
16666
16893
  mcpRuntime?.close();
16667
16894
  }
16668
- console.log(chalk9.gray("Goodbye."));
16895
+ console.log(chalk10.gray("Goodbye."));
16669
16896
  }
16670
- async function runTurn(session, input, question, signal) {
16897
+ async function runTurn(session, input, question, signal, drainQueuedMessages) {
16671
16898
  const spinner = new Spinner();
16672
16899
  const detachProgress = attachProgressUi();
16673
16900
  let deltaChars = 0;
@@ -16676,7 +16903,7 @@ async function runTurn(session, input, question, signal) {
16676
16903
  ...signal ? { signal } : {},
16677
16904
  onInterrupted: () => {
16678
16905
  spinner.stop();
16679
- console.log(chalk9.yellow("\n \u238B interrupted \u2014 the work so far is kept. Ask again to continue."));
16906
+ console.log(chalk10.dim("\n Interrupted \u2014 current work stopped. Session preserved."));
16680
16907
  },
16681
16908
  onModelStart: () => {
16682
16909
  deltaChars = 0;
@@ -16694,34 +16921,41 @@ ${message.trim()}`);
16694
16921
  }
16695
16922
  },
16696
16923
  onToolStart: (call) => {
16697
- console.log(chalk9.gray(` \u2699 ${call.tool} ${compactInput(call.input)}`));
16924
+ console.log(chalk10.dim(` ${toolActivityLabel(call)}`));
16698
16925
  },
16699
- onToolResult: (call, result) => printToolResult2(result),
16926
+ onToolResult: (_call, result) => printToolResult2(result),
16927
+ ...drainQueuedMessages ? {
16928
+ drainQueuedMessages,
16929
+ onQueuedMessage: () => {
16930
+ spinner.stop();
16931
+ console.log(chalk10.dim(" Queued message added to the current turn."));
16932
+ }
16933
+ } : {},
16700
16934
  onToolDenied: (call, permission) => {
16701
- console.log(chalk9.yellow(` \u2717 denied ${permission} for ${call.tool}`));
16935
+ console.log(chalk10.yellow(` \u2717 denied ${permission} for ${call.tool}`));
16702
16936
  },
16703
16937
  onProviderNotice: (notice) => {
16704
- console.log(chalk9.yellow(` ! ${notice}`));
16938
+ console.log(chalk10.yellow(` ! ${notice}`));
16705
16939
  },
16706
16940
  onStopped: (reason) => {
16707
16941
  if (reason === "aborted") return;
16708
16942
  const line = stopReasonLine(reason);
16709
16943
  const hint = turnStopHint(reason, session.maxSteps);
16710
16944
  if (!line || !hint) return;
16711
- console.log(chalk9.yellow(`
16945
+ console.log(chalk10.yellow(`
16712
16946
  \u26A0 ${line}`));
16713
- console.log(chalk9.gray(` ${hint}`));
16947
+ console.log(chalk10.gray(` ${hint}`));
16714
16948
  },
16715
16949
  requestPermission: async (call, permission) => {
16716
16950
  const prompt = permission === "shell" && call.tool === "run_shell" ? shellApprovalPrompt(call, session.toSession().workspace) : permission === "shell" ? ` ${call.tool} wants shell access. [o]nce (this call only) / [a]lways (all shell tools for this session) / [n]o (this call) / n[e]ver (this session) \u203A ` : ` ${call.tool} wants ${permission} access. [o]nce / [a]lways / [n]o / n[e]ver \u203A `;
16717
- const rawAnswer = await question(chalk9.yellow(prompt), signal);
16951
+ const rawAnswer = await question(chalk10.yellow(prompt), signal);
16718
16952
  if (signal?.aborted || rawAnswer === null) return "deny";
16719
16953
  const answer = rawAnswer.trim().toLowerCase();
16720
16954
  if (answer === "a" || answer === "always") return "always";
16721
16955
  if (answer === "o" || answer === "once" || answer === "y" || answer === "yes") return "once";
16722
16956
  if (answer === "e" || answer === "never") {
16723
16957
  console.log(
16724
- chalk9.gray(
16958
+ chalk10.gray(
16725
16959
  ` ${permission} refused for the rest of the session \u2014 nothing will ask again. /allow ${permission} undoes it.`
16726
16960
  )
16727
16961
  );
@@ -16739,9 +16973,9 @@ ${message.trim()}`);
16739
16973
  detachProgress();
16740
16974
  }
16741
16975
  }
16742
- async function runAndCheckpointTurn(session, input, question, signal, persist) {
16976
+ async function runAndCheckpointTurn(session, input, question, signal, persist, drainQueuedMessages) {
16743
16977
  try {
16744
- return await runTurn(session, input, question, signal);
16978
+ return await runTurn(session, input, question, signal, drainQueuedMessages);
16745
16979
  } finally {
16746
16980
  await persist(session);
16747
16981
  }
@@ -16758,27 +16992,27 @@ function turnStopHint(reason, maxSteps) {
16758
16992
  }
16759
16993
  function printTurnError(error) {
16760
16994
  if (error instanceof ProviderChainError) {
16761
- console.log(chalk9.red("\n \u2717 Turn failed: no provider in the chain could answer."));
16995
+ console.log(chalk10.red("\n \u2717 Turn failed: no provider in the chain could answer."));
16762
16996
  for (const failure of error.failures) {
16763
16997
  const attempts = failure.attempts === 1 ? "1 attempt" : `${failure.attempts} attempts`;
16764
16998
  console.log(
16765
- chalk9.red(` - ${failure.provider} (${failure.model}), ${attempts}: ${errorMessage2(failure.error)}`)
16999
+ chalk10.red(` - ${failure.provider} (${failure.model}), ${attempts}: ${errorMessage2(failure.error)}`)
16766
17000
  );
16767
17001
  }
16768
17002
  console.log(
16769
- chalk9.gray(" Fix the provider (ultrac login, or --provider echo) and try again; the session is still open.")
17003
+ chalk10.gray(" Fix the provider (ultrac login, or --provider echo) and try again; the session is still open.")
16770
17004
  );
16771
17005
  return;
16772
17006
  }
16773
- console.log(chalk9.red(`
17007
+ console.log(chalk10.red(`
16774
17008
  \u2717 Turn failed: ${errorMessage2(error)}`));
16775
- console.log(chalk9.gray(" The session is still open. Try again, or /exit to end it."));
17009
+ console.log(chalk10.gray(" The session is still open. Try again, or /exit to end it."));
16776
17010
  }
16777
17011
  async function saveTranscript(session, writeCheckpoint) {
16778
17012
  const snapshot = session.toSession();
16779
17013
  if (!hasSavedSessionContent(snapshot)) return;
16780
17014
  const stored = await writeCheckpoint(snapshot);
16781
- if (stored) console.log(chalk9.gray(`
17015
+ if (stored) console.log(chalk10.gray(`
16782
17016
  Session saved: ${stored}`));
16783
17017
  }
16784
17018
  function hasSavedSessionContent(session) {
@@ -16820,7 +17054,7 @@ async function handleSlashCommand(raw, session, providerName, workspace, onClear
16820
17054
  if (extra) console.log(`
16821
17055
  ${extra}`);
16822
17056
  for (const conflict of index.conflicts) {
16823
- console.log(chalk9.yellow(` ! ${conflict.message}`));
17057
+ console.log(chalk10.yellow(` ! ${conflict.message}`));
16824
17058
  }
16825
17059
  return "ok";
16826
17060
  }
@@ -16834,7 +17068,7 @@ ${extra}`);
16834
17068
  case "model":
16835
17069
  if (argument) {
16836
17070
  session.setModel(argument);
16837
- console.log(chalk9.gray(`Model set to ${argument}.`));
17071
+ console.log(chalk10.gray(`Model set to ${argument}.`));
16838
17072
  } else {
16839
17073
  console.log(`Model: ${session.model}`);
16840
17074
  }
@@ -16842,10 +17076,10 @@ ${extra}`);
16842
17076
  case "mode":
16843
17077
  if (argument) {
16844
17078
  if (!isAgentModeName(argument)) {
16845
- console.log(chalk9.red(`Unknown mode. Expected one of: ${agentModeNames.join(", ")}.`));
17079
+ console.log(chalk10.red(`Unknown mode. Expected one of: ${agentModeNames.join(", ")}.`));
16846
17080
  } else {
16847
17081
  session.setMode(argument);
16848
- console.log(chalk9.gray(`Mode set to ${argument}.`));
17082
+ console.log(chalk10.gray(`Mode set to ${argument}.`));
16849
17083
  }
16850
17084
  } else {
16851
17085
  console.log(`Mode: ${session.mode}`);
@@ -16894,7 +17128,7 @@ ${extra}`);
16894
17128
  console.log(
16895
17129
  `Selected ${info.selectedMessages.toLocaleString("en-US")} of ${info.messages.toLocaleString("en-US")} messages; would omit ${info.omittedMessages.toLocaleString("en-US")} messages in ${info.omittedTurns.toLocaleString("en-US")} earlier turns. Prompt headroom ${info.promptRemainingTokens.toLocaleString("en-US")} tokens; window headroom ${info.windowRemainingTokens.toLocaleString("en-US")} of ${info.contextTokens.toLocaleString("en-US")} tokens.`
16896
17130
  );
16897
- if (info.error) console.log(chalk9.yellow(`Context error: ${info.error}`));
17131
+ if (info.error) console.log(chalk10.yellow(`Context error: ${info.error}`));
16898
17132
  return "ok";
16899
17133
  }
16900
17134
  case "compact":
@@ -16902,7 +17136,7 @@ ${extra}`);
16902
17136
  await session.compactContext(argument, signal);
16903
17137
  console.log("Context compacted. User instructions were retained; earlier activity was replaced by a bounded continuation record.");
16904
17138
  } catch (error) {
16905
- console.log(chalk9.red(errorMessage2(error)));
17139
+ console.log(chalk10.red(errorMessage2(error)));
16906
17140
  }
16907
17141
  return "ok";
16908
17142
  case "resume":
@@ -16913,7 +17147,7 @@ ${extra}`);
16913
17147
  try {
16914
17148
  await resume(argument);
16915
17149
  } catch (error) {
16916
- console.log(chalk9.red(errorMessage2(error)));
17150
+ console.log(chalk10.red(errorMessage2(error)));
16917
17151
  }
16918
17152
  return "ok";
16919
17153
  case "save": {
@@ -16964,7 +17198,7 @@ ${extra}`);
16964
17198
  const refused = session.refusedPermissions;
16965
17199
  if (refused.length) {
16966
17200
  console.log(
16967
- chalk9.gray(`refused for this session: ${refused.join(", ")} (undo with /allow <perm>)`)
17201
+ chalk10.gray(`refused for this session: ${refused.join(", ")} (undo with /allow <perm>)`)
16968
17202
  );
16969
17203
  }
16970
17204
  return "ok";
@@ -16973,9 +17207,9 @@ ${extra}`);
16973
17207
  case "allow": {
16974
17208
  if (argument === "write" || argument === "shell" || argument === "network") {
16975
17209
  session.setPermission(argument, command === "allow");
16976
- console.log(chalk9.gray(`${command === "allow" ? "Granted" : "Revoked"} ${argument} for this session.`));
17210
+ console.log(chalk10.gray(`${command === "allow" ? "Granted" : "Revoked"} ${argument} for this session.`));
16977
17211
  } else {
16978
- console.log(chalk9.red(`Usage: /${command} <write|shell|network>`));
17212
+ console.log(chalk10.red(`Usage: /${command} <write|shell|network>`));
16979
17213
  }
16980
17214
  return "ok";
16981
17215
  }
@@ -16994,7 +17228,7 @@ ${extra}`);
16994
17228
  const names = session.advertisedToolNames;
16995
17229
  console.log(`Advertised to the model this turn (${names.length}): ${names.join(", ")}`);
16996
17230
  console.log(
16997
- chalk9.gray(
17231
+ chalk10.gray(
16998
17232
  "Registered tools remain subject to permission and mode checks. `ultrac tools` lists them all with schemas; --tools all advertises them all."
16999
17233
  )
17000
17234
  );
@@ -17003,7 +17237,7 @@ ${extra}`);
17003
17237
  case "memory": {
17004
17238
  const memories = await listMemories(workspace);
17005
17239
  if (!memories.length) {
17006
- console.log(chalk9.gray("No stored memories. Add one with `ultrac memory add`."));
17240
+ console.log(chalk10.gray("No stored memories. Add one with `ultrac memory add`."));
17007
17241
  } else {
17008
17242
  for (const memory of memories) {
17009
17243
  console.log(`- [${memory.scope}/${memory.name}] (${memory.type}) ${memory.description}`);
@@ -17014,7 +17248,7 @@ ${extra}`);
17014
17248
  case "skills": {
17015
17249
  const skills = await listSkills(workspace);
17016
17250
  if (!skills.length) {
17017
- console.log(chalk9.gray("No skills installed. Create one with `ultrac skills create`."));
17251
+ console.log(chalk10.gray("No skills installed. Create one with `ultrac skills create`."));
17018
17252
  } else {
17019
17253
  for (const skill of skills) {
17020
17254
  console.log(`- ${skill.name} (${skill.scope}): ${skill.description}`);
@@ -17030,12 +17264,12 @@ ${extra}`);
17030
17264
  force: true
17031
17265
  });
17032
17266
  if (!update.latestVersion) {
17033
- console.log(chalk9.yellow("Could not check npm. Confirm npm access, then run `ultrac update --check` outside this session."));
17267
+ console.log(chalk10.yellow("Could not check npm. Confirm npm access, then run `ultrac update --check` outside this session."));
17034
17268
  } else if (update.updateAvailable) {
17035
- console.log(chalk9.yellow(`Update available: ${update.currentVersion} \u2192 ${update.latestVersion}`));
17036
- console.log(chalk9.gray("Exit this session, then run `ultrac update`."));
17269
+ console.log(chalk10.yellow(`Update available: ${update.currentVersion} \u2192 ${update.latestVersion}`));
17270
+ console.log(chalk10.gray("Exit this session, then run `ultrac update`."));
17037
17271
  } else {
17038
- console.log(chalk9.green(`ultrac ${update.currentVersion} is up to date.`));
17272
+ console.log(chalk10.green(`ultrac ${update.currentVersion} is up to date.`));
17039
17273
  }
17040
17274
  return "ok";
17041
17275
  }
@@ -17047,27 +17281,27 @@ async function runSkillCommand(session, raw, workspace, question) {
17047
17281
  const resolved = await resolveSkillCommand(workspace, raw);
17048
17282
  if (resolved.status === "ok") {
17049
17283
  console.log(
17050
- chalk9.gray(` /${resolved.command.command} \u2192 skill ${resolved.command.skillName} (${resolved.command.scope})`)
17284
+ chalk10.gray(` /${resolved.command.command} \u2192 skill ${resolved.command.skillName} (${resolved.command.scope})`)
17051
17285
  );
17052
17286
  await runTurn(session, resolved.prompt, question);
17053
17287
  return;
17054
17288
  }
17055
17289
  if (resolved.status === "conflict") {
17056
- console.log(chalk9.red(resolved.message));
17290
+ console.log(chalk10.red(resolved.message));
17057
17291
  return;
17058
17292
  }
17059
17293
  if (resolved.status === "builtin") {
17060
- console.log(chalk9.red(`/${resolved.command} is a built-in command and was not handled. Try /help.`));
17294
+ console.log(chalk10.red(`/${resolved.command} is a built-in command and was not handled. Try /help.`));
17061
17295
  return;
17062
17296
  }
17063
17297
  if (resolved.status === "unknown") {
17064
- console.log(chalk9.red(`Unknown command: /${resolved.command}. Try /help.`));
17298
+ console.log(chalk10.red(`Unknown command: /${resolved.command}. Try /help.`));
17065
17299
  if (resolved.available.length) {
17066
- console.log(chalk9.gray(`Skill commands: ${resolved.available.map((name) => `/${name}`).join(", ")}`));
17300
+ console.log(chalk10.gray(`Skill commands: ${resolved.available.map((name) => `/${name}`).join(", ")}`));
17067
17301
  }
17068
17302
  return;
17069
17303
  }
17070
- console.log(chalk9.red("Unknown command. Try /help."));
17304
+ console.log(chalk10.red("Unknown command. Try /help."));
17071
17305
  }
17072
17306
  var LineReader = class {
17073
17307
  queue = [];
@@ -17090,9 +17324,36 @@ var LineReader = class {
17090
17324
  });
17091
17325
  }
17092
17326
  next(signal) {
17327
+ return this.waitForLine(signal, true);
17328
+ }
17329
+ /**
17330
+ * Nested prompts must not consume steering messages entered before the
17331
+ * question appeared. They wait for the next line while the queue stays put.
17332
+ */
17333
+ nextFresh(signal) {
17334
+ return this.waitForLine(signal, false);
17335
+ }
17336
+ /**
17337
+ * Drain ordinary messages in arrival order. A slash command is a control
17338
+ * boundary and remains queued for the main REPL loop along with everything
17339
+ * after it.
17340
+ */
17341
+ drainQueuedMessages() {
17342
+ const messages = [];
17343
+ while (this.queue.length) {
17344
+ const next = this.queue[0].trim();
17345
+ if (next.startsWith("/")) break;
17346
+ this.queue.shift();
17347
+ if (next) messages.push(next);
17348
+ }
17349
+ return messages;
17350
+ }
17351
+ waitForLine(signal, includeBuffered) {
17093
17352
  if (signal?.aborted) return Promise.resolve(null);
17094
- const buffered = this.queue.shift();
17095
- if (buffered !== void 0) return Promise.resolve(buffered);
17353
+ if (includeBuffered) {
17354
+ const buffered = this.queue.shift();
17355
+ if (buffered !== void 0) return Promise.resolve(buffered);
17356
+ }
17096
17357
  if (this.closed) return Promise.resolve(null);
17097
17358
  return new Promise((resolve15) => {
17098
17359
  const waiter = { resolve: resolve15, ...signal ? { signal } : {} };
@@ -17117,13 +17378,27 @@ var LineReader = class {
17117
17378
  waiter.resolve(line);
17118
17379
  }
17119
17380
  };
17381
+ function handleReplInterrupt(activeTurn) {
17382
+ if (activeTurn && !activeTurn.signal.aborted) {
17383
+ activeTurn.abort();
17384
+ return "abort-turn";
17385
+ }
17386
+ return "exit-session";
17387
+ }
17120
17388
  function printBanner(version, providerName, model, mode, workspace, protocol, budget, diagnosticPath2) {
17121
- console.log(chalk9.cyan("\u256D\u2500") + ` ${chalk9.bold("ultrac")} ${chalk9.gray(`v${version}`)}`);
17122
- console.log(chalk9.cyan("\u2502 ") + `${model} via ${providerName} \xB7 ${mode} \xB7 ${protocol}`);
17123
- console.log(chalk9.cyan("\u2502 ") + chalk9.gray(workspace));
17124
- console.log(chalk9.cyan("\u2502 ") + chalk9.gray(budget));
17125
- console.log(chalk9.cyan("\u2502 ") + chalk9.gray(`diagnostics ${diagnosticPath2}`));
17126
- console.log(chalk9.cyan("\u2570\u2500") + chalk9.gray(" Type /help for commands \xB7 /exit to quit"));
17389
+ console.log(renderWelcome(
17390
+ {
17391
+ version,
17392
+ providerName,
17393
+ model,
17394
+ mode,
17395
+ workspace,
17396
+ protocol,
17397
+ budget,
17398
+ diagnosticPath: diagnosticPath2
17399
+ },
17400
+ { columns: stdout5.columns, color: stdout5.isTTY }
17401
+ ));
17127
17402
  }
17128
17403
  function describeBudget(mode, selection, registry) {
17129
17404
  const window = resolveContextWindow();
@@ -17140,20 +17415,12 @@ function describeAdvertisedTools(mode, selection, registry = createDefaultToolRe
17140
17415
  };
17141
17416
  }
17142
17417
  function printToolResult2(result) {
17143
- const status = result.ok ? chalk9.green("ok") : chalk9.red("failed");
17144
- console.log(` ${chalk9.cyan(result.tool)} ${status}`);
17145
- const preview = result.content.split("\n").slice(0, 6).join("\n");
17146
- if (preview.trim()) {
17147
- console.log(chalk9.gray(indent3(truncate2(preview, 800))));
17148
- }
17418
+ const failure = toolFailurePreview(result);
17419
+ if (failure) console.log(chalk10.red(` Failed: ${failure}`));
17149
17420
  for (const warning of getMcpArchiveWarnings(result)) {
17150
- console.log(chalk9.yellow(indent3(`Warning: ${warning}`)));
17421
+ console.log(chalk10.yellow(indent3(`Warning: ${warning}`)));
17151
17422
  }
17152
17423
  }
17153
- function compactInput(input) {
17154
- const text = JSON.stringify(input);
17155
- return text.length > 80 ? `${text.slice(0, 77)}...` : text;
17156
- }
17157
17424
  function shellApprovalPrompt(call, workspace) {
17158
17425
  const command = typeof call.input.command === "string" ? call.input.command : "<invalid command>";
17159
17426
  const requestedCwd = typeof call.input.cwd === "string" ? call.input.cwd : ".";
@@ -17173,15 +17440,12 @@ function shellApprovalPrompt(call, workspace) {
17173
17440
  " [o]nce runs only this command / [a]lways grants shell access for this whole session, including other shell-using tools / [n]o (this call) / n[e]ver (this session) \u203A "
17174
17441
  ].join("\n");
17175
17442
  }
17176
- function truncate2(value, max) {
17177
- return value.length > max ? `${value.slice(0, max)}\u2026` : value;
17178
- }
17179
17443
  function indent3(value) {
17180
17444
  return value.split("\n").map((line) => ` ${line}`).join("\n");
17181
17445
  }
17182
17446
 
17183
17447
  // src/commands/run.ts
17184
- import chalk10 from "chalk";
17448
+ import chalk11 from "chalk";
17185
17449
  var EXIT_INCOMPLETE_RUN = 2;
17186
17450
  function registerRunCommand(program2) {
17187
17451
  program2.command("run").alias("chat").description("Run a one-shot coding agent task").argument("<task...>", "Task for the agent").option("-w, --workspace <path>", "Workspace path", ".").option("--provider <name>", "Provider: ultrac (default) or echo (offline test stub)").option("--model <model>", "Model name").option("--base-url <url>", "Ultrac API base URL (overrides ULTRAC_BASE_URL and the stored login)").option("--mode <name>", `Agent mode: ${agentModeNames.join(", ")}`).option("--max-steps <number>", "Maximum agent steps").option("--no-native-tools", "Use the legacy JSON tool protocol").option("--allow-write", "Allow file writes", false).option("--allow-shell", "Grant the model broad, unsandboxed shell access", false).option("--allow-network", "Allow HTTP(S) URL fetches", false).option("--continue <id>", "Continue from a saved session id").option("--tools <list>", "Tools to advertise: `all`, or a comma-separated list. Default: the mode's set").option("--context-tokens <number>", "Model context window in tokens (default 262144)").option("--max-tool-result-chars <number>", "Cap on one tool result in the transcript (default 6000)").option(
@@ -17195,17 +17459,17 @@ function registerRunCommand(program2) {
17195
17459
  }
17196
17460
  const workspace = await assertWorkspaceExists(options.workspace);
17197
17461
  const diagnosticPath2 = configureDiagnosticLog(workspace, "run");
17198
- console.error(chalk10.gray(`Diagnostic log: ${diagnosticPath2}`));
17462
+ console.error(chalk11.gray(`Diagnostic log: ${diagnosticPath2}`));
17199
17463
  if (options.baseUrl) {
17200
17464
  setBaseUrlFlag(options.baseUrl);
17201
17465
  }
17202
17466
  const loaded = await loadConfigWithTrust(workspace);
17203
17467
  const config = loaded.config;
17204
17468
  if (loaded.needsApproval) {
17205
- console.error(chalk10.yellow(withheldNotice(workspace, loaded.withheld, loaded.trustStatus)));
17469
+ console.error(chalk11.yellow(withheldNotice(workspace, loaded.withheld, loaded.trustStatus)));
17206
17470
  }
17207
17471
  const toolSelection = applyBudgetFlags(options, config);
17208
- setContextBudgetNotice((message) => console.error(chalk10.yellow(message)));
17472
+ setContextBudgetNotice((message) => console.error(chalk11.yellow(message)));
17209
17473
  const providerName = options.provider ?? config.provider;
17210
17474
  const mode = options.mode ?? config.mode;
17211
17475
  const previousSessionLineage = options.continue ? await loadSessionLineage(workspace, options.continue) : void 0;
@@ -17226,14 +17490,14 @@ function registerRunCommand(program2) {
17226
17490
  model,
17227
17491
  fallback: config.fallback,
17228
17492
  // stderr, so --json keeps stdout parseable.
17229
- onFailover: (event) => console.error(chalk10.yellow(formatFailoverNotice(event))),
17493
+ onFailover: (event) => console.error(chalk11.yellow(formatFailoverNotice(event))),
17230
17494
  // AgentRunner uses native tool calls when supported; wired
17231
17495
  // anyway so a non-interactive run stays honest if that ever changes.
17232
- onDowngrade: (event) => console.error(chalk10.yellow(formatDowngradeNotice(event))),
17496
+ onDowngrade: (event) => console.error(chalk11.yellow(formatDowngradeNotice(event))),
17233
17497
  onRetry: (event) => {
17234
17498
  diagnosticEvent("provider.retry", event);
17235
17499
  console.error(
17236
- chalk10.yellow(
17500
+ chalk11.yellow(
17237
17501
  `Retrying ${event.target.provider} (${event.target.model}) in ${event.delayMs}ms after ${event.classification.reason}`
17238
17502
  )
17239
17503
  );
@@ -17244,13 +17508,13 @@ function registerRunCommand(program2) {
17244
17508
  setSubagentRuntime({ provider, model, mode, registry });
17245
17509
  try {
17246
17510
  if (mcpRuntime && mcpRuntime.statuses.length > 0) {
17247
- console.error(chalk10.bold("MCP servers"));
17511
+ console.error(chalk11.bold("MCP servers"));
17248
17512
  console.error(renderMcpStatuses(mcpRuntime.statuses));
17249
17513
  }
17250
17514
  const detachProgress = attachProgressUi();
17251
17515
  const runner = new AgentRunner(registry);
17252
17516
  const writeCheckpoint = createSessionCheckpointWriter(workspace, (message) => {
17253
- console.error(chalk10.yellow(message));
17517
+ console.error(chalk11.yellow(message));
17254
17518
  });
17255
17519
  const controller = new AbortController();
17256
17520
  const abortRun = () => controller.abort();
@@ -17300,7 +17564,7 @@ function registerRunCommand(program2) {
17300
17564
  }
17301
17565
  const stopReason = result.session.stopReason;
17302
17566
  for (const warning of getSessionMcpArchiveWarnings(result.session)) {
17303
- console.error(chalk10.yellow(`Warning: ${warning}`));
17567
+ console.error(chalk11.yellow(`Warning: ${warning}`));
17304
17568
  }
17305
17569
  if (options.json) {
17306
17570
  console.log(
@@ -17585,7 +17849,7 @@ function parsePermissions(list) {
17585
17849
  }
17586
17850
 
17587
17851
  // src/commands/tasks.ts
17588
- import chalk11 from "chalk";
17852
+ import chalk12 from "chalk";
17589
17853
 
17590
17854
  // src/runs/api.ts
17591
17855
  var RUN_STATUSES = ["queued", "running", "succeeded", "failed", "cancelled"];
@@ -18121,7 +18385,7 @@ function registerTasksCommand(program2, deps = {}) {
18121
18385
  }
18122
18386
  r.out(formatRunTable(page.data, r.now()));
18123
18387
  if (page.next_cursor) {
18124
- r.err(chalk11.gray(`More: ultrac tasks --cursor ${page.next_cursor}
18388
+ r.err(chalk12.gray(`More: ultrac tasks --cursor ${page.next_cursor}
18125
18389
  `));
18126
18390
  }
18127
18391
  });
@@ -18180,7 +18444,7 @@ async function runTask(promptParts, options, deps) {
18180
18444
  `);
18181
18445
  } else {
18182
18446
  r.err(
18183
- chalk11.gray(
18447
+ chalk12.gray(
18184
18448
  `Run ${run.id} queued. Following; Ctrl-C ${options.detach ? "leaves it running" : "cancels it"}.
18185
18449
  `
18186
18450
  )
@@ -18198,7 +18462,7 @@ async function follow(r, runId, options) {
18198
18462
  controller.abort();
18199
18463
  return;
18200
18464
  }
18201
- r.err(chalk11.yellow(`
18465
+ r.err(chalk12.yellow(`
18202
18466
  Leaving run ${runId} as it is.
18203
18467
  `));
18204
18468
  r.exit(EXIT_CANCELLED);
@@ -18214,7 +18478,7 @@ Leaving run ${runId} as it is.
18214
18478
  if (status === "running") {
18215
18479
  const attempt = typeof data?.attempt === "number" ? data.attempt : null;
18216
18480
  if (data?.restarted === true || attempt !== null && attempt > 1) {
18217
- r.err(chalk11.yellow(`${lead()}[attempt ${attempt ?? "?"} restarted; streaming from the start]
18481
+ r.err(chalk12.yellow(`${lead()}[attempt ${attempt ?? "?"} restarted; streaming from the start]
18218
18482
  `));
18219
18483
  }
18220
18484
  printed = "";
@@ -18229,7 +18493,7 @@ Leaving run ${runId} as it is.
18229
18493
  return;
18230
18494
  }
18231
18495
  case "tool_call": {
18232
- r.err(chalk11.cyan(`${lead()}[tool_call ${String(data?.name ?? "")} ${String(data?.arguments ?? "")}]
18496
+ r.err(chalk12.cyan(`${lead()}[tool_call ${String(data?.name ?? "")} ${String(data?.arguments ?? "")}]
18233
18497
  `));
18234
18498
  return;
18235
18499
  }
@@ -18269,7 +18533,7 @@ Leaving run ${runId} as it is.
18269
18533
  `);
18270
18534
  } else {
18271
18535
  const seconds = Math.round(info.delayMs / 100) / 10;
18272
- r.err(chalk11.gray(`${lead()}[reconnecting in ${seconds}s${info.message ? `: ${info.message}` : ""}]
18536
+ r.err(chalk12.gray(`${lead()}[reconnecting in ${seconds}s${info.message ? `: ${info.message}` : ""}]
18273
18537
  `));
18274
18538
  }
18275
18539
  }
@@ -18301,27 +18565,27 @@ Leaving run ${runId} as it is.
18301
18565
  return result.outputSource === "incomplete" ? EXIT_FAILED : exitCodeFor(result.status);
18302
18566
  }
18303
18567
  if (result.outputSource === "incomplete") {
18304
- r.err(chalk11.yellow(lead() + "[stored task output could not be confirmed; showing text received from the stream]\n"));
18568
+ r.err(chalk12.yellow(lead() + "[stored task output could not be confirmed; showing text received from the stream]\n"));
18305
18569
  }
18306
18570
  switch (result.status) {
18307
18571
  case "succeeded":
18308
18572
  finishHuman(result.text);
18309
18573
  if (result.toolCalls.length && !result.text) {
18310
18574
  const count3 = result.toolCalls.length;
18311
- r.err(chalk11.gray(`Done with ${count3} tool call${count3 === 1 ? "" : "s"} and no text.
18575
+ r.err(chalk12.gray(`Done with ${count3} tool call${count3 === 1 ? "" : "s"} and no text.
18312
18576
  `));
18313
18577
  }
18314
18578
  return result.outputSource === "incomplete" ? EXIT_FAILED : EXIT_DONE;
18315
18579
  case "failed": {
18316
18580
  finishHuman(result.text);
18317
18581
  const code = result.error?.code ? ` (${result.error.code})` : "";
18318
- r.err(chalk11.red(`Run ${runId} failed${code}: ${result.error?.message ?? "The task failed."}
18582
+ r.err(chalk12.red(`Run ${runId} failed${code}: ${result.error?.message ?? "The task failed."}
18319
18583
  `));
18320
18584
  return EXIT_FAILED;
18321
18585
  }
18322
18586
  case "cancelled":
18323
18587
  finishHuman(result.text);
18324
- r.err(chalk11.yellow(`Run ${runId} was cancelled.
18588
+ r.err(chalk12.yellow(`Run ${runId} was cancelled.
18325
18589
  `));
18326
18590
  return EXIT_CANCELLED;
18327
18591
  }
@@ -18334,13 +18598,13 @@ async function interrupted(r, runId, options, printed) {
18334
18598
  `);
18335
18599
  } else {
18336
18600
  r.err(
18337
- chalk11.yellow(`${lead}Detached. Run ${runId} continues; follow it again with \`ultrac tasks follow ${runId}\`.
18601
+ chalk12.yellow(`${lead}Detached. Run ${runId} continues; follow it again with \`ultrac tasks follow ${runId}\`.
18338
18602
  `)
18339
18603
  );
18340
18604
  }
18341
18605
  return EXIT_CANCELLED;
18342
18606
  }
18343
- if (!options.json) r.err(chalk11.yellow(`${lead}Cancelling run ${runId}...
18607
+ if (!options.json) r.err(chalk12.yellow(`${lead}Cancelling run ${runId}...
18344
18608
  `));
18345
18609
  try {
18346
18610
  const cancelled = await r.client.cancelRun(runId);
@@ -18348,7 +18612,7 @@ async function interrupted(r, runId, options, printed) {
18348
18612
  r.out(`${JSON.stringify({ id: null, event: "cancel", data: cancelled })}
18349
18613
  `);
18350
18614
  } else {
18351
- r.err(chalk11.yellow(`${describeCancel(cancelled)}
18615
+ r.err(chalk12.yellow(`${describeCancel(cancelled)}
18352
18616
  `));
18353
18617
  }
18354
18618
  } catch (error) {
@@ -18357,12 +18621,12 @@ async function interrupted(r, runId, options, printed) {
18357
18621
  r.out(`${JSON.stringify({ id: null, event: "cancel_failed", data: { runId, message } })}
18358
18622
  `);
18359
18623
  } else if (error instanceof UltracApiError && error.code === "run_already_finished") {
18360
- r.err(chalk11.yellow(`${error.message} Nothing to cancel.
18624
+ r.err(chalk12.yellow(`${error.message} Nothing to cancel.
18361
18625
  `));
18362
18626
  } else {
18363
- r.err(chalk11.red(`Could not cancel run ${runId}: ${message}
18627
+ r.err(chalk12.red(`Could not cancel run ${runId}: ${message}
18364
18628
  `));
18365
- r.err(chalk11.gray(`Try again with \`ultrac tasks cancel ${runId}\`.
18629
+ r.err(chalk12.gray(`Try again with \`ultrac tasks cancel ${runId}\`.
18366
18630
  `));
18367
18631
  }
18368
18632
  }
@@ -18474,7 +18738,7 @@ ${run.output}${run.output.endsWith("\n") ? "" : "\n"}`;
18474
18738
 
18475
18739
  // src/commands/tools.ts
18476
18740
  import { resolve as resolve14 } from "path";
18477
- import chalk12 from "chalk";
18741
+ import chalk13 from "chalk";
18478
18742
  function registerToolsCommand(program2) {
18479
18743
  const toolsCommand = program2.command("tools");
18480
18744
  toolsCommand.description("List built-in agent tools").option("--json", "Print tool metadata as JSON", false).option("--mode <name>", `Only the tools a mode advertises: ${agentModeNames.join(", ")}`).option("--budget", "Print what each mode's tool set costs in characters", false).action((options) => {
@@ -18494,7 +18758,7 @@ function registerToolsCommand(program2) {
18494
18758
  printTools(tools);
18495
18759
  if (options.mode) {
18496
18760
  console.log(
18497
- chalk12.gray(
18761
+ chalk13.gray(
18498
18762
  `
18499
18763
  ${tools.length} of ${all.length} tools advertised in ${options.mode} mode, ${serializedToolsSize(
18500
18764
  toToolSchemas(tools)
@@ -18543,11 +18807,11 @@ function printToolBudget(all, json) {
18543
18807
  }
18544
18808
  for (const row of [...rows, { ...everything, names: all.map((tool) => tool.name) }]) {
18545
18809
  console.log(
18546
- `${chalk12.cyan(row.mode.padEnd(9))} ${String(row.tools).padStart(2)} tools ${String(
18810
+ `${chalk13.cyan(row.mode.padEnd(9))} ${String(row.tools).padStart(2)} tools ${String(
18547
18811
  row.chars.toLocaleString("en-US")
18548
18812
  ).padStart(7)} chars`
18549
18813
  );
18550
- console.log(chalk12.gray(` ${row.names.join(", ")}`));
18814
+ console.log(chalk13.gray(` ${row.names.join(", ")}`));
18551
18815
  }
18552
18816
  }
18553
18817
  function parseToolInput(inputText) {
@@ -18559,7 +18823,7 @@ function parseToolInput(inputText) {
18559
18823
  }
18560
18824
 
18561
18825
  // src/commands/update.ts
18562
- import chalk13 from "chalk";
18826
+ import chalk14 from "chalk";
18563
18827
  function registerUpdateCommand(program2) {
18564
18828
  const packageInfo = readPackageInfo();
18565
18829
  program2.command("update").alias("upgrade").description(`Check for and install the latest ${packageInfo.name} release`).option("--check", "Only check; do not install an available update", false).action(async (options) => {
@@ -18574,7 +18838,7 @@ function registerUpdateCommand(program2) {
18574
18838
  );
18575
18839
  }
18576
18840
  if (!status.updateAvailable) {
18577
- console.log(chalk13.green(`ultrac ${status.currentVersion} is up to date.`));
18841
+ console.log(chalk14.green(`ultrac ${status.currentVersion} is up to date.`));
18578
18842
  return;
18579
18843
  }
18580
18844
  if (options.check) {
@@ -18583,12 +18847,12 @@ function registerUpdateCommand(program2) {
18583
18847
  }
18584
18848
  console.log(`Updating ultrac ${status.currentVersion} \u2192 ${status.latestVersion}\u2026`);
18585
18849
  await installLatestVersion(packageInfo.name);
18586
- console.log(chalk13.green(`Updated to ultrac ${status.latestVersion}. Restart ultrac to use the new version.`));
18850
+ console.log(chalk14.green(`Updated to ultrac ${status.latestVersion}. Restart ultrac to use the new version.`));
18587
18851
  });
18588
18852
  }
18589
18853
  function printAvailable(currentVersion, latestVersion) {
18590
- console.log(chalk13.yellow(`Update available: ultrac ${currentVersion} \u2192 ${latestVersion}`));
18591
- console.log(chalk13.gray("Run `ultrac update` to install it."));
18854
+ console.log(chalk14.yellow(`Update available: ultrac ${currentVersion} \u2192 ${latestVersion}`));
18855
+ console.log(chalk14.gray("Run `ultrac update` to install it."));
18592
18856
  }
18593
18857
 
18594
18858
  // src/cli.ts
@@ -18618,6 +18882,6 @@ registerSkillsCommand(program);
18618
18882
  registerReplCommand(program);
18619
18883
  program.parseAsync(process.argv).catch((error) => {
18620
18884
  const message = error instanceof Error ? error.message : String(error);
18621
- console.error(chalk14.red(message));
18885
+ console.error(chalk15.red(message));
18622
18886
  process.exitCode = 1;
18623
18887
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spinalfluid/ultrac",
3
- "version": "0.1.0",
3
+ "version": "0.1.5",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "description": "Ultrac terminal agent harness for Cosmic 1, with local tools, browser sign-in, streamed answers, and resumable sessions.",
6
6
  "author": "Aditya Pundir",