@raegent/earshot 0.1.0 → 0.3.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 (3) hide show
  1. package/dist/main.js +1424 -199
  2. package/dist/main.js.map +16 -11
  3. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -17094,6 +17094,7 @@ class Agent {
17094
17094
  readFiles = new Set;
17095
17095
  queued = [];
17096
17096
  rules;
17097
+ resolved;
17097
17098
  mode;
17098
17099
  totalCostUsd = 0;
17099
17100
  maxCostUsd;
@@ -17129,6 +17130,18 @@ class Agent {
17129
17130
  this.askFn = options.ask;
17130
17131
  this.systemPrompt = options.system;
17131
17132
  this.maxCostUsd = options.maxCostUsd;
17133
+ this.resolved = options.model;
17134
+ }
17135
+ get model() {
17136
+ return this.resolved;
17137
+ }
17138
+ async changeModel(ref) {
17139
+ const resolved = await resolveModel(this.options.registry, ref);
17140
+ this.resolved = resolved;
17141
+ return resolved;
17142
+ }
17143
+ get permissionRules() {
17144
+ return [...this.rules];
17132
17145
  }
17133
17146
  get budgetUsd() {
17134
17147
  return this.maxCostUsd;
@@ -17176,7 +17189,7 @@ ${renderPlan(this.plan)}`;
17176
17189
  this.options.onCost?.(costUsd);
17177
17190
  }
17178
17191
  get contextUse() {
17179
- return { tokens: this.lastRequestTokens, window: this.options.model.model.contextWindow ?? 0 };
17192
+ return { tokens: this.lastRequestTokens, window: this.resolved.model.contextWindow ?? 0 };
17180
17193
  }
17181
17194
  get touchedFiles() {
17182
17195
  return [...this.readFiles];
@@ -17204,12 +17217,12 @@ ${renderPlan(this.plan)}`;
17204
17217
  this.checksThisTurn = 0;
17205
17218
  const promptText = userPromptText(prompt);
17206
17219
  const promptParts = typeof prompt === "string" ? [{ type: "text", text: prompt }] : prompt.map((part) => ({ ...part }));
17207
- if (promptParts.some((part) => part.type === "image") && !this.options.model.model.capabilities.vision) {
17220
+ if (promptParts.some((part) => part.type === "image") && !this.resolved.model.capabilities.vision) {
17208
17221
  yield {
17209
17222
  type: "error",
17210
17223
  error: {
17211
17224
  kind: "invalid_request",
17212
- message: `${this.options.model.model.name} does not support image input`,
17225
+ message: `${this.resolved.model.name} does not support image input`,
17213
17226
  retryable: false
17214
17227
  }
17215
17228
  };
@@ -17254,7 +17267,7 @@ ${context.join(`
17254
17267
  content
17255
17268
  });
17256
17269
  const maxSteps = this.options.maxSteps ?? DEFAULT_MAX_STEPS;
17257
- const modelName = `${this.options.model.provider.id}/${this.options.model.model.id}`;
17270
+ const modelName = `${this.resolved.provider.id}/${this.resolved.model.id}`;
17258
17271
  for (let step = 0;step < maxSteps; step++) {
17259
17272
  if (signal.aborted) {
17260
17273
  yield { type: "turn_end", reason: "aborted" };
@@ -17278,7 +17291,7 @@ ${context.join(`
17278
17291
  yield { type: "model_start", model: modelName };
17279
17292
  let assistant;
17280
17293
  let failed;
17281
- for await (const event of streamModel(this.options.registry, this.options.model, {
17294
+ for await (const event of streamModel(this.options.registry, this.resolved, {
17282
17295
  system: this.effectiveSystem,
17283
17296
  messages,
17284
17297
  tools: this.offeredTools(),
@@ -17293,7 +17306,7 @@ ${context.join(`
17293
17306
  break;
17294
17307
  case "finish": {
17295
17308
  assistant = event.message;
17296
- const costUsd = turnCost(this.options.model.model, event.usage);
17309
+ const costUsd = turnCost(this.resolved.model, event.usage);
17297
17310
  this.addCost(costUsd);
17298
17311
  yield { type: "usage", usage: event.usage, costUsd };
17299
17312
  break;
@@ -17598,11 +17611,20 @@ If something is failing, fix it or say what is ` + `failing and why - do not des
17598
17611
  return shapeMessages(base, { ...DEFAULT_SHAPER_OPTIONS, ...this.options.shapers });
17599
17612
  }
17600
17613
  async* prepareRequest(signal) {
17601
- const window = this.options.model.model.contextWindow ?? 0;
17614
+ const window = this.resolved.model.contextWindow ?? 0;
17602
17615
  const policy = { threshold: 0.8, keepRecentMessages: 8, ...this.options.compaction };
17603
17616
  const shaped = this.requestMessages();
17604
17617
  if (!shouldCompact(shaped, this.effectiveSystem, window, policy))
17605
17618
  return;
17619
+ yield* this.runCompaction(signal);
17620
+ }
17621
+ async* compactNow(signal) {
17622
+ yield* this.runCompaction(signal);
17623
+ }
17624
+ async* runCompaction(signal) {
17625
+ const window = this.resolved.model.contextWindow ?? 0;
17626
+ const policy = { threshold: 0.8, keepRecentMessages: 8, ...this.options.compaction };
17627
+ const shaped = this.requestMessages();
17606
17628
  const result = await compact({
17607
17629
  messages: shaped,
17608
17630
  system: this.effectiveSystem,
@@ -17625,7 +17647,7 @@ If something is failing, fix it or say what is ` + `failing and why - do not des
17625
17647
  }
17626
17648
  async summarise(messages, signal) {
17627
17649
  let text2 = "";
17628
- for await (const event of streamModel(this.options.registry, this.options.model, {
17650
+ for await (const event of streamModel(this.options.registry, this.resolved, {
17629
17651
  system: SUMMARY_PROMPT,
17630
17652
  messages: [...messages, { role: "user", content: [{ type: "text", text: SUMMARY_PROMPT }] }],
17631
17653
  abortSignal: signal
@@ -18359,7 +18381,7 @@ class ShadowGit {
18359
18381
  }
18360
18382
 
18361
18383
  // packages/core/src/version.ts
18362
- var VERSION = "0.1.0";
18384
+ var VERSION = "0.3.0";
18363
18385
 
18364
18386
  // packages/core/src/session/repair.ts
18365
18387
  var REPAIR_TEXT = "No result was recorded for this call: earshot exited before the tool finished. " + "The call may or may not have run, so treat its effect as unknown and check " + "the current state rather than assuming either outcome.";
@@ -18620,7 +18642,16 @@ function parseArgs(argv) {
18620
18642
  positionals.push(arg);
18621
18643
  }
18622
18644
  }
18623
- const known = new Set(["auth", "mcp", "extensions", "config", "models", "acp", "doctor"]);
18645
+ const known = new Set([
18646
+ "auth",
18647
+ "mcp",
18648
+ "extensions",
18649
+ "config",
18650
+ "models",
18651
+ "acp",
18652
+ "doctor",
18653
+ "update"
18654
+ ]);
18624
18655
  const command = positionals[0] !== undefined && known.has(positionals[0]) ? positionals[0] : undefined;
18625
18656
  return { command, flags, positionals: command ? positionals.slice(1) : positionals };
18626
18657
  }
@@ -20256,10 +20287,124 @@ run \`earshot models\` to see what is available
20256
20287
  }
20257
20288
 
20258
20289
  // packages/tui/src/app.tsx
20259
- import { Box as Box8, Static, Text as Text9, useApp, useInput as useInput4 } from "ink";
20290
+ import { Box as Box9, Static, Text as Text10, useApp, useInput as useInput4 } from "ink";
20260
20291
  import { useCallback, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
20261
20292
 
20262
- // packages/tui/src/components/markdown.tsx
20293
+ // packages/tui/src/commands.ts
20294
+ var SPECS = [
20295
+ {
20296
+ name: "help",
20297
+ summary: "List the commands you can type"
20298
+ },
20299
+ {
20300
+ name: "model",
20301
+ args: "[ref]",
20302
+ summary: "Show the model in use, or switch to another for the rest of the session"
20303
+ },
20304
+ {
20305
+ name: "mode",
20306
+ args: "<plan|ask|accept-edits|auto|yolo>",
20307
+ summary: "Change the permission mode"
20308
+ },
20309
+ {
20310
+ name: "plan",
20311
+ args: "<task>",
20312
+ summary: "Draft a plan in plan mode and write it to a file",
20313
+ idleOnly: true,
20314
+ verbs: [
20315
+ { name: "edit", summary: "Open the plan in $VISUAL/$EDITOR, or print its path" },
20316
+ { name: "approve", summary: "Pin the plan as the file now reads for the rest of the run" },
20317
+ { name: "show", summary: "Read the plan back" },
20318
+ { name: "clear", summary: "Unpin the plan" }
20319
+ ]
20320
+ },
20321
+ {
20322
+ name: "compact",
20323
+ summary: "Summarise the session so far and free up the context window",
20324
+ idleOnly: true
20325
+ },
20326
+ {
20327
+ name: "context",
20328
+ summary: "Show what is in the context window and what compaction has dropped"
20329
+ },
20330
+ {
20331
+ name: "cost",
20332
+ args: "[usd]",
20333
+ summary: "Show what this session has spent, or set the budget ceiling; 0 removes it"
20334
+ },
20335
+ {
20336
+ name: "todo",
20337
+ summary: "Show the agent's current todo list"
20338
+ },
20339
+ {
20340
+ name: "permissions",
20341
+ summary: "Show the permission mode and the rules in force"
20342
+ },
20343
+ {
20344
+ name: "init",
20345
+ summary: "Write an AGENTS.md describing this project",
20346
+ idleOnly: true
20347
+ },
20348
+ {
20349
+ name: "skills",
20350
+ summary: "List discovered skills and user-defined commands"
20351
+ },
20352
+ {
20353
+ name: "memory",
20354
+ summary: "List remembered preferences, each with the sentence it came from",
20355
+ verbs: [{ name: "forget", args: "<id>", summary: "Delete one remembered preference" }]
20356
+ },
20357
+ {
20358
+ name: "tree",
20359
+ summary: "List this session's prompts, numbered",
20360
+ idleOnly: true
20361
+ },
20362
+ {
20363
+ name: "rewind",
20364
+ args: "<n>",
20365
+ summary: "Go back to the state before prompt n; nothing is deleted",
20366
+ idleOnly: true
20367
+ },
20368
+ {
20369
+ name: "fork",
20370
+ args: "<n>",
20371
+ summary: "Branch from prompt n into a new transcript",
20372
+ idleOnly: true
20373
+ },
20374
+ {
20375
+ name: "undo",
20376
+ summary: "Revert the last tool batch's file changes; again to step back further"
20377
+ },
20378
+ {
20379
+ name: "exit",
20380
+ aliases: ["quit"],
20381
+ summary: "Quit"
20382
+ }
20383
+ ];
20384
+ var COMMANDS = SPECS;
20385
+ function findCommand(name) {
20386
+ return COMMANDS.find((command) => command.name === name || command.aliases?.includes(name));
20387
+ }
20388
+ function commandRows() {
20389
+ const rows = [];
20390
+ for (const spec of COMMANDS) {
20391
+ rows.push({
20392
+ command: spec.args ? `/${spec.name} ${spec.args}` : `/${spec.name}`,
20393
+ summary: spec.summary,
20394
+ spec
20395
+ });
20396
+ for (const verb of spec.verbs ?? []) {
20397
+ rows.push({
20398
+ command: `/${spec.name} ${verb.name}${verb.args ? ` ${verb.args}` : ""}`,
20399
+ summary: verb.summary,
20400
+ spec
20401
+ });
20402
+ }
20403
+ }
20404
+ return rows;
20405
+ }
20406
+
20407
+ // packages/tui/src/components/command-menu.tsx
20263
20408
  import { Box, Text } from "ink";
20264
20409
 
20265
20410
  // packages/tui/src/theme.ts
@@ -20292,13 +20437,123 @@ var MODE_COLOR = {
20292
20437
  yolo: theme.danger
20293
20438
  };
20294
20439
 
20440
+ // packages/tui/src/components/command-menu.tsx
20441
+ import { jsxDEV } from "react/jsx-dev-runtime";
20442
+ var SUMMARY_WIDTH = 58;
20443
+ var VISIBLE = 10;
20444
+ function menuEntries(query, commands, busy) {
20445
+ const entries = [];
20446
+ for (const spec of ranked(COMMANDS, query)) {
20447
+ if (!matches2(spec.name, query))
20448
+ continue;
20449
+ const disabled = busy && spec.idleOnly === true;
20450
+ const expanded = query !== "" && spec.name.startsWith(query);
20451
+ for (const row of commandRows()) {
20452
+ if (row.spec.name !== spec.name)
20453
+ continue;
20454
+ const isBase = row.command === (spec.args ? `/${spec.name} ${spec.args}` : `/${spec.name}`);
20455
+ if (!isBase && !expanded)
20456
+ continue;
20457
+ entries.push({
20458
+ insert: insertFor(row.command, spec),
20459
+ label: row.command,
20460
+ summary: row.summary,
20461
+ ...disabled ? { disabled: true } : {}
20462
+ });
20463
+ }
20464
+ }
20465
+ for (const command of ranked(commands, query)) {
20466
+ if (!matches2(command.name, query))
20467
+ continue;
20468
+ entries.push({
20469
+ insert: `/${command.name}`,
20470
+ label: `/${command.name}`,
20471
+ summary: command.description,
20472
+ scope: command.scope
20473
+ });
20474
+ }
20475
+ return entries;
20476
+ }
20477
+ function insertFor(command, spec) {
20478
+ const withoutArgs = spec.args ? command.replace(` ${spec.args}`, "") : command;
20479
+ return withoutArgs.replace(/\s<[^>]*>$/, "");
20480
+ }
20481
+ var clamp = (text2) => text2.length <= SUMMARY_WIDTH ? text2 : `${text2.slice(0, SUMMARY_WIDTH - 1)}…`;
20482
+ function rowColor(entry, active) {
20483
+ if (entry.disabled)
20484
+ return { color: theme.muted };
20485
+ if (active)
20486
+ return { color: theme.user };
20487
+ return {};
20488
+ }
20489
+ function matches2(name, query) {
20490
+ return query === "" || name.includes(query);
20491
+ }
20492
+ function ranked(items, query) {
20493
+ if (query === "")
20494
+ return [...items];
20495
+ return [
20496
+ ...items.filter((item) => item.name.startsWith(query)),
20497
+ ...items.filter((item) => !item.name.startsWith(query))
20498
+ ];
20499
+ }
20500
+ function CommandMenu({ entries, selected }) {
20501
+ if (entries.length === 0) {
20502
+ return /* @__PURE__ */ jsxDEV(Box, {
20503
+ marginTop: 1,
20504
+ children: /* @__PURE__ */ jsxDEV(Text, {
20505
+ color: theme.muted,
20506
+ children: "no command matches"
20507
+ }, undefined, false, undefined, this)
20508
+ }, undefined, false, undefined, this);
20509
+ }
20510
+ const start = Math.min(Math.max(0, selected - VISIBLE + 1), Math.max(0, entries.length - VISIBLE));
20511
+ const shown = entries.slice(start, start + VISIBLE);
20512
+ const hidden = entries.length - shown.length;
20513
+ return /* @__PURE__ */ jsxDEV(Box, {
20514
+ flexDirection: "column",
20515
+ marginTop: 1,
20516
+ children: [
20517
+ shown.map((entry, index) => {
20518
+ const active = start + index === selected;
20519
+ return /* @__PURE__ */ jsxDEV(Box, {
20520
+ children: [
20521
+ /* @__PURE__ */ jsxDEV(Text, {
20522
+ color: active ? theme.user : theme.muted,
20523
+ children: active ? "› " : " "
20524
+ }, undefined, false, undefined, this),
20525
+ /* @__PURE__ */ jsxDEV(Text, {
20526
+ ...rowColor(entry, active),
20527
+ children: entry.label.padEnd(34)
20528
+ }, undefined, false, undefined, this),
20529
+ /* @__PURE__ */ jsxDEV(Text, {
20530
+ color: theme.muted,
20531
+ children: clamp(`${entry.scope ? `[${entry.scope}] ` : ""}${entry.summary}${entry.disabled ? " (not while a turn is running)" : ""}`)
20532
+ }, undefined, false, undefined, this)
20533
+ ]
20534
+ }, entry.label, true, undefined, this);
20535
+ }),
20536
+ /* @__PURE__ */ jsxDEV(Box, {
20537
+ children: /* @__PURE__ */ jsxDEV(Text, {
20538
+ color: theme.muted,
20539
+ children: [
20540
+ hidden > 0 ? ` ${hidden} more · ` : " ",
20541
+ "↑↓ choose · tab complete · enter run · esc close"
20542
+ ]
20543
+ }, undefined, true, undefined, this)
20544
+ }, undefined, false, undefined, this)
20545
+ ]
20546
+ }, undefined, true, undefined, this);
20547
+ }
20548
+
20295
20549
  // packages/tui/src/components/markdown.tsx
20296
- import { jsxDEV, Fragment } from "react/jsx-dev-runtime";
20550
+ import { Box as Box2, Text as Text2 } from "ink";
20551
+ import { jsxDEV as jsxDEV2, Fragment } from "react/jsx-dev-runtime";
20297
20552
  function Markdown({ text: text2 }) {
20298
20553
  const blocks = splitBlocks(text2);
20299
- return /* @__PURE__ */ jsxDEV(Box, {
20554
+ return /* @__PURE__ */ jsxDEV2(Box2, {
20300
20555
  flexDirection: "column",
20301
- children: blocks.map((block, index) => /* @__PURE__ */ jsxDEV(Block, {
20556
+ children: blocks.map((block, index) => /* @__PURE__ */ jsxDEV2(Block, {
20302
20557
  block
20303
20558
  }, index, false, undefined, this))
20304
20559
  }, undefined, false, undefined, this);
@@ -20377,19 +20632,19 @@ function splitBlocks(text2) {
20377
20632
  function Block({ block }) {
20378
20633
  switch (block.kind) {
20379
20634
  case "code":
20380
- return /* @__PURE__ */ jsxDEV(Box, {
20635
+ return /* @__PURE__ */ jsxDEV2(Box2, {
20381
20636
  flexDirection: "column",
20382
20637
  marginY: 1,
20383
20638
  paddingLeft: 2,
20384
- children: block.lines.map((line, index) => /* @__PURE__ */ jsxDEV(Text, {
20639
+ children: block.lines.map((line, index) => /* @__PURE__ */ jsxDEV2(Text2, {
20385
20640
  color: theme.tool,
20386
20641
  children: line
20387
20642
  }, index, false, undefined, this))
20388
20643
  }, undefined, false, undefined, this);
20389
20644
  case "heading":
20390
- return /* @__PURE__ */ jsxDEV(Box, {
20645
+ return /* @__PURE__ */ jsxDEV2(Box2, {
20391
20646
  marginTop: block.level <= 2 ? 1 : 0,
20392
- children: /* @__PURE__ */ jsxDEV(Text, {
20647
+ children: /* @__PURE__ */ jsxDEV2(Text2, {
20393
20648
  bold: true,
20394
20649
  underline: block.level === 1,
20395
20650
  color: theme.assistant,
@@ -20397,29 +20652,29 @@ function Block({ block }) {
20397
20652
  }, undefined, false, undefined, this)
20398
20653
  }, undefined, false, undefined, this);
20399
20654
  case "rule":
20400
- return /* @__PURE__ */ jsxDEV(Text, {
20655
+ return /* @__PURE__ */ jsxDEV2(Text2, {
20401
20656
  color: theme.muted,
20402
20657
  children: "─".repeat(40)
20403
20658
  }, undefined, false, undefined, this);
20404
20659
  case "list":
20405
- return /* @__PURE__ */ jsxDEV(Box, {
20660
+ return /* @__PURE__ */ jsxDEV2(Box2, {
20406
20661
  flexDirection: "column",
20407
- children: block.items.map((item, index) => /* @__PURE__ */ jsxDEV(Text, {
20662
+ children: block.items.map((item, index) => /* @__PURE__ */ jsxDEV2(Text2, {
20408
20663
  children: [
20409
20664
  " ",
20410
20665
  block.ordered ? `${index + 1}.` : "-",
20411
20666
  " ",
20412
- /* @__PURE__ */ jsxDEV(Inline, {
20667
+ /* @__PURE__ */ jsxDEV2(Inline, {
20413
20668
  text: item
20414
20669
  }, undefined, false, undefined, this)
20415
20670
  ]
20416
20671
  }, index, true, undefined, this))
20417
20672
  }, undefined, false, undefined, this);
20418
20673
  case "quote":
20419
- return /* @__PURE__ */ jsxDEV(Box, {
20674
+ return /* @__PURE__ */ jsxDEV2(Box2, {
20420
20675
  flexDirection: "column",
20421
20676
  paddingLeft: 1,
20422
- children: block.lines.map((line, index) => /* @__PURE__ */ jsxDEV(Text, {
20677
+ children: block.lines.map((line, index) => /* @__PURE__ */ jsxDEV2(Text2, {
20423
20678
  color: theme.muted,
20424
20679
  italic: true,
20425
20680
  children: [
@@ -20429,8 +20684,8 @@ function Block({ block }) {
20429
20684
  }, index, true, undefined, this))
20430
20685
  }, undefined, false, undefined, this);
20431
20686
  default:
20432
- return /* @__PURE__ */ jsxDEV(Text, {
20433
- children: /* @__PURE__ */ jsxDEV(Inline, {
20687
+ return /* @__PURE__ */ jsxDEV2(Text2, {
20688
+ children: /* @__PURE__ */ jsxDEV2(Inline, {
20434
20689
  text: block.text
20435
20690
  }, undefined, false, undefined, this)
20436
20691
  }, undefined, false, undefined, this);
@@ -20442,41 +20697,41 @@ function inlineToText(text2) {
20442
20697
  function Inline({ text: text2 }) {
20443
20698
  const pattern = /(\*\*.+?\*\*|__.+?__|`.+?`|\*.+?\*|_.+?_)/g;
20444
20699
  const parts = text2.split(pattern);
20445
- return /* @__PURE__ */ jsxDEV(Fragment, {
20700
+ return /* @__PURE__ */ jsxDEV2(Fragment, {
20446
20701
  children: parts.map((part, index) => {
20447
20702
  if (part === "")
20448
20703
  return null;
20449
20704
  if (part.startsWith("**") && part.endsWith("**")) {
20450
- return /* @__PURE__ */ jsxDEV(Text, {
20705
+ return /* @__PURE__ */ jsxDEV2(Text2, {
20451
20706
  bold: true,
20452
20707
  children: part.slice(2, -2)
20453
20708
  }, index, false, undefined, this);
20454
20709
  }
20455
20710
  if (part.startsWith("__") && part.endsWith("__")) {
20456
- return /* @__PURE__ */ jsxDEV(Text, {
20711
+ return /* @__PURE__ */ jsxDEV2(Text2, {
20457
20712
  bold: true,
20458
20713
  children: part.slice(2, -2)
20459
20714
  }, index, false, undefined, this);
20460
20715
  }
20461
20716
  if (part.startsWith("`") && part.endsWith("`")) {
20462
- return /* @__PURE__ */ jsxDEV(Text, {
20717
+ return /* @__PURE__ */ jsxDEV2(Text2, {
20463
20718
  color: theme.tool,
20464
20719
  children: part.slice(1, -1)
20465
20720
  }, index, false, undefined, this);
20466
20721
  }
20467
20722
  if (part.startsWith("*") && part.endsWith("*")) {
20468
- return /* @__PURE__ */ jsxDEV(Text, {
20723
+ return /* @__PURE__ */ jsxDEV2(Text2, {
20469
20724
  italic: true,
20470
20725
  children: part.slice(1, -1)
20471
20726
  }, index, false, undefined, this);
20472
20727
  }
20473
20728
  if (part.startsWith("_") && part.endsWith("_")) {
20474
- return /* @__PURE__ */ jsxDEV(Text, {
20729
+ return /* @__PURE__ */ jsxDEV2(Text2, {
20475
20730
  italic: true,
20476
20731
  children: part.slice(1, -1)
20477
20732
  }, index, false, undefined, this);
20478
20733
  }
20479
- return /* @__PURE__ */ jsxDEV(Text, {
20734
+ return /* @__PURE__ */ jsxDEV2(Text2, {
20480
20735
  children: part
20481
20736
  }, index, false, undefined, this);
20482
20737
  })
@@ -20484,24 +20739,24 @@ function Inline({ text: text2 }) {
20484
20739
  }
20485
20740
 
20486
20741
  // packages/tui/src/components/memory-capture.tsx
20487
- import { Box as Box2, Text as Text2 } from "ink";
20488
- import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
20742
+ import { Box as Box3, Text as Text3 } from "ink";
20743
+ import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
20489
20744
  function MemoryCapture({ candidate }) {
20490
- return /* @__PURE__ */ jsxDEV2(Box2, {
20745
+ return /* @__PURE__ */ jsxDEV3(Box3, {
20491
20746
  marginTop: 1,
20492
20747
  children: [
20493
- /* @__PURE__ */ jsxDEV2(Text2, {
20748
+ /* @__PURE__ */ jsxDEV3(Text3, {
20494
20749
  color: theme.accent,
20495
20750
  children: "remember "
20496
20751
  }, undefined, false, undefined, this),
20497
- /* @__PURE__ */ jsxDEV2(Text2, {
20752
+ /* @__PURE__ */ jsxDEV3(Text3, {
20498
20753
  children: [
20499
20754
  "“",
20500
20755
  candidate.text,
20501
20756
  "”"
20502
20757
  ]
20503
20758
  }, undefined, true, undefined, this),
20504
- /* @__PURE__ */ jsxDEV2(Text2, {
20759
+ /* @__PURE__ */ jsxDEV3(Text3, {
20505
20760
  color: theme.muted,
20506
20761
  children: "? ctrl+r for this project · ctrl+g everywhere"
20507
20762
  }, undefined, false, undefined, this)
@@ -20510,12 +20765,12 @@ function MemoryCapture({ candidate }) {
20510
20765
  }
20511
20766
 
20512
20767
  // packages/tui/src/components/permission.tsx
20513
- import { Box as Box4, Text as Text4, useInput } from "ink";
20768
+ import { Box as Box5, Text as Text5, useInput } from "ink";
20514
20769
  import { useState } from "react";
20515
20770
 
20516
20771
  // packages/tui/src/components/diff.tsx
20517
- import { Box as Box3, Text as Text3 } from "ink";
20518
- import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
20772
+ import { Box as Box4, Text as Text4 } from "ink";
20773
+ import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
20519
20774
  var MAX_LINES = 60;
20520
20775
  function DiffView({ diff: diff2, maxLines = MAX_LINES }) {
20521
20776
  const lines = diff2.split(`
@@ -20523,15 +20778,15 @@ function DiffView({ diff: diff2, maxLines = MAX_LINES }) {
20523
20778
  const body = lines.filter((line) => !line.startsWith("---") && !line.startsWith("+++"));
20524
20779
  const shown = body.slice(0, maxLines);
20525
20780
  const hidden = body.length - shown.length;
20526
- return /* @__PURE__ */ jsxDEV3(Box3, {
20781
+ return /* @__PURE__ */ jsxDEV4(Box4, {
20527
20782
  flexDirection: "column",
20528
20783
  children: [
20529
- shown.map((line, index) => /* @__PURE__ */ jsxDEV3(Text3, {
20784
+ shown.map((line, index) => /* @__PURE__ */ jsxDEV4(Text4, {
20530
20785
  color: colorFor(line),
20531
20786
  wrap: "truncate-end",
20532
20787
  children: line === "" ? " " : line
20533
20788
  }, index, false, undefined, this)),
20534
- hidden > 0 && /* @__PURE__ */ jsxDEV3(Text3, {
20789
+ hidden > 0 && /* @__PURE__ */ jsxDEV4(Text4, {
20535
20790
  color: theme.muted,
20536
20791
  children: [
20537
20792
  " ",
@@ -20566,7 +20821,7 @@ function diffStat(diff2) {
20566
20821
  }
20567
20822
 
20568
20823
  // packages/tui/src/components/permission.tsx
20569
- import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
20824
+ import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
20570
20825
  function PermissionPrompt({ request, reason, onChoice }) {
20571
20826
  const [selected, setSelected] = useState(0);
20572
20827
  const options = [
@@ -20594,32 +20849,32 @@ function PermissionPrompt({ request, reason, onChoice }) {
20594
20849
  });
20595
20850
  const isDiff = request.detail.includes(`
20596
20851
  @@`) || request.detail.startsWith("---");
20597
- return /* @__PURE__ */ jsxDEV4(Box4, {
20852
+ return /* @__PURE__ */ jsxDEV5(Box5, {
20598
20853
  flexDirection: "column",
20599
20854
  borderStyle: "round",
20600
20855
  borderColor: theme.warning,
20601
20856
  paddingX: 1,
20602
20857
  children: [
20603
- /* @__PURE__ */ jsxDEV4(Text4, {
20858
+ /* @__PURE__ */ jsxDEV5(Text5, {
20604
20859
  bold: true,
20605
20860
  color: theme.warning,
20606
20861
  children: request.title
20607
20862
  }, undefined, false, undefined, this),
20608
- /* @__PURE__ */ jsxDEV4(Text4, {
20863
+ /* @__PURE__ */ jsxDEV5(Text5, {
20609
20864
  color: theme.muted,
20610
20865
  children: reason
20611
20866
  }, undefined, false, undefined, this),
20612
- /* @__PURE__ */ jsxDEV4(Box4, {
20867
+ /* @__PURE__ */ jsxDEV5(Box5, {
20613
20868
  marginY: 1,
20614
20869
  flexDirection: "column",
20615
- children: isDiff ? /* @__PURE__ */ jsxDEV4(DiffView, {
20870
+ children: isDiff ? /* @__PURE__ */ jsxDEV5(DiffView, {
20616
20871
  diff: request.detail
20617
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV4(Text4, {
20872
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV5(Text5, {
20618
20873
  wrap: "wrap",
20619
20874
  children: request.detail
20620
20875
  }, undefined, false, undefined, this)
20621
20876
  }, undefined, false, undefined, this),
20622
- options.map((option, index) => /* @__PURE__ */ jsxDEV4(Text4, {
20877
+ options.map((option, index) => /* @__PURE__ */ jsxDEV5(Text5, {
20623
20878
  color: index === selected ? theme.accent : option.color,
20624
20879
  children: [
20625
20880
  index === selected ? "❯ " : " ",
@@ -20634,13 +20889,13 @@ function truncate2(value, max) {
20634
20889
  }
20635
20890
 
20636
20891
  // packages/tui/src/components/question.tsx
20637
- import { Box as Box5, Text as Text6, useInput as useInput3 } from "ink";
20892
+ import { Box as Box6, Text as Text7, useInput as useInput3 } from "ink";
20638
20893
  import { useState as useState2 } from "react";
20639
20894
 
20640
20895
  // packages/tui/src/components/text-input.tsx
20641
- import { Text as Text5, useInput as useInput2 } from "ink";
20896
+ import { Text as Text6, useInput as useInput2 } from "ink";
20642
20897
  import { useEffect, useRef } from "react";
20643
- import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
20898
+ import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
20644
20899
  function TextInput({
20645
20900
  value,
20646
20901
  onChange,
@@ -20674,23 +20929,23 @@ function TextInput({
20674
20929
  }
20675
20930
  }, { isActive });
20676
20931
  if (value === "") {
20677
- return /* @__PURE__ */ jsxDEV5(Text5, {
20932
+ return /* @__PURE__ */ jsxDEV6(Text6, {
20678
20933
  children: [
20679
- /* @__PURE__ */ jsxDEV5(Text5, {
20934
+ /* @__PURE__ */ jsxDEV6(Text6, {
20680
20935
  inverse: true,
20681
20936
  children: " "
20682
20937
  }, undefined, false, undefined, this),
20683
- /* @__PURE__ */ jsxDEV5(Text5, {
20938
+ /* @__PURE__ */ jsxDEV6(Text6, {
20684
20939
  color: theme.muted,
20685
20940
  children: placeholder
20686
20941
  }, undefined, false, undefined, this)
20687
20942
  ]
20688
20943
  }, undefined, true, undefined, this);
20689
20944
  }
20690
- return /* @__PURE__ */ jsxDEV5(Text5, {
20945
+ return /* @__PURE__ */ jsxDEV6(Text6, {
20691
20946
  children: [
20692
20947
  value,
20693
- /* @__PURE__ */ jsxDEV5(Text5, {
20948
+ /* @__PURE__ */ jsxDEV6(Text6, {
20694
20949
  inverse: true,
20695
20950
  children: " "
20696
20951
  }, undefined, false, undefined, this)
@@ -20699,7 +20954,7 @@ function TextInput({
20699
20954
  }
20700
20955
 
20701
20956
  // packages/tui/src/components/question.tsx
20702
- import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
20957
+ import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
20703
20958
  function QuestionPrompt({ question, options = [], onAnswer }) {
20704
20959
  const [selected, setSelected] = useState2(0);
20705
20960
  const [typing, setTyping] = useState2(options.length === 0);
@@ -20716,29 +20971,29 @@ function QuestionPrompt({ question, options = [], onAnswer }) {
20716
20971
  setValue(input);
20717
20972
  }
20718
20973
  }, { isActive: !typing });
20719
- return /* @__PURE__ */ jsxDEV6(Box5, {
20974
+ return /* @__PURE__ */ jsxDEV7(Box6, {
20720
20975
  flexDirection: "column",
20721
20976
  borderStyle: "round",
20722
20977
  borderColor: theme.accent,
20723
20978
  paddingX: 1,
20724
20979
  children: [
20725
- /* @__PURE__ */ jsxDEV6(Text6, {
20980
+ /* @__PURE__ */ jsxDEV7(Text7, {
20726
20981
  bold: true,
20727
20982
  color: theme.accent,
20728
20983
  children: question
20729
20984
  }, undefined, false, undefined, this),
20730
- !typing && options.map((option, index) => /* @__PURE__ */ jsxDEV6(Text6, {
20985
+ !typing && options.map((option, index) => /* @__PURE__ */ jsxDEV7(Text7, {
20731
20986
  color: index === selected ? theme.accent : theme.muted,
20732
20987
  children: [
20733
20988
  index === selected ? "❯ " : " ",
20734
20989
  option
20735
20990
  ]
20736
20991
  }, option, true, undefined, this)),
20737
- !typing && /* @__PURE__ */ jsxDEV6(Text6, {
20992
+ !typing && /* @__PURE__ */ jsxDEV7(Text7, {
20738
20993
  color: theme.muted,
20739
20994
  children: "or start typing to answer in your own words"
20740
20995
  }, undefined, false, undefined, this),
20741
- typing && /* @__PURE__ */ jsxDEV6(TextInput, {
20996
+ typing && /* @__PURE__ */ jsxDEV7(TextInput, {
20742
20997
  value,
20743
20998
  onChange: setValue,
20744
20999
  onSubmit: (answer) => onAnswer(answer.trim()),
@@ -20749,8 +21004,8 @@ function QuestionPrompt({ question, options = [], onAnswer }) {
20749
21004
  }
20750
21005
 
20751
21006
  // packages/tui/src/components/status.tsx
20752
- import { Box as Box6, Text as Text7 } from "ink";
20753
- import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
21007
+ import { Box as Box7, Text as Text8 } from "ink";
21008
+ import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
20754
21009
  function StatusLine({
20755
21010
  model: model2,
20756
21011
  mode,
@@ -20764,27 +21019,27 @@ function StatusLine({
20764
21019
  const done = todos.filter((todo2) => todo2.status === "done").length;
20765
21020
  const current = todos.find((todo2) => todo2.status === "in_progress");
20766
21021
  const used = context2.window > 0 ? Math.min(100, context2.tokens / context2.window * 100) : 0;
20767
- return /* @__PURE__ */ jsxDEV7(Box6, {
21022
+ return /* @__PURE__ */ jsxDEV8(Box7, {
20768
21023
  children: [
20769
- /* @__PURE__ */ jsxDEV7(Text7, {
21024
+ /* @__PURE__ */ jsxDEV8(Text8, {
20770
21025
  color: MODE_COLOR[mode] ?? theme.muted,
20771
21026
  children: MODE_LABEL[mode] ?? mode
20772
21027
  }, undefined, false, undefined, this),
20773
- /* @__PURE__ */ jsxDEV7(Text7, {
21028
+ /* @__PURE__ */ jsxDEV8(Text8, {
20774
21029
  color: theme.muted,
20775
21030
  children: [
20776
21031
  " · ",
20777
21032
  model2
20778
21033
  ]
20779
21034
  }, undefined, true, undefined, this),
20780
- /* @__PURE__ */ jsxDEV7(Text7, {
21035
+ /* @__PURE__ */ jsxDEV8(Text8, {
20781
21036
  color: theme.muted,
20782
21037
  children: [
20783
21038
  " · $",
20784
21039
  costUsd.toFixed(4)
20785
21040
  ]
20786
21041
  }, undefined, true, undefined, this),
20787
- context2.window > 0 && /* @__PURE__ */ jsxDEV7(Text7, {
21042
+ context2.window > 0 && /* @__PURE__ */ jsxDEV8(Text8, {
20788
21043
  color: used >= 80 ? theme.warning : theme.muted,
20789
21044
  children: [
20790
21045
  " · ",
@@ -20792,7 +21047,7 @@ function StatusLine({
20792
21047
  "% ctx"
20793
21048
  ]
20794
21049
  }, undefined, true, undefined, this),
20795
- compacted > 0 && /* @__PURE__ */ jsxDEV7(Text7, {
21050
+ compacted > 0 && /* @__PURE__ */ jsxDEV8(Text8, {
20796
21051
  color: theme.muted,
20797
21052
  children: [
20798
21053
  " · ",
@@ -20800,7 +21055,7 @@ function StatusLine({
20800
21055
  " summarised"
20801
21056
  ]
20802
21057
  }, undefined, true, undefined, this),
20803
- todos.length > 0 && /* @__PURE__ */ jsxDEV7(Text7, {
21058
+ todos.length > 0 && /* @__PURE__ */ jsxDEV8(Text8, {
20804
21059
  color: theme.muted,
20805
21060
  children: [
20806
21061
  " ",
@@ -20811,11 +21066,11 @@ function StatusLine({
20811
21066
  current ? ` ${truncate3(current.text, 40)}` : ""
20812
21067
  ]
20813
21068
  }, undefined, true, undefined, this),
20814
- busy && /* @__PURE__ */ jsxDEV7(Text7, {
21069
+ busy && /* @__PURE__ */ jsxDEV8(Text8, {
20815
21070
  color: theme.warning,
20816
21071
  children: " · working (esc to interrupt)"
20817
21072
  }, undefined, false, undefined, this),
20818
- queued > 0 && /* @__PURE__ */ jsxDEV7(Text7, {
21073
+ queued > 0 && /* @__PURE__ */ jsxDEV8(Text8, {
20819
21074
  color: theme.accent,
20820
21075
  children: [
20821
21076
  " · ",
@@ -20831,8 +21086,8 @@ function truncate3(value, max) {
20831
21086
  }
20832
21087
 
20833
21088
  // packages/tui/src/components/tool-block.tsx
20834
- import { Box as Box7, Text as Text8 } from "ink";
20835
- import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
21089
+ import { Box as Box8, Text as Text9 } from "ink";
21090
+ import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
20836
21091
  var PREVIEW_LINES = 8;
20837
21092
  function ToolBlock({
20838
21093
  name,
@@ -20851,27 +21106,27 @@ function ToolBlock({
20851
21106
  const showAll = expanded || isError;
20852
21107
  const shown = showAll ? lines : lines.slice(0, PREVIEW_LINES);
20853
21108
  const hidden = lines.length - shown.length;
20854
- return /* @__PURE__ */ jsxDEV8(Box7, {
21109
+ return /* @__PURE__ */ jsxDEV9(Box8, {
20855
21110
  flexDirection: "column",
20856
21111
  marginTop: 1,
20857
21112
  children: [
20858
- /* @__PURE__ */ jsxDEV8(Text8, {
21113
+ /* @__PURE__ */ jsxDEV9(Text9, {
20859
21114
  color,
20860
21115
  children: [
20861
21116
  marker2,
20862
21117
  " ",
20863
- /* @__PURE__ */ jsxDEV8(Text8, {
21118
+ /* @__PURE__ */ jsxDEV9(Text9, {
20864
21119
  bold: true,
20865
21120
  children: name
20866
21121
  }, undefined, false, undefined, this),
20867
- title ? /* @__PURE__ */ jsxDEV8(Text8, {
21122
+ title ? /* @__PURE__ */ jsxDEV9(Text9, {
20868
21123
  color: theme.muted,
20869
21124
  children: [
20870
21125
  " ",
20871
21126
  title
20872
21127
  ]
20873
21128
  }, undefined, true, undefined, this) : null,
20874
- isDiff && !showAll ? /* @__PURE__ */ jsxDEV8(Text8, {
21129
+ isDiff && !showAll ? /* @__PURE__ */ jsxDEV9(Text9, {
20875
21130
  color: theme.muted,
20876
21131
  children: [
20877
21132
  " ",
@@ -20880,12 +21135,12 @@ function ToolBlock({
20880
21135
  }, undefined, true, undefined, this) : null
20881
21136
  ]
20882
21137
  }, undefined, true, undefined, this),
20883
- isDiff && showAll ? /* @__PURE__ */ jsxDEV8(Box7, {
21138
+ isDiff && showAll ? /* @__PURE__ */ jsxDEV9(Box8, {
20884
21139
  marginLeft: 2,
20885
- children: /* @__PURE__ */ jsxDEV8(DiffView, {
21140
+ children: /* @__PURE__ */ jsxDEV9(DiffView, {
20886
21141
  diff: output
20887
21142
  }, undefined, false, undefined, this)
20888
- }, undefined, false, undefined, this) : shown.map((line, index) => /* @__PURE__ */ jsxDEV8(Text8, {
21143
+ }, undefined, false, undefined, this) : shown.map((line, index) => /* @__PURE__ */ jsxDEV9(Text9, {
20889
21144
  color: theme.muted,
20890
21145
  wrap: "truncate-end",
20891
21146
  children: [
@@ -20893,7 +21148,7 @@ function ToolBlock({
20893
21148
  line
20894
21149
  ]
20895
21150
  }, index, true, undefined, this)),
20896
- hidden > 0 && !isDiff && /* @__PURE__ */ jsxDEV8(Text8, {
21151
+ hidden > 0 && !isDiff && /* @__PURE__ */ jsxDEV9(Text9, {
20897
21152
  color: theme.muted,
20898
21153
  children: [
20899
21154
  " ",
@@ -20907,7 +21162,7 @@ function ToolBlock({
20907
21162
  }
20908
21163
 
20909
21164
  // packages/tui/src/app.tsx
20910
- import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
21165
+ import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime";
20911
21166
  var sequence = 0;
20912
21167
  var nextId = () => `item_${sequence++}`;
20913
21168
  function promptLabel(prompt) {
@@ -20916,9 +21171,10 @@ function promptLabel(prompt) {
20916
21171
  return prompt.map((part) => part.type === "text" ? part.text : `[attached ${part.mediaType} image]`).join(`
20917
21172
  `);
20918
21173
  }
20919
- function App({ session: session2, model: model2, initialPrompt }) {
21174
+ function App({ session: session2, model: initialModel, initialPrompt }) {
20920
21175
  const { exit } = useApp();
20921
21176
  const agent3 = session2.agent;
21177
+ const [model2, setModel] = useState3(initialModel);
20922
21178
  const [items, setItems] = useState3(() => session2.problems.map((problem) => ({
20923
21179
  kind: "notice",
20924
21180
  id: nextId(),
@@ -20928,6 +21184,7 @@ function App({ session: session2, model: model2, initialPrompt }) {
20928
21184
  const [live, setLive] = useState3("");
20929
21185
  const [runningTool, setRunningTool] = useState3();
20930
21186
  const [input, setInput] = useState3("");
21187
+ const [menuIndex, setMenuIndex] = useState3(0);
20931
21188
  const [busy, setBusy] = useState3(false);
20932
21189
  const [mode, setMode] = useState3(agent3.permissionMode);
20933
21190
  const [cost, setCost] = useState3(0);
@@ -21154,8 +21411,8 @@ function App({ session: session2, model: model2, initialPrompt }) {
21154
21411
  });
21155
21412
  }, [agent3, candidate, model2, push, session2.skills]);
21156
21413
  const sessionTree = useCallback(async (name, argument) => {
21157
- const entries = await session2.branch();
21158
- const prompts = entries.filter((entry) => entry.type === "message" && entry.message.role === "user" && entry.message.content.some((part) => part.type === "text" && !part.text.startsWith("<self-check>")));
21414
+ const entries2 = await session2.branch();
21415
+ const prompts = entries2.filter((entry) => entry.type === "message" && entry.message.role === "user" && entry.message.content.some((part) => part.type === "text" && !part.text.startsWith("<self-check>")));
21159
21416
  if (name === "tree" || !argument) {
21160
21417
  if (prompts.length === 0) {
21161
21418
  push({ kind: "notice", id: nextId(), text: "nothing in this session yet" });
@@ -21187,7 +21444,7 @@ function App({ session: session2, model: model2, initialPrompt }) {
21187
21444
  });
21188
21445
  return;
21189
21446
  }
21190
- const previous = entries[entries.indexOf(target) - 1] ?? target;
21447
+ const previous = entries2[entries2.indexOf(target) - 1] ?? target;
21191
21448
  if (name === "rewind") {
21192
21449
  const kept = await session2.rewindTo(previous.id);
21193
21450
  push({
@@ -21304,16 +21561,149 @@ ${PLAN_PROMPT}`);
21304
21561
  /plan edit to change it, /plan approve to pin it`
21305
21562
  });
21306
21563
  }, [agent3, push, runTurn, session2.store]);
21307
- const handleCommand = useCallback((command) => {
21308
- const body = command.slice(1).trim();
21309
- const space = body.search(/\s/);
21310
- const name = space === -1 ? body : body.slice(0, space);
21311
- const argument = space === -1 ? undefined : body.slice(space + 1).trim();
21312
- if (name === "exit" || name === "quit") {
21313
- exit();
21564
+ const switchModel = useCallback(async (ref) => {
21565
+ if (!ref) {
21566
+ const current = agent3.model;
21567
+ const price = current.model.cost;
21568
+ push({
21569
+ kind: "notice",
21570
+ id: nextId(),
21571
+ text: [
21572
+ ` ${current.provider.id}/${current.model.id}`,
21573
+ ` context ${(current.model.contextWindow ?? 0).toLocaleString()} tokens`,
21574
+ ` price $${price?.input ?? "?"} in / $${price?.output ?? "?"} out per million`,
21575
+ "",
21576
+ " /model <provider/model> switches; `earshot models` lists them"
21577
+ ].join(`
21578
+ `)
21579
+ });
21314
21580
  return;
21315
21581
  }
21316
- if (name === "mode") {
21582
+ try {
21583
+ const resolved = await agent3.changeModel(ref);
21584
+ const next = `${resolved.provider.id}/${resolved.model.id}`;
21585
+ setModel(next);
21586
+ await refreshSystemPrompt(agent3, next, session2.skills);
21587
+ push({ kind: "notice", id: nextId(), text: `model: ${next}` });
21588
+ } catch (error) {
21589
+ push({
21590
+ kind: "notice",
21591
+ id: nextId(),
21592
+ text: `${error.message}`,
21593
+ color: theme.warning
21594
+ });
21595
+ }
21596
+ }, [agent3, push, session2.skills]);
21597
+ const compactNow = useCallback(async () => {
21598
+ const abort = new AbortController;
21599
+ controller.current = abort;
21600
+ setBusy(true);
21601
+ try {
21602
+ let compactedAnything = false;
21603
+ for await (const event of agent3.compactNow(abort.signal)) {
21604
+ if (event.type === "compacted") {
21605
+ compactedAnything = true;
21606
+ setCompacted((count) => count + event.replaced);
21607
+ push({
21608
+ kind: "notice",
21609
+ id: nextId(),
21610
+ text: `compacted: ${event.replaced} earlier messages are now a summary`
21611
+ });
21612
+ }
21613
+ }
21614
+ if (!compactedAnything) {
21615
+ push({ kind: "notice", id: nextId(), text: "nothing to compact yet" });
21616
+ }
21617
+ } catch (error) {
21618
+ push({
21619
+ kind: "notice",
21620
+ id: nextId(),
21621
+ text: `could not compact: ${error.message}`,
21622
+ color: theme.warning
21623
+ });
21624
+ } finally {
21625
+ setBusy(false);
21626
+ setContext(agent3.contextUse);
21627
+ controller.current = undefined;
21628
+ }
21629
+ }, [agent3, push]);
21630
+ const handlers = {
21631
+ exit: () => exit(),
21632
+ help: () => push({ kind: "notice", id: nextId(), text: describeCommands(session2) }),
21633
+ model: (argument) => void switchModel(argument),
21634
+ compact: () => void compactNow(),
21635
+ context: () => {
21636
+ const { tokens, window } = agent3.contextUse;
21637
+ const percent = window > 0 ? Math.round(tokens / window * 100) : 0;
21638
+ const files = agent3.touchedFiles;
21639
+ push({
21640
+ kind: "notice",
21641
+ id: nextId(),
21642
+ text: [
21643
+ ` model ${model2}`,
21644
+ ` context ~${tokens.toLocaleString()} of ${window.toLocaleString()} tokens (${percent}%)`,
21645
+ ` dropped ${compacted} earlier message${compacted === 1 ? "" : "s"} replaced by a summary`,
21646
+ ` files ${files.length === 0 ? "none touched yet" : files.join(", ")}`,
21647
+ "",
21648
+ " /compact summarises now rather than waiting for 80%"
21649
+ ].join(`
21650
+ `)
21651
+ });
21652
+ },
21653
+ cost: (argument) => {
21654
+ if (argument !== undefined && argument !== "") {
21655
+ const amount = Number.parseFloat(argument.replace(/^\$/, ""));
21656
+ if (Number.isNaN(amount)) {
21657
+ push({
21658
+ kind: "notice",
21659
+ id: nextId(),
21660
+ text: `usage: /cost [usd] - "${argument}" is not an amount`,
21661
+ color: theme.warning
21662
+ });
21663
+ return;
21664
+ }
21665
+ agent3.setBudget(amount > 0 ? amount : undefined);
21666
+ push({
21667
+ kind: "notice",
21668
+ id: nextId(),
21669
+ text: amount > 0 ? `budget: $${amount.toFixed(2)}` : "budget removed"
21670
+ });
21671
+ return;
21672
+ }
21673
+ const budget = agent3.budgetUsd;
21674
+ push({
21675
+ kind: "notice",
21676
+ id: nextId(),
21677
+ text: ` spent $${agent3.costUsd.toFixed(4)}
21678
+ ` + ` budget ${budget === undefined ? "none - /cost <usd> sets one" : `$${budget.toFixed(2)}`}`
21679
+ });
21680
+ },
21681
+ todo: () => {
21682
+ const todos2 = agent3.todos.list();
21683
+ push({
21684
+ kind: "notice",
21685
+ id: nextId(),
21686
+ text: todos2.length === 0 ? "no todos in this session" : todos2.map((todo2) => ` ${todo2.status === "done" ? "x" : todo2.status === "in_progress" ? ">" : " "} ${todo2.text}`).join(`
21687
+ `)
21688
+ });
21689
+ },
21690
+ permissions: () => {
21691
+ const rules2 = agent3.permissionRules;
21692
+ push({
21693
+ kind: "notice",
21694
+ id: nextId(),
21695
+ text: [
21696
+ ` mode ${agent3.permissionMode} (/mode changes it)`,
21697
+ ...rules2.length === 0 ? [" rules none configured"] : [
21698
+ " rules (deny always wins, whatever the mode or scope)",
21699
+ ...rules2.map((rule) => ` ${rule.effect.padEnd(5)} ${rule.source} [${rule.scope}]`)
21700
+ ]
21701
+ ].join(`
21702
+ `)
21703
+ });
21704
+ },
21705
+ init: () => void runTurn(INIT_PROMPT),
21706
+ mode: (argument) => {
21317
21707
  if (argument && isPermissionMode(argument)) {
21318
21708
  agent3.setPermissionMode(argument);
21319
21709
  setMode(argument);
@@ -21326,31 +21716,25 @@ ${PLAN_PROMPT}`);
21326
21716
  color: theme.warning
21327
21717
  });
21328
21718
  }
21329
- return;
21330
- }
21331
- if (name === "memory") {
21332
- showMemories(argument);
21333
- return;
21334
- }
21335
- if (name === "tree" || name === "rewind" || name === "fork") {
21336
- if (busy) {
21337
- push({
21338
- kind: "notice",
21339
- id: nextId(),
21340
- text: "finish or interrupt the current turn first (esc)",
21341
- color: theme.warning
21342
- });
21343
- return;
21344
- }
21345
- sessionTree(name, argument);
21346
- return;
21347
- }
21348
- if (name === "undo") {
21349
- undoLast();
21350
- return;
21351
- }
21352
- if (name === "plan") {
21353
- if (busy) {
21719
+ },
21720
+ memory: (argument) => void showMemories(argument),
21721
+ tree: (argument) => void sessionTree("tree", argument),
21722
+ rewind: (argument) => void sessionTree("rewind", argument),
21723
+ fork: (argument) => void sessionTree("fork", argument),
21724
+ undo: () => void undoLast(),
21725
+ plan: (argument) => void plan2(argument),
21726
+ skills: () => push({ kind: "notice", id: nextId(), text: describeExtensions(session2) })
21727
+ };
21728
+ const handlersRef = useRef2(handlers);
21729
+ handlersRef.current = handlers;
21730
+ const handleCommand = useCallback((command) => {
21731
+ const body = command.slice(1).trim();
21732
+ const space = body.search(/\s/);
21733
+ const name = space === -1 ? body : body.slice(0, space);
21734
+ const argument = space === -1 ? undefined : body.slice(space + 1).trim();
21735
+ const spec = findCommand(name);
21736
+ if (spec) {
21737
+ if (spec.idleOnly && busy) {
21354
21738
  push({
21355
21739
  kind: "notice",
21356
21740
  id: nextId(),
@@ -21359,15 +21743,7 @@ ${PLAN_PROMPT}`);
21359
21743
  });
21360
21744
  return;
21361
21745
  }
21362
- plan2(argument);
21363
- return;
21364
- }
21365
- if (name === "skills") {
21366
- push({
21367
- kind: "notice",
21368
- id: nextId(),
21369
- text: describeExtensions(session2)
21370
- });
21746
+ handlersRef.current[spec.name](argument);
21371
21747
  return;
21372
21748
  }
21373
21749
  const custom = session2.commands.find((entry) => entry.name === name);
@@ -21397,8 +21773,15 @@ ${PLAN_PROMPT}`);
21397
21773
  text: `unknown command "${name}"`,
21398
21774
  color: theme.warning
21399
21775
  });
21400
- }, [agent3, busy, exit, plan2, push, runTurn, session2, sessionTree, showMemories, undoLast]);
21776
+ }, [agent3, busy, push, runTurn, session2]);
21401
21777
  const submit = useCallback((text2) => {
21778
+ const entry = menuOpenRef.current ? menuEntriesRef.current[selectedRef.current] : undefined;
21779
+ if (entry) {
21780
+ setInput("");
21781
+ setMenuIndex(0);
21782
+ handleCommand(entry.insert);
21783
+ return;
21784
+ }
21402
21785
  const trimmed = text2.trim();
21403
21786
  setInput("");
21404
21787
  if (trimmed === "")
@@ -21417,7 +21800,38 @@ ${PLAN_PROMPT}`);
21417
21800
  runTurn(trimmed);
21418
21801
  }, [agent3, busy, push, runTurn, handleCommand]);
21419
21802
  const inputActive = !pending && !question;
21803
+ const menuOpen = inputActive && input.startsWith("/") && !input.includes(" ");
21804
+ const entries = menuOpen ? menuEntries(input.slice(1), session2.commands, busy) : [];
21805
+ const selected = Math.min(menuIndex, Math.max(0, entries.length - 1));
21806
+ const menuOpenRef = useRef2(menuOpen);
21807
+ menuOpenRef.current = menuOpen;
21808
+ const menuEntriesRef = useRef2(entries);
21809
+ menuEntriesRef.current = entries;
21810
+ const selectedRef = useRef2(selected);
21811
+ selectedRef.current = selected;
21420
21812
  useInput4((input_, key2) => {
21813
+ if (menuOpen && entries.length > 0) {
21814
+ if (key2.upArrow) {
21815
+ setMenuIndex((current) => current <= 0 ? entries.length - 1 : current - 1);
21816
+ return;
21817
+ }
21818
+ if (key2.downArrow) {
21819
+ setMenuIndex((current) => current >= entries.length - 1 ? 0 : current + 1);
21820
+ return;
21821
+ }
21822
+ if (key2.tab) {
21823
+ const entry = entries[selected];
21824
+ if (entry)
21825
+ setInput(`${entry.insert} `);
21826
+ setMenuIndex(0);
21827
+ return;
21828
+ }
21829
+ if (key2.escape) {
21830
+ setInput("");
21831
+ setMenuIndex(0);
21832
+ return;
21833
+ }
21834
+ }
21421
21835
  if (key2.escape) {
21422
21836
  setCandidate(undefined);
21423
21837
  if (busy)
@@ -21428,26 +21842,26 @@ ${PLAN_PROMPT}`);
21428
21842
  remember(input_ === "r" ? "project" : "user");
21429
21843
  }
21430
21844
  }, { isActive: inputActive });
21431
- return /* @__PURE__ */ jsxDEV9(Box8, {
21845
+ return /* @__PURE__ */ jsxDEV10(Box9, {
21432
21846
  flexDirection: "column",
21433
21847
  children: [
21434
- /* @__PURE__ */ jsxDEV9(Static, {
21848
+ /* @__PURE__ */ jsxDEV10(Static, {
21435
21849
  items,
21436
- children: (item) => /* @__PURE__ */ jsxDEV9(ScrollRow, {
21850
+ children: (item) => /* @__PURE__ */ jsxDEV10(ScrollRow, {
21437
21851
  item
21438
21852
  }, item.id, false, undefined, this)
21439
21853
  }, undefined, false, undefined, this),
21440
- live !== "" && /* @__PURE__ */ jsxDEV9(Box8, {
21854
+ live !== "" && /* @__PURE__ */ jsxDEV10(Box9, {
21441
21855
  marginTop: 1,
21442
- children: /* @__PURE__ */ jsxDEV9(Markdown, {
21856
+ children: /* @__PURE__ */ jsxDEV10(Markdown, {
21443
21857
  text: live
21444
21858
  }, undefined, false, undefined, this)
21445
21859
  }, undefined, false, undefined, this),
21446
- runningTool && /* @__PURE__ */ jsxDEV9(ToolBlock, {
21860
+ runningTool && /* @__PURE__ */ jsxDEV10(ToolBlock, {
21447
21861
  name: runningTool,
21448
21862
  running: true
21449
21863
  }, undefined, false, undefined, this),
21450
- pending && /* @__PURE__ */ jsxDEV9(PermissionPrompt, {
21864
+ pending && /* @__PURE__ */ jsxDEV10(PermissionPrompt, {
21451
21865
  request: pending.request,
21452
21866
  reason: pending.reason,
21453
21867
  onChoice: (choice) => {
@@ -21455,7 +21869,7 @@ ${PLAN_PROMPT}`);
21455
21869
  pending.resolve(choice);
21456
21870
  }
21457
21871
  }, undefined, false, undefined, this),
21458
- question && /* @__PURE__ */ jsxDEV9(QuestionPrompt, {
21872
+ question && /* @__PURE__ */ jsxDEV10(QuestionPrompt, {
21459
21873
  question: question.question,
21460
21874
  ...question.options ? { options: question.options } : {},
21461
21875
  onAnswer: (answer) => {
@@ -21463,25 +21877,32 @@ ${PLAN_PROMPT}`);
21463
21877
  question.resolve(answer);
21464
21878
  }
21465
21879
  }, undefined, false, undefined, this),
21466
- candidate && inputActive && /* @__PURE__ */ jsxDEV9(MemoryCapture, {
21880
+ candidate && inputActive && /* @__PURE__ */ jsxDEV10(MemoryCapture, {
21467
21881
  candidate
21468
21882
  }, undefined, false, undefined, this),
21469
- inputActive && /* @__PURE__ */ jsxDEV9(Box8, {
21883
+ menuOpen && /* @__PURE__ */ jsxDEV10(CommandMenu, {
21884
+ entries,
21885
+ selected
21886
+ }, undefined, false, undefined, this),
21887
+ inputActive && /* @__PURE__ */ jsxDEV10(Box9, {
21470
21888
  marginTop: 1,
21471
21889
  children: [
21472
- /* @__PURE__ */ jsxDEV9(Text9, {
21890
+ /* @__PURE__ */ jsxDEV10(Text10, {
21473
21891
  color: theme.user,
21474
21892
  children: "> "
21475
21893
  }, undefined, false, undefined, this),
21476
- /* @__PURE__ */ jsxDEV9(TextInput, {
21894
+ /* @__PURE__ */ jsxDEV10(TextInput, {
21477
21895
  value: input,
21478
- onChange: setInput,
21896
+ onChange: (value) => {
21897
+ setInput(value);
21898
+ setMenuIndex(0);
21899
+ },
21479
21900
  onSubmit: submit,
21480
21901
  placeholder: busy ? "steer the agent, or esc to interrupt" : "what should I do?"
21481
21902
  }, undefined, false, undefined, this)
21482
21903
  ]
21483
21904
  }, undefined, true, undefined, this),
21484
- /* @__PURE__ */ jsxDEV9(StatusLine, {
21905
+ /* @__PURE__ */ jsxDEV10(StatusLine, {
21485
21906
  model: model2,
21486
21907
  mode,
21487
21908
  costUsd: cost,
@@ -21496,45 +21917,60 @@ ${PLAN_PROMPT}`);
21496
21917
  }
21497
21918
  function ScrollRow({ item }) {
21498
21919
  if (item.kind === "user") {
21499
- return /* @__PURE__ */ jsxDEV9(Box8, {
21920
+ return /* @__PURE__ */ jsxDEV10(Box9, {
21500
21921
  marginTop: 1,
21501
21922
  children: [
21502
- /* @__PURE__ */ jsxDEV9(Text9, {
21923
+ /* @__PURE__ */ jsxDEV10(Text10, {
21503
21924
  color: theme.user,
21504
21925
  children: "> "
21505
21926
  }, undefined, false, undefined, this),
21506
- /* @__PURE__ */ jsxDEV9(Text9, {
21927
+ /* @__PURE__ */ jsxDEV10(Text10, {
21507
21928
  children: item.text
21508
21929
  }, undefined, false, undefined, this)
21509
21930
  ]
21510
21931
  }, undefined, true, undefined, this);
21511
21932
  }
21512
21933
  if (item.kind === "assistant") {
21513
- return /* @__PURE__ */ jsxDEV9(Box8, {
21934
+ return /* @__PURE__ */ jsxDEV10(Box9, {
21514
21935
  marginTop: 1,
21515
- children: /* @__PURE__ */ jsxDEV9(Markdown, {
21936
+ children: /* @__PURE__ */ jsxDEV10(Markdown, {
21516
21937
  text: item.text
21517
21938
  }, undefined, false, undefined, this)
21518
21939
  }, undefined, false, undefined, this);
21519
21940
  }
21520
21941
  if (item.kind === "tool") {
21521
- return /* @__PURE__ */ jsxDEV9(ToolBlock, {
21942
+ return /* @__PURE__ */ jsxDEV10(ToolBlock, {
21522
21943
  name: item.name,
21523
21944
  ...item.title ? { title: item.title } : {},
21524
21945
  ...item.output ? { output: item.output } : {},
21525
21946
  ...item.isError ? { isError: true } : {}
21526
21947
  }, undefined, false, undefined, this);
21527
21948
  }
21528
- return /* @__PURE__ */ jsxDEV9(Box8, {
21949
+ return /* @__PURE__ */ jsxDEV10(Box9, {
21529
21950
  marginTop: 1,
21530
- children: /* @__PURE__ */ jsxDEV9(Text9, {
21951
+ children: /* @__PURE__ */ jsxDEV10(Text10, {
21531
21952
  color: item.color ?? theme.muted,
21532
21953
  children: item.text
21533
21954
  }, undefined, false, undefined, this)
21534
21955
  }, undefined, false, undefined, this);
21535
21956
  }
21536
- function describeExtensions(session2) {
21537
- const lines = [];
21957
+ function describeCommands(session2) {
21958
+ const lines = commandRows().map((row) => ` ${row.command.padEnd(34)} ${row.summary}`);
21959
+ if (session2.commands.length > 0) {
21960
+ lines.push("", " commands from this directory:");
21961
+ for (const command of session2.commands) {
21962
+ lines.push(` ${`/${command.name}`.padEnd(34)} ${command.description}`);
21963
+ }
21964
+ }
21965
+ lines.push("", " type / at the prompt to filter this list and pick one");
21966
+ return lines.join(`
21967
+ `);
21968
+ }
21969
+ var INIT_PROMPT = `Write an AGENTS.md at the root of this project for a coding agent that has never seen it.
21970
+
21971
+ Read enough of the repository first to be accurate. Cover: what the project is, how it is laid out, the commands to build, test and lint it, and the conventions and rules that are not obvious from the code. Prefer rules that prevent a specific failure, and say what the failure is. If an AGENTS.md or CLAUDE.md already exists, improve it in place rather than replacing it.`;
21972
+ function describeExtensions(session2) {
21973
+ const lines = [];
21538
21974
  if (session2.skills.length > 0) {
21539
21975
  lines.push("skills (the agent loads these itself when they fit):");
21540
21976
  for (const skill of session2.skills) {
@@ -21552,14 +21988,347 @@ function describeExtensions(session2) {
21552
21988
  return lines.length === 0 ? "no skills or commands found in .earshot/skills, .earshot/commands or your config directory" : lines.join(`
21553
21989
  `);
21554
21990
  }
21991
+ // packages/tui/src/onboarding.tsx
21992
+ import { Box as Box10, Text as Text11, useApp as useApp2, useInput as useInput5 } from "ink";
21993
+ import { useCallback as useCallback2, useRef as useRef3, useState as useState4 } from "react";
21994
+ import { jsxDEV as jsxDEV11 } from "react/jsx-dev-runtime";
21995
+ function Onboarding({ onDone, ...rest }) {
21996
+ const { exit } = useApp2();
21997
+ const options = useRef3(rest);
21998
+ options.current = rest;
21999
+ const [screen, setScreen] = useState4({ name: "choose" });
22000
+ const [cursor, setCursor] = useState4(0);
22001
+ const [input, setInput] = useState4("");
22002
+ const [error, setError] = useState4();
22003
+ const secret = useRef3("");
22004
+ const providers = order(rest.providers, rest.wanted);
22005
+ const finish = useCallback2((result) => {
22006
+ onDone(result);
22007
+ exit();
22008
+ }, [exit, onDone]);
22009
+ const verify2 = useCallback2(async (provider) => {
22010
+ setScreen({ name: "probing", provider });
22011
+ const result = await options.current.probe(provider.id);
22012
+ if (result.ok) {
22013
+ setScreen({ name: "ready", provider });
22014
+ return;
22015
+ }
22016
+ if (result.reason === "rejected")
22017
+ await options.current.forgetKey(provider.id).catch(() => {});
22018
+ setScreen({ name: "failed", provider, result });
22019
+ }, []);
22020
+ const submitKey = useCallback2(async () => {
22021
+ const provider = screen.name === "key" ? screen.provider : undefined;
22022
+ const key2 = secret.current;
22023
+ secret.current = "";
22024
+ setInput("");
22025
+ if (!provider)
22026
+ return;
22027
+ if (key2.trim() === "") {
22028
+ setError("a key is needed, or press esc to go back");
22029
+ return;
22030
+ }
22031
+ setError(undefined);
22032
+ await options.current.storeKey(provider.id, key2.trim());
22033
+ await verify2(provider);
22034
+ }, [screen, verify2]);
22035
+ const startSignIn = useCallback2(async (provider) => {
22036
+ setScreen({ name: "oauth", provider });
22037
+ try {
22038
+ await options.current.signIn(provider.id, (url) => setScreen({ name: "oauth", provider, url }));
22039
+ } catch (failure) {
22040
+ setScreen({
22041
+ name: "failed",
22042
+ provider,
22043
+ result: { ok: false, reason: "other", message: failure.message }
22044
+ });
22045
+ return;
22046
+ }
22047
+ await verify2(provider);
22048
+ }, [verify2]);
22049
+ const choose = useCallback2((provider) => {
22050
+ setError(undefined);
22051
+ if (provider.kind === "oauth")
22052
+ startSignIn(provider);
22053
+ else
22054
+ setScreen({ name: "key", provider });
22055
+ }, [startSignIn]);
22056
+ useInput5((key2, meta) => {
22057
+ if (meta.ctrl && key2 === "c") {
22058
+ finish({ outcome: "quit" });
22059
+ return;
22060
+ }
22061
+ if (screen.name === "choose") {
22062
+ if (key2 === "q") {
22063
+ finish({ outcome: "quit" });
22064
+ return;
22065
+ }
22066
+ if (meta.upArrow)
22067
+ setCursor((c) => c <= 0 ? providers.length - 1 : c - 1);
22068
+ if (meta.downArrow)
22069
+ setCursor((c) => c >= providers.length - 1 ? 0 : c + 1);
22070
+ if (meta.return) {
22071
+ const provider = providers[cursor];
22072
+ if (provider)
22073
+ choose(provider);
22074
+ }
22075
+ return;
22076
+ }
22077
+ if (screen.name === "key" && !meta.escape) {
22078
+ if (meta.return) {
22079
+ submitKey();
22080
+ return;
22081
+ }
22082
+ if (meta.backspace || meta.delete) {
22083
+ secret.current = secret.current.slice(0, -1);
22084
+ setInput(secret.current);
22085
+ return;
22086
+ }
22087
+ if (meta.ctrl || meta.meta || meta.tab)
22088
+ return;
22089
+ if (key2) {
22090
+ secret.current += key2;
22091
+ setInput(secret.current);
22092
+ }
22093
+ return;
22094
+ }
22095
+ if (meta.escape) {
22096
+ secret.current = "";
22097
+ setInput("");
22098
+ setError(undefined);
22099
+ setScreen({ name: "choose" });
22100
+ return;
22101
+ }
22102
+ if (screen.name === "failed" && key2 === "k" && screen.result.reason === "unreachable") {
22103
+ setScreen({ name: "ready", provider: screen.provider });
22104
+ }
22105
+ });
22106
+ if (screen.name === "choose") {
22107
+ return /* @__PURE__ */ jsxDEV11(Box10, {
22108
+ flexDirection: "column",
22109
+ children: [
22110
+ /* @__PURE__ */ jsxDEV11(Header, {}, undefined, false, undefined, this),
22111
+ providers.map((provider, index) => /* @__PURE__ */ jsxDEV11(Box10, {
22112
+ children: [
22113
+ /* @__PURE__ */ jsxDEV11(Text11, {
22114
+ color: index === cursor ? theme.user : theme.muted,
22115
+ children: index === cursor ? "› " : " "
22116
+ }, undefined, false, undefined, this),
22117
+ /* @__PURE__ */ jsxDEV11(Text11, {
22118
+ ...index === cursor ? { color: theme.user } : {},
22119
+ children: provider.id.padEnd(14)
22120
+ }, undefined, false, undefined, this),
22121
+ /* @__PURE__ */ jsxDEV11(Text11, {
22122
+ color: theme.muted,
22123
+ children: hint(provider)
22124
+ }, undefined, false, undefined, this)
22125
+ ]
22126
+ }, provider.id, true, undefined, this)),
22127
+ /* @__PURE__ */ jsxDEV11(Box10, {
22128
+ marginTop: 1,
22129
+ children: /* @__PURE__ */ jsxDEV11(Text11, {
22130
+ color: theme.muted,
22131
+ children: "↑↓ choose · enter select · q quit"
22132
+ }, undefined, false, undefined, this)
22133
+ }, undefined, false, undefined, this),
22134
+ error ? /* @__PURE__ */ jsxDEV11(Text11, {
22135
+ color: theme.warning,
22136
+ children: error
22137
+ }, undefined, false, undefined, this) : null
22138
+ ]
22139
+ }, undefined, true, undefined, this);
22140
+ }
22141
+ if (screen.name === "key") {
22142
+ return /* @__PURE__ */ jsxDEV11(Box10, {
22143
+ flexDirection: "column",
22144
+ children: [
22145
+ /* @__PURE__ */ jsxDEV11(Header, {}, undefined, false, undefined, this),
22146
+ /* @__PURE__ */ jsxDEV11(Text11, {
22147
+ children: [
22148
+ "paste an api key for ",
22149
+ screen.provider.id
22150
+ ]
22151
+ }, undefined, true, undefined, this),
22152
+ /* @__PURE__ */ jsxDEV11(Box10, {
22153
+ marginTop: 1,
22154
+ children: /* @__PURE__ */ jsxDEV11(Text11, {
22155
+ color: theme.muted,
22156
+ children: [
22157
+ screen.provider.envVars?.length ? `or set ${screen.provider.envVars.join(" or ")} instead and restart.
22158
+ ` : "",
22159
+ "stored in your config directory, readable only by you, and never printed."
22160
+ ]
22161
+ }, undefined, true, undefined, this)
22162
+ }, undefined, false, undefined, this),
22163
+ /* @__PURE__ */ jsxDEV11(Box10, {
22164
+ marginTop: 1,
22165
+ children: [
22166
+ /* @__PURE__ */ jsxDEV11(Text11, {
22167
+ color: theme.user,
22168
+ children: "> "
22169
+ }, undefined, false, undefined, this),
22170
+ /* @__PURE__ */ jsxDEV11(Text11, {
22171
+ children: "•".repeat(input.length)
22172
+ }, undefined, false, undefined, this),
22173
+ /* @__PURE__ */ jsxDEV11(Text11, {
22174
+ inverse: true,
22175
+ children: " "
22176
+ }, undefined, false, undefined, this)
22177
+ ]
22178
+ }, undefined, true, undefined, this),
22179
+ error ? /* @__PURE__ */ jsxDEV11(Text11, {
22180
+ color: theme.warning,
22181
+ children: error
22182
+ }, undefined, false, undefined, this) : null,
22183
+ /* @__PURE__ */ jsxDEV11(Box10, {
22184
+ marginTop: 1,
22185
+ children: /* @__PURE__ */ jsxDEV11(Text11, {
22186
+ color: theme.muted,
22187
+ children: "enter continue · esc back"
22188
+ }, undefined, false, undefined, this)
22189
+ }, undefined, false, undefined, this)
22190
+ ]
22191
+ }, undefined, true, undefined, this);
22192
+ }
22193
+ if (screen.name === "oauth") {
22194
+ return /* @__PURE__ */ jsxDEV11(Box10, {
22195
+ flexDirection: "column",
22196
+ children: [
22197
+ /* @__PURE__ */ jsxDEV11(Header, {}, undefined, false, undefined, this),
22198
+ /* @__PURE__ */ jsxDEV11(Text11, {
22199
+ children: [
22200
+ "signing in to ",
22201
+ screen.provider.id,
22202
+ " in your browser…"
22203
+ ]
22204
+ }, undefined, true, undefined, this),
22205
+ screen.url ? /* @__PURE__ */ jsxDEV11(Box10, {
22206
+ marginTop: 1,
22207
+ flexDirection: "column",
22208
+ children: [
22209
+ /* @__PURE__ */ jsxDEV11(Text11, {
22210
+ color: theme.muted,
22211
+ children: "if it did not open, use this link:"
22212
+ }, undefined, false, undefined, this),
22213
+ /* @__PURE__ */ jsxDEV11(Text11, {
22214
+ children: screen.url
22215
+ }, undefined, false, undefined, this)
22216
+ ]
22217
+ }, undefined, true, undefined, this) : null,
22218
+ /* @__PURE__ */ jsxDEV11(Box10, {
22219
+ marginTop: 1,
22220
+ children: /* @__PURE__ */ jsxDEV11(Text11, {
22221
+ color: theme.muted,
22222
+ children: "esc cancel"
22223
+ }, undefined, false, undefined, this)
22224
+ }, undefined, false, undefined, this)
22225
+ ]
22226
+ }, undefined, true, undefined, this);
22227
+ }
22228
+ if (screen.name === "probing") {
22229
+ return /* @__PURE__ */ jsxDEV11(Box10, {
22230
+ flexDirection: "column",
22231
+ children: [
22232
+ /* @__PURE__ */ jsxDEV11(Header, {}, undefined, false, undefined, this),
22233
+ /* @__PURE__ */ jsxDEV11(Text11, {
22234
+ color: theme.muted,
22235
+ children: [
22236
+ "checking the credentials with ",
22237
+ screen.provider.id,
22238
+ "…"
22239
+ ]
22240
+ }, undefined, true, undefined, this)
22241
+ ]
22242
+ }, undefined, true, undefined, this);
22243
+ }
22244
+ if (screen.name === "failed") {
22245
+ return /* @__PURE__ */ jsxDEV11(Box10, {
22246
+ flexDirection: "column",
22247
+ children: [
22248
+ /* @__PURE__ */ jsxDEV11(Header, {}, undefined, false, undefined, this),
22249
+ /* @__PURE__ */ jsxDEV11(Text11, {
22250
+ color: theme.warning,
22251
+ children: screen.result.reason === "rejected" ? `${screen.provider.id} rejected that credential. It has not been kept.` : screen.result.reason === "unreachable" ? `could not reach ${screen.provider.id}.` : `${screen.provider.id} said:`
22252
+ }, undefined, false, undefined, this),
22253
+ /* @__PURE__ */ jsxDEV11(Text11, {
22254
+ color: theme.muted,
22255
+ children: screen.result.message
22256
+ }, undefined, false, undefined, this),
22257
+ /* @__PURE__ */ jsxDEV11(Box10, {
22258
+ marginTop: 1,
22259
+ children: /* @__PURE__ */ jsxDEV11(Text11, {
22260
+ color: theme.muted,
22261
+ children: screen.result.reason === "unreachable" ? "k keep it anyway and carry on · esc start over · ctrl-c quit" : "esc start over · ctrl-c quit"
22262
+ }, undefined, false, undefined, this)
22263
+ }, undefined, false, undefined, this)
22264
+ ]
22265
+ }, undefined, true, undefined, this);
22266
+ }
22267
+ return /* @__PURE__ */ jsxDEV11(Box10, {
22268
+ flexDirection: "column",
22269
+ children: [
22270
+ /* @__PURE__ */ jsxDEV11(Text11, {
22271
+ color: theme.user,
22272
+ children: [
22273
+ "ready: ",
22274
+ screen.provider.id
22275
+ ]
22276
+ }, undefined, true, undefined, this),
22277
+ /* @__PURE__ */ jsxDEV11(Box10, {
22278
+ marginTop: 1,
22279
+ children: /* @__PURE__ */ jsxDEV11(Text11, {
22280
+ color: theme.muted,
22281
+ children: "what should I do? (enter to start with nothing)"
22282
+ }, undefined, false, undefined, this)
22283
+ }, undefined, false, undefined, this),
22284
+ /* @__PURE__ */ jsxDEV11(Box10, {
22285
+ children: [
22286
+ /* @__PURE__ */ jsxDEV11(Text11, {
22287
+ color: theme.user,
22288
+ children: "> "
22289
+ }, undefined, false, undefined, this),
22290
+ /* @__PURE__ */ jsxDEV11(TextInput, {
22291
+ value: input,
22292
+ onChange: setInput,
22293
+ onSubmit: (text2) => finish({ outcome: "ready", providerId: screen.provider.id, firstPrompt: text2.trim() })
22294
+ }, undefined, false, undefined, this)
22295
+ ]
22296
+ }, undefined, true, undefined, this)
22297
+ ]
22298
+ }, undefined, true, undefined, this);
22299
+ }
22300
+ function Header() {
22301
+ return /* @__PURE__ */ jsxDEV11(Box10, {
22302
+ flexDirection: "column",
22303
+ marginBottom: 1,
22304
+ children: /* @__PURE__ */ jsxDEV11(Text11, {
22305
+ children: "earshot needs a model provider before it can do anything."
22306
+ }, undefined, false, undefined, this)
22307
+ }, undefined, false, undefined, this);
22308
+ }
22309
+ function hint(provider) {
22310
+ if (provider.configured)
22311
+ return provider.configured;
22312
+ if (provider.kind === "oauth")
22313
+ return "sign in with a browser - no key to paste";
22314
+ return provider.envVars?.length ? provider.envVars.join(" or ") : "api key";
22315
+ }
22316
+ function order(providers, wanted) {
22317
+ if (!wanted)
22318
+ return providers;
22319
+ return [
22320
+ ...providers.filter((provider) => provider.id === wanted),
22321
+ ...providers.filter((provider) => provider.id !== wanted)
22322
+ ];
22323
+ }
21555
22324
  // packages/tui/src/run.tsx
21556
22325
  import { platform as platform6 } from "node:os";
21557
22326
  import { render } from "ink";
21558
- import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime";
22327
+ import { jsxDEV as jsxDEV12 } from "react/jsx-dev-runtime";
21559
22328
  var WINDOWS_MAX_FPS = 30;
21560
22329
  async function runTui(options) {
21561
22330
  const isWindows = platform6() === "win32";
21562
- const instance = render(/* @__PURE__ */ jsxDEV10(App, {
22331
+ const instance = render(/* @__PURE__ */ jsxDEV12(App, {
21563
22332
  session: options.session,
21564
22333
  model: options.model,
21565
22334
  ...options.initialPrompt ? { initialPrompt: options.initialPrompt } : {}
@@ -21575,6 +22344,105 @@ async function runTui(options) {
21575
22344
  }
21576
22345
  return 0;
21577
22346
  }
22347
+ async function runOnboarding(options) {
22348
+ const isWindows = platform6() === "win32";
22349
+ let result = { outcome: "quit" };
22350
+ const instance = render(/* @__PURE__ */ jsxDEV12(Onboarding, {
22351
+ ...options,
22352
+ onDone: (decided) => {
22353
+ result = decided;
22354
+ }
22355
+ }, undefined, false, undefined, this), {
22356
+ exitOnCtrlC: false,
22357
+ patchConsole: true,
22358
+ ...isWindows ? { maxFps: WINDOWS_MAX_FPS } : {}
22359
+ });
22360
+ await instance.waitUntilExit();
22361
+ return result;
22362
+ }
22363
+ // packages/cli/src/onboard.ts
22364
+ async function buildOnboardingOptions(wanted) {
22365
+ const registry2 = buildRegistry();
22366
+ const store3 = new AuthStore;
22367
+ const providers = [];
22368
+ for (const provider of registry2.list()) {
22369
+ if (provider.auth.kind === "none")
22370
+ continue;
22371
+ providers.push(await describe3(provider, store3));
22372
+ }
22373
+ providers.sort((a, b) => Number(b.kind === "oauth") - Number(a.kind === "oauth"));
22374
+ return {
22375
+ providers,
22376
+ ...wanted ? { wanted } : {},
22377
+ storeKey: (providerId, key2) => store3.set(providerId, { type: "api-key", apiKey: key2 }),
22378
+ forgetKey: (providerId) => store3.remove(providerId),
22379
+ signIn: async (providerId, onUrl) => {
22380
+ const credentials = await loginToOpenRouter({ onUrl });
22381
+ await store3.set(providerId, credentials);
22382
+ },
22383
+ probe: (providerId) => probe(registry2, providerId)
22384
+ };
22385
+ }
22386
+ async function describe3(provider, store3) {
22387
+ const configured = await resolveCredentials(provider, { store: store3 }).catch(() => {
22388
+ return;
22389
+ });
22390
+ return {
22391
+ id: provider.id,
22392
+ kind: provider.auth.kind === "oauth" ? "oauth" : "api-key",
22393
+ ...envVarsOf2(provider.auth).length ? { envVars: envVarsOf2(provider.auth) } : {},
22394
+ ...configured ? { configured: describeConfigured(configured.type) } : {}
22395
+ };
22396
+ }
22397
+ function envVarsOf2(auth2) {
22398
+ return auth2.kind === "api-key" || auth2.kind === "oauth" ? auth2.envVars ?? [] : [];
22399
+ }
22400
+ function describeConfigured(kind) {
22401
+ return kind === "ambient" ? "ambient credentials" : kind === "oauth" ? "signed in" : "api key set";
22402
+ }
22403
+ async function probe(registry2, providerId) {
22404
+ const listed = registry2.list().find((provider) => provider.id === providerId);
22405
+ if (!listed)
22406
+ return { ok: false, reason: "other", message: `unknown provider "${providerId}"` };
22407
+ const model2 = listed.models()[0];
22408
+ if (!model2) {
22409
+ return {
22410
+ ok: false,
22411
+ reason: "other",
22412
+ message: `${providerId} publishes no models to check against`
22413
+ };
22414
+ }
22415
+ let resolved;
22416
+ try {
22417
+ resolved = await resolveModel(registry2, `${providerId}/${model2.id}`);
22418
+ } catch (error) {
22419
+ return { ok: false, reason: "other", message: error.message };
22420
+ }
22421
+ const controller = new AbortController;
22422
+ const timeout = setTimeout(() => controller.abort(), 1e4);
22423
+ try {
22424
+ for await (const event of streamModel(registry2, resolved, {
22425
+ system: "Reply with one word.",
22426
+ messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
22427
+ maxOutputTokens: 1,
22428
+ abortSignal: controller.signal
22429
+ })) {
22430
+ controller.abort();
22431
+ if (event.type === "error") {
22432
+ return event.error.kind === "auth" ? { ok: false, reason: "rejected", message: event.error.message } : event.error.kind === "network" ? { ok: false, reason: "unreachable", message: event.error.message } : { ok: false, reason: "other", message: event.error.message };
22433
+ }
22434
+ return { ok: true };
22435
+ }
22436
+ return { ok: true };
22437
+ } catch (error) {
22438
+ const message = error.message;
22439
+ const timedOut = controller.signal.aborted && error.name === "AbortError";
22440
+ return timedOut ? { ok: false, reason: "unreachable", message: "timed out waiting for a response" } : { ok: false, reason: "other", message };
22441
+ } finally {
22442
+ clearTimeout(timeout);
22443
+ }
22444
+ }
22445
+
21578
22446
  // packages/cli/src/commands/interactive.ts
21579
22447
  var DEFAULT_MODEL3 = "anthropic/claude-opus-5";
21580
22448
  async function interactiveCommand(args) {
@@ -21617,46 +22485,63 @@ async function interactiveCommand(args) {
21617
22485
  return 2;
21618
22486
  }
21619
22487
  const extensions = await startExtensions(process.cwd());
21620
- try {
21621
- const session2 = await createSession({
21622
- cwd: process.cwd(),
21623
- extraTools: extensions.tools,
21624
- problems: extensions.problems,
21625
- onDispose: () => extensions.close(),
21626
- model: typeof flags.model === "string" ? flags.model : DEFAULT_MODEL3,
21627
- ...mode ? { mode } : {},
21628
- ...typeof flags["api-key"] === "string" ? { apiKey: flags["api-key"] } : {},
21629
- ...curiosity ? { curiosity } : {},
21630
- ...maxCostUsd !== undefined ? { maxCostUsd } : {},
21631
- ...resumeFrom2(flags)
21632
- });
21633
- return await runTui({
21634
- session: session2,
21635
- model: typeof flags.model === "string" ? flags.model : DEFAULT_MODEL3,
21636
- ...initialPrompt !== "" || image ? {
21637
- initialPrompt: image ? [...initialPrompt ? [{ type: "text", text: initialPrompt }] : [], image] : initialPrompt
21638
- } : {}
21639
- });
21640
- } catch (error) {
21641
- await extensions.close();
21642
- if (error instanceof UnknownModelError) {
21643
- process.stderr.write(`${error.message}
22488
+ const model2 = typeof flags.model === "string" ? flags.model : DEFAULT_MODEL3;
22489
+ const onboardingAllowed = flags["no-onboarding"] !== true;
22490
+ let firstPrompt = initialPrompt;
22491
+ let attempted = false;
22492
+ for (;; ) {
22493
+ try {
22494
+ const session2 = await createSession({
22495
+ cwd: process.cwd(),
22496
+ extraTools: extensions.tools,
22497
+ problems: extensions.problems,
22498
+ onDispose: () => extensions.close(),
22499
+ model: model2,
22500
+ ...mode ? { mode } : {},
22501
+ ...typeof flags["api-key"] === "string" ? { apiKey: flags["api-key"] } : {},
22502
+ ...curiosity ? { curiosity } : {},
22503
+ ...maxCostUsd !== undefined ? { maxCostUsd } : {},
22504
+ ...resumeFrom2(flags)
22505
+ });
22506
+ return await runTui({
22507
+ session: session2,
22508
+ model: model2,
22509
+ ...firstPrompt !== "" || image ? {
22510
+ initialPrompt: image ? [...firstPrompt ? [{ type: "text", text: firstPrompt }] : [], image] : firstPrompt
22511
+ } : {}
22512
+ });
22513
+ } catch (error) {
22514
+ if (error instanceof MissingCredentialsError && onboardingAllowed && !attempted) {
22515
+ attempted = true;
22516
+ const result = await runOnboarding(await buildOnboardingOptions(error.provider.id));
22517
+ if (result.outcome === "quit") {
22518
+ process.stdout.write("nothing was stored. run `earshot auth login <provider>` when you are ready.\n");
22519
+ await extensions.close();
22520
+ return 0;
22521
+ }
22522
+ firstPrompt = result.firstPrompt ?? firstPrompt;
22523
+ continue;
22524
+ }
22525
+ await extensions.close();
22526
+ if (error instanceof UnknownModelError) {
22527
+ process.stderr.write(`${error.message}
21644
22528
 
21645
22529
  run \`earshot models\` to see what is available
21646
22530
  `);
21647
- return 2;
21648
- }
21649
- if (error instanceof MissingCredentialsError) {
21650
- process.stderr.write(`${error.message}
22531
+ return 2;
22532
+ }
22533
+ if (error instanceof MissingCredentialsError) {
22534
+ process.stderr.write(`${error.message}
21651
22535
  `);
21652
- return 3;
21653
- }
21654
- if (error instanceof NoSessionToResumeError) {
21655
- process.stderr.write(`${error.message}
22536
+ return 3;
22537
+ }
22538
+ if (error instanceof NoSessionToResumeError) {
22539
+ process.stderr.write(`${error.message}
21656
22540
  `);
21657
- return 2;
22541
+ return 2;
22542
+ }
22543
+ throw error;
21658
22544
  }
21659
- throw error;
21660
22545
  }
21661
22546
  }
21662
22547
  function resumeFrom2(flags) {
@@ -21778,6 +22663,343 @@ ${models.length} models across ${new Set(models.map((m) => m.providerId)).size}
21778
22663
  return 0;
21779
22664
  }
21780
22665
 
22666
+ // packages/cli/src/commands/update.ts
22667
+ import { spawnSync as spawnSync2 } from "node:child_process";
22668
+ import { createHash as createHash4 } from "node:crypto";
22669
+ import { existsSync as existsSync2, realpathSync } from "node:fs";
22670
+ import { chmod, readdir as readdir8, rename as rename2, rm as rm2, writeFile as writeFile10 } from "node:fs/promises";
22671
+ import { basename as basename2, dirname as dirname9, join as join19, sep as sep5 } from "node:path";
22672
+ import { createInterface } from "node:readline/promises";
22673
+ import { fileURLToPath } from "node:url";
22674
+ var PACKAGE = "@raegent/earshot";
22675
+ var REPO = "rishabhguptajs/earshot";
22676
+ var REGISTRY = `https://registry.npmjs.org/${PACKAGE.replace("/", "%2f")}/latest`;
22677
+ var RELEASE = `https://api.github.com/repos/${REPO}/releases/latest`;
22678
+ var BUNFS_MARKERS = ["/$bunfs/", "\\$bunfs\\", "/~BUN/", "\\~BUN\\"];
22679
+ var ASSETS = {
22680
+ "darwin-arm64": "earshot-darwin-arm64",
22681
+ "darwin-x64": "earshot-darwin-x64",
22682
+ "linux-x64": "earshot-linux-x64",
22683
+ "linux-arm64": "earshot-linux-arm64",
22684
+ "win32-x64": "earshot-windows-x64.exe"
22685
+ };
22686
+ function modulePath(moduleUrl) {
22687
+ try {
22688
+ return fileURLToPath(moduleUrl);
22689
+ } catch {
22690
+ return moduleUrl;
22691
+ }
22692
+ }
22693
+ function managerOf(dir) {
22694
+ const lower = dir.toLowerCase().replaceAll("\\", "/");
22695
+ if (lower.includes("/.bun/install/global"))
22696
+ return "bun";
22697
+ if (lower.includes("/.volta/"))
22698
+ return "volta";
22699
+ if (lower.includes("/pnpm/global") || lower.includes("/.pnpm/"))
22700
+ return "pnpm";
22701
+ if (lower.includes("/.yarn/") || lower.includes("/yarn/global"))
22702
+ return "yarn";
22703
+ return "npm";
22704
+ }
22705
+ function detectInstall(options) {
22706
+ const { execPath, moduleUrl, bunVersion, platform: platform7, arch } = options;
22707
+ const realpath = options.realpath ?? ((path) => path);
22708
+ const file = modulePath(moduleUrl);
22709
+ if (BUNFS_MARKERS.some((marker3) => file.includes(marker3)) && bunVersion !== undefined) {
22710
+ const asset = ASSETS[`${platform7}-${arch}`];
22711
+ const target = realpath(execPath);
22712
+ if (asset === undefined) {
22713
+ return {
22714
+ kind: "binary",
22715
+ path: target,
22716
+ reason: `no release asset is built for ${platform7}-${arch}`
22717
+ };
22718
+ }
22719
+ return { kind: "binary", path: target, asset };
22720
+ }
22721
+ const marker2 = `${sep5}node_modules${sep5}${PACKAGE.split("/").join(sep5)}${sep5}`;
22722
+ const index = `${file}${sep5}`.indexOf(marker2);
22723
+ if (index !== -1) {
22724
+ const packageDir = file.slice(0, index + marker2.length - 1);
22725
+ const tree = packageDir.slice(0, packageDir.indexOf(`${sep5}node_modules${sep5}`));
22726
+ const isProjectRoot = options.isProjectRoot ?? (() => false);
22727
+ return {
22728
+ kind: "npm",
22729
+ path: packageDir,
22730
+ manager: managerOf(packageDir),
22731
+ local: isProjectRoot(tree)
22732
+ };
22733
+ }
22734
+ if (file.includes(`${sep5}packages${sep5}cli${sep5}`) || file.endsWith(".ts")) {
22735
+ return { kind: "source", path: file, reason: "running from a source checkout" };
22736
+ }
22737
+ return { kind: "unknown", path: file, reason: `cannot tell how ${execPath} was installed` };
22738
+ }
22739
+ function compareVersions(a, b) {
22740
+ const split = (value) => {
22741
+ const [core = "", pre] = value.replace(/^v/, "").split("-");
22742
+ const parts = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
22743
+ return { parts, pre };
22744
+ };
22745
+ const left = split(a);
22746
+ const right = split(b);
22747
+ for (let i = 0;i < 3; i++) {
22748
+ const diff2 = (left.parts[i] ?? 0) - (right.parts[i] ?? 0);
22749
+ if (diff2 !== 0)
22750
+ return diff2 < 0 ? -1 : 1;
22751
+ }
22752
+ if (left.pre === right.pre)
22753
+ return 0;
22754
+ if (left.pre === undefined)
22755
+ return 1;
22756
+ if (right.pre === undefined)
22757
+ return -1;
22758
+ return left.pre < right.pre ? -1 : 1;
22759
+ }
22760
+ async function resolveLatest(kind, fetchImpl) {
22761
+ if (kind === "npm") {
22762
+ const response2 = await fetchImpl(REGISTRY);
22763
+ if (!response2.ok)
22764
+ throw new Error(`npm registry returned ${response2.status}`);
22765
+ const body2 = await response2.json();
22766
+ if (!body2.version)
22767
+ throw new Error("npm registry returned no version");
22768
+ return body2.version;
22769
+ }
22770
+ const response = await fetchImpl(RELEASE);
22771
+ if (!response.ok)
22772
+ throw new Error(`GitHub releases returned ${response.status}`);
22773
+ const body = await response.json();
22774
+ if (!body.tag_name)
22775
+ throw new Error("GitHub returned a release with no tag");
22776
+ return body.tag_name.replace(/^v/, "");
22777
+ }
22778
+ function findChecksum(sums, asset) {
22779
+ for (const line of sums.split(`
22780
+ `)) {
22781
+ const [hash, ...rest] = line.trim().split(/\s+/);
22782
+ const name = rest.join(" ").replace(/^\*/, "");
22783
+ if (hash && name && basename2(name) === asset)
22784
+ return hash.toLowerCase();
22785
+ }
22786
+ return;
22787
+ }
22788
+ var installCommand = {
22789
+ npm: `npm install -g ${PACKAGE}@latest`,
22790
+ bun: `bun add -g ${PACKAGE}@latest`,
22791
+ pnpm: `pnpm add -g ${PACKAGE}@latest`,
22792
+ yarn: `yarn global add ${PACKAGE}@latest`,
22793
+ volta: `volta install ${PACKAGE}@latest`
22794
+ };
22795
+ async function runUpdate(options = {}) {
22796
+ const out = options.out ?? ((text2) => process.stdout.write(text2));
22797
+ const err = options.err ?? ((text2) => process.stderr.write(text2));
22798
+ const current = options.current ?? VERSION;
22799
+ const install = options.install ?? detectInstall({
22800
+ execPath: process.execPath,
22801
+ moduleUrl: import.meta.url,
22802
+ bunVersion: process.versions.bun,
22803
+ platform: process.platform,
22804
+ arch: process.arch,
22805
+ realpath: (path) => {
22806
+ try {
22807
+ return realpathSync(path);
22808
+ } catch {
22809
+ return path;
22810
+ }
22811
+ },
22812
+ isProjectRoot: (dir) => existsSync2(join19(dir, "package.json"))
22813
+ });
22814
+ const fetchImpl = options.fetch ?? ((url) => fetch(url));
22815
+ if (install.kind === "source" || install.kind === "unknown") {
22816
+ err(`earshot update: ${install.reason}
22817
+ `);
22818
+ err(install.kind === "source" ? `update the checkout with git instead
22819
+ ` : `reinstall from ${`https://github.com/${REPO}/releases`}
22820
+ `);
22821
+ return 2;
22822
+ }
22823
+ if (install.kind === "binary" && install.asset === undefined) {
22824
+ err(`earshot update: ${install.reason}
22825
+ `);
22826
+ return 2;
22827
+ }
22828
+ let latest;
22829
+ try {
22830
+ latest = await resolveLatest(install.kind, fetchImpl);
22831
+ } catch (error) {
22832
+ err(`earshot update: ${error.message}
22833
+ `);
22834
+ return 1;
22835
+ }
22836
+ if (compareVersions(current, latest) >= 0) {
22837
+ out(`earshot ${current} is the latest version
22838
+ `);
22839
+ return 0;
22840
+ }
22841
+ const where = install.kind === "npm" ? `installed with ${install.manager}, ${install.local ? "in this project" : "globally"}` : "standalone binary";
22842
+ out(`earshot ${current} -> ${latest} (${where})
22843
+ `);
22844
+ if (install.kind === "npm") {
22845
+ const manager2 = install.manager ?? "npm";
22846
+ const command = install.local ? `${manager2 === "npm" ? "npm install" : `${manager2} add`} ${PACKAGE}@latest` : installCommand[manager2];
22847
+ out(`
22848
+ ${command}
22849
+
22850
+ `);
22851
+ if (install.local || manager2 !== "npm") {
22852
+ out(`run that to update
22853
+ `);
22854
+ return options.check ? 4 : 0;
22855
+ }
22856
+ if (options.check)
22857
+ return 4;
22858
+ if (!await confirmed(options, "run it now?")) {
22859
+ out(`run that to update
22860
+ `);
22861
+ return 0;
22862
+ }
22863
+ const run4 = options.run ?? runCommand2;
22864
+ const result = run4("npm", ["install", "-g", `${PACKAGE}@latest`]);
22865
+ if (result.status !== 0) {
22866
+ err(`earshot update: npm exited ${result.status}
22867
+ `);
22868
+ return 1;
22869
+ }
22870
+ out(`updated to ${latest}
22871
+ `);
22872
+ return 0;
22873
+ }
22874
+ return await updateBinary(install, latest, options, out, err);
22875
+ }
22876
+ function runCommand2(command, args) {
22877
+ const result = spawnSync2(command, args, { stdio: "inherit", windowsHide: true, shell: false });
22878
+ return { status: result.status };
22879
+ }
22880
+ async function confirmed(options, question) {
22881
+ if (options.yes)
22882
+ return true;
22883
+ const confirm = options.confirm ?? defaultConfirm;
22884
+ return await confirm(question);
22885
+ }
22886
+ async function defaultConfirm(question) {
22887
+ if (!process.stdin.isTTY || !process.stdout.isTTY)
22888
+ return false;
22889
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
22890
+ try {
22891
+ const answer = await rl.question(`${question} [y/N] `);
22892
+ return /^y(es)?$/i.test(answer.trim());
22893
+ } finally {
22894
+ rl.close();
22895
+ }
22896
+ }
22897
+ async function updateBinary(install, latest, options, out, err) {
22898
+ const target = install.path;
22899
+ const asset = install.asset;
22900
+ const dir = dirname9(target);
22901
+ const fetchImpl = options.fetch ?? ((url) => fetch(url));
22902
+ await sweepStale(dir, target);
22903
+ out(` ${target} ${asset}
22904
+ `);
22905
+ if (options.check)
22906
+ return 4;
22907
+ try {
22908
+ await writeFile10(join19(dir, `.earshot-update-probe-${process.pid}`), "");
22909
+ await rm2(join19(dir, `.earshot-update-probe-${process.pid}`), { force: true });
22910
+ } catch {
22911
+ err(`earshot update: cannot write to ${dir}
22912
+ `);
22913
+ err(`re-run with the permissions that own that directory
22914
+ `);
22915
+ return 1;
22916
+ }
22917
+ if (!await confirmed(options, `download ${asset} and replace it?`)) {
22918
+ out(`nothing was changed
22919
+ `);
22920
+ return 0;
22921
+ }
22922
+ const base = `https://github.com/${REPO}/releases/download/v${latest}`;
22923
+ const temp = join19(dir, `.earshot-update-${process.pid}.tmp`);
22924
+ try {
22925
+ out(" downloading… ");
22926
+ const download = await fetchImpl(`${base}/${asset}`);
22927
+ if (!download.ok)
22928
+ throw new Error(`downloading ${asset} returned ${download.status}`);
22929
+ const bytes = new Uint8Array(await download.arrayBuffer());
22930
+ out("verifying SHA256… ");
22931
+ const sumsResponse = await fetchImpl(`${base}/SHA256SUMS`);
22932
+ if (!sumsResponse.ok)
22933
+ throw new Error(`SHA256SUMS returned ${sumsResponse.status}`);
22934
+ const expected = findChecksum(await sumsResponse.text(), asset);
22935
+ if (expected === undefined)
22936
+ throw new Error(`SHA256SUMS does not list ${asset}`);
22937
+ const actual = createHash4("sha256").update(bytes).digest("hex");
22938
+ if (actual !== expected) {
22939
+ throw new Error(`checksum mismatch for ${asset}: expected ${expected}, got ${actual}`);
22940
+ }
22941
+ out("replacing… ");
22942
+ await writeFile10(temp, bytes);
22943
+ const platform7 = options.platform ?? process.platform;
22944
+ if (platform7 !== "win32")
22945
+ await chmod(temp, 493);
22946
+ await replace(temp, target, platform7);
22947
+ out(`
22948
+ updated to ${latest}
22949
+ `);
22950
+ return 0;
22951
+ } catch (error) {
22952
+ await rm2(temp, { force: true });
22953
+ out(`
22954
+ `);
22955
+ err(`earshot update: ${error.message}
22956
+ `);
22957
+ err(`nothing was replaced
22958
+ `);
22959
+ return 1;
22960
+ }
22961
+ }
22962
+ var realIo = {
22963
+ rename: (from, to) => rename2(from, to),
22964
+ remove: (path) => rm2(path, { force: true })
22965
+ };
22966
+ async function replace(temp, target, platform7, io = realIo) {
22967
+ if (platform7 !== "win32") {
22968
+ await io.rename(temp, target);
22969
+ return;
22970
+ }
22971
+ const aside = `${target}.old-${process.pid}`;
22972
+ await io.rename(target, aside);
22973
+ try {
22974
+ await io.rename(temp, target);
22975
+ } catch (error) {
22976
+ await io.rename(aside, target).catch(() => {});
22977
+ throw error;
22978
+ }
22979
+ await io.remove(aside).catch(() => {});
22980
+ }
22981
+ async function sweepStale(dir, target) {
22982
+ const prefix = `${basename2(target)}.old-`;
22983
+ const entries = await readdir8(dir).catch(() => []);
22984
+ for (const entry of entries) {
22985
+ if (entry.startsWith(prefix))
22986
+ await rm2(join19(dir, entry), { force: true }).catch(() => {});
22987
+ }
22988
+ }
22989
+ async function updateCommand(args) {
22990
+ if (args.positionals.length > 0) {
22991
+ process.stderr.write(`earshot update: unexpected argument "${args.positionals[0]}"
22992
+ `);
22993
+ process.stderr.write(`usage: earshot update [--check] [--yes]
22994
+ `);
22995
+ return 2;
22996
+ }
22997
+ return runUpdate({
22998
+ check: args.flags.check === true,
22999
+ yes: args.flags.yes === true || args.flags.y === true
23000
+ });
23001
+ }
23002
+
21781
23003
  // packages/cli/src/index.ts
21782
23004
  var HELP = `earshot ${VERSION} - a terminal coding agent that actually listens
21783
23005
 
@@ -21790,6 +23012,7 @@ Usage
21790
23012
  earshot extensions <cmd> list, trust or untrust in-process extensions
21791
23013
  earshot acp serve editor clients over ACP v1 on stdio
21792
23014
  earshot doctor diagnose the local setup
23015
+ earshot update [--check] update earshot to the latest release
21793
23016
 
21794
23017
  Flags
21795
23018
  --model <provider/model> model for this session
@@ -21829,6 +23052,8 @@ async function main(argv = process.argv.slice(2)) {
21829
23052
  return authCommand(args);
21830
23053
  if (command === "doctor")
21831
23054
  return doctorCommand(args);
23055
+ if (command === "update")
23056
+ return updateCommand(args);
21832
23057
  if (command === "acp")
21833
23058
  return acpCommand(args);
21834
23059
  if (command) {
@@ -21843,5 +23068,5 @@ async function main(argv = process.argv.slice(2)) {
21843
23068
  var code = await main();
21844
23069
  process.exitCode = code;
21845
23070
 
21846
- //# debugId=A0D0F9FB395CDA7464756E2164756E21
23071
+ //# debugId=8F9F015F9221906F64756E2164756E21
21847
23072
  //# sourceMappingURL=main.js.map