@raegent/earshot 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/main.js +1072 -196
  2. package/dist/main.js.map +13 -9
  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.2.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.";
@@ -20256,10 +20278,124 @@ run \`earshot models\` to see what is available
20256
20278
  }
20257
20279
 
20258
20280
  // packages/tui/src/app.tsx
20259
- import { Box as Box8, Static, Text as Text9, useApp, useInput as useInput4 } from "ink";
20281
+ import { Box as Box9, Static, Text as Text10, useApp, useInput as useInput4 } from "ink";
20260
20282
  import { useCallback, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
20261
20283
 
20262
- // packages/tui/src/components/markdown.tsx
20284
+ // packages/tui/src/commands.ts
20285
+ var SPECS = [
20286
+ {
20287
+ name: "help",
20288
+ summary: "List the commands you can type"
20289
+ },
20290
+ {
20291
+ name: "model",
20292
+ args: "[ref]",
20293
+ summary: "Show the model in use, or switch to another for the rest of the session"
20294
+ },
20295
+ {
20296
+ name: "mode",
20297
+ args: "<plan|ask|accept-edits|auto|yolo>",
20298
+ summary: "Change the permission mode"
20299
+ },
20300
+ {
20301
+ name: "plan",
20302
+ args: "<task>",
20303
+ summary: "Draft a plan in plan mode and write it to a file",
20304
+ idleOnly: true,
20305
+ verbs: [
20306
+ { name: "edit", summary: "Open the plan in $VISUAL/$EDITOR, or print its path" },
20307
+ { name: "approve", summary: "Pin the plan as the file now reads for the rest of the run" },
20308
+ { name: "show", summary: "Read the plan back" },
20309
+ { name: "clear", summary: "Unpin the plan" }
20310
+ ]
20311
+ },
20312
+ {
20313
+ name: "compact",
20314
+ summary: "Summarise the session so far and free up the context window",
20315
+ idleOnly: true
20316
+ },
20317
+ {
20318
+ name: "context",
20319
+ summary: "Show what is in the context window and what compaction has dropped"
20320
+ },
20321
+ {
20322
+ name: "cost",
20323
+ args: "[usd]",
20324
+ summary: "Show what this session has spent, or set the budget ceiling; 0 removes it"
20325
+ },
20326
+ {
20327
+ name: "todo",
20328
+ summary: "Show the agent's current todo list"
20329
+ },
20330
+ {
20331
+ name: "permissions",
20332
+ summary: "Show the permission mode and the rules in force"
20333
+ },
20334
+ {
20335
+ name: "init",
20336
+ summary: "Write an AGENTS.md describing this project",
20337
+ idleOnly: true
20338
+ },
20339
+ {
20340
+ name: "skills",
20341
+ summary: "List discovered skills and user-defined commands"
20342
+ },
20343
+ {
20344
+ name: "memory",
20345
+ summary: "List remembered preferences, each with the sentence it came from",
20346
+ verbs: [{ name: "forget", args: "<id>", summary: "Delete one remembered preference" }]
20347
+ },
20348
+ {
20349
+ name: "tree",
20350
+ summary: "List this session's prompts, numbered",
20351
+ idleOnly: true
20352
+ },
20353
+ {
20354
+ name: "rewind",
20355
+ args: "<n>",
20356
+ summary: "Go back to the state before prompt n; nothing is deleted",
20357
+ idleOnly: true
20358
+ },
20359
+ {
20360
+ name: "fork",
20361
+ args: "<n>",
20362
+ summary: "Branch from prompt n into a new transcript",
20363
+ idleOnly: true
20364
+ },
20365
+ {
20366
+ name: "undo",
20367
+ summary: "Revert the last tool batch's file changes; again to step back further"
20368
+ },
20369
+ {
20370
+ name: "exit",
20371
+ aliases: ["quit"],
20372
+ summary: "Quit"
20373
+ }
20374
+ ];
20375
+ var COMMANDS = SPECS;
20376
+ function findCommand(name) {
20377
+ return COMMANDS.find((command) => command.name === name || command.aliases?.includes(name));
20378
+ }
20379
+ function commandRows() {
20380
+ const rows = [];
20381
+ for (const spec of COMMANDS) {
20382
+ rows.push({
20383
+ command: spec.args ? `/${spec.name} ${spec.args}` : `/${spec.name}`,
20384
+ summary: spec.summary,
20385
+ spec
20386
+ });
20387
+ for (const verb of spec.verbs ?? []) {
20388
+ rows.push({
20389
+ command: `/${spec.name} ${verb.name}${verb.args ? ` ${verb.args}` : ""}`,
20390
+ summary: verb.summary,
20391
+ spec
20392
+ });
20393
+ }
20394
+ }
20395
+ return rows;
20396
+ }
20397
+
20398
+ // packages/tui/src/components/command-menu.tsx
20263
20399
  import { Box, Text } from "ink";
20264
20400
 
20265
20401
  // packages/tui/src/theme.ts
@@ -20292,13 +20428,123 @@ var MODE_COLOR = {
20292
20428
  yolo: theme.danger
20293
20429
  };
20294
20430
 
20431
+ // packages/tui/src/components/command-menu.tsx
20432
+ import { jsxDEV } from "react/jsx-dev-runtime";
20433
+ var SUMMARY_WIDTH = 58;
20434
+ var VISIBLE = 10;
20435
+ function menuEntries(query, commands, busy) {
20436
+ const entries = [];
20437
+ for (const spec of ranked(COMMANDS, query)) {
20438
+ if (!matches2(spec.name, query))
20439
+ continue;
20440
+ const disabled = busy && spec.idleOnly === true;
20441
+ const expanded = query !== "" && spec.name.startsWith(query);
20442
+ for (const row of commandRows()) {
20443
+ if (row.spec.name !== spec.name)
20444
+ continue;
20445
+ const isBase = row.command === (spec.args ? `/${spec.name} ${spec.args}` : `/${spec.name}`);
20446
+ if (!isBase && !expanded)
20447
+ continue;
20448
+ entries.push({
20449
+ insert: insertFor(row.command, spec),
20450
+ label: row.command,
20451
+ summary: row.summary,
20452
+ ...disabled ? { disabled: true } : {}
20453
+ });
20454
+ }
20455
+ }
20456
+ for (const command of ranked(commands, query)) {
20457
+ if (!matches2(command.name, query))
20458
+ continue;
20459
+ entries.push({
20460
+ insert: `/${command.name}`,
20461
+ label: `/${command.name}`,
20462
+ summary: command.description,
20463
+ scope: command.scope
20464
+ });
20465
+ }
20466
+ return entries;
20467
+ }
20468
+ function insertFor(command, spec) {
20469
+ const withoutArgs = spec.args ? command.replace(` ${spec.args}`, "") : command;
20470
+ return withoutArgs.replace(/\s<[^>]*>$/, "");
20471
+ }
20472
+ var clamp = (text2) => text2.length <= SUMMARY_WIDTH ? text2 : `${text2.slice(0, SUMMARY_WIDTH - 1)}…`;
20473
+ function rowColor(entry, active) {
20474
+ if (entry.disabled)
20475
+ return { color: theme.muted };
20476
+ if (active)
20477
+ return { color: theme.user };
20478
+ return {};
20479
+ }
20480
+ function matches2(name, query) {
20481
+ return query === "" || name.includes(query);
20482
+ }
20483
+ function ranked(items, query) {
20484
+ if (query === "")
20485
+ return [...items];
20486
+ return [
20487
+ ...items.filter((item) => item.name.startsWith(query)),
20488
+ ...items.filter((item) => !item.name.startsWith(query))
20489
+ ];
20490
+ }
20491
+ function CommandMenu({ entries, selected }) {
20492
+ if (entries.length === 0) {
20493
+ return /* @__PURE__ */ jsxDEV(Box, {
20494
+ marginTop: 1,
20495
+ children: /* @__PURE__ */ jsxDEV(Text, {
20496
+ color: theme.muted,
20497
+ children: "no command matches"
20498
+ }, undefined, false, undefined, this)
20499
+ }, undefined, false, undefined, this);
20500
+ }
20501
+ const start = Math.min(Math.max(0, selected - VISIBLE + 1), Math.max(0, entries.length - VISIBLE));
20502
+ const shown = entries.slice(start, start + VISIBLE);
20503
+ const hidden = entries.length - shown.length;
20504
+ return /* @__PURE__ */ jsxDEV(Box, {
20505
+ flexDirection: "column",
20506
+ marginTop: 1,
20507
+ children: [
20508
+ shown.map((entry, index) => {
20509
+ const active = start + index === selected;
20510
+ return /* @__PURE__ */ jsxDEV(Box, {
20511
+ children: [
20512
+ /* @__PURE__ */ jsxDEV(Text, {
20513
+ color: active ? theme.user : theme.muted,
20514
+ children: active ? "› " : " "
20515
+ }, undefined, false, undefined, this),
20516
+ /* @__PURE__ */ jsxDEV(Text, {
20517
+ ...rowColor(entry, active),
20518
+ children: entry.label.padEnd(34)
20519
+ }, undefined, false, undefined, this),
20520
+ /* @__PURE__ */ jsxDEV(Text, {
20521
+ color: theme.muted,
20522
+ children: clamp(`${entry.scope ? `[${entry.scope}] ` : ""}${entry.summary}${entry.disabled ? " (not while a turn is running)" : ""}`)
20523
+ }, undefined, false, undefined, this)
20524
+ ]
20525
+ }, entry.label, true, undefined, this);
20526
+ }),
20527
+ /* @__PURE__ */ jsxDEV(Box, {
20528
+ children: /* @__PURE__ */ jsxDEV(Text, {
20529
+ color: theme.muted,
20530
+ children: [
20531
+ hidden > 0 ? ` ${hidden} more · ` : " ",
20532
+ "↑↓ choose · tab complete · enter run · esc close"
20533
+ ]
20534
+ }, undefined, true, undefined, this)
20535
+ }, undefined, false, undefined, this)
20536
+ ]
20537
+ }, undefined, true, undefined, this);
20538
+ }
20539
+
20295
20540
  // packages/tui/src/components/markdown.tsx
20296
- import { jsxDEV, Fragment } from "react/jsx-dev-runtime";
20541
+ import { Box as Box2, Text as Text2 } from "ink";
20542
+ import { jsxDEV as jsxDEV2, Fragment } from "react/jsx-dev-runtime";
20297
20543
  function Markdown({ text: text2 }) {
20298
20544
  const blocks = splitBlocks(text2);
20299
- return /* @__PURE__ */ jsxDEV(Box, {
20545
+ return /* @__PURE__ */ jsxDEV2(Box2, {
20300
20546
  flexDirection: "column",
20301
- children: blocks.map((block, index) => /* @__PURE__ */ jsxDEV(Block, {
20547
+ children: blocks.map((block, index) => /* @__PURE__ */ jsxDEV2(Block, {
20302
20548
  block
20303
20549
  }, index, false, undefined, this))
20304
20550
  }, undefined, false, undefined, this);
@@ -20377,19 +20623,19 @@ function splitBlocks(text2) {
20377
20623
  function Block({ block }) {
20378
20624
  switch (block.kind) {
20379
20625
  case "code":
20380
- return /* @__PURE__ */ jsxDEV(Box, {
20626
+ return /* @__PURE__ */ jsxDEV2(Box2, {
20381
20627
  flexDirection: "column",
20382
20628
  marginY: 1,
20383
20629
  paddingLeft: 2,
20384
- children: block.lines.map((line, index) => /* @__PURE__ */ jsxDEV(Text, {
20630
+ children: block.lines.map((line, index) => /* @__PURE__ */ jsxDEV2(Text2, {
20385
20631
  color: theme.tool,
20386
20632
  children: line
20387
20633
  }, index, false, undefined, this))
20388
20634
  }, undefined, false, undefined, this);
20389
20635
  case "heading":
20390
- return /* @__PURE__ */ jsxDEV(Box, {
20636
+ return /* @__PURE__ */ jsxDEV2(Box2, {
20391
20637
  marginTop: block.level <= 2 ? 1 : 0,
20392
- children: /* @__PURE__ */ jsxDEV(Text, {
20638
+ children: /* @__PURE__ */ jsxDEV2(Text2, {
20393
20639
  bold: true,
20394
20640
  underline: block.level === 1,
20395
20641
  color: theme.assistant,
@@ -20397,29 +20643,29 @@ function Block({ block }) {
20397
20643
  }, undefined, false, undefined, this)
20398
20644
  }, undefined, false, undefined, this);
20399
20645
  case "rule":
20400
- return /* @__PURE__ */ jsxDEV(Text, {
20646
+ return /* @__PURE__ */ jsxDEV2(Text2, {
20401
20647
  color: theme.muted,
20402
20648
  children: "─".repeat(40)
20403
20649
  }, undefined, false, undefined, this);
20404
20650
  case "list":
20405
- return /* @__PURE__ */ jsxDEV(Box, {
20651
+ return /* @__PURE__ */ jsxDEV2(Box2, {
20406
20652
  flexDirection: "column",
20407
- children: block.items.map((item, index) => /* @__PURE__ */ jsxDEV(Text, {
20653
+ children: block.items.map((item, index) => /* @__PURE__ */ jsxDEV2(Text2, {
20408
20654
  children: [
20409
20655
  " ",
20410
20656
  block.ordered ? `${index + 1}.` : "-",
20411
20657
  " ",
20412
- /* @__PURE__ */ jsxDEV(Inline, {
20658
+ /* @__PURE__ */ jsxDEV2(Inline, {
20413
20659
  text: item
20414
20660
  }, undefined, false, undefined, this)
20415
20661
  ]
20416
20662
  }, index, true, undefined, this))
20417
20663
  }, undefined, false, undefined, this);
20418
20664
  case "quote":
20419
- return /* @__PURE__ */ jsxDEV(Box, {
20665
+ return /* @__PURE__ */ jsxDEV2(Box2, {
20420
20666
  flexDirection: "column",
20421
20667
  paddingLeft: 1,
20422
- children: block.lines.map((line, index) => /* @__PURE__ */ jsxDEV(Text, {
20668
+ children: block.lines.map((line, index) => /* @__PURE__ */ jsxDEV2(Text2, {
20423
20669
  color: theme.muted,
20424
20670
  italic: true,
20425
20671
  children: [
@@ -20429,8 +20675,8 @@ function Block({ block }) {
20429
20675
  }, index, true, undefined, this))
20430
20676
  }, undefined, false, undefined, this);
20431
20677
  default:
20432
- return /* @__PURE__ */ jsxDEV(Text, {
20433
- children: /* @__PURE__ */ jsxDEV(Inline, {
20678
+ return /* @__PURE__ */ jsxDEV2(Text2, {
20679
+ children: /* @__PURE__ */ jsxDEV2(Inline, {
20434
20680
  text: block.text
20435
20681
  }, undefined, false, undefined, this)
20436
20682
  }, undefined, false, undefined, this);
@@ -20442,41 +20688,41 @@ function inlineToText(text2) {
20442
20688
  function Inline({ text: text2 }) {
20443
20689
  const pattern = /(\*\*.+?\*\*|__.+?__|`.+?`|\*.+?\*|_.+?_)/g;
20444
20690
  const parts = text2.split(pattern);
20445
- return /* @__PURE__ */ jsxDEV(Fragment, {
20691
+ return /* @__PURE__ */ jsxDEV2(Fragment, {
20446
20692
  children: parts.map((part, index) => {
20447
20693
  if (part === "")
20448
20694
  return null;
20449
20695
  if (part.startsWith("**") && part.endsWith("**")) {
20450
- return /* @__PURE__ */ jsxDEV(Text, {
20696
+ return /* @__PURE__ */ jsxDEV2(Text2, {
20451
20697
  bold: true,
20452
20698
  children: part.slice(2, -2)
20453
20699
  }, index, false, undefined, this);
20454
20700
  }
20455
20701
  if (part.startsWith("__") && part.endsWith("__")) {
20456
- return /* @__PURE__ */ jsxDEV(Text, {
20702
+ return /* @__PURE__ */ jsxDEV2(Text2, {
20457
20703
  bold: true,
20458
20704
  children: part.slice(2, -2)
20459
20705
  }, index, false, undefined, this);
20460
20706
  }
20461
20707
  if (part.startsWith("`") && part.endsWith("`")) {
20462
- return /* @__PURE__ */ jsxDEV(Text, {
20708
+ return /* @__PURE__ */ jsxDEV2(Text2, {
20463
20709
  color: theme.tool,
20464
20710
  children: part.slice(1, -1)
20465
20711
  }, index, false, undefined, this);
20466
20712
  }
20467
20713
  if (part.startsWith("*") && part.endsWith("*")) {
20468
- return /* @__PURE__ */ jsxDEV(Text, {
20714
+ return /* @__PURE__ */ jsxDEV2(Text2, {
20469
20715
  italic: true,
20470
20716
  children: part.slice(1, -1)
20471
20717
  }, index, false, undefined, this);
20472
20718
  }
20473
20719
  if (part.startsWith("_") && part.endsWith("_")) {
20474
- return /* @__PURE__ */ jsxDEV(Text, {
20720
+ return /* @__PURE__ */ jsxDEV2(Text2, {
20475
20721
  italic: true,
20476
20722
  children: part.slice(1, -1)
20477
20723
  }, index, false, undefined, this);
20478
20724
  }
20479
- return /* @__PURE__ */ jsxDEV(Text, {
20725
+ return /* @__PURE__ */ jsxDEV2(Text2, {
20480
20726
  children: part
20481
20727
  }, index, false, undefined, this);
20482
20728
  })
@@ -20484,24 +20730,24 @@ function Inline({ text: text2 }) {
20484
20730
  }
20485
20731
 
20486
20732
  // 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";
20733
+ import { Box as Box3, Text as Text3 } from "ink";
20734
+ import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
20489
20735
  function MemoryCapture({ candidate }) {
20490
- return /* @__PURE__ */ jsxDEV2(Box2, {
20736
+ return /* @__PURE__ */ jsxDEV3(Box3, {
20491
20737
  marginTop: 1,
20492
20738
  children: [
20493
- /* @__PURE__ */ jsxDEV2(Text2, {
20739
+ /* @__PURE__ */ jsxDEV3(Text3, {
20494
20740
  color: theme.accent,
20495
20741
  children: "remember "
20496
20742
  }, undefined, false, undefined, this),
20497
- /* @__PURE__ */ jsxDEV2(Text2, {
20743
+ /* @__PURE__ */ jsxDEV3(Text3, {
20498
20744
  children: [
20499
20745
  "“",
20500
20746
  candidate.text,
20501
20747
  "”"
20502
20748
  ]
20503
20749
  }, undefined, true, undefined, this),
20504
- /* @__PURE__ */ jsxDEV2(Text2, {
20750
+ /* @__PURE__ */ jsxDEV3(Text3, {
20505
20751
  color: theme.muted,
20506
20752
  children: "? ctrl+r for this project · ctrl+g everywhere"
20507
20753
  }, undefined, false, undefined, this)
@@ -20510,12 +20756,12 @@ function MemoryCapture({ candidate }) {
20510
20756
  }
20511
20757
 
20512
20758
  // packages/tui/src/components/permission.tsx
20513
- import { Box as Box4, Text as Text4, useInput } from "ink";
20759
+ import { Box as Box5, Text as Text5, useInput } from "ink";
20514
20760
  import { useState } from "react";
20515
20761
 
20516
20762
  // 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";
20763
+ import { Box as Box4, Text as Text4 } from "ink";
20764
+ import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
20519
20765
  var MAX_LINES = 60;
20520
20766
  function DiffView({ diff: diff2, maxLines = MAX_LINES }) {
20521
20767
  const lines = diff2.split(`
