@raegent/earshot 0.3.1 → 0.4.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 +1949 -1210
  2. package/dist/main.js.map +26 -21
  3. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -14446,6 +14446,11 @@ function openBrowser(url) {
14446
14446
  child.unref();
14447
14447
  } catch {}
14448
14448
  }
14449
+ // packages/providers/src/types.ts
14450
+ var REASONING_EFFORTS = ["none", "low", "medium", "high", "xhigh"];
14451
+ function isReasoningEffort(value) {
14452
+ return typeof value === "string" && REASONING_EFFORTS.includes(value);
14453
+ }
14449
14454
  // packages/core/src/context/agents-md.ts
14450
14455
  var MEMORY_FILENAMES = ["AGENTS.md", "CLAUDE.md"];
14451
14456
  async function readIfPresent(path) {
@@ -14902,6 +14907,8 @@ Actions that change files, run commands or reach the network ` + "prompt the use
14902
14907
  }
14903
14908
  }
14904
14909
  // packages/core/src/model.ts
14910
+ var DEFAULT_MODEL = "anthropic/claude-opus-5";
14911
+
14905
14912
  class MissingCredentialsError extends Error {
14906
14913
  provider;
14907
14914
  constructor(provider) {
@@ -15174,6 +15181,8 @@ async function loadSettings(cwd) {
15174
15181
  let defaultMode;
15175
15182
  let curiosity;
15176
15183
  let maxCostUsd;
15184
+ let defaultModel;
15185
+ const reasoningEfforts = {};
15177
15186
  for (const scope of scopes) {
15178
15187
  const path = settingsPath(scope, cwd);
15179
15188
  const file = await readSettings(path).catch((error) => {
@@ -15182,6 +15191,15 @@ async function loadSettings(cwd) {
15182
15191
  });
15183
15192
  if (!file)
15184
15193
  continue;
15194
+ if (typeof file.defaultModel === "string" && file.defaultModel.trim() !== "") {
15195
+ defaultModel = file.defaultModel.trim();
15196
+ }
15197
+ for (const [model, effort] of Object.entries(file.reasoningEfforts ?? {})) {
15198
+ if (isReasoningEffort(effort))
15199
+ reasoningEfforts[model] = effort;
15200
+ else
15201
+ problems.push(`${path}: reasoning effort for "${model}" is invalid`);
15202
+ }
15185
15203
  if (file.curiosity !== undefined) {
15186
15204
  if (isCuriosity(file.curiosity))
15187
15205
  curiosity = file.curiosity;
@@ -15226,9 +15244,36 @@ async function loadSettings(cwd) {
15226
15244
  ...defaultMode ? { defaultMode } : {},
15227
15245
  ...curiosity ? { curiosity } : {},
15228
15246
  ...maxCostUsd !== undefined ? { maxCostUsd } : {},
15247
+ ...defaultModel ? { defaultModel } : {},
15248
+ reasoningEfforts,
15229
15249
  problems
15230
15250
  };
15231
15251
  }
15252
+ async function persistDefaultModel(model, scope, cwd) {
15253
+ const path = settingsPath(scope, cwd);
15254
+ const existing = await readSettings(path).catch(() => {
15255
+ return;
15256
+ }) ?? {};
15257
+ await mkdir3(dirname3(path), { recursive: true });
15258
+ await writeFile3(path, `${JSON.stringify({ ...existing, defaultModel: model }, null, 2)}
15259
+ `, "utf8");
15260
+ return path;
15261
+ }
15262
+ async function persistReasoningEffort(model, effort, scope, cwd) {
15263
+ const path = settingsPath(scope, cwd);
15264
+ const existing = await readSettings(path).catch(() => {
15265
+ return;
15266
+ }) ?? {};
15267
+ const reasoningEfforts = { ...existing.reasoningEfforts ?? {} };
15268
+ if (effort === undefined)
15269
+ delete reasoningEfforts[model];
15270
+ else
15271
+ reasoningEfforts[model] = effort;
15272
+ await mkdir3(dirname3(path), { recursive: true });
15273
+ await writeFile3(path, `${JSON.stringify({ ...existing, reasoningEfforts }, null, 2)}
15274
+ `, "utf8");
15275
+ return path;
15276
+ }
15232
15277
  async function persistRule(rule, scope, cwd) {
15233
15278
  const path = settingsPath(scope, cwd);
15234
15279
  const existing = await readSettings(path).catch(() => {
@@ -17095,6 +17140,7 @@ class Agent {
17095
17140
  queued = [];
17096
17141
  rules;
17097
17142
  resolved;
17143
+ effort;
17098
17144
  mode;
17099
17145
  totalCostUsd = 0;
17100
17146
  maxCostUsd;
@@ -17131,10 +17177,17 @@ class Agent {
17131
17177
  this.systemPrompt = options.system;
17132
17178
  this.maxCostUsd = options.maxCostUsd;
17133
17179
  this.resolved = options.model;
17180
+ this.effort = options.reasoningEffort;
17134
17181
  }
17135
17182
  get model() {
17136
17183
  return this.resolved;
17137
17184
  }
17185
+ get reasoningEffort() {
17186
+ return this.effort;
17187
+ }
17188
+ setReasoningEffort(effort) {
17189
+ this.effort = effort;
17190
+ }
17138
17191
  async changeModel(ref) {
17139
17192
  const resolved = await resolveModel(this.options.registry, ref);
17140
17193
  this.resolved = resolved;
@@ -17295,6 +17348,7 @@ ${context.join(`
17295
17348
  system: this.effectiveSystem,
17296
17349
  messages,
17297
17350
  tools: this.offeredTools(),
17351
+ ...this.effort ? { reasoningEffort: this.effort } : {},
17298
17352
  abortSignal: signal
17299
17353
  })) {
17300
17354
  switch (event.type) {
@@ -17650,6 +17704,7 @@ If something is failing, fix it or say what is ` + `failing and why - do not des
17650
17704
  for await (const event of streamModel(this.options.registry, this.resolved, {
17651
17705
  system: SUMMARY_PROMPT,
17652
17706
  messages: [...messages, { role: "user", content: [{ type: "text", text: SUMMARY_PROMPT }] }],
17707
+ ...this.effort ? { reasoningEffort: this.effort } : {},
17653
17708
  abortSignal: signal
17654
17709
  })) {
17655
17710
  if (event.type === "text_delta")
@@ -18234,17 +18289,40 @@ async function listSessions(cwd) {
18234
18289
  const meta = entries.find((entry) => entry.type === "meta");
18235
18290
  if (!info || meta?.type !== "meta")
18236
18291
  continue;
18292
+ const configuration = latestConfiguration(entries);
18237
18293
  infos.push({
18238
18294
  id: name.replace(/\.jsonl$/, ""),
18239
18295
  path,
18240
18296
  cwd: meta.cwd,
18241
- model: meta.model,
18297
+ model: configuration.model ?? meta.model,
18298
+ ...configuration.reasoningEffort ? { reasoningEffort: configuration.reasoningEffort } : {},
18242
18299
  updatedAt: info.mtimeMs,
18243
18300
  preview: firstUserText(entries)
18244
18301
  });
18245
18302
  }
18246
18303
  return infos.sort((a, b) => b.updatedAt - a.updatedAt || b.id.localeCompare(a.id));
18247
18304
  }
18305
+ function latestConfiguration(entries) {
18306
+ const meta = entries.find((entry) => entry.type === "meta");
18307
+ let model = meta?.type === "meta" ? meta.model : undefined;
18308
+ let reasoningEffort;
18309
+ let reasoningConfigured = false;
18310
+ for (const entry of branchTo([...entries])) {
18311
+ if (entry.type !== "configuration")
18312
+ continue;
18313
+ if (entry.model !== undefined)
18314
+ model = entry.model;
18315
+ if (entry.reasoningEffort !== undefined) {
18316
+ reasoningConfigured = true;
18317
+ reasoningEffort = entry.reasoningEffort === null ? undefined : entry.reasoningEffort;
18318
+ }
18319
+ }
18320
+ return {
18321
+ ...model ? { model } : {},
18322
+ ...reasoningEffort ? { reasoningEffort } : {},
18323
+ reasoningConfigured
18324
+ };
18325
+ }
18248
18326
  async function latestSession(cwd) {
18249
18327
  return (await listSessions(cwd))[0];
18250
18328
  }
@@ -18381,7 +18459,7 @@ class ShadowGit {
18381
18459
  }
18382
18460
 
18383
18461
  // packages/core/src/version.ts
18384
- var VERSION = "0.3.1";
18462
+ var VERSION = "0.4.0";
18385
18463
 
18386
18464
  // packages/core/src/session/repair.ts
18387
18465
  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.";
@@ -18419,21 +18497,26 @@ class NoSessionToResumeError extends Error {
18419
18497
  async function createSession(options) {
18420
18498
  const registry2 = options.registry ?? buildRegistry();
18421
18499
  const settings2 = await loadSettings(options.cwd);
18500
+ const resumePath = options.ephemeral ? undefined : await resolveResumePath(options);
18501
+ const resumeEntries = resumePath ? await readEntries(resumePath) : [];
18502
+ const resumedConfiguration = latestConfiguration(resumeEntries);
18503
+ const requestedModel = options.model ?? resumedConfiguration.model ?? settings2.defaultModel ?? DEFAULT_MODEL;
18504
+ const configuredEffort = options.reasoningEffort !== undefined ? options.reasoningEffort : resumedConfiguration.reasoningConfigured ? resumedConfiguration.reasoningEffort : settings2.reasoningEfforts[requestedModel];
18422
18505
  const mode = options.mode ?? settings2.defaultMode ?? "ask";
18423
18506
  const curiosity = options.curiosity ?? settings2.curiosity ?? "normal";
18424
18507
  const maxCostUsd = options.maxCostUsd !== undefined ? options.maxCostUsd > 0 ? options.maxCostUsd : undefined : settings2.maxCostUsd;
18425
- const resolved = await resolveModel(registry2, options.model, {
18508
+ const resolved = await resolveModel(registry2, requestedModel, {
18426
18509
  ...options.apiKey ? { apiKey: options.apiKey } : {}
18427
18510
  });
18428
18511
  const modelRef = `${resolved.provider.id}/${resolved.model.id}`;
18512
+ const reasoningEffort = resolved.model.capabilities.reasoning ? configuredEffort ?? undefined : undefined;
18429
18513
  const discovered = options.noExtensions ? { skills: [], commands: [], problems: [] } : await discoverExtensions(options.cwd);
18430
18514
  let store2;
18431
18515
  let replayed = [];
18432
18516
  const repairProblems = [];
18433
18517
  if (!options.ephemeral) {
18434
- const resumePath = await resolveResumePath(options);
18435
18518
  if (resumePath) {
18436
- const entries = await readEntries(resumePath);
18519
+ const entries = resumeEntries;
18437
18520
  replayed = messagesOf(branchTo(entries));
18438
18521
  store2 = await SessionStore.open(resumePath).catch(() => {
18439
18522
  return;
@@ -18485,6 +18568,7 @@ ${started.context.join(`
18485
18568
  const agentOptions = {
18486
18569
  registry: registry2,
18487
18570
  model: resolved,
18571
+ ...reasoningEffort ? { reasoningEffort } : {},
18488
18572
  cwd: options.cwd,
18489
18573
  system,
18490
18574
  mode,
@@ -18551,7 +18635,7 @@ ${started.context.join(`
18551
18635
  const from = entryId ?? store2.tailId;
18552
18636
  const entries = await readEntries(store2.path);
18553
18637
  const forked = await SessionStore.create(options.cwd, {
18554
- model: modelRef,
18638
+ model: `${agent.model.provider.id}/${agent.model.model.id}`,
18555
18639
  version: VERSION,
18556
18640
  ...from ? { forkedFrom: { sessionId: store2.id, entryId: from } } : {}
18557
18641
  }).catch(() => {
@@ -18562,10 +18646,18 @@ ${started.context.join(`
18562
18646
  const kept = messagesOf(branchTo(entries, from ?? undefined));
18563
18647
  for (const message of kept)
18564
18648
  await forked.appendMessage(message);
18649
+ await forked.append({
18650
+ type: "configuration",
18651
+ model: `${agent.model.provider.id}/${agent.model.model.id}`,
18652
+ reasoningEffort: agent.reasoningEffort ?? null
18653
+ });
18565
18654
  agent.replaceHistory(kept);
18566
18655
  store2 = forked;
18567
18656
  return forked.id;
18568
18657
  },
18658
+ async recordConfiguration(configuration) {
18659
+ await store2?.append({ type: "configuration", ...configuration });
18660
+ },
18569
18661
  async undo() {
18570
18662
  if (!shadow)
18571
18663
  return;
@@ -18650,7 +18742,8 @@ function parseArgs(argv) {
18650
18742
  "models",
18651
18743
  "acp",
18652
18744
  "doctor",
18653
- "update"
18745
+ "update",
18746
+ "sessions"
18654
18747
  ]);
18655
18748
  const command = positionals[0] !== undefined && known.has(positionals[0]) ? positionals[0] : undefined;
18656
18749
  return { command, flags, positionals: command ? positionals.slice(1) : positionals };
@@ -19578,7 +19671,6 @@ async function trustedExtensions(cwd) {
19578
19671
  }
19579
19672
 
19580
19673
  // packages/cli/src/commands/acp.ts
19581
- var DEFAULT_MODEL = "anthropic/claude-opus-5";
19582
19674
  async function acpCommand(args) {
19583
19675
  const requested = args.flags["permission-mode"];
19584
19676
  let mode;
@@ -19590,7 +19682,8 @@ async function acpCommand(args) {
19590
19682
  }
19591
19683
  mode = requested;
19592
19684
  }
19593
- const model2 = typeof args.flags.model === "string" ? args.flags.model : DEFAULT_MODEL;
19685
+ const requestedModel = typeof args.flags.model === "string" ? args.flags.model : undefined;
19686
+ const model2 = requestedModel ?? DEFAULT_MODEL;
19594
19687
  const apiKey = typeof args.flags["api-key"] === "string" ? args.flags["api-key"] : undefined;
19595
19688
  const sessionFactory = async ({ cwd, resumeSessionId }) => {
19596
19689
  const extensions = await startExtensions(cwd);
@@ -19606,7 +19699,7 @@ async function acpCommand(args) {
19606
19699
  try {
19607
19700
  return await createSession({
19608
19701
  cwd,
19609
- model: model2,
19702
+ ...requestedModel ? { model: requestedModel } : {},
19610
19703
  extraTools: extensions.tools,
19611
19704
  problems: extensions.problems,
19612
19705
  onDispose: () => extensions.close(),
@@ -20049,7 +20142,6 @@ function toStreamRecord(event) {
20049
20142
  }
20050
20143
 
20051
20144
  // packages/cli/src/commands/headless.ts
20052
- var DEFAULT_MODEL2 = "anthropic/claude-opus-5";
20053
20145
  async function headlessCommand(prompt, args) {
20054
20146
  const flags = args.flags;
20055
20147
  const requestedFormat = typeof flags["output-format"] === "string" ? flags["output-format"] : "text";
@@ -20080,6 +20172,12 @@ async function headlessCommand(prompt, args) {
20080
20172
  const maxCostUsd = parseMaxCost(flags["max-cost"]);
20081
20173
  if (maxCostUsd === "invalid") {
20082
20174
  process.stderr.write(`"${flags["max-cost"]}" is not an amount in dollars
20175
+ `);
20176
+ return 2;
20177
+ }
20178
+ const reasoningEffort = parseReasoningEffort(flags["reasoning-effort"]);
20179
+ if (reasoningEffort === "invalid") {
20180
+ process.stderr.write(`"${flags["reasoning-effort"]}" is not a reasoning effort
20083
20181
  `);
20084
20182
  return 2;
20085
20183
  }
@@ -20091,7 +20189,8 @@ async function headlessCommand(prompt, args) {
20091
20189
  extraTools: extensions.tools,
20092
20190
  problems: extensions.problems,
20093
20191
  onDispose: () => extensions.close(),
20094
- model: typeof flags.model === "string" ? flags.model : DEFAULT_MODEL2,
20192
+ ...typeof flags.model === "string" ? { model: flags.model } : {},
20193
+ ...reasoningEffort !== undefined ? { reasoningEffort } : {},
20095
20194
  ...mode ? { mode } : {},
20096
20195
  ...typeof flags["api-key"] === "string" ? { apiKey: flags["api-key"] } : {},
20097
20196
  ...curiosity ? { curiosity } : {},
@@ -20170,7 +20269,7 @@ async function headlessCommand(prompt, args) {
20170
20269
  costUsd: session2.agent.costUsd,
20171
20270
  durationMs: Date.now() - startedAt,
20172
20271
  numMessages: session2.agent.history.length,
20173
- model: typeof flags.model === "string" ? flags.model : DEFAULT_MODEL2,
20272
+ model: `${session2.agent.model.provider.id}/${session2.agent.model.model.id}`,
20174
20273
  permissionMode: session2.agent.permissionMode,
20175
20274
  ...session2.store ? { sessionId: session2.store.id } : {},
20176
20275
  ...failure ? { error: failure } : {}
@@ -20180,6 +20279,15 @@ async function headlessCommand(prompt, args) {
20180
20279
  `);
20181
20280
  return exitCode;
20182
20281
  }
20282
+ function parseReasoningEffort(value) {
20283
+ if (value === undefined)
20284
+ return;
20285
+ if (value === "auto")
20286
+ return null;
20287
+ if (typeof value !== "string")
20288
+ return "invalid";
20289
+ return ["none", "low", "medium", "high", "xhigh"].includes(value) ? value : "invalid";
20290
+ }
20183
20291
  function resumeFrom(flags) {
20184
20292
  if (typeof flags.resume === "string")
20185
20293
  return { resume: { path: flags.resume } };
@@ -20287,8 +20395,8 @@ run \`earshot models\` to see what is available
20287
20395
  }
20288
20396
 
20289
20397
  // packages/tui/src/app.tsx
20290
- import { Box as Box9, Static, Text as Text10, useApp, useInput as useInput4 } from "ink";
20291
- import { useCallback, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
20398
+ import { Box as Box12, Static, Text as Text14, useApp as useApp3, useInput as useInput7 } from "ink";
20399
+ import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef3, useState as useState7 } from "react";
20292
20400
 
20293
20401
  // packages/tui/src/commands.ts
20294
20402
  var SPECS = [
@@ -20301,6 +20409,16 @@ var SPECS = [
20301
20409
  args: "[ref]",
20302
20410
  summary: "Show the model in use, or switch to another for the rest of the session"
20303
20411
  },
20412
+ {
20413
+ name: "reasoning",
20414
+ args: "[auto|none|low|medium|high|xhigh]",
20415
+ summary: "Show or change reasoning effort for the current model"
20416
+ },
20417
+ {
20418
+ name: "thinking",
20419
+ args: "[show|hide]",
20420
+ summary: "Show or hide streamed model reasoning"
20421
+ },
20304
20422
  {
20305
20423
  name: "mode",
20306
20424
  args: "<plan|ask|accept-edits|auto|yolo>",
@@ -20359,6 +20477,11 @@ var SPECS = [
20359
20477
  summary: "List this session's prompts, numbered",
20360
20478
  idleOnly: true
20361
20479
  },
20480
+ {
20481
+ name: "sessions",
20482
+ summary: "Browse and resume chats saved for this project",
20483
+ idleOnly: true
20484
+ },
20362
20485
  {
20363
20486
  name: "rewind",
20364
20487
  args: "<n>",
@@ -20404,8 +20527,9 @@ function commandRows() {
20404
20527
  return rows;
20405
20528
  }
20406
20529
 
20407
- // packages/tui/src/components/command-menu.tsx
20408
- import { Box, Text } from "ink";
20530
+ // packages/tui/src/components/activity.tsx
20531
+ import { Text } from "ink";
20532
+ import { useEffect, useState } from "react";
20409
20533
 
20410
20534
  // packages/tui/src/theme.ts
20411
20535
  var theme = {
@@ -20437,8 +20561,24 @@ var MODE_COLOR = {
20437
20561
  yolo: theme.danger
20438
20562
  };
20439
20563
 
20440
- // packages/tui/src/components/command-menu.tsx
20564
+ // packages/tui/src/components/activity.tsx
20441
20565
  import { jsxDEV } from "react/jsx-dev-runtime";
20566
+ var FRAMES = ["· ", "·· ", "···", " ··", " ·", " "];
20567
+ function Activity({ label }) {
20568
+ const [frame, setFrame] = useState(0);
20569
+ useEffect(() => {
20570
+ const timer = setInterval(() => setFrame((value) => (value + 1) % FRAMES.length), 120);
20571
+ return () => clearInterval(timer);
20572
+ }, []);
20573
+ return /* @__PURE__ */ jsxDEV(Text, {
20574
+ color: theme.muted,
20575
+ children: label ? `${label} ${FRAMES[frame]}` : FRAMES[frame]
20576
+ }, undefined, false, undefined, this);
20577
+ }
20578
+
20579
+ // packages/tui/src/components/command-menu.tsx
20580
+ import { Box, Text as Text2 } from "ink";
20581
+ import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
20442
20582
  var SUMMARY_WIDTH = 58;
20443
20583
  var VISIBLE = 10;
20444
20584
  function menuEntries(query, commands, busy) {
@@ -20499,9 +20639,9 @@ function ranked(items, query) {
20499
20639
  }
20500
20640
  function CommandMenu({ entries, selected }) {
20501
20641
  if (entries.length === 0) {
20502
- return /* @__PURE__ */ jsxDEV(Box, {
20642
+ return /* @__PURE__ */ jsxDEV2(Box, {
20503
20643
  marginTop: 1,
20504
- children: /* @__PURE__ */ jsxDEV(Text, {
20644
+ children: /* @__PURE__ */ jsxDEV2(Text2, {
20505
20645
  color: theme.muted,
20506
20646
  children: "no command matches"
20507
20647
  }, undefined, false, undefined, this)
@@ -20510,31 +20650,31 @@ function CommandMenu({ entries, selected }) {
20510
20650
  const start = Math.min(Math.max(0, selected - VISIBLE + 1), Math.max(0, entries.length - VISIBLE));
20511
20651
  const shown = entries.slice(start, start + VISIBLE);
20512
20652
  const hidden = entries.length - shown.length;
20513
- return /* @__PURE__ */ jsxDEV(Box, {
20653
+ return /* @__PURE__ */ jsxDEV2(Box, {
20514
20654
  flexDirection: "column",
20515
20655
  marginTop: 1,
20516
20656
  children: [
20517
20657
  shown.map((entry, index) => {
20518
20658
  const active = start + index === selected;
20519
- return /* @__PURE__ */ jsxDEV(Box, {
20659
+ return /* @__PURE__ */ jsxDEV2(Box, {
20520
20660
  children: [
20521
- /* @__PURE__ */ jsxDEV(Text, {
20661
+ /* @__PURE__ */ jsxDEV2(Text2, {
20522
20662
  color: active ? theme.user : theme.muted,
20523
20663
  children: active ? "› " : " "
20524
20664
  }, undefined, false, undefined, this),
20525
- /* @__PURE__ */ jsxDEV(Text, {
20665
+ /* @__PURE__ */ jsxDEV2(Text2, {
20526
20666
  ...rowColor(entry, active),
20527
20667
  children: entry.label.padEnd(34)
20528
20668
  }, undefined, false, undefined, this),
20529
- /* @__PURE__ */ jsxDEV(Text, {
20669
+ /* @__PURE__ */ jsxDEV2(Text2, {
20530
20670
  color: theme.muted,
20531
20671
  children: clamp(`${entry.scope ? `[${entry.scope}] ` : ""}${entry.summary}${entry.disabled ? " (not while a turn is running)" : ""}`)
20532
20672
  }, undefined, false, undefined, this)
20533
20673
  ]
20534
20674
  }, entry.label, true, undefined, this);
20535
20675
  }),
20536
- /* @__PURE__ */ jsxDEV(Box, {
20537
- children: /* @__PURE__ */ jsxDEV(Text, {
20676
+ /* @__PURE__ */ jsxDEV2(Box, {
20677
+ children: /* @__PURE__ */ jsxDEV2(Text2, {
20538
20678
  color: theme.muted,
20539
20679
  children: [
20540
20680
  hidden > 0 ? ` ${hidden} more · ` : " ",
@@ -20547,13 +20687,13 @@ function CommandMenu({ entries, selected }) {
20547
20687
  }
20548
20688
 
20549
20689
  // packages/tui/src/components/markdown.tsx
20550
- import { Box as Box2, Text as Text2 } from "ink";
20551
- import { jsxDEV as jsxDEV2, Fragment } from "react/jsx-dev-runtime";
20690
+ import { Box as Box2, Text as Text3 } from "ink";
20691
+ import { jsxDEV as jsxDEV3, Fragment } from "react/jsx-dev-runtime";
20552
20692
  function Markdown({ text: text2 }) {
20553
20693
  const blocks = splitBlocks(text2);
20554
- return /* @__PURE__ */ jsxDEV2(Box2, {
20694
+ return /* @__PURE__ */ jsxDEV3(Box2, {
20555
20695
  flexDirection: "column",
20556
- children: blocks.map((block, index) => /* @__PURE__ */ jsxDEV2(Block, {
20696
+ children: blocks.map((block, index) => /* @__PURE__ */ jsxDEV3(Block, {
20557
20697
  block
20558
20698
  }, index, false, undefined, this))
20559
20699
  }, undefined, false, undefined, this);
@@ -20632,19 +20772,19 @@ function splitBlocks(text2) {
20632
20772
  function Block({ block }) {
20633
20773
  switch (block.kind) {
20634
20774
  case "code":
20635
- return /* @__PURE__ */ jsxDEV2(Box2, {
20775
+ return /* @__PURE__ */ jsxDEV3(Box2, {
20636
20776
  flexDirection: "column",
20637
20777
  marginY: 1,
20638
20778
  paddingLeft: 2,
20639
- children: block.lines.map((line, index) => /* @__PURE__ */ jsxDEV2(Text2, {
20779
+ children: block.lines.map((line, index) => /* @__PURE__ */ jsxDEV3(Text3, {
20640
20780
  color: theme.tool,
20641
20781
  children: line
20642
20782
  }, index, false, undefined, this))
20643
20783
  }, undefined, false, undefined, this);
20644
20784
  case "heading":
20645
- return /* @__PURE__ */ jsxDEV2(Box2, {
20785
+ return /* @__PURE__ */ jsxDEV3(Box2, {
20646
20786
  marginTop: block.level <= 2 ? 1 : 0,
20647
- children: /* @__PURE__ */ jsxDEV2(Text2, {
20787
+ children: /* @__PURE__ */ jsxDEV3(Text3, {
20648
20788
  bold: true,
20649
20789
  underline: block.level === 1,
20650
20790
  color: theme.assistant,
@@ -20652,29 +20792,29 @@ function Block({ block }) {
20652
20792
  }, undefined, false, undefined, this)
20653
20793
  }, undefined, false, undefined, this);
20654
20794
  case "rule":
20655
- return /* @__PURE__ */ jsxDEV2(Text2, {
20795
+ return /* @__PURE__ */ jsxDEV3(Text3, {
20656
20796
  color: theme.muted,
20657
20797
  children: "─".repeat(40)
20658
20798
  }, undefined, false, undefined, this);
20659
20799
  case "list":
20660
- return /* @__PURE__ */ jsxDEV2(Box2, {
20800
+ return /* @__PURE__ */ jsxDEV3(Box2, {
20661
20801
  flexDirection: "column",
20662
- children: block.items.map((item, index) => /* @__PURE__ */ jsxDEV2(Text2, {
20802
+ children: block.items.map((item, index) => /* @__PURE__ */ jsxDEV3(Text3, {
20663
20803
  children: [
20664
20804
  " ",
20665
20805
  block.ordered ? `${index + 1}.` : "-",
20666
20806
  " ",
20667
- /* @__PURE__ */ jsxDEV2(Inline, {
20807
+ /* @__PURE__ */ jsxDEV3(Inline, {
20668
20808
  text: item
20669
20809
  }, undefined, false, undefined, this)
20670
20810
  ]
20671
20811
  }, index, true, undefined, this))
20672
20812
  }, undefined, false, undefined, this);
20673
20813
  case "quote":
20674
- return /* @__PURE__ */ jsxDEV2(Box2, {
20814
+ return /* @__PURE__ */ jsxDEV3(Box2, {
20675
20815
  flexDirection: "column",
20676
20816
  paddingLeft: 1,
20677
- children: block.lines.map((line, index) => /* @__PURE__ */ jsxDEV2(Text2, {
20817
+ children: block.lines.map((line, index) => /* @__PURE__ */ jsxDEV3(Text3, {
20678
20818
  color: theme.muted,
20679
20819
  italic: true,
20680
20820
  children: [
@@ -20684,8 +20824,8 @@ function Block({ block }) {
20684
20824
  }, index, true, undefined, this))
20685
20825
  }, undefined, false, undefined, this);
20686
20826
  default:
20687
- return /* @__PURE__ */ jsxDEV2(Text2, {
20688
- children: /* @__PURE__ */ jsxDEV2(Inline, {
20827
+ return /* @__PURE__ */ jsxDEV3(Text3, {
20828
+ children: /* @__PURE__ */ jsxDEV3(Inline, {
20689
20829
  text: block.text
20690
20830
  }, undefined, false, undefined, this)
20691
20831
  }, undefined, false, undefined, this);
@@ -20697,41 +20837,41 @@ function inlineToText(text2) {
20697
20837
  function Inline({ text: text2 }) {
20698
20838
  const pattern = /(\*\*.+?\*\*|__.+?__|`.+?`|\*.+?\*|_.+?_)/g;
20699
20839
  const parts = text2.split(pattern);
20700
- return /* @__PURE__ */ jsxDEV2(Fragment, {
20840
+ return /* @__PURE__ */ jsxDEV3(Fragment, {
20701
20841
  children: parts.map((part, index) => {
20702
20842
  if (part === "")
20703
20843
  return null;
20704
20844
  if (part.startsWith("**") && part.endsWith("**")) {
20705
- return /* @__PURE__ */ jsxDEV2(Text2, {
20845
+ return /* @__PURE__ */ jsxDEV3(Text3, {
20706
20846
  bold: true,
20707
20847
  children: part.slice(2, -2)
20708
20848
  }, index, false, undefined, this);
20709
20849
  }
20710
20850
  if (part.startsWith("__") && part.endsWith("__")) {
20711
- return /* @__PURE__ */ jsxDEV2(Text2, {
20851
+ return /* @__PURE__ */ jsxDEV3(Text3, {
20712
20852
  bold: true,
20713
20853
  children: part.slice(2, -2)
20714
20854
  }, index, false, undefined, this);
20715
20855
  }
20716
20856
  if (part.startsWith("`") && part.endsWith("`")) {
20717
- return /* @__PURE__ */ jsxDEV2(Text2, {
20857
+ return /* @__PURE__ */ jsxDEV3(Text3, {
20718
20858
  color: theme.tool,
20719
20859
  children: part.slice(1, -1)
20720
20860
  }, index, false, undefined, this);
20721
20861
  }
20722
20862
  if (part.startsWith("*") && part.endsWith("*")) {
20723
- return /* @__PURE__ */ jsxDEV2(Text2, {
20863
+ return /* @__PURE__ */ jsxDEV3(Text3, {
20724
20864
  italic: true,
20725
20865
  children: part.slice(1, -1)
20726
20866
  }, index, false, undefined, this);
20727
20867
  }
20728
20868
  if (part.startsWith("_") && part.endsWith("_")) {
20729
- return /* @__PURE__ */ jsxDEV2(Text2, {
20869
+ return /* @__PURE__ */ jsxDEV3(Text3, {
20730
20870
  italic: true,
20731
20871
  children: part.slice(1, -1)
20732
20872
  }, index, false, undefined, this);
20733
20873
  }
20734
- return /* @__PURE__ */ jsxDEV2(Text2, {
20874
+ return /* @__PURE__ */ jsxDEV3(Text3, {
20735
20875
  children: part
20736
20876
  }, index, false, undefined, this);
20737
20877
  })
@@ -20739,24 +20879,24 @@ function Inline({ text: text2 }) {
20739
20879
  }
20740
20880
 
20741
20881
  // packages/tui/src/components/memory-capture.tsx
20742
- import { Box as Box3, Text as Text3 } from "ink";
20743
- import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
20882
+ import { Box as Box3, Text as Text4 } from "ink";
20883
+ import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
20744
20884
  function MemoryCapture({ candidate }) {
20745
- return /* @__PURE__ */ jsxDEV3(Box3, {
20885
+ return /* @__PURE__ */ jsxDEV4(Box3, {
20746
20886
  marginTop: 1,
20747
20887
  children: [
20748
- /* @__PURE__ */ jsxDEV3(Text3, {
20888
+ /* @__PURE__ */ jsxDEV4(Text4, {
20749
20889
  color: theme.accent,
20750
20890
  children: "remember "
20751
20891
  }, undefined, false, undefined, this),
20752
- /* @__PURE__ */ jsxDEV3(Text3, {
20892
+ /* @__PURE__ */ jsxDEV4(Text4, {
20753
20893
  children: [
20754
20894
  "“",
20755
20895
  candidate.text,
20756
20896
  "”"
20757
20897
  ]
20758
20898
  }, undefined, true, undefined, this),
20759
- /* @__PURE__ */ jsxDEV3(Text3, {
20899
+ /* @__PURE__ */ jsxDEV4(Text4, {
20760
20900
  color: theme.muted,
20761
20901
  children: "? ctrl+r for this project · ctrl+g everywhere"
20762
20902
  }, undefined, false, undefined, this)
@@ -20765,12 +20905,12 @@ function MemoryCapture({ candidate }) {
20765
20905
  }
20766
20906
 
20767
20907
  // packages/tui/src/components/permission.tsx
20768
- import { Box as Box5, Text as Text5, useInput } from "ink";
20769
- import { useState } from "react";
20908
+ import { Box as Box5, Text as Text6, useInput } from "ink";
20909
+ import { useState as useState2 } from "react";
20770
20910
 
20771
20911
  // packages/tui/src/components/diff.tsx
20772
- import { Box as Box4, Text as Text4 } from "ink";
20773
- import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
20912
+ import { Box as Box4, Text as Text5 } from "ink";
20913
+ import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
20774
20914
  var MAX_LINES = 60;
20775
20915
  function DiffView({ diff: diff2, maxLines = MAX_LINES }) {
20776
20916
  const lines = diff2.split(`
@@ -20778,15 +20918,15 @@ function DiffView({ diff: diff2, maxLines = MAX_LINES }) {
20778
20918
  const body = lines.filter((line) => !line.startsWith("---") && !line.startsWith("+++"));
20779
20919
  const shown = body.slice(0, maxLines);
20780
20920
  const hidden = body.length - shown.length;
20781
- return /* @__PURE__ */ jsxDEV4(Box4, {
20921
+ return /* @__PURE__ */ jsxDEV5(Box4, {
20782
20922
  flexDirection: "column",
20783
20923
  children: [
20784
- shown.map((line, index) => /* @__PURE__ */ jsxDEV4(Text4, {
20924
+ shown.map((line, index) => /* @__PURE__ */ jsxDEV5(Text5, {
20785
20925
  color: colorFor(line),
20786
20926
  wrap: "truncate-end",
20787
20927
  children: line === "" ? " " : line
20788
20928
  }, index, false, undefined, this)),
20789
- hidden > 0 && /* @__PURE__ */ jsxDEV4(Text4, {
20929
+ hidden > 0 && /* @__PURE__ */ jsxDEV5(Text5, {
20790
20930
  color: theme.muted,
20791
20931
  children: [
20792
20932
  " ",
@@ -20821,9 +20961,9 @@ function diffStat(diff2) {
20821
20961
  }
20822
20962
 
20823
20963
  // packages/tui/src/components/permission.tsx
20824
- import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
20964
+ import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
20825
20965
  function PermissionPrompt({ request, reason, onChoice }) {
20826
- const [selected, setSelected] = useState(0);
20966
+ const [selected, setSelected] = useState2(0);
20827
20967
  const options = [
20828
20968
  { label: "Allow once", choice: { kind: "allow-once" }, color: theme.assistant },
20829
20969
  {
@@ -20849,32 +20989,32 @@ function PermissionPrompt({ request, reason, onChoice }) {
20849
20989
  });
20850
20990
  const isDiff = request.detail.includes(`
20851
20991
  @@`) || request.detail.startsWith("---");
20852
- return /* @__PURE__ */ jsxDEV5(Box5, {
20992
+ return /* @__PURE__ */ jsxDEV6(Box5, {
20853
20993
  flexDirection: "column",
20854
20994
  borderStyle: "round",
20855
20995
  borderColor: theme.warning,
20856
20996
  paddingX: 1,
20857
20997
  children: [
20858
- /* @__PURE__ */ jsxDEV5(Text5, {
20998
+ /* @__PURE__ */ jsxDEV6(Text6, {
20859
20999
  bold: true,
20860
21000
  color: theme.warning,
20861
21001
  children: request.title
20862
21002
  }, undefined, false, undefined, this),
20863
- /* @__PURE__ */ jsxDEV5(Text5, {
21003
+ /* @__PURE__ */ jsxDEV6(Text6, {
20864
21004
  color: theme.muted,
20865
21005
  children: reason
20866
21006
  }, undefined, false, undefined, this),
20867
- /* @__PURE__ */ jsxDEV5(Box5, {
21007
+ /* @__PURE__ */ jsxDEV6(Box5, {
20868
21008
  marginY: 1,
20869
21009
  flexDirection: "column",
20870
- children: isDiff ? /* @__PURE__ */ jsxDEV5(DiffView, {
21010
+ children: isDiff ? /* @__PURE__ */ jsxDEV6(DiffView, {
20871
21011
  diff: request.detail
20872
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV5(Text5, {
21012
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV6(Text6, {
20873
21013
  wrap: "wrap",
20874
21014
  children: request.detail
20875
21015
  }, undefined, false, undefined, this)
20876
21016
  }, undefined, false, undefined, this),
20877
- options.map((option, index) => /* @__PURE__ */ jsxDEV5(Text5, {
21017
+ options.map((option, index) => /* @__PURE__ */ jsxDEV6(Text6, {
20878
21018
  color: index === selected ? theme.accent : option.color,
20879
21019
  children: [
20880
21020
  index === selected ? "❯ " : " ",
@@ -20889,13 +21029,13 @@ function truncate2(value, max) {
20889
21029
  }
20890
21030
 
20891
21031
  // packages/tui/src/components/question.tsx
20892
- import { Box as Box6, Text as Text7, useInput as useInput3 } from "ink";
20893
- import { useState as useState2 } from "react";
21032
+ import { Box as Box6, Text as Text8, useInput as useInput3 } from "ink";
21033
+ import { useState as useState3 } from "react";
20894
21034
 
20895
21035
  // packages/tui/src/components/text-input.tsx
20896
- import { Text as Text6, useInput as useInput2 } from "ink";
20897
- import { useEffect, useRef } from "react";
20898
- import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
21036
+ import { Text as Text7, useInput as useInput2 } from "ink";
21037
+ import { useEffect as useEffect2, useRef } from "react";
21038
+ import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
20899
21039
  function TextInput({
20900
21040
  value,
20901
21041
  onChange,
@@ -20904,7 +21044,7 @@ function TextInput({
20904
21044
  isActive = true
20905
21045
  }) {
20906
21046
  const buffer = useRef(value);
20907
- useEffect(() => {
21047
+ useEffect2(() => {
20908
21048
  buffer.current = value;
20909
21049
  }, [value]);
20910
21050
  useInput2((input, key2) => {
@@ -20929,23 +21069,23 @@ function TextInput({
20929
21069
  }
20930
21070
  }, { isActive });
20931
21071
  if (value === "") {
20932
- return /* @__PURE__ */ jsxDEV6(Text6, {
21072
+ return /* @__PURE__ */ jsxDEV7(Text7, {
20933
21073
  children: [
20934
- /* @__PURE__ */ jsxDEV6(Text6, {
21074
+ /* @__PURE__ */ jsxDEV7(Text7, {
20935
21075
  inverse: true,
20936
21076
  children: " "
20937
21077
  }, undefined, false, undefined, this),
20938
- /* @__PURE__ */ jsxDEV6(Text6, {
21078
+ /* @__PURE__ */ jsxDEV7(Text7, {
20939
21079
  color: theme.muted,
20940
21080
  children: placeholder
20941
21081
  }, undefined, false, undefined, this)
20942
21082
  ]
20943
21083
  }, undefined, true, undefined, this);
20944
21084
  }
20945
- return /* @__PURE__ */ jsxDEV6(Text6, {
21085
+ return /* @__PURE__ */ jsxDEV7(Text7, {
20946
21086
  children: [
20947
21087
  value,
20948
- /* @__PURE__ */ jsxDEV6(Text6, {
21088
+ /* @__PURE__ */ jsxDEV7(Text7, {
20949
21089
  inverse: true,
20950
21090
  children: " "
20951
21091
  }, undefined, false, undefined, this)
@@ -20954,11 +21094,11 @@ function TextInput({
20954
21094
  }
20955
21095
 
20956
21096
  // packages/tui/src/components/question.tsx
20957
- import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
21097
+ import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
20958
21098
  function QuestionPrompt({ question, options = [], onAnswer }) {
20959
- const [selected, setSelected] = useState2(0);
20960
- const [typing, setTyping] = useState2(options.length === 0);
20961
- const [value, setValue] = useState2("");
21099
+ const [selected, setSelected] = useState3(0);
21100
+ const [typing, setTyping] = useState3(options.length === 0);
21101
+ const [value, setValue] = useState3("");
20962
21102
  useInput3((input, key2) => {
20963
21103
  if (key2.upArrow)
20964
21104
  setSelected((n) => (n + options.length - 1) % options.length);
@@ -20971,29 +21111,29 @@ function QuestionPrompt({ question, options = [], onAnswer }) {
20971
21111
  setValue(input);
20972
21112
  }
20973
21113
  }, { isActive: !typing });
20974
- return /* @__PURE__ */ jsxDEV7(Box6, {
21114
+ return /* @__PURE__ */ jsxDEV8(Box6, {
20975
21115
  flexDirection: "column",
20976
21116
  borderStyle: "round",
20977
21117
  borderColor: theme.accent,
20978
21118
  paddingX: 1,
20979
21119
  children: [
20980
- /* @__PURE__ */ jsxDEV7(Text7, {
21120
+ /* @__PURE__ */ jsxDEV8(Text8, {
20981
21121
  bold: true,
20982
21122
  color: theme.accent,
20983
21123
  children: question
20984
21124
  }, undefined, false, undefined, this),
20985
- !typing && options.map((option, index) => /* @__PURE__ */ jsxDEV7(Text7, {
21125
+ !typing && options.map((option, index) => /* @__PURE__ */ jsxDEV8(Text8, {
20986
21126
  color: index === selected ? theme.accent : theme.muted,
20987
21127
  children: [
20988
21128
  index === selected ? "❯ " : " ",
20989
21129
  option
20990
21130
  ]
20991
21131
  }, option, true, undefined, this)),
20992
- !typing && /* @__PURE__ */ jsxDEV7(Text7, {
21132
+ !typing && /* @__PURE__ */ jsxDEV8(Text8, {
20993
21133
  color: theme.muted,
20994
21134
  children: "or start typing to answer in your own words"
20995
21135
  }, undefined, false, undefined, this),
20996
- typing && /* @__PURE__ */ jsxDEV7(TextInput, {
21136
+ typing && /* @__PURE__ */ jsxDEV8(TextInput, {
20997
21137
  value,
20998
21138
  onChange: setValue,
20999
21139
  onSubmit: (answer) => onAnswer(answer.trim()),
@@ -21003,11 +21143,60 @@ function QuestionPrompt({ question, options = [], onAnswer }) {
21003
21143
  }, undefined, true, undefined, this);
21004
21144
  }
21005
21145
 
21146
+ // packages/tui/src/components/reasoning-picker.tsx
21147
+ import { Box as Box7, Text as Text9, useInput as useInput4 } from "ink";
21148
+ import { useState as useState4 } from "react";
21149
+ import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
21150
+ var choices = ["auto", "none", "low", "medium", "high", "xhigh"];
21151
+ function ReasoningPicker({
21152
+ current,
21153
+ onDone,
21154
+ onCancel
21155
+ }) {
21156
+ const [cursor, setCursor] = useState4(Math.max(0, choices.indexOf(current ?? "auto")));
21157
+ useInput4((_input, key2) => {
21158
+ if (key2.escape)
21159
+ return onCancel();
21160
+ if (key2.upArrow)
21161
+ setCursor((value) => value <= 0 ? choices.length - 1 : value - 1);
21162
+ if (key2.downArrow)
21163
+ setCursor((value) => value >= choices.length - 1 ? 0 : value + 1);
21164
+ if (key2.return) {
21165
+ const selected = choices[cursor] ?? "auto";
21166
+ onDone(selected === "auto" ? undefined : selected);
21167
+ }
21168
+ });
21169
+ return /* @__PURE__ */ jsxDEV9(Box7, {
21170
+ flexDirection: "column",
21171
+ children: [
21172
+ /* @__PURE__ */ jsxDEV9(Text9, {
21173
+ children: "reasoning effort"
21174
+ }, undefined, false, undefined, this),
21175
+ /* @__PURE__ */ jsxDEV9(Box7, {
21176
+ flexDirection: "column",
21177
+ marginTop: 1,
21178
+ children: choices.map((choice, index) => /* @__PURE__ */ jsxDEV9(Text9, {
21179
+ color: index === cursor ? theme.user : theme.muted,
21180
+ children: [
21181
+ index === cursor ? "› " : " ",
21182
+ choice
21183
+ ]
21184
+ }, choice, true, undefined, this))
21185
+ }, undefined, false, undefined, this),
21186
+ /* @__PURE__ */ jsxDEV9(Text9, {
21187
+ color: theme.muted,
21188
+ children: "↑↓ choose · enter select · esc cancel"
21189
+ }, undefined, false, undefined, this)
21190
+ ]
21191
+ }, undefined, true, undefined, this);
21192
+ }
21193
+
21006
21194
  // packages/tui/src/components/status.tsx
21007
- import { Box as Box7, Text as Text8 } from "ink";
21008
- import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
21195
+ import { Box as Box8, Text as Text10 } from "ink";
21196
+ import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime";
21009
21197
  function StatusLine({
21010
21198
  model: model2,
21199
+ reasoningEffort,
21011
21200
  mode,
21012
21201
  costUsd,
21013
21202
  todos,
@@ -21019,27 +21208,34 @@ function StatusLine({
21019
21208
  const done = todos.filter((todo2) => todo2.status === "done").length;
21020
21209
  const current = todos.find((todo2) => todo2.status === "in_progress");
21021
21210
  const used = context2.window > 0 ? Math.min(100, context2.tokens / context2.window * 100) : 0;
21022
- return /* @__PURE__ */ jsxDEV8(Box7, {
21211
+ return /* @__PURE__ */ jsxDEV10(Box8, {
21023
21212
  children: [
21024
- /* @__PURE__ */ jsxDEV8(Text8, {
21213
+ /* @__PURE__ */ jsxDEV10(Text10, {
21025
21214
  color: MODE_COLOR[mode] ?? theme.muted,
21026
21215
  children: MODE_LABEL[mode] ?? mode
21027
21216
  }, undefined, false, undefined, this),
21028
- /* @__PURE__ */ jsxDEV8(Text8, {
21217
+ /* @__PURE__ */ jsxDEV10(Text10, {
21029
21218
  color: theme.muted,
21030
21219
  children: [
21031
21220
  " · ",
21032
21221
  model2
21033
21222
  ]
21034
21223
  }, undefined, true, undefined, this),
21035
- /* @__PURE__ */ jsxDEV8(Text8, {
21224
+ reasoningEffort && /* @__PURE__ */ jsxDEV10(Text10, {
21225
+ color: theme.muted,
21226
+ children: [
21227
+ " · ",
21228
+ reasoningEffort
21229
+ ]
21230
+ }, undefined, true, undefined, this),
21231
+ /* @__PURE__ */ jsxDEV10(Text10, {
21036
21232
  color: theme.muted,
21037
21233
  children: [
21038
21234
  " · $",
21039
21235
  costUsd.toFixed(4)
21040
21236
  ]
21041
21237
  }, undefined, true, undefined, this),
21042
- context2.window > 0 && /* @__PURE__ */ jsxDEV8(Text8, {
21238
+ context2.window > 0 && /* @__PURE__ */ jsxDEV10(Text10, {
21043
21239
  color: used >= 80 ? theme.warning : theme.muted,
21044
21240
  children: [
21045
21241
  " · ",
@@ -21047,7 +21243,7 @@ function StatusLine({
21047
21243
  "% ctx"
21048
21244
  ]
21049
21245
  }, undefined, true, undefined, this),
21050
- compacted > 0 && /* @__PURE__ */ jsxDEV8(Text8, {
21246
+ compacted > 0 && /* @__PURE__ */ jsxDEV10(Text10, {
21051
21247
  color: theme.muted,
21052
21248
  children: [
21053
21249
  " · ",
@@ -21055,7 +21251,7 @@ function StatusLine({
21055
21251
  " summarised"
21056
21252
  ]
21057
21253
  }, undefined, true, undefined, this),
21058
- todos.length > 0 && /* @__PURE__ */ jsxDEV8(Text8, {
21254
+ todos.length > 0 && /* @__PURE__ */ jsxDEV10(Text10, {
21059
21255
  color: theme.muted,
21060
21256
  children: [
21061
21257
  " ",
@@ -21066,11 +21262,11 @@ function StatusLine({
21066
21262
  current ? ` ${truncate3(current.text, 40)}` : ""
21067
21263
  ]
21068
21264
  }, undefined, true, undefined, this),
21069
- busy && /* @__PURE__ */ jsxDEV8(Text8, {
21265
+ busy && /* @__PURE__ */ jsxDEV10(Text10, {
21070
21266
  color: theme.warning,
21071
21267
  children: " · working (esc to interrupt)"
21072
21268
  }, undefined, false, undefined, this),
21073
- queued > 0 && /* @__PURE__ */ jsxDEV8(Text8, {
21269
+ queued > 0 && /* @__PURE__ */ jsxDEV10(Text10, {
21074
21270
  color: theme.accent,
21075
21271
  children: [
21076
21272
  " · ",
@@ -21086,8 +21282,8 @@ function truncate3(value, max) {
21086
21282
  }
21087
21283
 
21088
21284
  // packages/tui/src/components/tool-block.tsx
21089
- import { Box as Box8, Text as Text9 } from "ink";
21090
- import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
21285
+ import { Box as Box9, Text as Text11 } from "ink";
21286
+ import { jsxDEV as jsxDEV11 } from "react/jsx-dev-runtime";
21091
21287
  var PREVIEW_LINES = 8;
21092
21288
  function ToolBlock({
21093
21289
  name,
@@ -21106,27 +21302,27 @@ function ToolBlock({
21106
21302
  const showAll = expanded || isError;
21107
21303
  const shown = showAll ? lines : lines.slice(0, PREVIEW_LINES);
21108
21304
  const hidden = lines.length - shown.length;
21109
- return /* @__PURE__ */ jsxDEV9(Box8, {
21305
+ return /* @__PURE__ */ jsxDEV11(Box9, {
21110
21306
  flexDirection: "column",
21111
21307
  marginTop: 1,
21112
21308
  children: [
21113
- /* @__PURE__ */ jsxDEV9(Text9, {
21309
+ /* @__PURE__ */ jsxDEV11(Text11, {
21114
21310
  color,
21115
21311
  children: [
21116
21312
  marker2,
21117
21313
  " ",
21118
- /* @__PURE__ */ jsxDEV9(Text9, {
21314
+ /* @__PURE__ */ jsxDEV11(Text11, {
21119
21315
  bold: true,
21120
21316
  children: name
21121
21317
  }, undefined, false, undefined, this),
21122
- title ? /* @__PURE__ */ jsxDEV9(Text9, {
21318
+ title ? /* @__PURE__ */ jsxDEV11(Text11, {
21123
21319
  color: theme.muted,
21124
21320
  children: [
21125
21321
  " ",
21126
21322
  title
21127
21323
  ]
21128
21324
  }, undefined, true, undefined, this) : null,
21129
- isDiff && !showAll ? /* @__PURE__ */ jsxDEV9(Text9, {
21325
+ isDiff && !showAll ? /* @__PURE__ */ jsxDEV11(Text11, {
21130
21326
  color: theme.muted,
21131
21327
  children: [
21132
21328
  " ",
@@ -21135,12 +21331,12 @@ function ToolBlock({
21135
21331
  }, undefined, true, undefined, this) : null
21136
21332
  ]
21137
21333
  }, undefined, true, undefined, this),
21138
- isDiff && showAll ? /* @__PURE__ */ jsxDEV9(Box8, {
21334
+ isDiff && showAll ? /* @__PURE__ */ jsxDEV11(Box9, {
21139
21335
  marginLeft: 2,
21140
- children: /* @__PURE__ */ jsxDEV9(DiffView, {
21336
+ children: /* @__PURE__ */ jsxDEV11(DiffView, {
21141
21337
  diff: output
21142
21338
  }, undefined, false, undefined, this)
21143
- }, undefined, false, undefined, this) : shown.map((line, index) => /* @__PURE__ */ jsxDEV9(Text9, {
21339
+ }, undefined, false, undefined, this) : shown.map((line, index) => /* @__PURE__ */ jsxDEV11(Text11, {
21144
21340
  color: theme.muted,
21145
21341
  wrap: "truncate-end",
21146
21342
  children: [
@@ -21148,7 +21344,7 @@ function ToolBlock({
21148
21344
  line
21149
21345
  ]
21150
21346
  }, index, true, undefined, this)),
21151
- hidden > 0 && !isDiff && /* @__PURE__ */ jsxDEV9(Text9, {
21347
+ hidden > 0 && !isDiff && /* @__PURE__ */ jsxDEV11(Text11, {
21152
21348
  color: theme.muted,
21153
21349
  children: [
21154
21350
  " ",
@@ -21161,1177 +21357,1640 @@ function ToolBlock({
21161
21357
  }, undefined, true, undefined, this);
21162
21358
  }
21163
21359
 
21164
- // packages/tui/src/app.tsx
21165
- import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime";
21166
- var sequence = 0;
21167
- var nextId = () => `item_${sequence++}`;
21168
- function promptLabel(prompt) {
21169
- if (typeof prompt === "string")
21170
- return prompt;
21171
- return prompt.map((part) => part.type === "text" ? part.text : `[attached ${part.mediaType} image]`).join(`
21172
- `);
21173
- }
21174
- function App({ session: session2, model: initialModel, initialPrompt }) {
21360
+ // packages/tui/src/onboarding.tsx
21361
+ import { Box as Box10, Text as Text12, useApp, useInput as useInput5 } from "ink";
21362
+ import { useCallback, useRef as useRef2, useState as useState5 } from "react";
21363
+ import { jsxDEV as jsxDEV12 } from "react/jsx-dev-runtime";
21364
+ var EFFORTS = ["auto", "none", "low", "medium", "high", "xhigh"];
21365
+ function Onboarding({
21366
+ onDone,
21367
+ embedded = false,
21368
+ defaultScope = "global",
21369
+ ...rest
21370
+ }) {
21175
21371
  const { exit } = useApp();
21176
- const agent3 = session2.agent;
21177
- const [model2, setModel] = useState3(initialModel);
21178
- const [items, setItems] = useState3(() => session2.problems.map((problem) => ({
21179
- kind: "notice",
21180
- id: nextId(),
21181
- text: `warning: ${problem}`,
21182
- color: theme.warning
21183
- })));
21184
- const [live, setLive] = useState3("");
21185
- const [runningTool, setRunningTool] = useState3();
21186
- const [input, setInput] = useState3("");
21187
- const [menuIndex, setMenuIndex] = useState3(0);
21188
- const [busy, setBusy] = useState3(false);
21189
- const [mode, setMode] = useState3(agent3.permissionMode);
21190
- const [cost, setCost] = useState3(0);
21191
- const [todos, setTodos] = useState3([]);
21192
- const [queued, setQueued] = useState3(0);
21193
- const [candidate, setCandidate] = useState3();
21194
- const [context2, setContext] = useState3(() => agent3.contextUse);
21195
- const [compacted, setCompacted] = useState3(0);
21196
- const [pending, setPending] = useState3();
21197
- const [question, setQuestion] = useState3();
21198
- const controller = useRef2(undefined);
21199
- const push = useCallback((item) => setItems((current) => [...current, item]), []);
21200
- const pendingRef = useRef2(setPending);
21201
- pendingRef.current = setPending;
21202
- const questionRef = useRef2(setQuestion);
21203
- questionRef.current = setQuestion;
21204
- useEffect2(() => {
21205
- session2.installPrompt((request, reason) => new Promise((resolve4) => {
21206
- pendingRef.current({ request, reason, resolve: resolve4 });
21207
- }));
21208
- session2.installAsk((q, options) => new Promise((resolve4) => {
21209
- questionRef.current({ question: q, ...options ? { options } : {}, resolve: resolve4 });
21210
- }));
21211
- }, [session2]);
21212
- const modeBeforePlan = useRef2(undefined);
21213
- const lastAssistantText = useRef2("");
21214
- const runTurn = useCallback(async (prompt) => {
21215
- setBusy(true);
21216
- lastAssistantText.current = "";
21217
- push({ kind: "user", id: nextId(), text: promptLabel(prompt) });
21218
- const abort = new AbortController;
21219
- controller.current = abort;
21220
- let assistantText = "";
21372
+ const options = useRef2(rest);
21373
+ options.current = rest;
21374
+ const [screen, setScreen] = useState5({ name: "choose" });
21375
+ const [cursor, setCursor] = useState5(0);
21376
+ const [input, setInput] = useState5("");
21377
+ const [error, setError] = useState5();
21378
+ const secret = useRef2("");
21379
+ const providers = order(rest.providers, rest.wanted);
21380
+ const finish = useCallback((result) => {
21381
+ onDone(result);
21382
+ if (!embedded)
21383
+ exit();
21384
+ }, [embedded, exit, onDone]);
21385
+ const complete = useCallback((provider, model2, scope2 = defaultScope, effort) => finish({
21386
+ outcome: "ready",
21387
+ providerId: provider.id,
21388
+ model: `${provider.id}/${model2.id}`,
21389
+ scope: scope2,
21390
+ ...effort ? { reasoningEffort: effort } : {}
21391
+ }), [defaultScope, finish]);
21392
+ const readyForEffort = useCallback((provider, model2) => {
21393
+ const remembered = options.current.reasoningFor?.(`${provider.id}/${model2.id}`);
21394
+ setCursor(remembered ? Math.max(0, EFFORTS.indexOf(remembered)) : 0);
21395
+ if (model2.reasoning)
21396
+ setScreen({ name: "effort", provider, model: model2 });
21397
+ else
21398
+ complete(provider, model2);
21399
+ }, [complete]);
21400
+ const verify2 = useCallback(async (provider, model2) => {
21401
+ setScreen({ name: "probing", provider, model: model2 });
21402
+ const result = await options.current.probe(provider.id, model2.id);
21403
+ if (result.ok) {
21404
+ readyForEffort(provider, model2);
21405
+ return;
21406
+ }
21407
+ if (result.reason === "rejected")
21408
+ await options.current.forgetKey(provider.id).catch(() => {});
21409
+ setScreen({ name: "failed", provider, model: model2, result });
21410
+ }, [readyForEffort]);
21411
+ const submitKey = useCallback(async () => {
21412
+ const selected = screen.name === "key" ? screen : undefined;
21413
+ const key2 = secret.current;
21414
+ secret.current = "";
21415
+ setInput("");
21416
+ if (!selected)
21417
+ return;
21418
+ if (key2.trim() === "") {
21419
+ setError("a key is needed, or press esc to go back");
21420
+ return;
21421
+ }
21422
+ setError(undefined);
21423
+ await options.current.storeKey(selected.provider.id, key2.trim());
21424
+ await verify2(selected.provider, selected.model);
21425
+ }, [screen, verify2]);
21426
+ const startSignIn = useCallback(async (provider, model2) => {
21427
+ setScreen({ name: "oauth", provider, model: model2 });
21221
21428
  try {
21222
- for await (const event of agent3.runTurn(prompt, abort.signal)) {
21223
- switch (event.type) {
21224
- case "text_delta":
21225
- assistantText += event.text;
21226
- setLive(assistantText);
21227
- break;
21228
- case "intent":
21229
- if (event.text === undefined) {
21230
- push({
21231
- kind: "notice",
21232
- id: nextId(),
21233
- text: `about to run ${event.calls} tool call${event.calls === 1 ? "" : "s"} without saying why`,
21234
- color: theme.warning
21235
- });
21236
- }
21237
- break;
21238
- case "tool_start":
21239
- if (assistantText.trim() !== "") {
21240
- push({ kind: "assistant", id: nextId(), text: assistantText.trimEnd() });
21241
- assistantText = "";
21242
- setLive("");
21243
- }
21244
- setRunningTool(event.call.toolName);
21245
- break;
21246
- case "tool_end": {
21247
- setRunningTool(undefined);
21248
- const output = event.result.output.type === "text" ? event.result.output.value : "";
21249
- push({
21250
- kind: "tool",
21251
- id: nextId(),
21252
- name: event.toolName,
21253
- ...event.result.title ? { title: event.result.title } : {},
21254
- output,
21255
- ...event.result.isError ? { isError: true } : {}
21256
- });
21257
- setTodos(agent3.todos.list());
21258
- break;
21259
- }
21260
- case "usage":
21261
- setCost(agent3.costUsd);
21262
- setContext(agent3.contextUse);
21263
- break;
21264
- case "budget":
21265
- push({
21266
- kind: "notice",
21267
- id: nextId(),
21268
- text: event.raisedTo === undefined ? `stopped: $${event.spentUsd.toFixed(2)} spent against a ` + `$${event.limitUsd.toFixed(2)} budget` : `budget raised to $${event.raisedTo.toFixed(2)} after ` + `$${event.spentUsd.toFixed(2)} spent`,
21269
- color: theme.warning
21270
- });
21271
- break;
21272
- case "verification":
21273
- push({
21274
- kind: "tool",
21275
- id: nextId(),
21276
- name: event.result.command,
21277
- title: `${event.result.command} - exit ${event.result.exitCode ?? "killed"}`,
21278
- output: event.result.output,
21279
- ...event.result.exitCode === 0 ? {} : { isError: true }
21280
- });
21281
- break;
21282
- case "subagent":
21283
- push({
21284
- kind: "notice",
21285
- id: nextId(),
21286
- text: `subagent "${event.description}": ${event.steps} step${event.steps === 1 ? "" : "s"}, $${event.costUsd.toFixed(4)}`
21287
- });
21288
- break;
21289
- case "hook":
21290
- if (event.blocked) {
21291
- push({
21292
- kind: "notice",
21293
- id: nextId(),
21294
- text: `${event.event} hook blocked this: ${event.blocked}`,
21295
- color: theme.warning
21296
- });
21297
- }
21298
- for (const problem of event.problems) {
21299
- push({ kind: "notice", id: nextId(), text: problem, color: theme.warning });
21300
- }
21301
- break;
21302
- case "compacted":
21303
- setCompacted((count) => count + event.replaced);
21304
- push({
21305
- kind: "notice",
21306
- id: nextId(),
21307
- text: `compacted: ${event.replaced} earlier messages are now a summary`
21308
- });
21309
- break;
21310
- case "error":
21311
- push({
21312
- kind: "notice",
21313
- id: nextId(),
21314
- text: `error: ${event.error.message}`,
21315
- color: theme.danger
21316
- });
21317
- break;
21318
- case "turn_end":
21319
- if (event.reason === "aborted") {
21320
- push({
21321
- kind: "notice",
21322
- id: nextId(),
21323
- text: "interrupted",
21324
- color: theme.warning
21325
- });
21326
- }
21327
- if (event.reason === "max_steps") {
21328
- push({
21329
- kind: "notice",
21330
- id: nextId(),
21331
- text: "stopped: step limit reached",
21332
- color: theme.warning
21333
- });
21334
- }
21335
- break;
21336
- default:
21337
- break;
21338
- }
21339
- setQueued(agent3.pendingSteers);
21340
- }
21341
- } finally {
21342
- if (assistantText.trim() !== "") {
21343
- lastAssistantText.current = assistantText;
21344
- push({ kind: "assistant", id: nextId(), text: assistantText.trimEnd() });
21345
- }
21346
- setLive("");
21347
- setRunningTool(undefined);
21348
- setBusy(false);
21349
- setQueued(agent3.pendingSteers);
21350
- controller.current = undefined;
21351
- }
21352
- }, [agent3, push]);
21353
- const started = useRef2(false);
21354
- useEffect2(() => {
21355
- if (started.current || !initialPrompt)
21356
- return;
21357
- started.current = true;
21358
- runTurn(initialPrompt);
21359
- }, [initialPrompt, runTurn]);
21360
- const showMemories = useCallback(async (argument) => {
21361
- const [verb, ...rest] = (argument ?? "").split(/\s+/);
21362
- const id = rest.join(" ").trim();
21363
- if (verb === "forget" && id) {
21364
- const gone = await deleteMemory(id, agent3.cwd);
21365
- if (gone)
21366
- await refreshSystemPrompt(agent3, model2, session2.skills);
21367
- push({
21368
- kind: "notice",
21369
- id: nextId(),
21370
- text: gone ? `forgot ${id}` : `no memory called "${id}"`,
21371
- ...gone ? {} : { color: theme.warning }
21429
+ await options.current.signIn(provider.id, (url) => setScreen({ name: "oauth", provider, model: model2, url }));
21430
+ } catch (failure) {
21431
+ setScreen({
21432
+ name: "failed",
21433
+ provider,
21434
+ model: model2,
21435
+ result: { ok: false, reason: "other", message: failure.message }
21372
21436
  });
21373
21437
  return;
21374
21438
  }
21375
- const memories = await loadMemories(agent3.cwd);
21376
- if (memories.length === 0) {
21377
- push({ kind: "notice", id: nextId(), text: "nothing remembered yet" });
21378
- return;
21379
- }
21380
- const lines = memories.map((memory2) => {
21381
- const when = memory2.created.slice(0, 10);
21382
- const why = memory2.source ? `
21383
- from "${memory2.source}" on ${when}` : "";
21384
- return ` [${memory2.id}] (${memory2.scope}) ${memory2.text}${why}`;
21385
- });
21386
- push({
21387
- kind: "notice",
21388
- id: nextId(),
21389
- text: `${lines.join(`
21390
- `)}
21391
-
21392
- /memory forget <id> removes one`
21393
- });
21394
- }, [agent3, model2, push, session2.skills]);
21395
- const remember = useCallback(async (scope2) => {
21396
- if (!candidate)
21397
- return;
21398
- setCandidate(undefined);
21399
- const saved = await saveMemory({ ...candidate, scope: scope2 }, agent3.cwd).catch(() => {
21400
- return;
21401
- });
21402
- if (!saved) {
21403
- push({ kind: "notice", id: nextId(), text: "could not save that", color: theme.warning });
21439
+ await verify2(provider, model2);
21440
+ }, [verify2]);
21441
+ const chooseModel = useCallback((provider, model2) => {
21442
+ setInput("");
21443
+ setError(undefined);
21444
+ if (provider.configured)
21445
+ readyForEffort(provider, model2);
21446
+ else if (provider.kind === "oauth")
21447
+ startSignIn(provider, model2);
21448
+ else
21449
+ setScreen({ name: "key", provider, model: model2 });
21450
+ }, [readyForEffort, startSignIn]);
21451
+ const chooseProvider = useCallback((provider) => {
21452
+ setInput("");
21453
+ setError(undefined);
21454
+ setCursor(preferredModelIndex(provider.models, rest.wantedModel));
21455
+ setScreen({ name: "model", provider });
21456
+ }, [rest.wantedModel]);
21457
+ useInput5((key2, meta) => {
21458
+ if (meta.ctrl && key2 === "c") {
21459
+ finish({ outcome: "quit" });
21404
21460
  return;
21405
21461
  }
21406
- await refreshSystemPrompt(agent3, model2, session2.skills);
21407
- push({
21408
- kind: "notice",
21409
- id: nextId(),
21410
- text: `remembered [${saved.id}] (${scope2}) - /memory to review or forget it`
21411
- });
21412
- }, [agent3, candidate, model2, push, session2.skills]);
21413
- const sessionTree = useCallback(async (name, argument) => {
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>")));
21416
- if (name === "tree" || !argument) {
21417
- if (prompts.length === 0) {
21418
- push({ kind: "notice", id: nextId(), text: "nothing in this session yet" });
21462
+ if (screen.name === "choose") {
21463
+ if (key2 === "q") {
21464
+ finish({ outcome: "quit" });
21419
21465
  return;
21420
21466
  }
21421
- const lines = prompts.map((entry, index2) => {
21422
- const text2 = entry.type === "message" ? entry.message.content.find((part) => part.type === "text")?.text ?? "" : "";
21423
- return ` ${index2 + 1}. ${text2.split(`
21424
- `)[0]?.slice(0, 70) ?? ""}`;
21425
- });
21426
- push({
21427
- kind: "notice",
21428
- id: nextId(),
21429
- text: `${lines.join(`
21430
- `)}
21431
-
21432
- /rewind <n> goes back to one · /fork <n> branches from it`
21433
- });
21467
+ if (meta.upArrow)
21468
+ setCursor((c) => c <= 0 ? providers.length - 1 : c - 1);
21469
+ if (meta.downArrow)
21470
+ setCursor((c) => c >= providers.length - 1 ? 0 : c + 1);
21471
+ if (meta.return) {
21472
+ const provider = providers[cursor];
21473
+ if (provider)
21474
+ chooseProvider(provider);
21475
+ }
21434
21476
  return;
21435
21477
  }
21436
- const index = Number.parseInt(argument, 10) - 1;
21437
- const target = prompts[index];
21438
- if (!target) {
21439
- push({
21440
- kind: "notice",
21441
- id: nextId(),
21442
- text: `no prompt ${argument} in this session - /tree lists them`,
21443
- color: theme.warning
21444
- });
21478
+ if (screen.name === "model") {
21479
+ if (meta.escape) {
21480
+ setInput("");
21481
+ setError(undefined);
21482
+ setCursor(0);
21483
+ setScreen({ name: "choose" });
21484
+ return;
21485
+ }
21486
+ const matches3 = matchingModels(screen.provider.models, input);
21487
+ if (embedded && meta.ctrl && key2 === "g") {
21488
+ const selected = matches3[cursor];
21489
+ if (selected) {
21490
+ if (selected.reasoning) {
21491
+ setCursor(0);
21492
+ setScreen({ name: "effort", provider: screen.provider, model: selected });
21493
+ } else
21494
+ complete(screen.provider, selected, "global");
21495
+ }
21496
+ return;
21497
+ }
21498
+ if (meta.upArrow)
21499
+ setCursor((c) => c <= 0 ? Math.max(0, matches3.length - 1) : c - 1);
21500
+ if (meta.downArrow)
21501
+ setCursor((c) => c >= matches3.length - 1 ? 0 : c + 1);
21445
21502
  return;
21446
21503
  }
21447
- const previous = entries2[entries2.indexOf(target) - 1] ?? target;
21448
- if (name === "rewind") {
21449
- const kept = await session2.rewindTo(previous.id);
21450
- push({
21451
- kind: "notice",
21452
- id: nextId(),
21453
- text: `rewound to before prompt ${index + 1}; ${kept} message${kept === 1 ? "" : "s"} kept. Nothing was deleted - the rest is still in the transcript as another branch.`
21454
- });
21504
+ if (screen.name === "effort") {
21505
+ if (meta.escape) {
21506
+ setCursor(preferredModelIndex(screen.provider.models, rest.wantedModel));
21507
+ setScreen({ name: "model", provider: screen.provider });
21508
+ return;
21509
+ }
21510
+ if (meta.upArrow)
21511
+ setCursor((c) => c <= 0 ? EFFORTS.length - 1 : c - 1);
21512
+ if (meta.downArrow)
21513
+ setCursor((c) => c >= EFFORTS.length - 1 ? 0 : c + 1);
21514
+ const selected = EFFORTS[cursor] ?? "auto";
21515
+ if (meta.return || embedded && key2 === "g") {
21516
+ complete(screen.provider, screen.model, embedded && key2 === "g" ? "global" : defaultScope, selected === "auto" ? undefined : selected);
21517
+ }
21455
21518
  return;
21456
21519
  }
21457
- const forked = await session2.fork(previous.id);
21458
- push({
21459
- kind: "notice",
21460
- id: nextId(),
21461
- text: forked ? `forked from prompt ${index + 1} into ${forked}; this session continues there and the original is untouched` : "could not fork this session",
21462
- ...forked ? {} : { color: theme.warning }
21463
- });
21464
- }, [push, session2]);
21465
- const undoLast = useCallback(async () => {
21466
- const result = await session2.undo();
21467
- if (!result) {
21468
- push({ kind: "notice", id: nextId(), text: "nothing to undo", color: theme.warning });
21520
+ if (screen.name === "key" && !meta.escape) {
21521
+ if (meta.return) {
21522
+ submitKey();
21523
+ return;
21524
+ }
21525
+ if (meta.backspace || meta.delete) {
21526
+ secret.current = secret.current.slice(0, -1);
21527
+ setInput(secret.current);
21528
+ return;
21529
+ }
21530
+ if (meta.ctrl || meta.meta || meta.tab)
21531
+ return;
21532
+ if (key2) {
21533
+ secret.current += key2;
21534
+ setInput(secret.current);
21535
+ }
21469
21536
  return;
21470
21537
  }
21471
- const created2 = result.wasCreated.length ? ` Left in place because the batch created them: ${result.wasCreated.join(", ")}.` : "";
21472
- push({
21473
- kind: "notice",
21474
- id: nextId(),
21475
- text: result.restored.length ? `undid ${result.label}: restored ${result.restored.join(", ")}.${created2}` : `nothing to restore from ${result.label}.${created2}`
21476
- });
21477
- }, [push, session2]);
21478
- const plan2 = useCallback(async (argument) => {
21479
- const path = planPath(session2.store?.id ?? "scratch");
21480
- const [verb = "", ...rest] = (argument ?? "").split(/\s+/);
21481
- const task2 = [verb, ...rest].join(" ").trim();
21482
- if (verb === "show") {
21483
- const text2 = await readPlan(path);
21484
- push({
21485
- kind: "notice",
21486
- id: nextId(),
21487
- text: text2 ? `${path}
21488
-
21489
- ${text2}` : `no plan yet at ${path}`
21490
- });
21538
+ if (meta.escape) {
21539
+ secret.current = "";
21540
+ setInput("");
21541
+ setError(undefined);
21542
+ setScreen(screen.name === "key" || screen.name === "oauth" || screen.name === "failed" ? { name: "model", provider: screen.provider } : { name: "choose" });
21491
21543
  return;
21492
21544
  }
21493
- if (verb === "edit") {
21494
- const result = await openInEditor(path);
21495
- push({
21496
- kind: "notice",
21497
- id: nextId(),
21498
- text: result.message,
21499
- ...result.edited ? {} : { color: theme.warning }
21500
- });
21501
- return;
21545
+ if (screen.name === "failed" && key2 === "k" && screen.result.reason === "unreachable") {
21546
+ readyForEffort(screen.provider, screen.model);
21502
21547
  }
21503
- if (verb === "approve") {
21504
- const text2 = await readPlan(path);
21505
- if (!text2) {
21506
- push({
21507
- kind: "notice",
21508
- id: nextId(),
21509
- text: `there is no plan at ${path} to approve`,
21510
- color: theme.warning
21511
- });
21512
- return;
21513
- }
21514
- agent3.setPlan(text2);
21515
- const restored = modeBeforePlan.current ?? "ask";
21516
- agent3.setPermissionMode(restored);
21517
- setMode(restored);
21518
- modeBeforePlan.current = undefined;
21519
- push({
21520
- kind: "notice",
21521
- id: nextId(),
21522
- text: `plan approved and pinned for this run; permission mode: ${restored}`
21523
- });
21524
- return;
21525
- }
21526
- if (verb === "clear") {
21527
- agent3.setPlan(undefined);
21528
- push({ kind: "notice", id: nextId(), text: "plan unpinned" });
21529
- return;
21530
- }
21531
- if (task2 === "") {
21532
- push({
21533
- kind: "notice",
21534
- id: nextId(),
21535
- text: "usage: /plan <what you want planned>, then /plan edit, /plan approve",
21536
- color: theme.warning
21537
- });
21538
- return;
21539
- }
21540
- modeBeforePlan.current = agent3.permissionMode;
21541
- agent3.setPermissionMode("plan");
21542
- setMode("plan");
21543
- await runTurn(`${task2}
21544
-
21545
- ${PLAN_PROMPT}`);
21546
- const drafted = lastAssistantText.current.trim();
21547
- if (drafted === "") {
21548
- push({
21549
- kind: "notice",
21550
- id: nextId(),
21551
- text: "the model produced no plan to write",
21552
- color: theme.warning
21553
- });
21554
- return;
21555
- }
21556
- await savePlan(path, drafted);
21557
- push({
21558
- kind: "notice",
21559
- id: nextId(),
21560
- text: `plan written to ${path}
21561
- /plan edit to change it, /plan approve to pin it`
21562
- });
21563
- }, [agent3, push, runTurn, session2.store]);
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
- });
21580
- return;
21581
- }
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}]`)
21548
+ });
21549
+ if (screen.name === "choose") {
21550
+ return /* @__PURE__ */ jsxDEV12(Box10, {
21551
+ flexDirection: "column",
21552
+ children: [
21553
+ /* @__PURE__ */ jsxDEV12(Header, {
21554
+ embedded
21555
+ }, undefined, false, undefined, this),
21556
+ providers.map((provider, index) => /* @__PURE__ */ jsxDEV12(Box10, {
21557
+ children: [
21558
+ /* @__PURE__ */ jsxDEV12(Text12, {
21559
+ color: index === cursor ? theme.user : theme.muted,
21560
+ children: index === cursor ? "" : " "
21561
+ }, undefined, false, undefined, this),
21562
+ /* @__PURE__ */ jsxDEV12(Text12, {
21563
+ ...index === cursor ? { color: theme.user } : {},
21564
+ children: provider.id.padEnd(14)
21565
+ }, undefined, false, undefined, this),
21566
+ /* @__PURE__ */ jsxDEV12(Text12, {
21567
+ color: theme.muted,
21568
+ children: hint(provider)
21569
+ }, undefined, false, undefined, this)
21700
21570
  ]
21701
- ].join(`
21702
- `)
21703
- });
21704
- },
21705
- init: () => void runTurn(INIT_PROMPT),
21706
- mode: (argument) => {
21707
- if (argument && isPermissionMode(argument)) {
21708
- agent3.setPermissionMode(argument);
21709
- setMode(argument);
21710
- push({ kind: "notice", id: nextId(), text: `permission mode: ${argument}` });
21711
- } else {
21712
- push({
21713
- kind: "notice",
21714
- id: nextId(),
21715
- text: `usage: /mode <${PERMISSION_MODES.join("|")}>`,
21716
- color: theme.warning
21717
- });
21718
- }
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) })
21571
+ }, provider.id, true, undefined, this)),
21572
+ /* @__PURE__ */ jsxDEV12(Box10, {
21573
+ marginTop: 1,
21574
+ children: /* @__PURE__ */ jsxDEV12(Text12, {
21575
+ color: theme.muted,
21576
+ children: "↑↓ choose · enter select · q quit"
21577
+ }, undefined, false, undefined, this)
21578
+ }, undefined, false, undefined, this),
21579
+ error ? /* @__PURE__ */ jsxDEV12(Text12, {
21580
+ color: theme.warning,
21581
+ children: error
21582
+ }, undefined, false, undefined, this) : null
21583
+ ]
21584
+ }, undefined, true, undefined, this);
21585
+ }
21586
+ if (screen.name === "model") {
21587
+ const matches3 = matchingModels(screen.provider.models, input);
21588
+ const selected = matches3[cursor];
21589
+ const start = Math.max(0, Math.min(cursor - 3, matches3.length - 8));
21590
+ return /* @__PURE__ */ jsxDEV12(Box10, {
21591
+ flexDirection: "column",
21592
+ children: [
21593
+ /* @__PURE__ */ jsxDEV12(Header, {
21594
+ embedded
21595
+ }, undefined, false, undefined, this),
21596
+ /* @__PURE__ */ jsxDEV12(Text12, {
21597
+ children: [
21598
+ "choose a model from ",
21599
+ screen.provider.id,
21600
+ " (",
21601
+ matches3.length,
21602
+ " matching)"
21603
+ ]
21604
+ }, undefined, true, undefined, this),
21605
+ /* @__PURE__ */ jsxDEV12(Box10, {
21606
+ children: [
21607
+ /* @__PURE__ */ jsxDEV12(Text12, {
21608
+ color: theme.user,
21609
+ children: "> "
21610
+ }, undefined, false, undefined, this),
21611
+ /* @__PURE__ */ jsxDEV12(TextInput, {
21612
+ value: input,
21613
+ onChange: (value) => {
21614
+ setInput(value);
21615
+ setCursor(0);
21616
+ setError(undefined);
21617
+ },
21618
+ onSubmit: () => {
21619
+ if (selected)
21620
+ chooseModel(screen.provider, selected);
21621
+ else
21622
+ setError("no model matches that search");
21623
+ },
21624
+ placeholder: "type to filter models"
21625
+ }, undefined, false, undefined, this)
21626
+ ]
21627
+ }, undefined, true, undefined, this),
21628
+ /* @__PURE__ */ jsxDEV12(Box10, {
21629
+ flexDirection: "column",
21630
+ marginTop: 1,
21631
+ children: [
21632
+ matches3.slice(start, start + 8).map((model2) => /* @__PURE__ */ jsxDEV12(Text12, {
21633
+ color: model2 === selected ? theme.user : theme.muted,
21634
+ children: [
21635
+ model2 === selected ? "› " : " ",
21636
+ model2.id,
21637
+ " · ",
21638
+ model2.name
21639
+ ]
21640
+ }, model2.id, true, undefined, this)),
21641
+ matches3.length === 0 ? /* @__PURE__ */ jsxDEV12(Text12, {
21642
+ color: theme.muted,
21643
+ children: "no matching models"
21644
+ }, undefined, false, undefined, this) : null
21645
+ ]
21646
+ }, undefined, true, undefined, this),
21647
+ /* @__PURE__ */ jsxDEV12(Box10, {
21648
+ marginTop: 1,
21649
+ children: /* @__PURE__ */ jsxDEV12(Text12, {
21650
+ color: theme.muted,
21651
+ children: "type filter · ↑↓ choose · enter select · esc back"
21652
+ }, undefined, false, undefined, this)
21653
+ }, undefined, false, undefined, this),
21654
+ error ? /* @__PURE__ */ jsxDEV12(Text12, {
21655
+ color: theme.warning,
21656
+ children: error
21657
+ }, undefined, false, undefined, this) : null
21658
+ ]
21659
+ }, undefined, true, undefined, this);
21660
+ }
21661
+ if (screen.name === "key") {
21662
+ return /* @__PURE__ */ jsxDEV12(Box10, {
21663
+ flexDirection: "column",
21664
+ children: [
21665
+ /* @__PURE__ */ jsxDEV12(Header, {
21666
+ embedded
21667
+ }, undefined, false, undefined, this),
21668
+ /* @__PURE__ */ jsxDEV12(Text12, {
21669
+ children: [
21670
+ "paste an api key for ",
21671
+ screen.provider.id
21672
+ ]
21673
+ }, undefined, true, undefined, this),
21674
+ /* @__PURE__ */ jsxDEV12(Box10, {
21675
+ marginTop: 1,
21676
+ children: /* @__PURE__ */ jsxDEV12(Text12, {
21677
+ color: theme.muted,
21678
+ children: [
21679
+ screen.provider.envVars?.length ? `or set ${screen.provider.envVars.join(" or ")} instead and restart.
21680
+ ` : "",
21681
+ "stored in your config directory, readable only by you, and never printed."
21682
+ ]
21683
+ }, undefined, true, undefined, this)
21684
+ }, undefined, false, undefined, this),
21685
+ /* @__PURE__ */ jsxDEV12(Box10, {
21686
+ marginTop: 1,
21687
+ children: [
21688
+ /* @__PURE__ */ jsxDEV12(Text12, {
21689
+ color: theme.user,
21690
+ children: "> "
21691
+ }, undefined, false, undefined, this),
21692
+ /* @__PURE__ */ jsxDEV12(Text12, {
21693
+ children: "•".repeat(input.length)
21694
+ }, undefined, false, undefined, this),
21695
+ /* @__PURE__ */ jsxDEV12(Text12, {
21696
+ inverse: true,
21697
+ children: " "
21698
+ }, undefined, false, undefined, this)
21699
+ ]
21700
+ }, undefined, true, undefined, this),
21701
+ error ? /* @__PURE__ */ jsxDEV12(Text12, {
21702
+ color: theme.warning,
21703
+ children: error
21704
+ }, undefined, false, undefined, this) : null,
21705
+ /* @__PURE__ */ jsxDEV12(Box10, {
21706
+ marginTop: 1,
21707
+ children: /* @__PURE__ */ jsxDEV12(Text12, {
21708
+ color: theme.muted,
21709
+ children: "enter continue · esc back"
21710
+ }, undefined, false, undefined, this)
21711
+ }, undefined, false, undefined, this)
21712
+ ]
21713
+ }, undefined, true, undefined, this);
21714
+ }
21715
+ if (screen.name === "oauth") {
21716
+ return /* @__PURE__ */ jsxDEV12(Box10, {
21717
+ flexDirection: "column",
21718
+ children: [
21719
+ /* @__PURE__ */ jsxDEV12(Header, {
21720
+ embedded
21721
+ }, undefined, false, undefined, this),
21722
+ /* @__PURE__ */ jsxDEV12(Text12, {
21723
+ children: [
21724
+ "signing in to ",
21725
+ screen.provider.id,
21726
+ " in your browser…"
21727
+ ]
21728
+ }, undefined, true, undefined, this),
21729
+ screen.url ? /* @__PURE__ */ jsxDEV12(Box10, {
21730
+ marginTop: 1,
21731
+ flexDirection: "column",
21732
+ children: [
21733
+ /* @__PURE__ */ jsxDEV12(Text12, {
21734
+ color: theme.muted,
21735
+ children: "if it did not open, use this link:"
21736
+ }, undefined, false, undefined, this),
21737
+ /* @__PURE__ */ jsxDEV12(Text12, {
21738
+ children: screen.url
21739
+ }, undefined, false, undefined, this)
21740
+ ]
21741
+ }, undefined, true, undefined, this) : null,
21742
+ /* @__PURE__ */ jsxDEV12(Box10, {
21743
+ marginTop: 1,
21744
+ children: /* @__PURE__ */ jsxDEV12(Text12, {
21745
+ color: theme.muted,
21746
+ children: "esc cancel"
21747
+ }, undefined, false, undefined, this)
21748
+ }, undefined, false, undefined, this)
21749
+ ]
21750
+ }, undefined, true, undefined, this);
21751
+ }
21752
+ if (screen.name === "probing") {
21753
+ return /* @__PURE__ */ jsxDEV12(Box10, {
21754
+ flexDirection: "column",
21755
+ children: [
21756
+ /* @__PURE__ */ jsxDEV12(Header, {
21757
+ embedded
21758
+ }, undefined, false, undefined, this),
21759
+ /* @__PURE__ */ jsxDEV12(Text12, {
21760
+ color: theme.muted,
21761
+ children: [
21762
+ "checking the credentials with ",
21763
+ screen.provider.id,
21764
+ "…"
21765
+ ]
21766
+ }, undefined, true, undefined, this)
21767
+ ]
21768
+ }, undefined, true, undefined, this);
21769
+ }
21770
+ if (screen.name === "failed") {
21771
+ return /* @__PURE__ */ jsxDEV12(Box10, {
21772
+ flexDirection: "column",
21773
+ children: [
21774
+ /* @__PURE__ */ jsxDEV12(Header, {
21775
+ embedded
21776
+ }, undefined, false, undefined, this),
21777
+ /* @__PURE__ */ jsxDEV12(Text12, {
21778
+ color: theme.warning,
21779
+ 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:`
21780
+ }, undefined, false, undefined, this),
21781
+ /* @__PURE__ */ jsxDEV12(Text12, {
21782
+ color: theme.muted,
21783
+ children: screen.result.message
21784
+ }, undefined, false, undefined, this),
21785
+ /* @__PURE__ */ jsxDEV12(Box10, {
21786
+ marginTop: 1,
21787
+ children: /* @__PURE__ */ jsxDEV12(Text12, {
21788
+ color: theme.muted,
21789
+ children: screen.result.reason === "unreachable" ? "k keep it anyway and carry on · esc start over · ctrl-c quit" : "esc start over · ctrl-c quit"
21790
+ }, undefined, false, undefined, this)
21791
+ }, undefined, false, undefined, this)
21792
+ ]
21793
+ }, undefined, true, undefined, this);
21794
+ }
21795
+ if (screen.name === "effort")
21796
+ return /* @__PURE__ */ jsxDEV12(Box10, {
21797
+ flexDirection: "column",
21798
+ children: [
21799
+ /* @__PURE__ */ jsxDEV12(Header, {
21800
+ embedded
21801
+ }, undefined, false, undefined, this),
21802
+ /* @__PURE__ */ jsxDEV12(Text12, {
21803
+ children: [
21804
+ "reasoning effort for ",
21805
+ screen.model.name
21806
+ ]
21807
+ }, undefined, true, undefined, this),
21808
+ /* @__PURE__ */ jsxDEV12(Box10, {
21809
+ flexDirection: "column",
21810
+ marginTop: 1,
21811
+ children: EFFORTS.map((effort, index) => /* @__PURE__ */ jsxDEV12(Text12, {
21812
+ color: index === cursor ? theme.user : theme.muted,
21813
+ children: [
21814
+ index === cursor ? "› " : " ",
21815
+ effort
21816
+ ]
21817
+ }, effort, true, undefined, this))
21818
+ }, undefined, false, undefined, this),
21819
+ /* @__PURE__ */ jsxDEV12(Text12, {
21820
+ color: theme.muted,
21821
+ children: [
21822
+ "↑↓ choose · enter use",
21823
+ embedded ? " here · g use everywhere" : "",
21824
+ " · esc back"
21825
+ ]
21826
+ }, undefined, true, undefined, this)
21827
+ ]
21828
+ }, undefined, true, undefined, this);
21829
+ return null;
21830
+ }
21831
+ function Header({ embedded }) {
21832
+ return /* @__PURE__ */ jsxDEV12(Box10, {
21833
+ flexDirection: "column",
21834
+ marginBottom: 1,
21835
+ children: /* @__PURE__ */ jsxDEV12(Text12, {
21836
+ children: embedded ? "switch model" : "earshot needs a model provider before it can do anything."
21837
+ }, undefined, false, undefined, this)
21838
+ }, undefined, false, undefined, this);
21839
+ }
21840
+ function hint(provider) {
21841
+ if (provider.configured)
21842
+ return provider.configured;
21843
+ if (provider.kind === "oauth")
21844
+ return "sign in with a browser - no key to paste";
21845
+ return provider.envVars?.length ? provider.envVars.join(" or ") : "api key";
21846
+ }
21847
+ function matchingModels(models, query) {
21848
+ const wanted = query.trim().toLowerCase();
21849
+ if (!wanted)
21850
+ return models;
21851
+ return models.filter((model2) => model2.id.toLowerCase().includes(wanted) || model2.name.toLowerCase().includes(wanted));
21852
+ }
21853
+ function preferredModelIndex(models, wanted) {
21854
+ if (!wanted)
21855
+ return 0;
21856
+ const modelId = wanted.includes("/") ? wanted.slice(wanted.indexOf("/") + 1) : wanted;
21857
+ const index = models.findIndex((model2) => model2.id === wanted || model2.id === modelId);
21858
+ return Math.max(0, index);
21859
+ }
21860
+ function order(providers, wanted) {
21861
+ if (!wanted)
21862
+ return providers;
21863
+ return [
21864
+ ...providers.filter((provider) => provider.id === wanted),
21865
+ ...providers.filter((provider) => provider.id !== wanted)
21866
+ ];
21867
+ }
21868
+
21869
+ // packages/tui/src/sessions.tsx
21870
+ import { Box as Box11, Text as Text13, useApp as useApp2, useInput as useInput6 } from "ink";
21871
+ import { useMemo, useState as useState6 } from "react";
21872
+ import { jsxDEV as jsxDEV13 } from "react/jsx-dev-runtime";
21873
+ function SessionPicker({
21874
+ sessions,
21875
+ onDone,
21876
+ embedded = false
21877
+ }) {
21878
+ const { exit } = useApp2();
21879
+ const [query, setQuery] = useState6("");
21880
+ const [cursor, setCursor] = useState6(0);
21881
+ const matches3 = useMemo(() => {
21882
+ const wanted = query.trim().toLowerCase();
21883
+ return wanted ? sessions.filter((session2) => `${session2.preview} ${session2.model} ${session2.id}`.toLowerCase().includes(wanted)) : sessions;
21884
+ }, [query, sessions]);
21885
+ const finish = (path) => {
21886
+ onDone(path);
21887
+ if (!embedded)
21888
+ exit();
21727
21889
  };
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) {
21738
- push({
21739
- kind: "notice",
21740
- id: nextId(),
21741
- text: "finish or interrupt the current turn first (esc)",
21742
- color: theme.warning
21743
- });
21744
- return;
21745
- }
21746
- handlersRef.current[spec.name](argument);
21747
- return;
21748
- }
21749
- const custom = session2.commands.find((entry) => entry.name === name);
21750
- if (custom) {
21751
- const prompt = expandCommand(custom, argument ?? "");
21752
- if (prompt.trim() === "") {
21753
- push({
21754
- kind: "notice",
21755
- id: nextId(),
21756
- text: `/${name} expanded to nothing`,
21757
- color: theme.warning
21758
- });
21759
- return;
21760
- }
21761
- if (busy) {
21762
- agent3.steer(prompt);
21890
+ useInput6((input, key2) => {
21891
+ if (key2.escape || input === "q" && query === "")
21892
+ return finish();
21893
+ if (key2.upArrow)
21894
+ setCursor((value) => value <= 0 ? Math.max(0, matches3.length - 1) : value - 1);
21895
+ if (key2.downArrow)
21896
+ setCursor((value) => value >= matches3.length - 1 ? 0 : value + 1);
21897
+ });
21898
+ return /* @__PURE__ */ jsxDEV13(Box11, {
21899
+ flexDirection: "column",
21900
+ children: [
21901
+ /* @__PURE__ */ jsxDEV13(Text13, {
21902
+ children: "saved chats for this project"
21903
+ }, undefined, false, undefined, this),
21904
+ /* @__PURE__ */ jsxDEV13(Box11, {
21905
+ marginTop: 1,
21906
+ children: [
21907
+ /* @__PURE__ */ jsxDEV13(Text13, {
21908
+ color: theme.user,
21909
+ children: "> "
21910
+ }, undefined, false, undefined, this),
21911
+ /* @__PURE__ */ jsxDEV13(TextInput, {
21912
+ value: query,
21913
+ onChange: (value) => {
21914
+ setQuery(value);
21915
+ setCursor(0);
21916
+ },
21917
+ onSubmit: () => {
21918
+ const selected = matches3[cursor];
21919
+ if (selected)
21920
+ finish(selected.path);
21921
+ },
21922
+ placeholder: "filter chats"
21923
+ }, undefined, false, undefined, this)
21924
+ ]
21925
+ }, undefined, true, undefined, this),
21926
+ /* @__PURE__ */ jsxDEV13(Box11, {
21927
+ flexDirection: "column",
21928
+ marginTop: 1,
21929
+ children: [
21930
+ matches3.slice(Math.max(0, cursor - 4), Math.max(0, cursor - 4) + 9).map((session2) => /* @__PURE__ */ jsxDEV13(Text13, {
21931
+ color: session2 === matches3[cursor] ? theme.user : theme.muted,
21932
+ children: [
21933
+ session2 === matches3[cursor] ? "› " : " ",
21934
+ session2.preview.padEnd(42).slice(0, 42),
21935
+ " ",
21936
+ relativeTime(session2.updatedAt).padStart(8),
21937
+ " ",
21938
+ session2.model,
21939
+ " ",
21940
+ session2.id.slice(0, 8)
21941
+ ]
21942
+ }, session2.id, true, undefined, this)),
21943
+ matches3.length === 0 && /* @__PURE__ */ jsxDEV13(Text13, {
21944
+ color: theme.muted,
21945
+ children: "no saved chats match"
21946
+ }, undefined, false, undefined, this)
21947
+ ]
21948
+ }, undefined, true, undefined, this),
21949
+ /* @__PURE__ */ jsxDEV13(Box11, {
21950
+ marginTop: 1,
21951
+ children: /* @__PURE__ */ jsxDEV13(Text13, {
21952
+ color: theme.muted,
21953
+ children: "type filter · ↑↓ choose · enter resume · esc quit"
21954
+ }, undefined, false, undefined, this)
21955
+ }, undefined, false, undefined, this)
21956
+ ]
21957
+ }, undefined, true, undefined, this);
21958
+ }
21959
+ function relativeTime(updatedAt) {
21960
+ const seconds = Math.max(0, Math.floor((Date.now() - updatedAt) / 1000));
21961
+ if (seconds < 60)
21962
+ return "now";
21963
+ const minutes = Math.floor(seconds / 60);
21964
+ if (minutes < 60)
21965
+ return `${minutes}m ago`;
21966
+ const hours = Math.floor(minutes / 60);
21967
+ if (hours < 24)
21968
+ return `${hours}h ago`;
21969
+ return `${Math.floor(hours / 24)}d ago`;
21970
+ }
21971
+
21972
+ // packages/tui/src/app.tsx
21973
+ import { jsxDEV as jsxDEV14 } from "react/jsx-dev-runtime";
21974
+ var sequence = 0;
21975
+ var nextId = () => `item_${sequence++}`;
21976
+ function promptLabel(prompt) {
21977
+ if (typeof prompt === "string")
21978
+ return prompt;
21979
+ return prompt.map((part) => part.type === "text" ? part.text : `[attached ${part.mediaType} image]`).join(`
21980
+ `);
21981
+ }
21982
+ function App({
21983
+ session: session2,
21984
+ model: initialModel,
21985
+ initialPrompt,
21986
+ modelOptions,
21987
+ onResume
21988
+ }) {
21989
+ const { exit } = useApp3();
21990
+ const agent3 = session2.agent;
21991
+ const [model2, setModel] = useState7(initialModel);
21992
+ const [items, setItems] = useState7(() => session2.problems.map((problem) => ({
21993
+ kind: "notice",
21994
+ id: nextId(),
21995
+ text: `warning: ${problem}`,
21996
+ color: theme.warning
21997
+ })));
21998
+ const [live, setLive] = useState7("");
21999
+ const [reasoningLive, setReasoningLive] = useState7("");
22000
+ const [showThinking, setShowThinking] = useState7(true);
22001
+ const [activity, setActivity] = useState7();
22002
+ const [choosingModel, setChoosingModel] = useState7(false);
22003
+ const [choosingReasoning, setChoosingReasoning] = useState7(false);
22004
+ const [sessionChoices, setSessionChoices] = useState7();
22005
+ const [runningTool, setRunningTool] = useState7();
22006
+ const [input, setInput] = useState7("");
22007
+ const [menuIndex, setMenuIndex] = useState7(0);
22008
+ const [busy, setBusy] = useState7(false);
22009
+ const [mode, setMode] = useState7(agent3.permissionMode);
22010
+ const [cost, setCost] = useState7(0);
22011
+ const [todos, setTodos] = useState7([]);
22012
+ const [queued, setQueued] = useState7(0);
22013
+ const [candidate, setCandidate] = useState7();
22014
+ const [context2, setContext] = useState7(() => agent3.contextUse);
22015
+ const [compacted, setCompacted] = useState7(0);
22016
+ const [pending, setPending] = useState7();
22017
+ const [question, setQuestion] = useState7();
22018
+ const controller = useRef3(undefined);
22019
+ const push = useCallback2((item) => setItems((current) => [...current, item]), []);
22020
+ const pendingRef = useRef3(setPending);
22021
+ pendingRef.current = setPending;
22022
+ const questionRef = useRef3(setQuestion);
22023
+ questionRef.current = setQuestion;
22024
+ useEffect3(() => {
22025
+ session2.installPrompt((request, reason) => new Promise((resolve4) => {
22026
+ pendingRef.current({ request, reason, resolve: resolve4 });
22027
+ }));
22028
+ session2.installAsk((q, options) => new Promise((resolve4) => {
22029
+ questionRef.current({ question: q, ...options ? { options } : {}, resolve: resolve4 });
22030
+ }));
22031
+ }, [session2]);
22032
+ const modeBeforePlan = useRef3(undefined);
22033
+ const lastAssistantText = useRef3("");
22034
+ const runTurn = useCallback2(async (prompt) => {
22035
+ setBusy(true);
22036
+ setActivity("preparing");
22037
+ lastAssistantText.current = "";
22038
+ push({ kind: "user", id: nextId(), text: promptLabel(prompt) });
22039
+ const abort = new AbortController;
22040
+ controller.current = abort;
22041
+ let assistantText = "";
22042
+ let reasoningText = "";
22043
+ let reasoningFlushed = false;
22044
+ try {
22045
+ for await (const event of agent3.runTurn(prompt, abort.signal)) {
22046
+ switch (event.type) {
22047
+ case "model_start":
22048
+ setActivity("thinking");
22049
+ break;
22050
+ case "text_delta":
22051
+ if (!reasoningFlushed && reasoningText.trim() !== "" && showThinking) {
22052
+ push({ kind: "reasoning", id: nextId(), text: reasoningText.trimEnd() });
22053
+ reasoningFlushed = true;
22054
+ setReasoningLive("");
22055
+ }
22056
+ setActivity(undefined);
22057
+ assistantText += event.text;
22058
+ setLive(assistantText);
22059
+ break;
22060
+ case "reasoning_delta":
22061
+ setActivity("reasoning");
22062
+ reasoningText += event.text;
22063
+ if (showThinking)
22064
+ setReasoningLive(reasoningText);
22065
+ break;
22066
+ case "intent":
22067
+ if (event.text === undefined) {
22068
+ push({
22069
+ kind: "notice",
22070
+ id: nextId(),
22071
+ text: `about to run ${event.calls} tool call${event.calls === 1 ? "" : "s"} without saying why`,
22072
+ color: theme.warning
22073
+ });
22074
+ }
22075
+ break;
22076
+ case "tool_start":
22077
+ setActivity(undefined);
22078
+ if (assistantText.trim() !== "") {
22079
+ push({ kind: "assistant", id: nextId(), text: assistantText.trimEnd() });
22080
+ assistantText = "";
22081
+ setLive("");
22082
+ }
22083
+ setRunningTool(event.call.toolName);
22084
+ break;
22085
+ case "tool_end": {
22086
+ setRunningTool(undefined);
22087
+ const output = event.result.output.type === "text" ? event.result.output.value : "";
22088
+ push({
22089
+ kind: "tool",
22090
+ id: nextId(),
22091
+ name: event.toolName,
22092
+ ...event.result.title ? { title: event.result.title } : {},
22093
+ output,
22094
+ ...event.result.isError ? { isError: true } : {}
22095
+ });
22096
+ setTodos(agent3.todos.list());
22097
+ break;
22098
+ }
22099
+ case "usage":
22100
+ setCost(agent3.costUsd);
22101
+ setContext(agent3.contextUse);
22102
+ break;
22103
+ case "budget":
22104
+ push({
22105
+ kind: "notice",
22106
+ id: nextId(),
22107
+ text: event.raisedTo === undefined ? `stopped: $${event.spentUsd.toFixed(2)} spent against a ` + `$${event.limitUsd.toFixed(2)} budget` : `budget raised to $${event.raisedTo.toFixed(2)} after ` + `$${event.spentUsd.toFixed(2)} spent`,
22108
+ color: theme.warning
22109
+ });
22110
+ break;
22111
+ case "verification":
22112
+ push({
22113
+ kind: "tool",
22114
+ id: nextId(),
22115
+ name: event.result.command,
22116
+ title: `${event.result.command} - exit ${event.result.exitCode ?? "killed"}`,
22117
+ output: event.result.output,
22118
+ ...event.result.exitCode === 0 ? {} : { isError: true }
22119
+ });
22120
+ break;
22121
+ case "subagent":
22122
+ push({
22123
+ kind: "notice",
22124
+ id: nextId(),
22125
+ text: `subagent "${event.description}": ${event.steps} step${event.steps === 1 ? "" : "s"}, $${event.costUsd.toFixed(4)}`
22126
+ });
22127
+ break;
22128
+ case "hook":
22129
+ if (event.blocked) {
22130
+ push({
22131
+ kind: "notice",
22132
+ id: nextId(),
22133
+ text: `${event.event} hook blocked this: ${event.blocked}`,
22134
+ color: theme.warning
22135
+ });
22136
+ }
22137
+ for (const problem of event.problems) {
22138
+ push({ kind: "notice", id: nextId(), text: problem, color: theme.warning });
22139
+ }
22140
+ break;
22141
+ case "compacted":
22142
+ setCompacted((count) => count + event.replaced);
22143
+ push({
22144
+ kind: "notice",
22145
+ id: nextId(),
22146
+ text: `compacted: ${event.replaced} earlier messages are now a summary`
22147
+ });
22148
+ break;
22149
+ case "error":
22150
+ setActivity(undefined);
22151
+ push({
22152
+ kind: "notice",
22153
+ id: nextId(),
22154
+ text: `error: ${event.error.message}`,
22155
+ color: theme.danger
22156
+ });
22157
+ break;
22158
+ case "turn_end":
22159
+ if (event.reason === "aborted") {
22160
+ push({
22161
+ kind: "notice",
22162
+ id: nextId(),
22163
+ text: "interrupted",
22164
+ color: theme.warning
22165
+ });
22166
+ }
22167
+ if (event.reason === "max_steps") {
22168
+ push({
22169
+ kind: "notice",
22170
+ id: nextId(),
22171
+ text: "stopped: step limit reached",
22172
+ color: theme.warning
22173
+ });
22174
+ }
22175
+ break;
22176
+ default:
22177
+ break;
22178
+ }
21763
22179
  setQueued(agent3.pendingSteers);
21764
- push({ kind: "user", id: nextId(), text: command });
21765
- return;
21766
22180
  }
21767
- runTurn(prompt);
22181
+ } finally {
22182
+ if (!reasoningFlushed && reasoningText.trim() !== "" && showThinking) {
22183
+ push({ kind: "reasoning", id: nextId(), text: reasoningText.trimEnd() });
22184
+ }
22185
+ if (assistantText.trim() !== "") {
22186
+ lastAssistantText.current = assistantText;
22187
+ push({ kind: "assistant", id: nextId(), text: assistantText.trimEnd() });
22188
+ }
22189
+ setLive("");
22190
+ setReasoningLive("");
22191
+ setActivity(undefined);
22192
+ setRunningTool(undefined);
22193
+ setBusy(false);
22194
+ setQueued(agent3.pendingSteers);
22195
+ controller.current = undefined;
22196
+ }
22197
+ }, [agent3, push, showThinking]);
22198
+ const started = useRef3(false);
22199
+ useEffect3(() => {
22200
+ if (started.current || !initialPrompt)
22201
+ return;
22202
+ started.current = true;
22203
+ runTurn(initialPrompt);
22204
+ }, [initialPrompt, runTurn]);
22205
+ const showMemories = useCallback2(async (argument) => {
22206
+ const [verb, ...rest] = (argument ?? "").split(/\s+/);
22207
+ const id = rest.join(" ").trim();
22208
+ if (verb === "forget" && id) {
22209
+ const gone = await deleteMemory(id, agent3.cwd);
22210
+ if (gone)
22211
+ await refreshSystemPrompt(agent3, model2, session2.skills);
22212
+ push({
22213
+ kind: "notice",
22214
+ id: nextId(),
22215
+ text: gone ? `forgot ${id}` : `no memory called "${id}"`,
22216
+ ...gone ? {} : { color: theme.warning }
22217
+ });
22218
+ return;
22219
+ }
22220
+ const memories = await loadMemories(agent3.cwd);
22221
+ if (memories.length === 0) {
22222
+ push({ kind: "notice", id: nextId(), text: "nothing remembered yet" });
21768
22223
  return;
21769
22224
  }
22225
+ const lines = memories.map((memory2) => {
22226
+ const when = memory2.created.slice(0, 10);
22227
+ const why = memory2.source ? `
22228
+ from "${memory2.source}" on ${when}` : "";
22229
+ return ` [${memory2.id}] (${memory2.scope}) ${memory2.text}${why}`;
22230
+ });
21770
22231
  push({
21771
22232
  kind: "notice",
21772
22233
  id: nextId(),
21773
- text: `unknown command "${name}"`,
21774
- color: theme.warning
22234
+ text: `${lines.join(`
22235
+ `)}
22236
+
22237
+ /memory forget <id> removes one`
21775
22238
  });
21776
- }, [agent3, busy, push, runTurn, session2]);
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
- }
21785
- const trimmed = text2.trim();
21786
- setInput("");
21787
- if (trimmed === "")
22239
+ }, [agent3, model2, push, session2.skills]);
22240
+ const remember = useCallback2(async (scope2) => {
22241
+ if (!candidate)
21788
22242
  return;
21789
- if (trimmed.startsWith("/")) {
21790
- handleCommand(trimmed);
22243
+ setCandidate(undefined);
22244
+ const saved = await saveMemory({ ...candidate, scope: scope2 }, agent3.cwd).catch(() => {
21791
22245
  return;
21792
- }
21793
- setCandidate(detectPreference(trimmed));
21794
- if (busy) {
21795
- agent3.steer(trimmed);
21796
- setQueued(agent3.pendingSteers);
21797
- push({ kind: "user", id: nextId(), text: trimmed });
22246
+ });
22247
+ if (!saved) {
22248
+ push({ kind: "notice", id: nextId(), text: "could not save that", color: theme.warning });
21798
22249
  return;
21799
22250
  }
21800
- runTurn(trimmed);
21801
- }, [agent3, busy, push, runTurn, handleCommand]);
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;
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);
22251
+ await refreshSystemPrompt(agent3, model2, session2.skills);
22252
+ push({
22253
+ kind: "notice",
22254
+ id: nextId(),
22255
+ text: `remembered [${saved.id}] (${scope2}) - /memory to review or forget it`
22256
+ });
22257
+ }, [agent3, candidate, model2, push, session2.skills]);
22258
+ const sessionTree = useCallback2(async (name, argument) => {
22259
+ const entries2 = await session2.branch();
22260
+ 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>")));
22261
+ if (name === "tree" || !argument) {
22262
+ if (prompts.length === 0) {
22263
+ push({ kind: "notice", id: nextId(), text: "nothing in this session yet" });
21832
22264
  return;
21833
22265
  }
22266
+ const lines = prompts.map((entry, index2) => {
22267
+ const text2 = entry.type === "message" ? entry.message.content.find((part) => part.type === "text")?.text ?? "" : "";
22268
+ return ` ${index2 + 1}. ${text2.split(`
22269
+ `)[0]?.slice(0, 70) ?? ""}`;
22270
+ });
22271
+ push({
22272
+ kind: "notice",
22273
+ id: nextId(),
22274
+ text: `${lines.join(`
22275
+ `)}
22276
+
22277
+ /rewind <n> goes back to one · /fork <n> branches from it`
22278
+ });
22279
+ return;
21834
22280
  }
21835
- if (key2.escape) {
21836
- setCandidate(undefined);
21837
- if (busy)
21838
- controller.current?.abort();
22281
+ const index = Number.parseInt(argument, 10) - 1;
22282
+ const target = prompts[index];
22283
+ if (!target) {
22284
+ push({
22285
+ kind: "notice",
22286
+ id: nextId(),
22287
+ text: `no prompt ${argument} in this session - /tree lists them`,
22288
+ color: theme.warning
22289
+ });
21839
22290
  return;
21840
22291
  }
21841
- if (key2.ctrl && candidate && (input_ === "r" || input_ === "g")) {
21842
- remember(input_ === "r" ? "project" : "user");
22292
+ const previous = entries2[entries2.indexOf(target) - 1] ?? target;
22293
+ if (name === "rewind") {
22294
+ const kept = await session2.rewindTo(previous.id);
22295
+ push({
22296
+ kind: "notice",
22297
+ id: nextId(),
22298
+ text: `rewound to before prompt ${index + 1}; ${kept} message${kept === 1 ? "" : "s"} kept. Nothing was deleted - the rest is still in the transcript as another branch.`
22299
+ });
22300
+ return;
21843
22301
  }
21844
- }, { isActive: inputActive });
21845
- return /* @__PURE__ */ jsxDEV10(Box9, {
21846
- flexDirection: "column",
21847
- children: [
21848
- /* @__PURE__ */ jsxDEV10(Static, {
21849
- items,
21850
- children: (item) => /* @__PURE__ */ jsxDEV10(ScrollRow, {
21851
- item
21852
- }, item.id, false, undefined, this)
21853
- }, undefined, false, undefined, this),
21854
- live !== "" && /* @__PURE__ */ jsxDEV10(Box9, {
21855
- marginTop: 1,
21856
- children: /* @__PURE__ */ jsxDEV10(Markdown, {
21857
- text: live
21858
- }, undefined, false, undefined, this)
21859
- }, undefined, false, undefined, this),
21860
- runningTool && /* @__PURE__ */ jsxDEV10(ToolBlock, {
21861
- name: runningTool,
21862
- running: true
21863
- }, undefined, false, undefined, this),
21864
- pending && /* @__PURE__ */ jsxDEV10(PermissionPrompt, {
21865
- request: pending.request,
21866
- reason: pending.reason,
21867
- onChoice: (choice) => {
21868
- setPending(undefined);
21869
- pending.resolve(choice);
21870
- }
21871
- }, undefined, false, undefined, this),
21872
- question && /* @__PURE__ */ jsxDEV10(QuestionPrompt, {
21873
- question: question.question,
21874
- ...question.options ? { options: question.options } : {},
21875
- onAnswer: (answer) => {
21876
- setQuestion(undefined);
21877
- question.resolve(answer);
21878
- }
21879
- }, undefined, false, undefined, this),
21880
- candidate && inputActive && /* @__PURE__ */ jsxDEV10(MemoryCapture, {
21881
- candidate
21882
- }, undefined, false, undefined, this),
21883
- menuOpen && /* @__PURE__ */ jsxDEV10(CommandMenu, {
21884
- entries,
21885
- selected
21886
- }, undefined, false, undefined, this),
21887
- inputActive && /* @__PURE__ */ jsxDEV10(Box9, {
21888
- marginTop: 1,
21889
- children: [
21890
- /* @__PURE__ */ jsxDEV10(Text10, {
21891
- color: theme.user,
21892
- children: "> "
21893
- }, undefined, false, undefined, this),
21894
- /* @__PURE__ */ jsxDEV10(TextInput, {
21895
- value: input,
21896
- onChange: (value) => {
21897
- setInput(value);
21898
- setMenuIndex(0);
21899
- },
21900
- onSubmit: submit,
21901
- placeholder: busy ? "steer the agent, or esc to interrupt" : "what should I do?"
21902
- }, undefined, false, undefined, this)
21903
- ]
21904
- }, undefined, true, undefined, this),
21905
- /* @__PURE__ */ jsxDEV10(StatusLine, {
21906
- model: model2,
21907
- mode,
21908
- costUsd: cost,
21909
- todos,
21910
- busy,
21911
- queued,
21912
- context: context2,
21913
- compacted
21914
- }, undefined, false, undefined, this)
21915
- ]
21916
- }, undefined, true, undefined, this);
21917
- }
21918
- function ScrollRow({ item }) {
21919
- if (item.kind === "user") {
21920
- return /* @__PURE__ */ jsxDEV10(Box9, {
21921
- marginTop: 1,
21922
- children: [
21923
- /* @__PURE__ */ jsxDEV10(Text10, {
21924
- color: theme.user,
21925
- children: "> "
21926
- }, undefined, false, undefined, this),
21927
- /* @__PURE__ */ jsxDEV10(Text10, {
21928
- children: item.text
21929
- }, undefined, false, undefined, this)
21930
- ]
21931
- }, undefined, true, undefined, this);
21932
- }
21933
- if (item.kind === "assistant") {
21934
- return /* @__PURE__ */ jsxDEV10(Box9, {
21935
- marginTop: 1,
21936
- children: /* @__PURE__ */ jsxDEV10(Markdown, {
21937
- text: item.text
21938
- }, undefined, false, undefined, this)
21939
- }, undefined, false, undefined, this);
21940
- }
21941
- if (item.kind === "tool") {
21942
- return /* @__PURE__ */ jsxDEV10(ToolBlock, {
21943
- name: item.name,
21944
- ...item.title ? { title: item.title } : {},
21945
- ...item.output ? { output: item.output } : {},
21946
- ...item.isError ? { isError: true } : {}
21947
- }, undefined, false, undefined, this);
21948
- }
21949
- return /* @__PURE__ */ jsxDEV10(Box9, {
21950
- marginTop: 1,
21951
- children: /* @__PURE__ */ jsxDEV10(Text10, {
21952
- color: item.color ?? theme.muted,
21953
- children: item.text
21954
- }, undefined, false, undefined, this)
21955
- }, undefined, false, undefined, this);
21956
- }
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}`);
22302
+ const forked = await session2.fork(previous.id);
22303
+ push({
22304
+ kind: "notice",
22305
+ id: nextId(),
22306
+ text: forked ? `forked from prompt ${index + 1} into ${forked}; this session continues there and the original is untouched` : "could not fork this session",
22307
+ ...forked ? {} : { color: theme.warning }
22308
+ });
22309
+ }, [push, session2]);
22310
+ const undoLast = useCallback2(async () => {
22311
+ const result = await session2.undo();
22312
+ if (!result) {
22313
+ push({ kind: "notice", id: nextId(), text: "nothing to undo", color: theme.warning });
22314
+ return;
21963
22315
  }
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.
22316
+ const created2 = result.wasCreated.length ? ` Left in place because the batch created them: ${result.wasCreated.join(", ")}.` : "";
22317
+ push({
22318
+ kind: "notice",
22319
+ id: nextId(),
22320
+ text: result.restored.length ? `undid ${result.label}: restored ${result.restored.join(", ")}.${created2}` : `nothing to restore from ${result.label}.${created2}`
22321
+ });
22322
+ }, [push, session2]);
22323
+ const plan2 = useCallback2(async (argument) => {
22324
+ const path = planPath(session2.store?.id ?? "scratch");
22325
+ const [verb = "", ...rest] = (argument ?? "").split(/\s+/);
22326
+ const task2 = [verb, ...rest].join(" ").trim();
22327
+ if (verb === "show") {
22328
+ const text2 = await readPlan(path);
22329
+ push({
22330
+ kind: "notice",
22331
+ id: nextId(),
22332
+ text: text2 ? `${path}
21970
22333
 
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 = [];
21974
- if (session2.skills.length > 0) {
21975
- lines.push("skills (the agent loads these itself when they fit):");
21976
- for (const skill of session2.skills) {
21977
- lines.push(` ${skill.name} [${skill.scope}] ${skill.description}`);
22334
+ ${text2}` : `no plan yet at ${path}`
22335
+ });
22336
+ return;
21978
22337
  }
21979
- }
21980
- if (session2.commands.length > 0) {
21981
- if (lines.length > 0)
21982
- lines.push("");
21983
- lines.push("commands you can type:");
21984
- for (const command of session2.commands) {
21985
- lines.push(` /${command.name} [${command.scope}] ${command.description}`);
22338
+ if (verb === "edit") {
22339
+ const result = await openInEditor(path);
22340
+ push({
22341
+ kind: "notice",
22342
+ id: nextId(),
22343
+ text: result.message,
22344
+ ...result.edited ? {} : { color: theme.warning }
22345
+ });
22346
+ return;
21986
22347
  }
21987
- }
21988
- return lines.length === 0 ? "no skills or commands found in .earshot/skills, .earshot/commands or your config directory" : lines.join(`
21989
- `);
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 });
22348
+ if (verb === "approve") {
22349
+ const text2 = await readPlan(path);
22350
+ if (!text2) {
22351
+ push({
22352
+ kind: "notice",
22353
+ id: nextId(),
22354
+ text: `there is no plan at ${path} to approve`,
22355
+ color: theme.warning
22356
+ });
22357
+ return;
22358
+ }
22359
+ agent3.setPlan(text2);
22360
+ const restored = modeBeforePlan.current ?? "ask";
22361
+ agent3.setPermissionMode(restored);
22362
+ setMode(restored);
22363
+ modeBeforePlan.current = undefined;
22364
+ push({
22365
+ kind: "notice",
22366
+ id: nextId(),
22367
+ text: `plan approved and pinned for this run; permission mode: ${restored}`
22368
+ });
22014
22369
  return;
22015
22370
  }
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)
22371
+ if (verb === "clear") {
22372
+ agent3.setPlan(undefined);
22373
+ push({ kind: "notice", id: nextId(), text: "plan unpinned" });
22026
22374
  return;
22027
- if (key2.trim() === "") {
22028
- setError("a key is needed, or press esc to go back");
22375
+ }
22376
+ if (task2 === "") {
22377
+ push({
22378
+ kind: "notice",
22379
+ id: nextId(),
22380
+ text: "usage: /plan <what you want planned>, then /plan edit, /plan approve",
22381
+ color: theme.warning
22382
+ });
22383
+ return;
22384
+ }
22385
+ modeBeforePlan.current = agent3.permissionMode;
22386
+ agent3.setPermissionMode("plan");
22387
+ setMode("plan");
22388
+ await runTurn(`${task2}
22389
+
22390
+ ${PLAN_PROMPT}`);
22391
+ const drafted = lastAssistantText.current.trim();
22392
+ if (drafted === "") {
22393
+ push({
22394
+ kind: "notice",
22395
+ id: nextId(),
22396
+ text: "the model produced no plan to write",
22397
+ color: theme.warning
22398
+ });
22399
+ return;
22400
+ }
22401
+ await savePlan(path, drafted);
22402
+ push({
22403
+ kind: "notice",
22404
+ id: nextId(),
22405
+ text: `plan written to ${path}
22406
+ /plan edit to change it, /plan approve to pin it`
22407
+ });
22408
+ }, [agent3, push, runTurn, session2.store]);
22409
+ const switchModel = useCallback2(async (ref) => {
22410
+ if (!ref) {
22411
+ if (modelOptions)
22412
+ setChoosingModel(true);
22413
+ else
22414
+ push({ kind: "notice", id: nextId(), text: "model picker is unavailable" });
22029
22415
  return;
22030
22416
  }
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
22417
  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 }
22418
+ const resolved = await agent3.changeModel(ref);
22419
+ const next = `${resolved.provider.id}/${resolved.model.id}`;
22420
+ const effort = modelOptions?.reasoningFor?.(next);
22421
+ agent3.setReasoningEffort(effort);
22422
+ setModel(next);
22423
+ await refreshSystemPrompt(agent3, next, session2.skills);
22424
+ await modelOptions?.remember?.(next, effort, "project");
22425
+ await session2.recordConfiguration({ model: next, reasoningEffort: effort ?? null });
22426
+ push({
22427
+ kind: "notice",
22428
+ id: nextId(),
22429
+ text: `model: ${next}${effort ? ` · ${effort}` : ""}`
22430
+ });
22431
+ } catch (error) {
22432
+ push({
22433
+ kind: "notice",
22434
+ id: nextId(),
22435
+ text: `${error.message}`,
22436
+ color: theme.warning
22044
22437
  });
22438
+ }
22439
+ }, [agent3, modelOptions, push, session2]);
22440
+ const finishModelChoice = useCallback2(async (result) => {
22441
+ if (result.outcome === "quit" || !result.model) {
22442
+ setChoosingModel(false);
22045
22443
  return;
22046
22444
  }
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" });
22445
+ try {
22446
+ const resolved = await agent3.changeModel(result.model);
22447
+ const next = `${resolved.provider.id}/${resolved.model.id}`;
22448
+ agent3.setReasoningEffort(result.reasoningEffort);
22449
+ setModel(next);
22450
+ await refreshSystemPrompt(agent3, next, session2.skills);
22451
+ await modelOptions?.remember?.(next, result.reasoningEffort, result.scope ?? "project");
22452
+ await session2.recordConfiguration({
22453
+ model: next,
22454
+ reasoningEffort: result.reasoningEffort ?? null
22455
+ });
22456
+ push({
22457
+ kind: "notice",
22458
+ id: nextId(),
22459
+ text: `model: ${next}${result.reasoningEffort ? ` · ${result.reasoningEffort}` : " · auto"}`
22460
+ });
22461
+ } catch (error) {
22462
+ push({
22463
+ kind: "notice",
22464
+ id: nextId(),
22465
+ text: error.message,
22466
+ color: theme.warning
22467
+ });
22468
+ } finally {
22469
+ setChoosingModel(false);
22470
+ }
22471
+ }, [agent3, modelOptions, push, session2, session2.skills]);
22472
+ const changeReasoning = useCallback2(async (argument) => {
22473
+ const value = argument?.trim().toLowerCase();
22474
+ if (!value) {
22475
+ if (!agent3.model.model.capabilities.reasoning) {
22476
+ push({
22477
+ kind: "notice",
22478
+ id: nextId(),
22479
+ text: `${agent3.model.model.name} does not support reasoning`,
22480
+ color: theme.warning
22481
+ });
22482
+ } else
22483
+ setChoosingReasoning(true);
22059
22484
  return;
22060
22485
  }
22061
- if (screen.name === "choose") {
22062
- if (key2 === "q") {
22063
- finish({ outcome: "quit" });
22064
- return;
22486
+ const effort = value === "auto" ? undefined : value;
22487
+ if (value !== "auto" && !["none", "low", "medium", "high", "xhigh"].includes(value)) {
22488
+ push({
22489
+ kind: "notice",
22490
+ id: nextId(),
22491
+ text: `unknown reasoning effort "${value}"`,
22492
+ color: theme.warning
22493
+ });
22494
+ return;
22495
+ }
22496
+ if (!agent3.model.model.capabilities.reasoning && effort !== undefined) {
22497
+ push({
22498
+ kind: "notice",
22499
+ id: nextId(),
22500
+ text: `${agent3.model.model.name} does not support reasoning`,
22501
+ color: theme.warning
22502
+ });
22503
+ return;
22504
+ }
22505
+ agent3.setReasoningEffort(effort);
22506
+ await modelOptions?.remember?.(model2, effort, "project");
22507
+ await session2.recordConfiguration({ reasoningEffort: effort ?? null });
22508
+ push({ kind: "notice", id: nextId(), text: `reasoning: ${effort ?? "auto"}` });
22509
+ }, [agent3, model2, modelOptions, push, session2]);
22510
+ const finishReasoningChoice = useCallback2(async (effort) => {
22511
+ setChoosingReasoning(false);
22512
+ agent3.setReasoningEffort(effort);
22513
+ await modelOptions?.remember?.(model2, effort, "project");
22514
+ await session2.recordConfiguration({ reasoningEffort: effort ?? null });
22515
+ push({ kind: "notice", id: nextId(), text: `reasoning: ${effort ?? "auto"}` });
22516
+ }, [agent3, model2, modelOptions, push, session2]);
22517
+ const compactNow = useCallback2(async () => {
22518
+ const abort = new AbortController;
22519
+ controller.current = abort;
22520
+ setBusy(true);
22521
+ try {
22522
+ let compactedAnything = false;
22523
+ for await (const event of agent3.compactNow(abort.signal)) {
22524
+ if (event.type === "compacted") {
22525
+ compactedAnything = true;
22526
+ setCompacted((count) => count + event.replaced);
22527
+ push({
22528
+ kind: "notice",
22529
+ id: nextId(),
22530
+ text: `compacted: ${event.replaced} earlier messages are now a summary`
22531
+ });
22532
+ }
22065
22533
  }
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);
22534
+ if (!compactedAnything) {
22535
+ push({ kind: "notice", id: nextId(), text: "nothing to compact yet" });
22074
22536
  }
22075
- return;
22537
+ } catch (error) {
22538
+ push({
22539
+ kind: "notice",
22540
+ id: nextId(),
22541
+ text: `could not compact: ${error.message}`,
22542
+ color: theme.warning
22543
+ });
22544
+ } finally {
22545
+ setBusy(false);
22546
+ setContext(agent3.contextUse);
22547
+ controller.current = undefined;
22076
22548
  }
22077
- if (screen.name === "key" && !meta.escape) {
22078
- if (meta.return) {
22079
- submitKey();
22549
+ }, [agent3, push]);
22550
+ const handlers = {
22551
+ exit: () => exit(),
22552
+ help: () => push({ kind: "notice", id: nextId(), text: describeCommands(session2) }),
22553
+ model: (argument) => void switchModel(argument),
22554
+ reasoning: (argument) => void changeReasoning(argument),
22555
+ thinking: (argument) => {
22556
+ if (argument !== "show" && argument !== "hide") {
22557
+ push({
22558
+ kind: "notice",
22559
+ id: nextId(),
22560
+ text: `thinking: ${showThinking ? "shown" : "hidden"}
22561
+ /thinking <show|hide>`
22562
+ });
22080
22563
  return;
22081
22564
  }
22082
- if (meta.backspace || meta.delete) {
22083
- secret.current = secret.current.slice(0, -1);
22084
- setInput(secret.current);
22565
+ const shown = argument === "show";
22566
+ setShowThinking(shown);
22567
+ if (!shown)
22568
+ setReasoningLive("");
22569
+ push({ kind: "notice", id: nextId(), text: `thinking: ${shown ? "shown" : "hidden"}` });
22570
+ },
22571
+ compact: () => void compactNow(),
22572
+ context: () => {
22573
+ const { tokens, window } = agent3.contextUse;
22574
+ const percent = window > 0 ? Math.round(tokens / window * 100) : 0;
22575
+ const files = agent3.touchedFiles;
22576
+ push({
22577
+ kind: "notice",
22578
+ id: nextId(),
22579
+ text: [
22580
+ ` model ${model2}`,
22581
+ ` context ~${tokens.toLocaleString()} of ${window.toLocaleString()} tokens (${percent}%)`,
22582
+ ` dropped ${compacted} earlier message${compacted === 1 ? "" : "s"} replaced by a summary`,
22583
+ ` files ${files.length === 0 ? "none touched yet" : files.join(", ")}`,
22584
+ "",
22585
+ " /compact summarises now rather than waiting for 80%"
22586
+ ].join(`
22587
+ `)
22588
+ });
22589
+ },
22590
+ cost: (argument) => {
22591
+ if (argument !== undefined && argument !== "") {
22592
+ const amount = Number.parseFloat(argument.replace(/^\$/, ""));
22593
+ if (Number.isNaN(amount)) {
22594
+ push({
22595
+ kind: "notice",
22596
+ id: nextId(),
22597
+ text: `usage: /cost [usd] - "${argument}" is not an amount`,
22598
+ color: theme.warning
22599
+ });
22600
+ return;
22601
+ }
22602
+ agent3.setBudget(amount > 0 ? amount : undefined);
22603
+ push({
22604
+ kind: "notice",
22605
+ id: nextId(),
22606
+ text: amount > 0 ? `budget: $${amount.toFixed(2)}` : "budget removed"
22607
+ });
22085
22608
  return;
22086
22609
  }
22087
- if (meta.ctrl || meta.meta || meta.tab)
22610
+ const budget = agent3.budgetUsd;
22611
+ push({
22612
+ kind: "notice",
22613
+ id: nextId(),
22614
+ text: ` spent $${agent3.costUsd.toFixed(4)}
22615
+ ` + ` budget ${budget === undefined ? "none - /cost <usd> sets one" : `$${budget.toFixed(2)}`}`
22616
+ });
22617
+ },
22618
+ todo: () => {
22619
+ const todos2 = agent3.todos.list();
22620
+ push({
22621
+ kind: "notice",
22622
+ id: nextId(),
22623
+ text: todos2.length === 0 ? "no todos in this session" : todos2.map((todo2) => ` ${todo2.status === "done" ? "x" : todo2.status === "in_progress" ? ">" : " "} ${todo2.text}`).join(`
22624
+ `)
22625
+ });
22626
+ },
22627
+ permissions: () => {
22628
+ const rules2 = agent3.permissionRules;
22629
+ push({
22630
+ kind: "notice",
22631
+ id: nextId(),
22632
+ text: [
22633
+ ` mode ${agent3.permissionMode} (/mode changes it)`,
22634
+ ...rules2.length === 0 ? [" rules none configured"] : [
22635
+ " rules (deny always wins, whatever the mode or scope)",
22636
+ ...rules2.map((rule) => ` ${rule.effect.padEnd(5)} ${rule.source} [${rule.scope}]`)
22637
+ ]
22638
+ ].join(`
22639
+ `)
22640
+ });
22641
+ },
22642
+ init: () => void runTurn(INIT_PROMPT),
22643
+ mode: (argument) => {
22644
+ if (argument && isPermissionMode(argument)) {
22645
+ agent3.setPermissionMode(argument);
22646
+ setMode(argument);
22647
+ push({ kind: "notice", id: nextId(), text: `permission mode: ${argument}` });
22648
+ } else {
22649
+ push({
22650
+ kind: "notice",
22651
+ id: nextId(),
22652
+ text: `usage: /mode <${PERMISSION_MODES.join("|")}>`,
22653
+ color: theme.warning
22654
+ });
22655
+ }
22656
+ },
22657
+ memory: (argument) => void showMemories(argument),
22658
+ tree: (argument) => void sessionTree("tree", argument),
22659
+ sessions: () => {
22660
+ listSessions(agent3.cwd).then((saved) => setSessionChoices(saved));
22661
+ },
22662
+ rewind: (argument) => void sessionTree("rewind", argument),
22663
+ fork: (argument) => void sessionTree("fork", argument),
22664
+ undo: () => void undoLast(),
22665
+ plan: (argument) => void plan2(argument),
22666
+ skills: () => push({ kind: "notice", id: nextId(), text: describeExtensions(session2) })
22667
+ };
22668
+ const handlersRef = useRef3(handlers);
22669
+ handlersRef.current = handlers;
22670
+ const handleCommand = useCallback2((command) => {
22671
+ const body = command.slice(1).trim();
22672
+ const space = body.search(/\s/);
22673
+ const name = space === -1 ? body : body.slice(0, space);
22674
+ const argument = space === -1 ? undefined : body.slice(space + 1).trim();
22675
+ const spec = findCommand(name);
22676
+ if (spec) {
22677
+ if (spec.idleOnly && busy) {
22678
+ push({
22679
+ kind: "notice",
22680
+ id: nextId(),
22681
+ text: "finish or interrupt the current turn first (esc)",
22682
+ color: theme.warning
22683
+ });
22088
22684
  return;
22089
- if (key2) {
22090
- secret.current += key2;
22091
- setInput(secret.current);
22092
22685
  }
22686
+ handlersRef.current[spec.name](argument);
22093
22687
  return;
22094
22688
  }
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, {
22689
+ const custom = session2.commands.find((entry) => entry.name === name);
22690
+ if (custom) {
22691
+ const prompt = expandCommand(custom, argument ?? "");
22692
+ if (prompt.trim() === "") {
22693
+ push({
22694
+ kind: "notice",
22695
+ id: nextId(),
22696
+ text: `/${name} expanded to nothing`,
22697
+ color: theme.warning
22698
+ });
22699
+ return;
22700
+ }
22701
+ if (busy) {
22702
+ agent3.steer(prompt);
22703
+ setQueued(agent3.pendingSteers);
22704
+ push({ kind: "user", id: nextId(), text: command });
22705
+ return;
22706
+ }
22707
+ runTurn(prompt);
22708
+ return;
22709
+ }
22710
+ push({
22711
+ kind: "notice",
22712
+ id: nextId(),
22713
+ text: `unknown command "${name}"`,
22714
+ color: theme.warning
22715
+ });
22716
+ }, [agent3, busy, push, runTurn, session2]);
22717
+ const submit = useCallback2((text2) => {
22718
+ const entry = menuOpenRef.current ? menuEntriesRef.current[selectedRef.current] : undefined;
22719
+ if (entry) {
22720
+ setInput("");
22721
+ setMenuIndex(0);
22722
+ handleCommand(entry.insert);
22723
+ return;
22724
+ }
22725
+ const trimmed = text2.trim();
22726
+ setInput("");
22727
+ if (trimmed === "")
22728
+ return;
22729
+ if (trimmed.startsWith("/")) {
22730
+ handleCommand(trimmed);
22731
+ return;
22732
+ }
22733
+ setCandidate(detectPreference(trimmed));
22734
+ if (busy) {
22735
+ agent3.steer(trimmed);
22736
+ setQueued(agent3.pendingSteers);
22737
+ push({ kind: "user", id: nextId(), text: trimmed });
22738
+ return;
22739
+ }
22740
+ runTurn(trimmed);
22741
+ }, [agent3, busy, push, runTurn, handleCommand]);
22742
+ const inputActive = !pending && !question && !choosingModel && !choosingReasoning && !sessionChoices;
22743
+ const menuOpen = inputActive && input.startsWith("/") && !input.includes(" ");
22744
+ const entries = menuOpen ? menuEntries(input.slice(1), session2.commands, busy) : [];
22745
+ const selected = Math.min(menuIndex, Math.max(0, entries.length - 1));
22746
+ const menuOpenRef = useRef3(menuOpen);
22747
+ menuOpenRef.current = menuOpen;
22748
+ const menuEntriesRef = useRef3(entries);
22749
+ menuEntriesRef.current = entries;
22750
+ const selectedRef = useRef3(selected);
22751
+ selectedRef.current = selected;
22752
+ useInput7((input_, key2) => {
22753
+ if (menuOpen && entries.length > 0) {
22754
+ if (key2.upArrow) {
22755
+ setMenuIndex((current) => current <= 0 ? entries.length - 1 : current - 1);
22756
+ return;
22757
+ }
22758
+ if (key2.downArrow) {
22759
+ setMenuIndex((current) => current >= entries.length - 1 ? 0 : current + 1);
22760
+ return;
22761
+ }
22762
+ if (key2.tab) {
22763
+ const entry = entries[selected];
22764
+ if (entry)
22765
+ setInput(`${entry.insert} `);
22766
+ setMenuIndex(0);
22767
+ return;
22768
+ }
22769
+ if (key2.escape) {
22770
+ setInput("");
22771
+ setMenuIndex(0);
22772
+ return;
22773
+ }
22774
+ }
22775
+ if (key2.escape) {
22776
+ setCandidate(undefined);
22777
+ if (busy)
22778
+ controller.current?.abort();
22779
+ return;
22780
+ }
22781
+ if (key2.ctrl && candidate && (input_ === "r" || input_ === "g")) {
22782
+ remember(input_ === "r" ? "project" : "user");
22783
+ }
22784
+ }, { isActive: inputActive });
22785
+ return /* @__PURE__ */ jsxDEV14(Box12, {
22268
22786
  flexDirection: "column",
22269
22787
  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, {
22788
+ /* @__PURE__ */ jsxDEV14(Static, {
22789
+ items,
22790
+ children: (item) => /* @__PURE__ */ jsxDEV14(ScrollRow, {
22791
+ item
22792
+ }, item.id, false, undefined, this)
22793
+ }, undefined, false, undefined, this),
22794
+ choosingModel && modelOptions && /* @__PURE__ */ jsxDEV14(Onboarding, {
22795
+ ...modelOptions,
22796
+ wanted: agent3.model.provider.id,
22797
+ wantedModel: model2,
22798
+ embedded: true,
22799
+ defaultScope: "project",
22800
+ onDone: (result) => void finishModelChoice(result)
22801
+ }, undefined, false, undefined, this),
22802
+ choosingReasoning && /* @__PURE__ */ jsxDEV14(ReasoningPicker, {
22803
+ ...agent3.reasoningEffort ? { current: agent3.reasoningEffort } : {},
22804
+ onDone: finishReasoningChoice,
22805
+ onCancel: () => setChoosingReasoning(false)
22806
+ }, undefined, false, undefined, this),
22807
+ sessionChoices && /* @__PURE__ */ jsxDEV14(SessionPicker, {
22808
+ sessions: sessionChoices,
22809
+ embedded: true,
22810
+ onDone: (path) => {
22811
+ setSessionChoices(undefined);
22812
+ if (path) {
22813
+ onResume?.(path);
22814
+ exit();
22815
+ }
22816
+ }
22817
+ }, undefined, false, undefined, this),
22818
+ !choosingModel && !choosingReasoning && !sessionChoices && reasoningLive !== "" && showThinking && /* @__PURE__ */ jsxDEV14(Box12, {
22278
22819
  marginTop: 1,
22279
- children: /* @__PURE__ */ jsxDEV11(Text11, {
22280
- color: theme.muted,
22281
- children: "what should I do? (enter to start with nothing)"
22820
+ children: /* @__PURE__ */ jsxDEV14(Text14, {
22821
+ color: theme.reasoning,
22822
+ children: reasoningLive
22282
22823
  }, undefined, false, undefined, this)
22283
22824
  }, undefined, false, undefined, this),
22284
- /* @__PURE__ */ jsxDEV11(Box10, {
22825
+ !choosingModel && !choosingReasoning && live !== "" && /* @__PURE__ */ jsxDEV14(Box12, {
22826
+ marginTop: 1,
22827
+ children: /* @__PURE__ */ jsxDEV14(Markdown, {
22828
+ text: live
22829
+ }, undefined, false, undefined, this)
22830
+ }, undefined, false, undefined, this),
22831
+ runningTool && /* @__PURE__ */ jsxDEV14(ToolBlock, {
22832
+ name: runningTool,
22833
+ running: true
22834
+ }, undefined, false, undefined, this),
22835
+ !choosingModel && !choosingReasoning && activity && live === "" && !runningTool && !pending && !question && /* @__PURE__ */ jsxDEV14(Box12, {
22836
+ marginTop: 1,
22837
+ children: /* @__PURE__ */ jsxDEV14(Activity, {
22838
+ ...activity === "preparing" ? {} : { label: activity === "thinking" ? "Thinking" : "Reasoning" }
22839
+ }, undefined, false, undefined, this)
22840
+ }, undefined, false, undefined, this),
22841
+ pending && /* @__PURE__ */ jsxDEV14(PermissionPrompt, {
22842
+ request: pending.request,
22843
+ reason: pending.reason,
22844
+ onChoice: (choice) => {
22845
+ setPending(undefined);
22846
+ pending.resolve(choice);
22847
+ }
22848
+ }, undefined, false, undefined, this),
22849
+ question && /* @__PURE__ */ jsxDEV14(QuestionPrompt, {
22850
+ question: question.question,
22851
+ ...question.options ? { options: question.options } : {},
22852
+ onAnswer: (answer) => {
22853
+ setQuestion(undefined);
22854
+ question.resolve(answer);
22855
+ }
22856
+ }, undefined, false, undefined, this),
22857
+ candidate && inputActive && /* @__PURE__ */ jsxDEV14(MemoryCapture, {
22858
+ candidate
22859
+ }, undefined, false, undefined, this),
22860
+ menuOpen && /* @__PURE__ */ jsxDEV14(CommandMenu, {
22861
+ entries,
22862
+ selected
22863
+ }, undefined, false, undefined, this),
22864
+ inputActive && /* @__PURE__ */ jsxDEV14(Box12, {
22865
+ marginTop: 1,
22285
22866
  children: [
22286
- /* @__PURE__ */ jsxDEV11(Text11, {
22867
+ /* @__PURE__ */ jsxDEV14(Text14, {
22287
22868
  color: theme.user,
22288
22869
  children: "> "
22289
22870
  }, undefined, false, undefined, this),
22290
- /* @__PURE__ */ jsxDEV11(TextInput, {
22871
+ /* @__PURE__ */ jsxDEV14(TextInput, {
22291
22872
  value: input,
22292
- onChange: setInput,
22293
- onSubmit: (text2) => finish({ outcome: "ready", providerId: screen.provider.id, firstPrompt: text2.trim() })
22873
+ onChange: (value) => {
22874
+ setInput(value);
22875
+ setMenuIndex(0);
22876
+ },
22877
+ onSubmit: submit,
22878
+ placeholder: busy ? "steer the agent, or esc to interrupt" : "what should I do?"
22294
22879
  }, undefined, false, undefined, this)
22295
22880
  ]
22296
- }, undefined, true, undefined, this)
22881
+ }, undefined, true, undefined, this),
22882
+ /* @__PURE__ */ jsxDEV14(StatusLine, {
22883
+ model: model2,
22884
+ ...agent3.reasoningEffort ? { reasoningEffort: agent3.reasoningEffort } : {},
22885
+ mode,
22886
+ costUsd: cost,
22887
+ todos,
22888
+ busy,
22889
+ queued,
22890
+ context: context2,
22891
+ compacted
22892
+ }, undefined, false, undefined, this)
22297
22893
  ]
22298
22894
  }, undefined, true, undefined, this);
22299
22895
  }
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."
22896
+ function ScrollRow({ item }) {
22897
+ if (item.kind === "user") {
22898
+ return /* @__PURE__ */ jsxDEV14(Box12, {
22899
+ marginTop: 1,
22900
+ children: [
22901
+ /* @__PURE__ */ jsxDEV14(Text14, {
22902
+ color: theme.user,
22903
+ children: "> "
22904
+ }, undefined, false, undefined, this),
22905
+ /* @__PURE__ */ jsxDEV14(Text14, {
22906
+ children: item.text
22907
+ }, undefined, false, undefined, this)
22908
+ ]
22909
+ }, undefined, true, undefined, this);
22910
+ }
22911
+ if (item.kind === "assistant") {
22912
+ return /* @__PURE__ */ jsxDEV14(Box12, {
22913
+ marginTop: 1,
22914
+ children: /* @__PURE__ */ jsxDEV14(Markdown, {
22915
+ text: item.text
22916
+ }, undefined, false, undefined, this)
22917
+ }, undefined, false, undefined, this);
22918
+ }
22919
+ if (item.kind === "reasoning") {
22920
+ return /* @__PURE__ */ jsxDEV14(Box12, {
22921
+ marginTop: 1,
22922
+ children: /* @__PURE__ */ jsxDEV14(Text14, {
22923
+ color: theme.reasoning,
22924
+ children: item.text
22925
+ }, undefined, false, undefined, this)
22926
+ }, undefined, false, undefined, this);
22927
+ }
22928
+ if (item.kind === "tool") {
22929
+ return /* @__PURE__ */ jsxDEV14(ToolBlock, {
22930
+ name: item.name,
22931
+ ...item.title ? { title: item.title } : {},
22932
+ ...item.output ? { output: item.output } : {},
22933
+ ...item.isError ? { isError: true } : {}
22934
+ }, undefined, false, undefined, this);
22935
+ }
22936
+ return /* @__PURE__ */ jsxDEV14(Box12, {
22937
+ marginTop: 1,
22938
+ children: /* @__PURE__ */ jsxDEV14(Text14, {
22939
+ color: item.color ?? theme.muted,
22940
+ children: item.text
22306
22941
  }, undefined, false, undefined, this)
22307
22942
  }, undefined, false, undefined, this);
22308
22943
  }
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";
22944
+ function describeCommands(session2) {
22945
+ const lines = commandRows().map((row) => ` ${row.command.padEnd(34)} ${row.summary}`);
22946
+ if (session2.commands.length > 0) {
22947
+ lines.push("", " commands from this directory:");
22948
+ for (const command of session2.commands) {
22949
+ lines.push(` ${`/${command.name}`.padEnd(34)} ${command.description}`);
22950
+ }
22951
+ }
22952
+ lines.push("", " type / at the prompt to filter this list and pick one");
22953
+ return lines.join(`
22954
+ `);
22315
22955
  }
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
- ];
22956
+ var INIT_PROMPT = `Write an AGENTS.md at the root of this project for a coding agent that has never seen it.
22957
+
22958
+ 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.`;
22959
+ function describeExtensions(session2) {
22960
+ const lines = [];
22961
+ if (session2.skills.length > 0) {
22962
+ lines.push("skills (the agent loads these itself when they fit):");
22963
+ for (const skill of session2.skills) {
22964
+ lines.push(` ${skill.name} [${skill.scope}] ${skill.description}`);
22965
+ }
22966
+ }
22967
+ if (session2.commands.length > 0) {
22968
+ if (lines.length > 0)
22969
+ lines.push("");
22970
+ lines.push("commands you can type:");
22971
+ for (const command of session2.commands) {
22972
+ lines.push(` /${command.name} [${command.scope}] ${command.description}`);
22973
+ }
22974
+ }
22975
+ return lines.length === 0 ? "no skills or commands found in .earshot/skills, .earshot/commands or your config directory" : lines.join(`
22976
+ `);
22323
22977
  }
22324
22978
  // packages/tui/src/run.tsx
22325
22979
  import { platform as platform6 } from "node:os";
22326
22980
  import { render } from "ink";
22327
- import { jsxDEV as jsxDEV12 } from "react/jsx-dev-runtime";
22981
+ import { jsxDEV as jsxDEV15 } from "react/jsx-dev-runtime";
22328
22982
  var WINDOWS_MAX_FPS = 30;
22329
22983
  async function runTui(options) {
22330
22984
  const isWindows = platform6() === "win32";
22331
- const instance = render(/* @__PURE__ */ jsxDEV12(App, {
22985
+ let resumePath;
22986
+ const instance = render(/* @__PURE__ */ jsxDEV15(App, {
22332
22987
  session: options.session,
22333
22988
  model: options.model,
22334
- ...options.initialPrompt ? { initialPrompt: options.initialPrompt } : {}
22989
+ ...options.initialPrompt ? { initialPrompt: options.initialPrompt } : {},
22990
+ ...options.modelOptions ? { modelOptions: options.modelOptions } : {},
22991
+ onResume: (path) => {
22992
+ resumePath = path;
22993
+ }
22335
22994
  }, undefined, false, undefined, this), {
22336
22995
  exitOnCtrlC: false,
22337
22996
  patchConsole: true,
@@ -22342,12 +23001,12 @@ async function runTui(options) {
22342
23001
  } finally {
22343
23002
  await options.session.dispose();
22344
23003
  }
22345
- return 0;
23004
+ return { exitCode: 0, ...resumePath ? { resumePath } : {} };
22346
23005
  }
22347
23006
  async function runOnboarding(options) {
22348
23007
  const isWindows = platform6() === "win32";
22349
23008
  let result = { outcome: "quit" };
22350
- const instance = render(/* @__PURE__ */ jsxDEV12(Onboarding, {
23009
+ const instance = render(/* @__PURE__ */ jsxDEV15(Onboarding, {
22351
23010
  ...options,
22352
23011
  onDone: (decided) => {
22353
23012
  result = decided;
@@ -22360,27 +23019,47 @@ async function runOnboarding(options) {
22360
23019
  await instance.waitUntilExit();
22361
23020
  return result;
22362
23021
  }
23022
+ async function runSessionPicker(sessions) {
23023
+ const isWindows = platform6() === "win32";
23024
+ let selected;
23025
+ const instance = render(/* @__PURE__ */ jsxDEV15(SessionPicker, {
23026
+ sessions,
23027
+ onDone: (path) => {
23028
+ selected = path;
23029
+ }
23030
+ }, undefined, false, undefined, this), { exitOnCtrlC: false, patchConsole: true, ...isWindows ? { maxFps: WINDOWS_MAX_FPS } : {} });
23031
+ await instance.waitUntilExit();
23032
+ return selected;
23033
+ }
22363
23034
  // packages/cli/src/onboard.ts
22364
- async function buildOnboardingOptions(wanted) {
23035
+ async function buildOnboardingOptions(wanted, wantedModel) {
22365
23036
  const registry2 = buildRegistry();
22366
23037
  const store3 = new AuthStore;
23038
+ const cwd = process.cwd();
23039
+ const settings2 = await loadSettings(cwd);
22367
23040
  const providers = [];
22368
23041
  for (const provider of registry2.list()) {
22369
23042
  if (provider.auth.kind === "none")
22370
23043
  continue;
22371
23044
  providers.push(await describe3(provider, store3));
22372
23045
  }
22373
- providers.sort((a, b) => Number(b.kind === "oauth") - Number(a.kind === "oauth"));
23046
+ providers.sort((a, b) => Number(Boolean(b.configured)) - Number(Boolean(a.configured)) || Number(b.kind === "oauth") - Number(a.kind === "oauth"));
22374
23047
  return {
22375
23048
  providers,
22376
23049
  ...wanted ? { wanted } : {},
23050
+ ...wantedModel ? { wantedModel } : {},
22377
23051
  storeKey: (providerId, key2) => store3.set(providerId, { type: "api-key", apiKey: key2 }),
22378
23052
  forgetKey: (providerId) => store3.remove(providerId),
22379
23053
  signIn: async (providerId, onUrl) => {
22380
23054
  const credentials = await loginToOpenRouter({ onUrl });
22381
23055
  await store3.set(providerId, credentials);
22382
23056
  },
22383
- probe: (providerId) => probe(registry2, providerId)
23057
+ probe: (providerId, modelId) => probe(registry2, providerId, modelId),
23058
+ reasoningFor: (model2) => settings2.reasoningEfforts[model2],
23059
+ remember: async (model2, effort, scope2) => {
23060
+ await persistDefaultModel(model2, scope2, cwd);
23061
+ await persistReasoningEffort(model2, effort, scope2, cwd);
23062
+ }
22384
23063
  };
22385
23064
  }
22386
23065
  async function describe3(provider, store3) {
@@ -22389,6 +23068,11 @@ async function describe3(provider, store3) {
22389
23068
  });
22390
23069
  return {
22391
23070
  id: provider.id,
23071
+ models: provider.models().map((model2) => ({
23072
+ id: model2.id,
23073
+ name: model2.name,
23074
+ reasoning: model2.capabilities.reasoning
23075
+ })),
22392
23076
  kind: provider.auth.kind === "oauth" ? "oauth" : "api-key",
22393
23077
  ...envVarsOf2(provider.auth).length ? { envVars: envVarsOf2(provider.auth) } : {},
22394
23078
  ...configured ? { configured: describeConfigured(configured.type) } : {}
@@ -22400,16 +23084,16 @@ function envVarsOf2(auth2) {
22400
23084
  function describeConfigured(kind) {
22401
23085
  return kind === "ambient" ? "ambient credentials" : kind === "oauth" ? "signed in" : "api key set";
22402
23086
  }
22403
- async function probe(registry2, providerId) {
23087
+ async function probe(registry2, providerId, modelId) {
22404
23088
  const listed = registry2.list().find((provider) => provider.id === providerId);
22405
23089
  if (!listed)
22406
23090
  return { ok: false, reason: "other", message: `unknown provider "${providerId}"` };
22407
- const model2 = listed.models()[0];
23091
+ const model2 = listed.models().find((candidate) => candidate.id === modelId);
22408
23092
  if (!model2) {
22409
23093
  return {
22410
23094
  ok: false,
22411
23095
  reason: "other",
22412
- message: `${providerId} publishes no models to check against`
23096
+ message: `${providerId} does not publish model "${modelId}"`
22413
23097
  };
22414
23098
  }
22415
23099
  let resolved;
@@ -22444,7 +23128,6 @@ async function probe(registry2, providerId) {
22444
23128
  }
22445
23129
 
22446
23130
  // packages/cli/src/commands/interactive.ts
22447
- var DEFAULT_MODEL3 = "anthropic/claude-opus-5";
22448
23131
  async function interactiveCommand(args) {
22449
23132
  const flags = args.flags;
22450
23133
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
@@ -22485,9 +23168,14 @@ async function interactiveCommand(args) {
22485
23168
  return 2;
22486
23169
  }
22487
23170
  const extensions = await startExtensions(process.cwd());
22488
- const model2 = typeof flags.model === "string" ? flags.model : DEFAULT_MODEL3;
23171
+ let model2 = typeof flags.model === "string" ? flags.model : undefined;
23172
+ let reasoningEffort = parseReasoningEffort2(flags["reasoning-effort"]);
23173
+ if (reasoningEffort === "invalid") {
23174
+ process.stderr.write(`"${flags["reasoning-effort"]}" is not a reasoning effort
23175
+ `);
23176
+ return 2;
23177
+ }
22489
23178
  const onboardingAllowed = flags["no-onboarding"] !== true;
22490
- let firstPrompt = initialPrompt;
22491
23179
  let attempted = false;
22492
23180
  for (;; ) {
22493
23181
  try {
@@ -22496,30 +23184,47 @@ async function interactiveCommand(args) {
22496
23184
  extraTools: extensions.tools,
22497
23185
  problems: extensions.problems,
22498
23186
  onDispose: () => extensions.close(),
22499
- model: model2,
23187
+ ...model2 ? { model: model2 } : {},
23188
+ ...reasoningEffort !== undefined ? { reasoningEffort } : {},
22500
23189
  ...mode ? { mode } : {},
22501
23190
  ...typeof flags["api-key"] === "string" ? { apiKey: flags["api-key"] } : {},
22502
23191
  ...curiosity ? { curiosity } : {},
22503
23192
  ...maxCostUsd !== undefined ? { maxCostUsd } : {},
22504
23193
  ...resumeFrom2(flags)
22505
23194
  });
22506
- return await runTui({
23195
+ const tui = await runTui({
22507
23196
  session: session2,
22508
- model: model2,
22509
- ...firstPrompt !== "" || image ? {
22510
- initialPrompt: image ? [...firstPrompt ? [{ type: "text", text: firstPrompt }] : [], image] : firstPrompt
23197
+ model: `${session2.agent.model.provider.id}/${session2.agent.model.model.id}`,
23198
+ modelOptions: await buildOnboardingOptions(),
23199
+ ...initialPrompt !== "" || image ? {
23200
+ initialPrompt: image ? [
23201
+ ...initialPrompt ? [{ type: "text", text: initialPrompt }] : [],
23202
+ image
23203
+ ] : initialPrompt
22511
23204
  } : {}
22512
23205
  });
23206
+ if (tui.resumePath) {
23207
+ return interactiveCommand({
23208
+ command: undefined,
23209
+ flags: { ...flags, resume: tui.resumePath, continue: false },
23210
+ positionals: []
23211
+ });
23212
+ }
23213
+ return tui.exitCode;
22513
23214
  } catch (error) {
22514
23215
  if (error instanceof MissingCredentialsError && onboardingAllowed && !attempted) {
22515
23216
  attempted = true;
22516
- const result = await runOnboarding(await buildOnboardingOptions(error.provider.id));
23217
+ const result = await runOnboarding(await buildOnboardingOptions(error.provider.id, model2));
22517
23218
  if (result.outcome === "quit") {
22518
23219
  process.stdout.write("nothing was stored. run `earshot auth login <provider>` when you are ready.\n");
22519
23220
  await extensions.close();
22520
23221
  return 0;
22521
23222
  }
22522
- firstPrompt = result.firstPrompt ?? firstPrompt;
23223
+ if (result.model) {
23224
+ model2 = result.model;
23225
+ reasoningEffort = result.reasoningEffort;
23226
+ await (await buildOnboardingOptions()).remember?.(result.model, result.reasoningEffort, "global");
23227
+ }
22523
23228
  continue;
22524
23229
  }
22525
23230
  await extensions.close();
@@ -22544,6 +23249,13 @@ run \`earshot models\` to see what is available
22544
23249
  }
22545
23250
  }
22546
23251
  }
23252
+ function parseReasoningEffort2(value) {
23253
+ if (value === undefined)
23254
+ return;
23255
+ if (typeof value !== "string")
23256
+ return "invalid";
23257
+ return ["none", "low", "medium", "high", "xhigh"].includes(value) ? value : value === "auto" ? null : "invalid";
23258
+ }
22547
23259
  function resumeFrom2(flags) {
22548
23260
  if (typeof flags.resume === "string")
22549
23261
  return { resume: { path: flags.resume } };
@@ -22663,6 +23375,29 @@ ${models.length} models across ${new Set(models.map((m) => m.providerId)).size}
22663
23375
  return 0;
22664
23376
  }
22665
23377
 
23378
+ // packages/cli/src/commands/sessions.ts
23379
+ async function sessionsCommand(args) {
23380
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
23381
+ process.stderr.write(`earshot sessions needs an interactive terminal.
23382
+ `);
23383
+ return 2;
23384
+ }
23385
+ const sessions2 = await listSessions(process.cwd());
23386
+ if (sessions2.length === 0) {
23387
+ process.stdout.write(`no saved chats for this project
23388
+ `);
23389
+ return 0;
23390
+ }
23391
+ const path = await runSessionPicker(sessions2);
23392
+ if (!path)
23393
+ return 0;
23394
+ return interactiveCommand({
23395
+ command: undefined,
23396
+ flags: { ...args.flags, resume: path },
23397
+ positionals: []
23398
+ });
23399
+ }
23400
+
22666
23401
  // packages/cli/src/commands/update.ts
22667
23402
  import { spawnSync as spawnSync2 } from "node:child_process";
22668
23403
  import { createHash as createHash4 } from "node:crypto";
@@ -23048,6 +23783,7 @@ Usage
23048
23783
  earshot acp serve editor clients over ACP v1 on stdio
23049
23784
  earshot doctor diagnose the local setup
23050
23785
  earshot update [--check] update earshot to the latest release
23786
+ earshot sessions browse and resume saved chats for this project
23051
23787
 
23052
23788
  Flags
23053
23789
  --model <provider/model> model for this session
@@ -23059,6 +23795,7 @@ Flags
23059
23795
  --image <path|https-url> attach one PNG, JPEG, GIF or WebP image
23060
23796
  --max-cost <usd> stop and ask before spending past this
23061
23797
  --curiosity <level> low, normal or high: how readily it asks
23798
+ --reasoning-effort <level> auto | none | low | medium | high | xhigh
23062
23799
  --version, -v print the version
23063
23800
  --help, -h print this help
23064
23801
  `;
@@ -23089,6 +23826,8 @@ async function main(argv = process.argv.slice(2)) {
23089
23826
  return doctorCommand(args);
23090
23827
  if (command === "update")
23091
23828
  return updateCommand(args);
23829
+ if (command === "sessions")
23830
+ return sessionsCommand(args);
23092
23831
  if (command === "acp")
23093
23832
  return acpCommand(args);
23094
23833
  if (command) {
@@ -23103,5 +23842,5 @@ async function main(argv = process.argv.slice(2)) {
23103
23842
  var code = await main();
23104
23843
  process.exitCode = code;
23105
23844
 
23106
- //# debugId=6AB54613CF301A9064756E2164756E21
23845
+ //# debugId=BF0091864A65A6E164756E2164756E21
23107
23846
  //# sourceMappingURL=main.js.map