@@ -20523,15 +20769,15 @@ function DiffView({ diff: diff2, maxLines = MAX_LINES }) {
20523
20769
  const body = lines.filter((line) => !line.startsWith("---") && !line.startsWith("+++"));
20524
20770
  const shown = body.slice(0, maxLines);
20525
20771
  const hidden = body.length - shown.length;
20526
- return /* @__PURE__ */ jsxDEV3(Box3, {
20772
+ return /* @__PURE__ */ jsxDEV4(Box4, {
20527
20773
  flexDirection: "column",
20528
20774
  children: [
20529
- shown.map((line, index) => /* @__PURE__ */ jsxDEV3(Text3, {
20775
+ shown.map((line, index) => /* @__PURE__ */ jsxDEV4(Text4, {
20530
20776
  color: colorFor(line),
20531
20777
  wrap: "truncate-end",
20532
20778
  children: line === "" ? " " : line
20533
20779
  }, index, false, undefined, this)),
20534
- hidden > 0 && /* @__PURE__ */ jsxDEV3(Text3, {
20780
+ hidden > 0 && /* @__PURE__ */ jsxDEV4(Text4, {
20535
20781
  color: theme.muted,
20536
20782
  children: [
20537
20783
  " ",
@@ -20566,7 +20812,7 @@ function diffStat(diff2) {
20566
20812
  }
20567
20813
 
20568
20814
  // packages/tui/src/components/permission.tsx
20569
- import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
20815
+ import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
20570
20816
  function PermissionPrompt({ request, reason, onChoice }) {
20571
20817
  const [selected, setSelected] = useState(0);
20572
20818
  const options = [
@@ -20594,32 +20840,32 @@ function PermissionPrompt({ request, reason, onChoice }) {
20594
20840
  });
20595
20841
  const isDiff = request.detail.includes(`
20596
20842
  @@`) || request.detail.startsWith("---");
20597
- return /* @__PURE__ */ jsxDEV4(Box4, {
20843
+ return /* @__PURE__ */ jsxDEV5(Box5, {
20598
20844
  flexDirection: "column",
20599
20845
  borderStyle: "round",
20600
20846
  borderColor: theme.warning,
20601
20847
  paddingX: 1,
20602
20848
  children: [
20603
- /* @__PURE__ */ jsxDEV4(Text4, {
20849
+ /* @__PURE__ */ jsxDEV5(Text5, {
20604
20850
  bold: true,
20605
20851
  color: theme.warning,
20606
20852
  children: request.title
20607
20853
  }, undefined, false, undefined, this),
20608
- /* @__PURE__ */ jsxDEV4(Text4, {
20854
+ /* @__PURE__ */ jsxDEV5(Text5, {
20609
20855
  color: theme.muted,
20610
20856
  children: reason
20611
20857
  }, undefined, false, undefined, this),
20612
- /* @__PURE__ */ jsxDEV4(Box4, {
20858
+ /* @__PURE__ */ jsxDEV5(Box5, {
20613
20859
  marginY: 1,
20614
20860
  flexDirection: "column",
20615
- children: isDiff ? /* @__PURE__ */ jsxDEV4(DiffView, {
20861
+ children: isDiff ? /* @__PURE__ */ jsxDEV5(DiffView, {
20616
20862
  diff: request.detail
20617
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV4(Text4, {
20863
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV5(Text5, {
20618
20864
  wrap: "wrap",
20619
20865
  children: request.detail
20620
20866
  }, undefined, false, undefined, this)
20621
20867
  }, undefined, false, undefined, this),
20622
- options.map((option, index) => /* @__PURE__ */ jsxDEV4(Text4, {
20868
+ options.map((option, index) => /* @__PURE__ */ jsxDEV5(Text5, {
20623
20869
  color: index === selected ? theme.accent : option.color,
20624
20870
  children: [
20625
20871
  index === selected ? "❯ " : " ",
@@ -20634,13 +20880,13 @@ function truncate2(value, max) {
20634
20880
  }
20635
20881
 
20636
20882
  // packages/tui/src/components/question.tsx
20637
- import { Box as Box5, Text as Text6, useInput as useInput3 } from "ink";
20883
+ import { Box as Box6, Text as Text7, useInput as useInput3 } from "ink";
20638
20884
  import { useState as useState2 } from "react";
20639
20885
 
20640
20886
  // packages/tui/src/components/text-input.tsx
20641
- import { Text as Text5, useInput as useInput2 } from "ink";
20887
+ import { Text as Text6, useInput as useInput2 } from "ink";
20642
20888
  import { useEffect, useRef } from "react";
20643
- import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
20889
+ import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
20644
20890
  function TextInput({
20645
20891
  value,
20646
20892
  onChange,
@@ -20674,23 +20920,23 @@ function TextInput({
20674
20920
  }
20675
20921
  }, { isActive });
20676
20922
  if (value === "") {
20677
- return /* @__PURE__ */ jsxDEV5(Text5, {
20923
+ return /* @__PURE__ */ jsxDEV6(Text6, {
20678
20924
  children: [
20679
- /* @__PURE__ */ jsxDEV5(Text5, {
20925
+ /* @__PURE__ */ jsxDEV6(Text6, {
20680
20926
  inverse: true,
20681
20927
  children: " "
20682
20928
  }, undefined, false, undefined, this),
20683
- /* @__PURE__ */ jsxDEV5(Text5, {
20929
+ /* @__PURE__ */ jsxDEV6(Text6, {
20684
20930
  color: theme.muted,
20685
20931
  children: placeholder
20686
20932
  }, undefined, false, undefined, this)
20687
20933
  ]
20688
20934
  }, undefined, true, undefined, this);
20689
20935
  }
20690
- return /* @__PURE__ */ jsxDEV5(Text5, {
20936
+ return /* @__PURE__ */ jsxDEV6(Text6, {
20691
20937
  children: [
20692
20938
  value,
20693
- /* @__PURE__ */ jsxDEV5(Text5, {
20939
+ /* @__PURE__ */ jsxDEV6(Text6, {
20694
20940
  inverse: true,
20695
20941
  children: " "
20696
20942
  }, undefined, false, undefined, this)
@@ -20699,7 +20945,7 @@ function TextInput({
20699
20945
  }
20700
20946
 
20701
20947
  // packages/tui/src/components/question.tsx
20702
- import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
20948
+ import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
20703
20949
  function QuestionPrompt({ question, options = [], onAnswer }) {
20704
20950
  const [selected, setSelected] = useState2(0);
20705
20951
  const [typing, setTyping] = useState2(options.length === 0);
@@ -20716,29 +20962,29 @@ function QuestionPrompt({ question, options = [], onAnswer }) {
20716
20962
  setValue(input);
20717
20963
  }
20718
20964
  }, { isActive: !typing });
20719
- return /* @__PURE__ */ jsxDEV6(Box5, {
20965
+ return /* @__PURE__ */ jsxDEV7(Box6, {
20720
20966
  flexDirection: "column",
20721
20967
  borderStyle: "round",
20722
20968
  borderColor: theme.accent,
20723
20969
  paddingX: 1,
20724
20970
  children: [
20725
- /* @__PURE__ */ jsxDEV6(Text6, {
20971
+ /* @__PURE__ */ jsxDEV7(Text7, {
20726
20972
  bold: true,
20727
20973
  color: theme.accent,
20728
20974
  children: question
20729
20975
  }, undefined, false, undefined, this),
20730
- !typing && options.map((option, index) => /* @__PURE__ */ jsxDEV6(Text6, {
20976
+ !typing && options.map((option, index) => /* @__PURE__ */ jsxDEV7(Text7, {
20731
20977
  color: index === selected ? theme.accent : theme.muted,
20732
20978
  children: [
20733
20979
  index === selected ? "❯ " : " ",
20734
20980
  option
20735
20981
  ]
20736
20982
  }, option, true, undefined, this)),
20737
- !typing && /* @__PURE__ */ jsxDEV6(Text6, {
20983
+ !typing && /* @__PURE__ */ jsxDEV7(Text7, {
20738
20984
  color: theme.muted,
20739
20985
  children: "or start typing to answer in your own words"
20740
20986
  }, undefined, false, undefined, this),
20741
- typing && /* @__PURE__ */ jsxDEV6(TextInput, {
20987
+ typing && /* @__PURE__ */ jsxDEV7(TextInput, {
20742
20988
  value,
20743
20989
  onChange: setValue,
20744
20990
  onSubmit: (answer) => onAnswer(answer.trim()),
@@ -20749,8 +20995,8 @@ function QuestionPrompt({ question, options = [], onAnswer }) {
20749
20995
  }
20750
20996
 
20751
20997
  // 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";
20998
+ import { Box as Box7, Text as Text8 } from "ink";
20999
+ import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
20754
21000
  function StatusLine({
20755
21001
  model: model2,
20756
21002
  mode,
@@ -20764,27 +21010,27 @@ function StatusLine({
20764
21010
  const done = todos.filter((todo2) => todo2.status === "done").length;
20765
21011
  const current = todos.find((todo2) => todo2.status === "in_progress");
20766
21012
  const used = context2.window > 0 ? Math.min(100, context2.tokens / context2.window * 100) : 0;
20767
- return /* @__PURE__ */ jsxDEV7(Box6, {
21013
+ return /* @__PURE__ */ jsxDEV8(Box7, {
20768
21014
  children: [
20769
- /* @__PURE__ */ jsxDEV7(Text7, {
21015
+ /* @__PURE__ */ jsxDEV8(Text8, {
20770
21016
  color: MODE_COLOR[mode] ?? theme.muted,
20771
21017
  children: MODE_LABEL[mode] ?? mode
20772
21018
  }, undefined, false, undefined, this),
20773
- /* @__PURE__ */ jsxDEV7(Text7, {
21019
+ /* @__PURE__ */ jsxDEV8(Text8, {
20774
21020
  color: theme.muted,
20775
21021
  children: [
20776
21022
  " · ",
20777
21023
  model2
20778
21024
  ]
20779
21025
  }, undefined, true, undefined, this),
20780
- /* @__PURE__ */ jsxDEV7(Text7, {
21026
+ /* @__PURE__ */ jsxDEV8(Text8, {
20781
21027
  color: theme.muted,
20782
21028
  children: [
20783
21029
  " · $",
20784
21030
  costUsd.toFixed(4)
20785
21031
  ]
20786
21032
  }, undefined, true, undefined, this),
20787
- context2.window > 0 && /* @__PURE__ */ jsxDEV7(Text7, {
21033
+ context2.window > 0 && /* @__PURE__ */ jsxDEV8(Text8, {
20788
21034
  color: used >= 80 ? theme.warning : theme.muted,
20789
21035
  children: [
20790
21036
  " · ",
@@ -20792,7 +21038,7 @@ function StatusLine({
20792
21038
  "% ctx"
20793
21039
  ]
20794
21040
  }, undefined, true, undefined, this),
20795
- compacted > 0 && /* @__PURE__ */ jsxDEV7(Text7, {
21041
+ compacted > 0 && /* @__PURE__ */ jsxDEV8(Text8, {
20796
21042
  color: theme.muted,
20797
21043
  children: [
20798
21044
  " · ",
@@ -20800,7 +21046,7 @@ function StatusLine({
20800
21046
  " summarised"
20801
21047
  ]
20802
21048
  }, undefined, true, undefined, this),
20803
- todos.length > 0 && /* @__PURE__ */ jsxDEV7(Text7, {
21049
+ todos.length > 0 && /* @__PURE__ */ jsxDEV8(Text8, {
20804
21050
  color: theme.muted,
20805
21051
  children: [
20806
21052
  " ",
@@ -20811,11 +21057,11 @@ function StatusLine({
20811
21057
  current ? ` ${truncate3(current.text, 40)}` : ""
20812
21058
  ]
20813
21059
  }, undefined, true, undefined, this),
20814
- busy && /* @__PURE__ */ jsxDEV7(Text7, {
21060
+ busy && /* @__PURE__ */ jsxDEV8(Text8, {
20815
21061
  color: theme.warning,
20816
21062
  children: " · working (esc to interrupt)"
20817
21063
  }, undefined, false, undefined, this),
20818
- queued > 0 && /* @__PURE__ */ jsxDEV7(Text7, {
21064
+ queued > 0 && /* @__PURE__ */ jsxDEV8(Text8, {
20819
21065
  color: theme.accent,
20820
21066
  children: [
20821
21067
  " · ",
@@ -20831,8 +21077,8 @@ function truncate3(value, max) {
20831
21077
  }
20832
21078
 
20833
21079
  // 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";
21080
+ import { Box as Box8, Text as Text9 } from "ink";
21081
+ import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
20836
21082
  var PREVIEW_LINES = 8;
20837
21083
  function ToolBlock({
20838
21084
  name,
@@ -20851,27 +21097,27 @@ function ToolBlock({
20851
21097
  const showAll = expanded || isError;
20852
21098
  const shown = showAll ? lines : lines.slice(0, PREVIEW_LINES);
20853
21099
  const hidden = lines.length - shown.length;
20854
- return /* @__PURE__ */ jsxDEV8(Box7, {
21100
+ return /* @__PURE__ */ jsxDEV9(Box8, {
20855
21101
  flexDirection: "column",
20856
21102
  marginTop: 1,
20857
21103
  children: [
20858
- /* @__PURE__ */ jsxDEV8(Text8, {
21104
+ /* @__PURE__ */ jsxDEV9(Text9, {
20859
21105
  color,
20860
21106
  children: [
20861
21107
  marker2,
20862
21108
  " ",
20863
- /* @__PURE__ */ jsxDEV8(Text8, {
21109
+ /* @__PURE__ */ jsxDEV9(Text9, {
20864
21110
  bold: true,
20865
21111
  children: name
20866
21112
  }, undefined, false, undefined, this),
20867
- title ? /* @__PURE__ */ jsxDEV8(Text8, {
21113
+ title ? /* @__PURE__ */ jsxDEV9(Text9, {
20868
21114
  color: theme.muted,
20869
21115
  children: [
20870
21116
  " ",
20871
21117
  title
20872
21118
  ]
20873
21119
  }, undefined, true, undefined, this) : null,
20874
- isDiff && !showAll ? /* @__PURE__ */ jsxDEV8(Text8, {
21120
+ isDiff && !showAll ? /* @__PURE__ */ jsxDEV9(Text9, {
20875
21121
  color: theme.muted,
20876
21122
  children: [
20877
21123
  " ",
@@ -20880,12 +21126,12 @@ function ToolBlock({
20880
21126
  }, undefined, true, undefined, this) : null
20881
21127
  ]
20882
21128
  }, undefined, true, undefined, this),
20883
- isDiff && showAll ? /* @__PURE__ */ jsxDEV8(Box7, {
21129
+ isDiff && showAll ? /* @__PURE__ */ jsxDEV9(Box8, {
20884
21130
  marginLeft: 2,
20885
- children: /* @__PURE__ */ jsxDEV8(DiffView, {
21131
+ children: /* @__PURE__ */ jsxDEV9(DiffView, {
20886
21132
  diff: output
20887
21133
  }, undefined, false, undefined, this)
20888
- }, undefined, false, undefined, this) : shown.map((line, index) => /* @__PURE__ */ jsxDEV8(Text8, {
21134
+ }, undefined, false, undefined, this) : shown.map((line, index) => /* @__PURE__ */ jsxDEV9(Text9, {
20889
21135
  color: theme.muted,
20890
21136
  wrap: "truncate-end",
20891
21137
  children: [
@@ -20893,7 +21139,7 @@ function ToolBlock({
20893
21139
  line
20894
21140
  ]
20895
21141
  }, index, true, undefined, this)),
20896
- hidden > 0 && !isDiff && /* @__PURE__ */ jsxDEV8(Text8, {
21142
+ hidden > 0 && !isDiff && /* @__PURE__ */ jsxDEV9(Text9, {
20897
21143
  color: theme.muted,
20898
21144
  children: [
20899
21145
  " ",
@@ -20907,7 +21153,7 @@ function ToolBlock({
20907
21153
  }
20908
21154
 
20909
21155
  // packages/tui/src/app.tsx
20910
- import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
21156
+ import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime";
20911
21157
  var sequence = 0;
20912
21158
  var nextId = () => `item_${sequence++}`;
20913
21159
  function promptLabel(prompt) {
@@ -20916,9 +21162,10 @@ function promptLabel(prompt) {
20916
21162
  return prompt.map((part) => part.type === "text" ? part.text : `[attached ${part.mediaType} image]`).join(`
20917
21163
  `);
20918
21164
  }
20919
- function App({ session: session2, model: model2, initialPrompt }) {
21165
+ function App({ session: session2, model: initialModel, initialPrompt }) {
20920
21166
  const { exit } = useApp();
20921
21167
  const agent3 = session2.agent;
21168
+ const [model2, setModel] = useState3(initialModel);
20922
21169
  const [items, setItems] = useState3(() => session2.problems.map((problem) => ({
20923
21170
  kind: "notice",
20924
21171
  id: nextId(),
@@ -20928,6 +21175,7 @@ function App({ session: session2, model: model2, initialPrompt }) {
20928
21175
  const [live, setLive] = useState3("");
20929
21176
  const [runningTool, setRunningTool] = useState3();
20930
21177
  const [input, setInput] = useState3("");
21178
+ const [menuIndex, setMenuIndex] = useState3(0);
20931
21179
  const [busy, setBusy] = useState3(false);
20932
21180
  const [mode, setMode] = useState3(agent3.permissionMode);
20933
21181
  const [cost, setCost] = useState3(0);
@@ -21154,8 +21402,8 @@ function App({ session: session2, model: model2, initialPrompt }) {
21154
21402
  });
21155
21403
  }, [agent3, candidate, model2, push, session2.skills]);
21156
21404
  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>")));
21405
+ const entries2 = await session2.branch();
21406
+ 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
21407
  if (name === "tree" || !argument) {
21160
21408
  if (prompts.length === 0) {
21161
21409
  push({ kind: "notice", id: nextId(), text: "nothing in this session yet" });
@@ -21187,7 +21435,7 @@ function App({ session: session2, model: model2, initialPrompt }) {
21187
21435
  });
21188
21436
  return;
21189
21437
  }
21190
- const previous = entries[entries.indexOf(target) - 1] ?? target;
21438
+ const previous = entries2[entries2.indexOf(target) - 1] ?? target;
21191
21439
  if (name === "rewind") {
21192
21440
  const kept = await session2.rewindTo(previous.id);
21193
21441
  push({
@@ -21304,16 +21552,149 @@ ${PLAN_PROMPT}`);
21304
21552
  /plan edit to change it, /plan approve to pin it`
21305
21553
  });
21306
21554
  }, [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();
21555
+ const switchModel = useCallback(async (ref) => {
21556
+ if (!ref) {
21557
+ const current = agent3.model;
21558
+ const price = current.model.cost;
21559
+ push({
21560
+ kind: "notice",
21561
+ id: nextId(),
21562
+ text: [
21563
+ ` ${current.provider.id}/${current.model.id}`,
21564
+ ` context ${(current.model.contextWindow ?? 0).toLocaleString()} tokens`,
21565
+ ` price $${price?.input ?? "?"} in / $${price?.output ?? "?"} out per million`,
21566
+ "",
21567
+ " /model <provider/model> switches; `earshot models` lists them"
21568
+ ].join(`
21569
+ `)
21570
+ });
21314
21571
  return;
21315
21572
  }
21316
- if (name === "mode") {
21573
+ try {
21574
+ const resolved = await agent3.changeModel(ref);
21575
+ const next = `${resolved.provider.id}/${resolved.model.id}`;
21576
+ setModel(next);
21577
+ await refreshSystemPrompt(agent3, next, session2.skills);
21578
+ push({ kind: "notice", id: nextId(), text: `model: ${next}` });
21579
+ } catch (error) {
21580
+ push({
21581
+ kind: "notice",
21582
+ id: nextId(),
21583
+ text: `${error.message}`,
21584
+ color: theme.warning
21585
+ });
21586
+ }
21587
+ }, [agent3, push, session2.skills]);
21588
+ const compactNow = useCallback(async () => {
21589
+ const abort = new AbortController;
21590
+ controller.current = abort;
21591
+ setBusy(true);
21592
+ try {
21593
+ let compactedAnything = false;
21594
+ for await (const event of agent3.compactNow(abort.signal)) {
21595
+ if (event.type === "compacted") {
21596
+ compactedAnything = true;
21597
+ setCompacted((count) => count + event.replaced);
21598
+ push({
21599
+ kind: "notice",
21600
+ id: nextId(),
21601
+ text: `compacted: ${event.replaced} earlier messages are now a summary`
21602
+ });
21603
+ }
21604
+ }
21605
+ if (!compactedAnything) {
21606
+ push({ kind: "notice", id: nextId(), text: "nothing to compact yet" });
21607
+ }
21608
+ } catch (error) {
21609
+ push({
21610
+ kind: "notice",
21611
+ id: nextId(),
21612
+ text: `could not compact: ${error.message}`,
21613
+ color: theme.warning
21614
+ });
21615
+ } finally {
21616
+ setBusy(false);
21617
+ setContext(agent3.contextUse);
21618
+ controller.current = undefined;
21619
+ }
21620
+ }, [agent3, push]);
21621
+ const handlers = {
21622
+ exit: () => exit(),
21623
+ help: () => push({ kind: "notice", id: nextId(), text: describeCommands(session2) }),
21624
+ model: (argument) => void switchModel(argument),
21625
+ compact: () => void compactNow(),
21626
+ context: () => {
21627
+ const { tokens, window } = agent3.contextUse;
21628
+ const percent = window > 0 ? Math.round(tokens / window * 100) : 0;
21629
+ const files = agent3.touchedFiles;
21630
+ push({
21631
+ kind: "notice",
21632
+ id: nextId(),
21633
+ text: [
21634
+ ` model ${model2}`,
21635
+ ` context ~${tokens.toLocaleString()} of ${window.toLocaleString()} tokens (${percent}%)`,
21636
+ ` dropped ${compacted} earlier message${compacted === 1 ? "" : "s"} replaced by a summary`,
21637
+ ` files ${files.length === 0 ? "none touched yet" : files.join(", ")}`,
21638
+ "",
21639
+ " /compact summarises now rather than waiting for 80%"
21640
+ ].join(`
21641
+ `)
21642
+ });
21643
+ },
21644
+ cost: (argument) => {
21645
+ if (argument !== undefined && argument !== "") {
21646
+ const amount = Number.parseFloat(argument.replace(/^\$/, ""));
21647
+ if (Number.isNaN(amount)) {
21648
+ push({
21649
+ kind: "notice",
21650
+ id: nextId(),
21651
+ text: `usage: /cost [usd] - "${argument}" is not an amount`,
21652
+ color: theme.warning
21653
+ });
21654
+ return;
21655
+ }
21656
+ agent3.setBudget(amount > 0 ? amount : undefined);
21657
+ push({
21658
+ kind: "notice",
21659
+ id: nextId(),
21660
+ text: amount > 0 ? `budget: $${amount.toFixed(2)}` : "budget removed"
21661
+ });
21662
+ return;
21663
+ }
21664
+ const budget = agent3.budgetUsd;
21665
+ push({
21666
+ kind: "notice",
21667
+ id: nextId(),
21668
+ text: ` spent $${agent3.costUsd.toFixed(4)}
21669
+ ` + ` budget ${budget === undefined ? "none - /cost <usd> sets one" : `$${budget.toFixed(2)}`}`
21670
+ });
21671
+ },
21672
+ todo: () => {
21673
+ const todos2 = agent3.todos.list();
21674
+ push({
21675
+ kind: "notice",
21676
+ id: nextId(),
21677
+ text: todos2.length === 0 ? "no todos in this session" : todos2.map((todo2) => ` ${todo2.status === "done" ? "x" : todo2.status === "in_progress" ? ">" : " "} ${todo2.text}`).join(`
21678
+ `)
21679
+ });
21680
+ },
21681
+ permissions: () => {
21682
+ const rules2 = agent3.permissionRules;
21683
+ push({
21684
+ kind: "notice",
21685
+ id: nextId(),
21686
+ text: [
21687
+ ` mode ${agent3.permissionMode} (/mode changes it)`,
21688
+ ...rules2.length === 0 ? [" rules none configured"] : [
21689
+ " rules (deny always wins, whatever the mode or scope)",
21690
+ ...rules2.map((rule) => ` ${rule.effect.padEnd(5)} ${rule.source} [${rule.scope}]`)
21691
+ ]
21692
+ ].join(`
21693
+ `)
21694
+ });
21695
+ },
21696
+ init: () => void runTurn(INIT_PROMPT),
21697
+ mode: (argument) => {
21317
21698
  if (argument && isPermissionMode(argument)) {
21318
21699
  agent3.setPermissionMode(argument);
21319
21700
  setMode(argument);
@@ -21326,31 +21707,25 @@ ${PLAN_PROMPT}`);
21326
21707
  color: theme.warning
21327
21708
  });
21328
21709
  }
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) {
21710
+ },
21711
+ memory: (argument) => void showMemories(argument),
21712
+ tree: (argument) => void sessionTree("tree", argument),
21713
+ rewind: (argument) => void sessionTree("rewind", argument),
21714
+ fork: (argument) => void sessionTree("fork", argument),
21715
+ undo: () => void undoLast(),
21716
+ plan: (argument) => void plan2(argument),
21717
+ skills: () => push({ kind: "notice", id: nextId(), text: describeExtensions(session2) })
21718
+ };
21719
+ const handlersRef = useRef2(handlers);
21720
+ handlersRef.current = handlers;
21721
+ const handleCommand = useCallback((command) => {
21722
+ const body = command.slice(1).trim();
21723
+ const space = body.search(/\s/);
21724
+ const name = space === -1 ? body : body.slice(0, space);
21725
+ const argument = space === -1 ? undefined : body.slice(space + 1).trim();
21726
+ const spec = findCommand(name);
21727
+ if (spec) {
21728
+ if (spec.idleOnly && busy) {
21354
21729
  push({
21355
21730
  kind: "notice",
21356
21731
  id: nextId(),
@@ -21359,15 +21734,7 @@ ${PLAN_PROMPT}`);
21359
21734
  });
21360
21735
  return;
21361
21736
  }
21362
- plan2(argument);
21363
- return;
21364
- }
21365
- if (name === "skills") {
21366
- push({
21367
- kind: "notice",
21368
- id: nextId(),
21369
- text: describeExtensions(session2)
21370
- });
21737
+ handlersRef.current[spec.name](argument);
21371
21738
  return;
21372
21739
  }
21373
21740
  const custom = session2.commands.find((entry) => entry.name === name);
@@ -21397,8 +21764,15 @@ ${PLAN_PROMPT}`);
21397
21764
  text: `unknown command "${name}"`,
21398
21765
  color: theme.warning
21399
21766
  });
21400
- }, [agent3, busy, exit, plan2, push, runTurn, session2, sessionTree, showMemories, undoLast]);
21767
+ }, [agent3, busy, push, runTurn, session2]);
21401
21768
  const submit = useCallback((text2) => {
21769
+ const entry = menuOpenRef.current ? menuEntriesRef.current[selectedRef.current] : undefined;
21770
+ if (entry) {
21771
+ setInput("");
21772
+ setMenuIndex(0);
21773
+ handleCommand(entry.insert);
21774
+ return;
21775
+ }
21402
21776
  const trimmed = text2.trim();
21403
21777
  setInput("");
21404
21778
  if (trimmed === "")
@@ -21417,7 +21791,38 @@ ${PLAN_PROMPT}`);
21417
21791
  runTurn(trimmed);
21418
21792
  }, [agent3, busy, push, runTurn, handleCommand]);
21419
21793
  const inputActive = !pending && !question;
21794
+ const menuOpen = inputActive && input.startsWith("/") && !input.includes(" ");
21795
+ const entries = menuOpen ? menuEntries(input.slice(1), session2.commands, busy) : [];
21796
+ const selected = Math.min(menuIndex, Math.max(0, entries.length - 1));
21797
+ const menuOpenRef = useRef2(menuOpen);
21798
+ menuOpenRef.current = menuOpen;
21799
+ const menuEntriesRef = useRef2(entries);
21800
+ menuEntriesRef.current = entries;
21801
+ const selectedRef = useRef2(selected);
21802
+ selectedRef.current = selected;
21420
21803
  useInput4((input_, key2) => {
21804
+ if (menuOpen && entries.length > 0) {
21805
+ if (key2.upArrow) {
21806
+ setMenuIndex((current) => current <= 0 ? entries.length - 1 : current - 1);
21807
+ return;
21808
+ }
21809
+ if (key2.downArrow) {
21810
+ setMenuIndex((current) => current >= entries.length - 1 ? 0 : current + 1);
21811
+ return;
21812
+ }
21813
+ if (key2.tab) {
21814
+ const entry = entries[selected];
21815
+ if (entry)
21816
+ setInput(`${entry.insert} `);
21817
+ setMenuIndex(0);
21818
+ return;
21819
+ }
21820
+ if (key2.escape) {
21821
+ setInput("");
21822
+ setMenuIndex(0);
21823
+ return;
21824
+ }
21825
+ }
21421
21826
  if (key2.escape) {
21422
21827
  setCandidate(undefined);
21423
21828
  if (busy)
@@ -21428,26 +21833,26 @@ ${PLAN_PROMPT}`);
21428
21833
  remember(input_ === "r" ? "project" : "user");
21429
21834
  }
21430
21835
  }, { isActive: inputActive });
21431
- return /* @__PURE__ */ jsxDEV9(Box8, {
21836
+ return /* @__PURE__ */ jsxDEV10(Box9, {
21432
21837
  flexDirection: "column",
21433
21838
  children: [
21434
- /* @__PURE__ */ jsxDEV9(Static, {
21839
+ /* @__PURE__ */ jsxDEV10(Static, {
21435
21840
  items,
21436
- children: (item) => /* @__PURE__ */ jsxDEV9(ScrollRow, {
21841
+ children: (item) => /* @__PURE__ */ jsxDEV10(ScrollRow, {
21437
21842
  item
21438
21843
  }, item.id, false, undefined, this)
21439
21844
  }, undefined, false, undefined, this),
21440
- live !== "" && /* @__PURE__ */ jsxDEV9(Box8, {
21845
+ live !== "" && /* @__PURE__ */ jsxDEV10(Box9, {
21441
21846
  marginTop: 1,
21442
- children: /* @__PURE__ */ jsxDEV9(Markdown, {
21847
+ children: /* @__PURE__ */ jsxDEV10(Markdown, {
21443
21848
  text: live
21444
21849
  }, undefined, false, undefined, this)
21445
21850
  }, undefined, false, undefined, this),
21446
- runningTool && /* @__PURE__ */ jsxDEV9(ToolBlock, {
21851
+ runningTool && /* @__PURE__ */ jsxDEV10(ToolBlock, {
21447
21852
  name: runningTool,
21448
21853
  running: true
21449
21854
  }, undefined, false, undefined, this),
21450
- pending && /* @__PURE__ */ jsxDEV9(PermissionPrompt, {
21855
+ pending && /* @__PURE__ */ jsxDEV10(PermissionPrompt, {
21451
21856
  request: pending.request,
21452
21857
  reason: pending.reason,
21453
21858
  onChoice: (choice) => {
@@ -21455,7 +21860,7 @@ ${PLAN_PROMPT}`);
21455
21860
  pending.resolve(choice);
21456
21861
  }
21457
21862
  }, undefined, false, undefined, this),
21458
- question && /* @__PURE__ */ jsxDEV9(QuestionPrompt, {
21863
+ question && /* @__PURE__ */ jsxDEV10(QuestionPrompt, {
21459
21864
  question: question.question,
21460
21865
  ...question.options ? { options: question.options } : {},
21461
21866
  onAnswer: (answer) => {
@@ -21463,25 +21868,32 @@ ${PLAN_PROMPT}`);
21463
21868
  question.resolve(answer);
21464
21869
  }
21465
21870
  }, undefined, false, undefined, this),
21466
- candidate && inputActive && /* @__PURE__ */ jsxDEV9(MemoryCapture, {
21871
+ candidate && inputActive && /* @__PURE__ */ jsxDEV10(MemoryCapture, {
21467
21872
  candidate
21468
21873
  }, undefined, false, undefined, this),
21469
- inputActive && /* @__PURE__ */ jsxDEV9(Box8, {
21874
+ menuOpen && /* @__PURE__ */ jsxDEV10(CommandMenu, {
21875
+ entries,
21876
+ selected
21877
+ }, undefined, false, undefined, this),
21878
+ inputActive && /* @__PURE__ */ jsxDEV10(Box9, {
21470
21879
  marginTop: 1,
21471
21880
  children: [
21472
- /* @__PURE__ */ jsxDEV9(Text9, {
21881
+ /* @__PURE__ */ jsxDEV10(Text10, {
21473
21882
  color: theme.user,
21474
21883
  children: "> "
21475
21884
  }, undefined, false, undefined, this),
21476
- /* @__PURE__ */ jsxDEV9(TextInput, {
21885
+ /* @__PURE__ */ jsxDEV10(TextInput, {
21477
21886
  value: input,
21478
- onChange: setInput,
21887
+ onChange: (value) => {
21888
+ setInput(value);
21889
+ setMenuIndex(0);
21890
+ },
21479
21891
  onSubmit: submit,
21480
21892
  placeholder: busy ? "steer the agent, or esc to interrupt" : "what should I do?"
21481
21893
  }, undefined, false, undefined, this)
21482
21894
  ]
21483
21895
  }, undefined, true, undefined, this),
21484
- /* @__PURE__ */ jsxDEV9(StatusLine, {
21896
+ /* @__PURE__ */ jsxDEV10(StatusLine, {
21485
21897
  model: model2,
21486
21898
  mode,
21487
21899
  costUsd: cost,
@@ -21496,43 +21908,58 @@ ${PLAN_PROMPT}`);
21496
21908
  }
21497
21909
  function ScrollRow({ item }) {
21498
21910
  if (item.kind === "user") {
21499
- return /* @__PURE__ */ jsxDEV9(Box8, {
21911
+ return /* @__PURE__ */ jsxDEV10(Box9, {
21500
21912
  marginTop: 1,
21501
21913
  children: [
21502
- /* @__PURE__ */ jsxDEV9(Text9, {
21914
+ /* @__PURE__ */ jsxDEV10(Text10, {
21503
21915
  color: theme.user,
21504
21916
  children: "> "
21505
21917
  }, undefined, false, undefined, this),
21506
- /* @__PURE__ */ jsxDEV9(Text9, {
21918
+ /* @__PURE__ */ jsxDEV10(Text10, {
21507
21919
  children: item.text
21508
21920
  }, undefined, false, undefined, this)
21509
21921
  ]
21510
21922
  }, undefined, true, undefined, this);
21511
21923
  }
21512
21924
  if (item.kind === "assistant") {
21513
- return /* @__PURE__ */ jsxDEV9(Box8, {
21925
+ return /* @__PURE__ */ jsxDEV10(Box9, {
21514
21926
  marginTop: 1,
21515
- children: /* @__PURE__ */ jsxDEV9(Markdown, {
21927
+ children: /* @__PURE__ */ jsxDEV10(Markdown, {
21516
21928
  text: item.text
21517
21929
  }, undefined, false, undefined, this)
21518
21930
  }, undefined, false, undefined, this);
21519
21931
  }
21520
21932
  if (item.kind === "tool") {
21521
- return /* @__PURE__ */ jsxDEV9(ToolBlock, {
21933
+ return /* @__PURE__ */ jsxDEV10(ToolBlock, {
21522
21934
  name: item.name,
21523
21935
  ...item.title ? { title: item.title } : {},
21524
21936
  ...item.output ? { output: item.output } : {},
21525
21937
  ...item.isError ? { isError: true } : {}
21526
21938
  }, undefined, false, undefined, this);
21527
21939
  }
21528
- return /* @__PURE__ */ jsxDEV9(Box8, {
21940
+ return /* @__PURE__ */ jsxDEV10(Box9, {
21529
21941
  marginTop: 1,
21530
- children: /* @__PURE__ */ jsxDEV9(Text9, {
21942
+ children: /* @__PURE__ */ jsxDEV10(Text10, {
21531
21943
  color: item.color ?? theme.muted,
21532
21944
  children: item.text
21533
21945
  }, undefined, false, undefined, this)
21534
21946
  }, undefined, false, undefined, this);
21535
21947
  }
21948
+ function describeCommands(session2) {
21949
+ const lines = commandRows().map((row) => ` ${row.command.padEnd(34)} ${row.summary}`);
21950
+ if (session2.commands.length > 0) {
21951
+ lines.push("", " commands from this directory:");
21952
+ for (const command of session2.commands) {
21953
+ lines.push(` ${`/${command.name}`.padEnd(34)} ${command.description}`);
21954
+ }
21955
+ }
21956
+ lines.push("", " type / at the prompt to filter this list and pick one");
21957
+ return lines.join(`
21958
+ `);
21959
+ }
21960
+ var INIT_PROMPT = `Write an AGENTS.md at the root of this project for a coding agent that has never seen it.
21961
+
21962
+ 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.`;
21536
21963
  function describeExtensions(session2) {
21537
21964
  const lines = [];
21538
21965
  if (session2.skills.length > 0) {
@@ -21552,14 +21979,347 @@ function describeExtensions(session2) {
21552
21979
  return lines.length === 0 ? "no skills or commands found in .earshot/skills, .earshot/commands or your config directory" : lines.join(`
21553
21980
  `);
21554
21981
  }
21982
+ // packages/tui/src/onboarding.tsx
21983
+ import { Box as Box10, Text as Text11, useApp as useApp2, useInput as useInput5 } from "ink";
21984
+ import { useCallback as useCallback2, useRef as useRef3, useState as useState4 } from "react";
21985
+ import { jsxDEV as jsxDEV11 } from "react/jsx-dev-runtime";
21986
+ function Onboarding({ onDone, ...rest }) {
21987
+ const { exit } = useApp2();
21988
+ const options = useRef3(rest);
21989
+ options.current = rest;
21990
+ const [screen, setScreen] = useState4({ name: "choose" });
21991
+ const [cursor, setCursor] = useState4(0);
21992
+ const [input, setInput] = useState4("");
21993
+ const [error, setError] = useState4();
21994
+ const secret = useRef3("");
21995
+ const providers = order(rest.providers, rest.wanted);
21996
+ const finish = useCallback2((result) => {
21997
+ onDone(result);
21998
+ exit();
21999
+ }, [exit, onDone]);
22000
+ const verify2 = useCallback2(async (provider) => {
22001
+ setScreen({ name: "probing", provider });
22002
+ const result = await options.current.probe(provider.id);
22003
+ if (result.ok) {
22004
+ setScreen({ name: "ready", provider });
22005
+ return;
22006
+ }
22007
+ if (result.reason === "rejected")
22008
+ await options.current.forgetKey(provider.id).catch(() => {});
22009
+ setScreen({ name: "failed", provider, result });
22010
+ }, []);
22011
+ const submitKey = useCallback2(async () => {
22012
+ const provider = screen.name === "key" ? screen.provider : undefined;
22013
+ const key2 = secret.current;
22014
+ secret.current = "";
22015
+ setInput("");
22016
+ if (!provider)
22017
+ return;
22018
+ if (key2.trim() === "") {
22019
+ setError("a key is needed, or press esc to go back");
22020
+ return;
22021
+ }
22022
+ setError(undefined);
22023
+ await options.current.storeKey(provider.id, key2.trim());
22024
+ await verify2(provider);
22025
+ }, [screen, verify2]);
22026
+ const startSignIn = useCallback2(async (provider) => {
22027
+ setScreen({ name: "oauth", provider });
22028
+ try {
22029
+ await options.current.signIn(provider.id, (url) => setScreen({ name: "oauth", provider, url }));
22030
+ } catch (failure) {
22031
+ setScreen({
22032
+ name: "failed",
22033
+ provider,
22034
+ result: { ok: false, reason: "other", message: failure.message }
22035
+ });
22036
+ return;
22037
+ }
22038
+ await verify2(provider);
22039
+ }, [verify2]);
22040
+ const choose = useCallback2((provider) => {
22041
+ setError(undefined);
22042
+ if (provider.kind === "oauth")
22043
+ startSignIn(provider);
22044
+ else
22045
+ setScreen({ name: "key", provider });
22046
+ }, [startSignIn]);
22047
+ useInput5((key2, meta) => {
22048
+ if (meta.ctrl && key2 === "c") {
22049
+ finish({ outcome: "quit" });
22050
+ return;
22051
+ }
22052
+ if (screen.name === "choose") {
22053
+ if (key2 === "q") {
22054
+ finish({ outcome: "quit" });
22055
+ return;
22056
+ }
22057
+ if (meta.upArrow)
22058
+ setCursor((c) => c <= 0 ? providers.length - 1 : c - 1);
22059
+ if (meta.downArrow)
22060
+ setCursor((c) => c >= providers.length - 1 ? 0 : c + 1);
22061
+ if (meta.return) {
22062
+ const provider = providers[cursor];
22063
+ if (provider)
22064
+ choose(provider);
22065
+ }
22066
+ return;
22067
+ }
22068
+ if (screen.name === "key" && !meta.escape) {
22069
+ if (meta.return) {
22070
+ submitKey();
22071
+ return;
22072
+ }
22073
+ if (meta.backspace || meta.delete) {
22074
+ secret.current = secret.current.slice(0, -1);
22075
+ setInput(secret.current);
22076
+ return;
22077
+ }
22078
+ if (meta.ctrl || meta.meta || meta.tab)
22079
+ return;
22080
+ if (key2) {
22081
+ secret.current += key2;
22082
+ setInput(secret.current);
22083
+ }
22084
+ return;
22085
+ }
22086
+ if (meta.escape) {
22087
+ secret.current = "";
22088
+ setInput("");
22089
+ setError(undefined);
22090
+ setScreen({ name: "choose" });
22091
+ return;
22092
+ }
22093
+ if (screen.name === "failed" && key2 === "k" && screen.result.reason === "unreachable") {
22094
+ setScreen({ name: "ready", provider: screen.provider });
22095
+ }
22096
+ });
22097
+ if (screen.name === "choose") {
22098
+ return /* @__PURE__ */ jsxDEV11(Box10, {
22099
+ flexDirection: "column",
22100
+ children: [
22101
+ /* @__PURE__ */ jsxDEV11(Header, {}, undefined, false, undefined, this),
22102
+ providers.map((provider, index) => /* @__PURE__ */ jsxDEV11(Box10, {
22103
+ children: [
22104
+ /* @__PURE__ */ jsxDEV11(Text11, {
22105
+ color: index === cursor ? theme.user : theme.muted,
22106
+ children: index === cursor ? "› " : " "
22107
+ }, undefined, false, undefined, this),
22108
+ /* @__PURE__ */ jsxDEV11(Text11, {
22109
+ ...index === cursor ? { color: theme.user } : {},
22110
+ children: provider.id.padEnd(14)
22111
+ }, undefined, false, undefined, this),
22112
+ /* @__PURE__ */ jsxDEV11(Text11, {
22113
+ color: theme.muted,
22114
+ children: hint(provider)
22115
+ }, undefined, false, undefined, this)
22116
+ ]
22117
+ }, provider.id, true, undefined, this)),
22118
+ /* @__PURE__ */ jsxDEV11(Box10, {
22119
+ marginTop: 1,
22120
+ children: /* @__PURE__ */ jsxDEV11(Text11, {
22121
+ color: theme.muted,
22122
+ children: "↑↓ choose · enter select · q quit"
22123
+ }, undefined, false, undefined, this)
22124
+ }, undefined, false, undefined, this),
22125
+ error ? /* @__PURE__ */ jsxDEV11(Text11, {
22126
+ color: theme.warning,
22127
+ children: error
22128
+ }, undefined, false, undefined, this) : null
22129
+ ]
22130
+ }, undefined, true, undefined, this);
22131
+ }
22132
+ if (screen.name === "key") {
22133
+ return /* @__PURE__ */ jsxDEV11(Box10, {
22134
+ flexDirection: "column",
22135
+ children: [
22136
+ /* @__PURE__ */ jsxDEV11(Header, {}, undefined, false, undefined, this),
22137
+ /* @__PURE__ */ jsxDEV11(Text11, {
22138
+ children: [
22139
+ "paste an api key for ",
22140
+ screen.provider.id
22141
+ ]
22142
+ }, undefined, true, undefined, this),
22143
+ /* @__PURE__ */ jsxDEV11(Box10, {
22144
+ marginTop: 1,
22145
+ children: /* @__PURE__ */ jsxDEV11(Text11, {
22146
+ color: theme.muted,
22147
+ children: [
22148
+ screen.provider.envVars?.length ? `or set ${screen.provider.envVars.join(" or ")} instead and restart.
22149
+ ` : "",
22150
+ "stored in your config directory, readable only by you, and never printed."
22151
+ ]
22152
+ }, undefined, true, undefined, this)
22153
+ }, undefined, false, undefined, this),
22154
+ /* @__PURE__ */ jsxDEV11(Box10, {
22155
+ marginTop: 1,
22156
+ children: [
22157
+ /* @__PURE__ */ jsxDEV11(Text11, {
22158
+ color: theme.user,
22159
+ children: "> "
22160
+ }, undefined, false, undefined, this),
22161
+ /* @__PURE__ */ jsxDEV11(Text11, {
22162
+ children: "•".repeat(input.length)
22163
+ }, undefined, false, undefined, this),
22164
+ /* @__PURE__ */ jsxDEV11(Text11, {
22165
+ inverse: true,
22166
+ children: " "
22167
+ }, undefined, false, undefined, this)
22168
+ ]
22169
+ }, undefined, true, undefined, this),
22170
+ error ? /* @__PURE__ */ jsxDEV11(Text11, {
22171
+ color: theme.warning,
22172
+ children: error
22173
+ }, undefined, false, undefined, this) : null,
22174
+ /* @__PURE__ */ jsxDEV11(Box10, {
22175
+ marginTop: 1,
22176
+ children: /* @__PURE__ */ jsxDEV11(Text11, {
22177
+ color: theme.muted,
22178
+ children: "enter continue · esc back"
22179
+ }, undefined, false, undefined, this)
22180
+ }, undefined, false, undefined, this)
22181
+ ]
22182
+ }, undefined, true, undefined, this);
22183
+ }
22184
+ if (screen.name === "oauth") {
22185
+ return /* @__PURE__ */ jsxDEV11(Box10, {
22186
+ flexDirection: "column",
22187
+ children: [
22188
+ /* @__PURE__ */ jsxDEV11(Header, {}, undefined, false, undefined, this),
22189
+ /* @__PURE__ */ jsxDEV11(Text11, {
22190
+ children: [
22191
+ "signing in to ",
22192
+ screen.provider.id,
22193
+ " in your browser…"
22194
+ ]
22195
+ }, undefined, true, undefined, this),
22196
+ screen.url ? /* @__PURE__ */ jsxDEV11(Box10, {
22197
+ marginTop: 1,
22198
+ flexDirection: "column",
22199
+ children: [
22200
+ /* @__PURE__ */ jsxDEV11(Text11, {
22201
+ color: theme.muted,
22202
+ children: "if it did not open, use this link:"
22203
+ }, undefined, false, undefined, this),
22204
+ /* @__PURE__ */ jsxDEV11(Text11, {
22205
+ children: screen.url
22206
+ }, undefined, false, undefined, this)
22207
+ ]
22208
+ }, undefined, true, undefined, this) : null,
22209
+ /* @__PURE__ */ jsxDEV11(Box10, {
22210
+ marginTop: 1,
22211
+ children: /* @__PURE__ */ jsxDEV11(Text11, {
22212
+ color: theme.muted,
22213
+ children: "esc cancel"
22214
+ }, undefined, false, undefined, this)
22215
+ }, undefined, false, undefined, this)
22216
+ ]
22217
+ }, undefined, true, undefined, this);
22218
+ }
22219
+ if (screen.name === "probing") {
22220
+ return /* @__PURE__ */ jsxDEV11(Box10, {
22221
+ flexDirection: "column",
22222
+ children: [
22223
+ /* @__PURE__ */ jsxDEV11(Header, {}, undefined, false, undefined, this),
22224
+ /* @__PURE__ */ jsxDEV11(Text11, {
22225
+ color: theme.muted,
22226
+ children: [
22227
+ "checking the credentials with ",
22228
+ screen.provider.id,
22229
+ "…"
22230
+ ]
22231
+ }, undefined, true, undefined, this)
22232
+ ]
22233
+ }, undefined, true, undefined, this);
22234
+ }
22235
+ if (screen.name === "failed") {
22236
+ return /* @__PURE__ */ jsxDEV11(Box10, {
22237
+ flexDirection: "column",
22238
+ children: [
22239
+ /* @__PURE__ */ jsxDEV11(Header, {}, undefined, false, undefined, this),
22240
+ /* @__PURE__ */ jsxDEV11(Text11, {
22241
+ color: theme.warning,
22242
+ 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:`
22243
+ }, undefined, false, undefined, this),
22244
+ /* @__PURE__ */ jsxDEV11(Text11, {
22245
+ color: theme.muted,
22246
+ children: screen.result.message
22247
+ }, undefined, false, undefined, this),
22248
+ /* @__PURE__ */ jsxDEV11(Box10, {
22249
+ marginTop: 1,
22250
+ children: /* @__PURE__ */ jsxDEV11(Text11, {
22251
+ color: theme.muted,
22252
+ children: screen.result.reason === "unreachable" ? "k keep it anyway and carry on · esc start over · ctrl-c quit" : "esc start over · ctrl-c quit"
22253
+ }, undefined, false, undefined, this)
22254
+ }, undefined, false, undefined, this)
22255
+ ]
22256
+ }, undefined, true, undefined, this);
22257
+ }
22258
+ return /* @__PURE__ */ jsxDEV11(Box10, {
22259
+ flexDirection: "column",
22260
+ children: [
22261
+ /* @__PURE__ */ jsxDEV11(Text11, {
22262
+ color: theme.user,
22263
+ children: [
22264
+ "ready: ",
22265
+ screen.provider.id
22266
+ ]
22267
+ }, undefined, true, undefined, this),
22268
+ /* @__PURE__ */ jsxDEV11(Box10, {
22269
+ marginTop: 1,
22270
+ children: /* @__PURE__ */ jsxDEV11(Text11, {
22271
+ color: theme.muted,
22272
+ children: "what should I do? (enter to start with nothing)"
22273
+ }, undefined, false, undefined, this)
22274
+ }, undefined, false, undefined, this),
22275
+ /* @__PURE__ */ jsxDEV11(Box10, {
22276
+ children: [
22277
+ /* @__PURE__ */ jsxDEV11(Text11, {
22278
+ color: theme.user,
22279
+ children: "> "
22280
+ }, undefined, false, undefined, this),
22281
+ /* @__PURE__ */ jsxDEV11(TextInput, {
22282
+ value: input,
22283
+ onChange: setInput,
22284
+ onSubmit: (text2) => finish({ outcome: "ready", providerId: screen.provider.id, firstPrompt: text2.trim() })
22285
+ }, undefined, false, undefined, this)
22286
+ ]
22287
+ }, undefined, true, undefined, this)
22288
+ ]
22289
+ }, undefined, true, undefined, this);
22290
+ }
22291
+ function Header() {
22292
+ return /* @__PURE__ */ jsxDEV11(Box10, {
22293
+ flexDirection: "column",
22294
+ marginBottom: 1,
22295
+ children: /* @__PURE__ */ jsxDEV11(Text11, {
22296
+ children: "earshot needs a model provider before it can do anything."
22297
+ }, undefined, false, undefined, this)
22298
+ }, undefined, false, undefined, this);
22299
+ }
22300
+ function hint(provider) {
22301
+ if (provider.configured)
22302
+ return provider.configured;
22303
+ if (provider.kind === "oauth")
22304
+ return "sign in with a browser - no key to paste";
22305
+ return provider.envVars?.length ? provider.envVars.join(" or ") : "api key";
22306
+ }
22307
+ function order(providers, wanted) {
22308
+ if (!wanted)
22309
+ return providers;
22310
+ return [
22311
+ ...providers.filter((provider) => provider.id === wanted),
22312
+ ...providers.filter((provider) => provider.id !== wanted)
22313
+ ];
22314
+ }
21555
22315
  // packages/tui/src/run.tsx
21556
22316
  import { platform as platform6 } from "node:os";
21557
22317
  import { render } from "ink";
21558
- import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime";
22318
+ import { jsxDEV as jsxDEV12 } from "react/jsx-dev-runtime";
21559
22319
  var WINDOWS_MAX_FPS = 30;
21560
22320
  async function runTui(options) {
21561
22321
  const isWindows = platform6() === "win32";
21562
- const instance = render(/* @__PURE__ */ jsxDEV10(App, {
22322
+ const instance = render(/* @__PURE__ */ jsxDEV12(App, {
21563
22323
  session: options.session,
21564
22324
  model: options.model,
21565
22325
  ...options.initialPrompt ? { initialPrompt: options.initialPrompt } : {}
@@ -21575,6 +22335,105 @@ async function runTui(options) {
21575
22335
  }
21576
22336
  return 0;
21577
22337
  }
22338
+ async function runOnboarding(options) {
22339
+ const isWindows = platform6() === "win32";
22340
+ let result = { outcome: "quit" };
22341
+ const instance = render(/* @__PURE__ */ jsxDEV12(Onboarding, {
22342
+ ...options,
22343
+ onDone: (decided) => {
22344
+ result = decided;
22345
+ }
22346
+ }, undefined, false, undefined, this), {
22347
+ exitOnCtrlC: false,
22348
+ patchConsole: true,
22349
+ ...isWindows ? { maxFps: WINDOWS_MAX_FPS } : {}
22350
+ });
22351
+ await instance.waitUntilExit();
22352
+ return result;
22353
+ }
22354
+ // packages/cli/src/onboard.ts
22355
+ async function buildOnboardingOptions(wanted) {
22356
+ const registry2 = buildRegistry();
22357
+ const store3 = new AuthStore;
22358
+ const providers = [];
22359
+ for (const provider of registry2.list()) {
22360
+ if (provider.auth.kind === "none")
22361
+ continue;
22362
+ providers.push(await describe3(provider, store3));
22363
+ }
22364
+ providers.sort((a, b) => Number(b.kind === "oauth") - Number(a.kind === "oauth"));
22365
+ return {
22366
+ providers,
22367
+ ...wanted ? { wanted } : {},
22368
+ storeKey: (providerId, key2) => store3.set(providerId, { type: "api-key", apiKey: key2 }),
22369
+ forgetKey: (providerId) => store3.remove(providerId),
22370
+ signIn: async (providerId, onUrl) => {
22371
+ const credentials = await loginToOpenRouter({ onUrl });
22372
+ await store3.set(providerId, credentials);
22373
+ },
22374
+ probe: (providerId) => probe(registry2, providerId)
22375
+ };
22376
+ }
22377
+ async function describe3(provider, store3) {
22378
+ const configured = await resolveCredentials(provider, { store: store3 }).catch(() => {
22379
+ return;
22380
+ });
22381
+ return {
22382
+ id: provider.id,
22383
+ kind: provider.auth.kind === "oauth" ? "oauth" : "api-key",
22384
+ ...envVarsOf2(provider.auth).length ? { envVars: envVarsOf2(provider.auth) } : {},
22385
+ ...configured ? { configured: describeConfigured(configured.type) } : {}
22386
+ };
22387
+ }
22388
+ function envVarsOf2(auth2) {
22389
+ return auth2.kind === "api-key" || auth2.kind === "oauth" ? auth2.envVars ?? [] : [];
22390
+ }
22391
+ function describeConfigured(kind) {
22392
+ return kind === "ambient" ? "ambient credentials" : kind === "oauth" ? "signed in" : "api key set";
22393
+ }
22394
+ async function probe(registry2, providerId) {
22395
+ const listed = registry2.list().find((provider) => provider.id === providerId);
22396
+ if (!listed)
22397
+ return { ok: false, reason: "other", message: `unknown provider "${providerId}"` };
22398
+ const model2 = listed.models()[0];
22399
+ if (!model2) {
22400
+ return {
22401
+ ok: false,
22402
+ reason: "other",
22403
+ message: `${providerId} publishes no models to check against`
22404
+ };
22405
+ }
22406
+ let resolved;
22407
+ try {
22408
+ resolved = await resolveModel(registry2, `${providerId}/${model2.id}`);
22409
+ } catch (error) {
22410
+ return { ok: false, reason: "other", message: error.message };
22411
+ }
22412
+ const controller = new AbortController;
22413
+ const timeout = setTimeout(() => controller.abort(), 1e4);
22414
+ try {
22415
+ for await (const event of streamModel(registry2, resolved, {
22416
+ system: "Reply with one word.",
22417
+ messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
22418
+ maxOutputTokens: 1,
22419
+ abortSignal: controller.signal
22420
+ })) {
22421
+ controller.abort();
22422
+ if (event.type === "error") {
22423
+ 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 };
22424
+ }
22425
+ return { ok: true };
22426
+ }
22427
+ return { ok: true };
22428
+ } catch (error) {
22429
+ const message = error.message;
22430
+ const timedOut = controller.signal.aborted && error.name === "AbortError";
22431
+ return timedOut ? { ok: false, reason: "unreachable", message: "timed out waiting for a response" } : { ok: false, reason: "other", message };
22432
+ } finally {
22433
+ clearTimeout(timeout);
22434
+ }
22435
+ }
22436
+
21578
22437
  // packages/cli/src/commands/interactive.ts
21579
22438
  var DEFAULT_MODEL3 = "anthropic/claude-opus-5";
21580
22439
  async function interactiveCommand(args) {
@@ -21617,46 +22476,63 @@ async function interactiveCommand(args) {
21617
22476
  return 2;
21618
22477
  }
21619
22478
  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}
22479
+ const model2 = typeof flags.model === "string" ? flags.model : DEFAULT_MODEL3;
22480
+ const onboardingAllowed = flags["no-onboarding"] !== true;
22481
+ let firstPrompt = initialPrompt;
22482
+ let attempted = false;
22483
+ for (;; ) {
22484
+ try {
22485
+ const session2 = await createSession({
22486
+ cwd: process.cwd(),
22487
+ extraTools: extensions.tools,
22488
+ problems: extensions.problems,
22489
+ onDispose: () => extensions.close(),
22490
+ model: model2,
22491
+ ...mode ? { mode } : {},
22492
+ ...typeof flags["api-key"] === "string" ? { apiKey: flags["api-key"] } : {},
22493
+ ...curiosity ? { curiosity } : {},
22494
+ ...maxCostUsd !== undefined ? { maxCostUsd } : {},
22495
+ ...resumeFrom2(flags)
22496
+ });
22497
+ return await runTui({
22498
+ session: session2,
22499
+ model: model2,
22500
+ ...firstPrompt !== "" || image ? {
22501
+ initialPrompt: image ? [...firstPrompt ? [{ type: "text", text: firstPrompt }] : [], image] : firstPrompt
22502
+ } : {}
22503
+ });
22504
+ } catch (error) {
22505
+ if (error instanceof MissingCredentialsError && onboardingAllowed && !attempted) {
22506
+ attempted = true;
22507
+ const result = await runOnboarding(await buildOnboardingOptions(error.provider.id));
22508
+ if (result.outcome === "quit") {
22509
+ process.stdout.write("nothing was stored. run `earshot auth login <provider>` when you are ready.\n");
22510
+ await extensions.close();
22511
+ return 0;
22512
+ }
22513
+ firstPrompt = result.firstPrompt ?? firstPrompt;
22514
+ continue;
22515
+ }
22516
+ await extensions.close();
22517
+ if (error instanceof UnknownModelError) {
22518
+ process.stderr.write(`${error.message}
21644
22519
 
21645
22520
  run \`earshot models\` to see what is available
21646
22521
  `);
21647
- return 2;
21648
- }
21649
- if (error instanceof MissingCredentialsError) {
21650
- process.stderr.write(`${error.message}
22522
+ return 2;
22523
+ }
22524
+ if (error instanceof MissingCredentialsError) {
22525
+ process.stderr.write(`${error.message}
21651
22526
  `);
21652
- return 3;
21653
- }
21654
- if (error instanceof NoSessionToResumeError) {
21655
- process.stderr.write(`${error.message}
22527
+ return 3;
22528
+ }
22529
+ if (error instanceof NoSessionToResumeError) {
22530
+ process.stderr.write(`${error.message}
21656
22531
  `);
21657
- return 2;
22532
+ return 2;
22533
+ }
22534
+ throw error;
21658
22535
  }
21659
- throw error;
21660
22536
  }
21661
22537
  }
21662
22538
  function resumeFrom2(flags) {
@@ -21843,5 +22719,5 @@ async function main(argv = process.argv.slice(2)) {
21843
22719
  var code = await main();
21844
22720
  process.exitCode = code;
21845
22721
 
21846
- //# debugId=A0D0F9FB395CDA7464756E2164756E21
22722
+ //# debugId=B062C0361E20E53764756E2164756E21
21847
22723
  //# sourceMappingURL=main.js.map