@pentoshi/clai 2.0.6 → 2.0.9

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.
package/dist/tui/App.js CHANGED
@@ -1,29 +1,30 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Box, Text, useApp, useInput, useStdin } from "ink";
3
- import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
3
+ import chalk from "chalk";
4
+ import { useCallback, useEffect, useMemo, useReducer, useRef, useState, } from "react";
4
5
  import { providerIds } from "../types.js";
5
6
  import { assertProvider } from "../llm/provider.js";
6
7
  import { getProvider } from "../llm/router.js";
7
- import { envValue, getProviderSecret, getSearchProviderKey, listProviderStatuses, maskSecret, setProviderSecret, setSecret } from "../store/keys.js";
8
+ import { envValue, getProviderSecret, getSearchProviderKey, listProviderStatuses, maskSecret, setProviderSecret, setSecret, unsetProviderSecret, } from "../store/keys.js";
8
9
  import { setActiveSearchProvider } from "../store/config.js";
9
- import { searchProviderIds } from "../tools/web/types.js";
10
- import { assertSearchProvider, searchProviders } from "../tools/web/providers/provider.js";
10
+ import { searchProviderIds, } from "../tools/web/types.js";
11
+ import { assertSearchProvider, searchProviders, } from "../tools/web/providers/provider.js";
11
12
  import "../tools/web/providers/duckduckgo.js";
12
13
  import "../tools/web/providers/brave.js";
13
14
  import "../tools/web/providers/tavily.js";
14
- import { modelSupportsThinking, modelSupportsVision } from "../llm/capabilities.js";
15
+ import { modelSupportsThinking, modelSupportsVision, } from "../llm/capabilities.js";
15
16
  import { getConfig, getProviderModel, setDefaultMode, setDefaultProvider, setProviderModel, setThinking, updateConfig, } from "../store/config.js";
16
17
  import { estimateMessagesTokens } from "../agent/context-manager.js";
17
- import { clearAllHistory, getSession, listSessions, saveSession, upsertSession } from "../store/history.js";
18
+ import { clearAllHistory, getSession, listSessions, saveSession, upsertSession, } from "../store/history.js";
18
19
  import { safeCwd } from "../os/cwd.js";
19
20
  import { resolve } from "node:path";
20
21
  import { existsSync } from "node:fs";
21
22
  import { readFile } from "node:fs/promises";
22
- import { expandMentions, findFileSuggestions, getMentionQuery, loadImageAttachments } from "../ui/mentions.js";
23
+ import { expandMentions, findFileSuggestions, getMentionQuery, loadImageAttachments, } from "../ui/mentions.js";
23
24
  import { deletePlan, loadPlan, savePlan } from "../store/plan.js";
24
25
  import { renderPlanDocument } from "../ui/plan-pane.js";
25
- import { getSlashCommandSuggestions, isKnownSlashCommand, knownModels, slashCommands } from "../repl.js";
26
- import { initialState, reducer, serializeTranscriptForCompaction } from "./state.js";
26
+ import { getSlashCommandSuggestions, isKnownSlashCommand, knownModels, slashCommands, } from "../repl.js";
27
+ import { initialState, reducer, serializeTranscriptForCompaction, } from "./state.js";
27
28
  import { renderTranscriptLines } from "./render-lines.js";
28
29
  import { createTuiConfirmPort } from "./confirm.js";
29
30
  import { useAgentRunner } from "./hooks/useAgentRunner.js";
@@ -36,10 +37,10 @@ import { JobsPanel } from "./components/JobsPanel.js";
36
37
  import { PickerPanel } from "./components/PickerPanel.js";
37
38
  import { SecretInputPanel } from "./components/SecretInputPanel.js";
38
39
  import { clearArtifacts, clearAuditLogs } from "../store/logs.js";
39
- import { addScopeTargets, clearScope, loadScope, saveScope } from "../store/scope.js";
40
+ import { addScopeTargets, clearScope, loadScope, saveScope, } from "../store/scope.js";
40
41
  import { formatKeyStatus } from "./format-keys.js";
41
42
  import { shouldStoreInPromptHistory } from "./input-history.js";
42
- import { DISABLE_MOUSE_REPORTING, ENABLE_MOUSE_REPORTING, isMouseReport, mouseWheelDirection, stripMouseReports } from "./mouse.js";
43
+ import { DISABLE_MOUSE_REPORTING, ENABLE_MOUSE_REPORTING, isMouseReport, mouseWheelDirection, stripMouseReports, } from "./mouse.js";
43
44
  const IMPLEMENT_PROMPT = "I approve the plan. Execute it now in STRICT ORDER. Task 1 (explore) is ALREADY COMPLETE from the planning phase — " +
44
45
  "do NOT re-list or re-read the directory. Start with the FIRST pending task that still needs implementation work. " +
45
46
  "For each task: call task.update {taskId, state:'in_progress'} → do the real work → VERIFY it succeeded → " +
@@ -49,7 +50,7 @@ const IMPLEMENT_PROMPT = "I approve the plan. Execute it now in STRICT ORDER. Ta
49
50
  "Do NOT call web.search — you already know everything needed. " +
50
51
  "Run real commands (installs, servers, verification) — do not claim anything ran without a successful tool call.";
51
52
  const MAX_FILE_SUGGESTIONS = 6;
52
- export function App({ version, initialMode, provider: initialProvider, initialModel, noHistory = false }) {
53
+ export function App({ version, initialMode, provider: initialProvider, initialModel, noHistory = false, }) {
53
54
  const { exit } = useApp();
54
55
  const { stdin } = useStdin();
55
56
  const { columns: cols, rows } = useTerminalSize();
@@ -89,7 +90,10 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
89
90
  useEffect(() => {
90
91
  confirmController.setHandler((req) => new Promise((res) => {
91
92
  confirmResolver.current = res;
92
- dispatch({ type: "event", event: { type: "confirm-request", id: "c", ...req } });
93
+ dispatch({
94
+ type: "event",
95
+ event: { type: "confirm-request", id: "c", ...req },
96
+ });
93
97
  }));
94
98
  }, [confirmController]);
95
99
  const ctxRef = useRef({ mode, provider, model });
@@ -135,7 +139,9 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
135
139
  if (autosaveTimer.current)
136
140
  clearTimeout(autosaveTimer.current);
137
141
  const messages = runner.getMessages();
138
- if (!noHistory && !getConfig().privateMode && (messages.length > 0 || latestItems.current.length > 0)) {
142
+ if (!noHistory &&
143
+ !getConfig().privateMode &&
144
+ (messages.length > 0 || latestItems.current.length > 0)) {
139
145
  void upsertSession(runner.getSession().sessionId, messages, undefined, latestItems.current).catch(() => undefined);
140
146
  }
141
147
  }, [noHistory, runner]);
@@ -148,11 +154,15 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
148
154
  const vision = modelSupportsVision(provider, model);
149
155
  const expansion = expandMentions(rawText, safeCwd(), vision);
150
156
  const images = vision ? loadImageAttachments(rawText, safeCwd()) : [];
151
- const modelInput = expansion.contextBlock ? `${rawText}\n\n${expansion.contextBlock}` : rawText;
157
+ const modelInput = expansion.contextBlock
158
+ ? `${rawText}\n\n${expansion.contextBlock}`
159
+ : rawText;
152
160
  void startTurn(rawText, modelInput, images);
153
161
  }, [provider, model, startTurn]);
154
162
  useEffect(() => {
155
- if (!state.status.running && state.queued.length > 0 && !runner.isRunning()) {
163
+ if (!state.status.running &&
164
+ state.queued.length > 0 &&
165
+ !runner.isRunning()) {
156
166
  const next = state.queued[0];
157
167
  dispatch({ type: "dequeue" });
158
168
  beginTurn(next);
@@ -177,17 +187,29 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
177
187
  const session = runner.getSession();
178
188
  const plan = await loadPlan(session.sessionId).catch(() => undefined);
179
189
  if (!plan) {
180
- dispatch({ type: "notice", level: "info", text: "no plan to implement — ask clai to plan a multi-step task first" });
190
+ dispatch({
191
+ type: "notice",
192
+ level: "info",
193
+ text: "no plan to implement — ask clai to plan a multi-step task first",
194
+ });
181
195
  return;
182
196
  }
183
197
  if (plan.tasks.every((t) => t.state === "done")) {
184
- dispatch({ type: "notice", level: "info", text: "this plan is already complete ✓" });
198
+ dispatch({
199
+ type: "notice",
200
+ level: "info",
201
+ text: "this plan is already complete ✓",
202
+ });
185
203
  return;
186
204
  }
187
205
  plan.status = "approved";
188
206
  await savePlan(plan).catch(() => undefined);
189
207
  session.planApproved.value = true;
190
- dispatch({ type: "notice", level: "info", text: "✦ plan approved — executing it now" });
208
+ dispatch({
209
+ type: "notice",
210
+ level: "info",
211
+ text: "✦ plan approved — executing it now",
212
+ });
191
213
  void startTurn("/implement", IMPLEMENT_PROMPT);
192
214
  }, [runner, startTurn]);
193
215
  const chooseModel = useCallback(async () => {
@@ -216,7 +238,11 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
216
238
  setOverlay({
217
239
  kind: "picker",
218
240
  title: `Models · ${provider}`,
219
- options: models.map((value) => ({ value, label: value, active: value === model })),
241
+ options: models.map((value) => ({
242
+ value,
243
+ label: value,
244
+ active: value === model,
245
+ })),
220
246
  onSelect: (value) => {
221
247
  setModel(value);
222
248
  setProviderModel(provider, value);
@@ -226,18 +252,28 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
226
252
  });
227
253
  }, [model, provider]);
228
254
  const activateProvider = useCallback(async (next) => {
229
- const configured = next === "ollama" || Boolean(envValue(next)) || Boolean((await getProviderSecret(next)).value);
255
+ const configured = next === "ollama" ||
256
+ Boolean(envValue(next)) ||
257
+ Boolean((await getProviderSecret(next)).value);
230
258
  if (!configured) {
231
259
  const key = await requestSecret({
232
260
  title: `${next} API key`,
233
261
  prompt: `No API key is configured for ${next}. Enter it now to activate this provider.`,
234
262
  });
235
263
  if (!key) {
236
- dispatch({ type: "notice", level: "warn", text: `provider unchanged · ${next} needs an API key` });
264
+ dispatch({
265
+ type: "notice",
266
+ level: "warn",
267
+ text: `provider unchanged · ${next} needs an API key`,
268
+ });
237
269
  return;
238
270
  }
239
271
  if (!getProvider(next).validateKey(key)) {
240
- dispatch({ type: "notice", level: "warn", text: `invalid API key format for ${next}` });
272
+ dispatch({
273
+ type: "notice",
274
+ level: "warn",
275
+ text: `invalid API key format for ${next}`,
276
+ });
241
277
  return;
242
278
  }
243
279
  await setProviderSecret(next, key);
@@ -247,7 +283,11 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
247
283
  setProvider(next);
248
284
  setModel(nextModel);
249
285
  setOverlay({ kind: "none" });
250
- dispatch({ type: "notice", level: "info", text: `provider → ${next} · model → ${nextModel}` });
286
+ dispatch({
287
+ type: "notice",
288
+ level: "info",
289
+ text: `provider → ${next} · model → ${nextModel}`,
290
+ });
251
291
  }, [requestSecret]);
252
292
  const chooseProvider = useCallback(() => {
253
293
  setOverlay({
@@ -275,7 +315,11 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
275
315
  prompt: `No API key is configured for ${adapter.displayName}. Enter it now to use this search provider.`,
276
316
  });
277
317
  if (!key) {
278
- dispatch({ type: "notice", level: "warn", text: `search provider unchanged · ${next} needs an API key` });
318
+ dispatch({
319
+ type: "notice",
320
+ level: "warn",
321
+ text: `search provider unchanged · ${next} needs an API key`,
322
+ });
279
323
  return;
280
324
  }
281
325
  await setSecret("search", next, key);
@@ -283,7 +327,11 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
283
327
  }
284
328
  setActiveSearchProvider(next);
285
329
  setOverlay({ kind: "none" });
286
- dispatch({ type: "notice", level: "info", text: `search provider → ${next}` });
330
+ dispatch({
331
+ type: "notice",
332
+ level: "info",
333
+ text: `search provider → ${next}`,
334
+ });
287
335
  }, [requestSecret]);
288
336
  const chooseSearchProvider = useCallback(() => {
289
337
  const active = getConfig().activeSearchProvider;
@@ -300,7 +348,9 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
300
348
  : `${adapter?.displayName ?? id} · keyless`,
301
349
  };
302
350
  }),
303
- onSelect: (value) => { void activateSearchProvider(assertSearchProvider(value)); },
351
+ onSelect: (value) => {
352
+ void activateSearchProvider(assertSearchProvider(value));
353
+ },
304
354
  });
305
355
  }, [activateSearchProvider]);
306
356
  const setReasoning = useCallback((value) => {
@@ -311,7 +361,9 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
311
361
  dispatch({
312
362
  type: "notice",
313
363
  level: "info",
314
- text: value === "off" || value === "none" ? "thinking → off" : `thinking → ${value}`,
364
+ text: value === "off" || value === "none"
365
+ ? "thinking → off"
366
+ : `thinking → ${value}`,
315
367
  });
316
368
  }, []);
317
369
  const chooseReasoning = useCallback(() => {
@@ -391,7 +443,11 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
391
443
  if (!plan)
392
444
  info("no active plan yet");
393
445
  else
394
- setOverlay({ kind: "pager", title: "Plan", body: renderPlanDocument(plan) });
446
+ setOverlay({
447
+ kind: "pager",
448
+ title: "Plan",
449
+ body: renderPlanDocument(plan),
450
+ });
395
451
  })();
396
452
  return true;
397
453
  case "/jobs":
@@ -399,11 +455,22 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
399
455
  return true;
400
456
  case "/output": {
401
457
  const outputs = state.items.filter((item) => item.kind === "tool" && Boolean(item.output));
458
+ if (!arg) {
459
+ if (outputs.length)
460
+ dispatch({ type: "toggle-output" });
461
+ else
462
+ info("no tool output yet");
463
+ return true;
464
+ }
402
465
  if (arg === "list" || arg === "ls") {
403
- info(outputs.length ? outputs.map((item) => `${item.id} · ${item.name}`).join("\n") : "no tool output yet");
466
+ info(outputs.length
467
+ ? outputs.map((item) => `${item.id} · ${item.name}`).join("\n")
468
+ : "no tool output yet");
404
469
  return true;
405
470
  }
406
- const selectedOutput = arg && arg !== "last" ? outputs.find((item) => item.id === arg) : lastToolOutput();
471
+ const selectedOutput = arg && arg !== "last"
472
+ ? outputs.find((item) => item.id === arg)
473
+ : lastToolOutput();
407
474
  if (!selectedOutput)
408
475
  info(arg ? `no tool output: ${arg}` : "no tool output yet");
409
476
  else
@@ -418,7 +485,9 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
418
485
  {
419
486
  const options = knownModels[provider] ?? [];
420
487
  const index = Number.parseInt(arg, 10);
421
- const nextModel = Number.isInteger(index) && index >= 1 && index <= options.length ? options[index - 1] : arg;
488
+ const nextModel = Number.isInteger(index) && index >= 1 && index <= options.length
489
+ ? options[index - 1]
490
+ : arg;
422
491
  setModel(nextModel);
423
492
  setProviderModel(provider, nextModel);
424
493
  info(`model → ${nextModel}`);
@@ -514,7 +583,9 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
514
583
  case "/allow":
515
584
  if (!arg || arg === "list" || arg === "ls") {
516
585
  const list = [...runner.getSession().allow];
517
- info(list.length ? `allowed: ${list.join(", ")}` : "no session allowances");
586
+ info(list.length
587
+ ? `allowed: ${list.join(", ")}`
588
+ : "no session allowances");
518
589
  return true;
519
590
  }
520
591
  runner.getSession().allow.add(arg);
@@ -538,14 +609,17 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
538
609
  setCompacting(true);
539
610
  info("compacting conversation…");
540
611
  const fullSession = serializeTranscriptForCompaction(state.items);
541
- void runner.compact(fullSession).then((result) => {
612
+ void runner
613
+ .compact(fullSession)
614
+ .then((result) => {
542
615
  if (result.after === result.before) {
543
616
  info("nothing to compact yet — more than 8 recent messages are required");
544
617
  }
545
618
  else {
546
619
  info(`compacted ${result.before} → ${result.after} messages · ~${result.beforeTokens.toLocaleString()} → ~${result.afterTokens.toLocaleString()} tokens${result.summarized ? "" : " · local fallback"}`);
547
620
  }
548
- }).catch((error) => warn(`compaction failed: ${error instanceof Error ? error.message : String(error)}`))
621
+ })
622
+ .catch((error) => warn(`compaction failed: ${error instanceof Error ? error.message : String(error)}`))
549
623
  .finally(() => setCompacting(false));
550
624
  return true;
551
625
  }
@@ -572,12 +646,16 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
572
646
  kind: "picker",
573
647
  title: "Session history",
574
648
  options: [
575
- ...(currentMessages.length ? [{
576
- value: "__current__",
577
- label: "Current session",
578
- description: `${currentMessages.length} messages · active now`,
579
- active: true,
580
- }] : []),
649
+ ...(currentMessages.length
650
+ ? [
651
+ {
652
+ value: "__current__",
653
+ label: "Current session",
654
+ description: `${currentMessages.length} messages · active now`,
655
+ active: true,
656
+ },
657
+ ]
658
+ : []),
581
659
  ...sessions.map((session) => ({
582
660
  value: session.id,
583
661
  label: session.name ?? session.id,
@@ -598,7 +676,11 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
598
676
  }
599
677
  runner.setMessages(session.messages);
600
678
  setOverlay({ kind: "none" });
601
- dispatch({ type: "load-history", messages: session.messages, transcript: session.transcript });
679
+ dispatch({
680
+ type: "load-history",
681
+ messages: session.messages,
682
+ transcript: session.transcript,
683
+ });
602
684
  setScroll(0);
603
685
  info(`session resumed · ${session.transcript?.length ?? session.messages.length} items`);
604
686
  })();
@@ -632,11 +714,16 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
632
714
  }
633
715
  if (sub === "show" || sub === "list" || sub === "ls") {
634
716
  const scope = await loadScope();
635
- info(scope ? `scope: ${scope.name ?? "unnamed"} · ${scope.authorizedTargets.join(", ")}` : "no engagement scope configured");
717
+ info(scope
718
+ ? `scope: ${scope.name ?? "unnamed"} · ${scope.authorizedTargets.join(", ")}`
719
+ : "no engagement scope configured");
636
720
  return;
637
721
  }
638
722
  if (sub === "add") {
639
- const targets = parts.join(" ").split(/[\s,]+/).filter(Boolean);
723
+ const targets = parts
724
+ .join(" ")
725
+ .split(/[\s,]+/)
726
+ .filter(Boolean);
640
727
  if (!targets.length) {
641
728
  warn("usage: /scope add <target1,target2>");
642
729
  return;
@@ -646,12 +733,18 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
646
733
  return;
647
734
  }
648
735
  if (sub === "new" || sub === "set") {
649
- const targets = parts.join(" ").split(/[\s,]+/).filter(Boolean);
736
+ const targets = parts
737
+ .join(" ")
738
+ .split(/[\s,]+/)
739
+ .filter(Boolean);
650
740
  if (!targets.length) {
651
741
  warn("usage: /scope new <target1,target2>");
652
742
  return;
653
743
  }
654
- await saveScope({ authorizedTargets: targets, createdAt: new Date().toISOString() });
744
+ await saveScope({
745
+ authorizedTargets: targets,
746
+ createdAt: new Date().toISOString(),
747
+ });
655
748
  info(`scope created · ${targets.join(", ")}`);
656
749
  return;
657
750
  }
@@ -691,7 +784,11 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
691
784
  return;
692
785
  }
693
786
  if (sub === "clear-all") {
694
- const [historyResult, logResult, artifactResult] = await Promise.all([clearAllHistory(), clearAuditLogs(), clearArtifacts()]);
787
+ const [historyResult, logResult, artifactResult] = await Promise.all([
788
+ clearAllHistory(),
789
+ clearAuditLogs(),
790
+ clearArtifacts(),
791
+ ]);
695
792
  info(`cleared history (${historyResult.detail || "ok"}), logs (${logResult.removed}), artifacts (${artifactResult.removed})`);
696
793
  return;
697
794
  }
@@ -730,16 +827,360 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
730
827
  active: id === activeSearch,
731
828
  configured: keyless || Boolean(secret.value),
732
829
  source: keyless ? "keyless" : secret.source,
733
- maskedKey: secret.value ? maskSecret(secret.value) : undefined,
830
+ maskedKey: secret.value
831
+ ? maskSecret(secret.value)
832
+ : undefined,
734
833
  };
735
834
  }));
736
- setOverlay({ kind: "pager", title: "Credential status", body: formatKeyStatus(llm, search) });
835
+ setOverlay({
836
+ kind: "pager",
837
+ title: "Credential status",
838
+ body: formatKeyStatus(llm, search),
839
+ });
737
840
  })().catch((error) => warn(`could not read keys: ${error instanceof Error ? error.message : String(error)}`));
738
841
  return true;
739
- case "/set":
740
- case "/unset":
842
+ case "/set": {
843
+ const parts = arg.split(/\s+/).filter(Boolean);
844
+ const providerVal = parts[0];
845
+ const keyVal = parts[1];
846
+ void (async () => {
847
+ if (!providerVal) {
848
+ const llm = await listProviderStatuses(provider);
849
+ const activeSearch = getConfig().activeSearchProvider;
850
+ const search = await Promise.all(searchProviderIds.map(async (id) => {
851
+ const secret = await getSearchProviderKey(id);
852
+ const keyless = id === "duckduckgo";
853
+ return {
854
+ provider: id,
855
+ configured: keyless || Boolean(secret.value),
856
+ maskedKey: secret.value ? maskSecret(secret.value) : undefined,
857
+ };
858
+ }));
859
+ setOverlay({
860
+ kind: "picker",
861
+ title: "Set API key for provider",
862
+ options: [
863
+ ...llm.map((status) => ({
864
+ value: `llm:${status.provider}`,
865
+ label: `${status.provider.padEnd(12)} ${status.configured ? chalk.green("✓ key set") : chalk.red("✗ no key")}${status.active ? " (active)" : ""}`,
866
+ description: status.model,
867
+ })),
868
+ ...search.map((status) => ({
869
+ value: `search:${status.provider}`,
870
+ label: `${status.provider.padEnd(12)} ${status.configured ? chalk.green("✓ key set") : chalk.red("✗ no key")}`,
871
+ description: `Search provider${status.provider === activeSearch ? " (active)" : ""}`,
872
+ })),
873
+ ],
874
+ onSelect: (val) => {
875
+ setOverlay({ kind: "none" });
876
+ void (async () => {
877
+ const isSearch = val.startsWith("search:");
878
+ const id = val.split(":")[1];
879
+ if (isSearch) {
880
+ const next = id;
881
+ if (next === "duckduckgo") {
882
+ info("duckduckgo is keyless and requires no setup");
883
+ return;
884
+ }
885
+ const secret = await getSearchProviderKey(next);
886
+ if (secret.value) {
887
+ const reset = await new Promise((resolveConfirm) => {
888
+ confirmResolver.current = resolveConfirm;
889
+ dispatch({
890
+ type: "event",
891
+ event: {
892
+ type: "confirm-request",
893
+ id: "c",
894
+ kind: "reset",
895
+ prompt: `${next} already has a key (${maskSecret(secret.value)}). Reset it?`,
896
+ },
897
+ });
898
+ });
899
+ if (!reset) {
900
+ info("cancelled");
901
+ return;
902
+ }
903
+ }
904
+ const key = await requestSecret({
905
+ title: `${next} API key`,
906
+ prompt: `Enter API key for ${next}:`,
907
+ });
908
+ if (!key) {
909
+ info("cancelled");
910
+ return;
911
+ }
912
+ await setSecret("search", next, key.trim());
913
+ info(`saved ${next} ${maskSecret(key.trim())}`);
914
+ }
915
+ else {
916
+ const next = id;
917
+ if (next === "ollama") {
918
+ const key = await requestSecret({
919
+ title: "Ollama host URL",
920
+ prompt: `Enter host URL for Ollama:`,
921
+ });
922
+ if (!key) {
923
+ info("cancelled");
924
+ return;
925
+ }
926
+ updateConfig({ ollamaHost: key.trim() });
927
+ info(`saved ollama host → ${key.trim()}`);
928
+ return;
929
+ }
930
+ const secret = await getProviderSecret(next);
931
+ if (secret.value) {
932
+ const reset = await new Promise((resolveConfirm) => {
933
+ confirmResolver.current = resolveConfirm;
934
+ dispatch({
935
+ type: "event",
936
+ event: {
937
+ type: "confirm-request",
938
+ id: "c",
939
+ kind: "reset",
940
+ prompt: `${next} already has a key (${maskSecret(secret.value)}). Reset it?`,
941
+ },
942
+ });
943
+ });
944
+ if (!reset) {
945
+ info("cancelled");
946
+ return;
947
+ }
948
+ }
949
+ const key = await requestSecret({
950
+ title: `${next} API key`,
951
+ prompt: `Enter API key for ${next}:`,
952
+ });
953
+ if (!key) {
954
+ info("cancelled");
955
+ return;
956
+ }
957
+ if (!getProvider(next).validateKey(key.trim())) {
958
+ warn(`invalid API key format for ${next}`);
959
+ return;
960
+ }
961
+ await setProviderSecret(next, key.trim());
962
+ info(`saved ${next} ${maskSecret(key.trim())}`);
963
+ }
964
+ })();
965
+ },
966
+ });
967
+ }
968
+ else {
969
+ try {
970
+ const isSearch = ["brave", "tavily", "duckduckgo"].includes(providerVal);
971
+ if (isSearch) {
972
+ const next = providerVal;
973
+ if (next === "duckduckgo") {
974
+ info("duckduckgo is keyless and requires no setup");
975
+ return;
976
+ }
977
+ let key = keyVal;
978
+ if (!key) {
979
+ const secret = await getSearchProviderKey(next);
980
+ if (secret.value) {
981
+ const reset = await new Promise((resolveConfirm) => {
982
+ confirmResolver.current = resolveConfirm;
983
+ dispatch({
984
+ type: "event",
985
+ event: {
986
+ type: "confirm-request",
987
+ id: "c",
988
+ kind: "reset",
989
+ prompt: `${next} already has a key (${maskSecret(secret.value)}). Reset it?`,
990
+ },
991
+ });
992
+ });
993
+ if (!reset) {
994
+ info("cancelled");
995
+ return;
996
+ }
997
+ }
998
+ key = await requestSecret({
999
+ title: `${next} API key`,
1000
+ prompt: `Enter API key for ${next}:`,
1001
+ });
1002
+ if (!key) {
1003
+ info("cancelled");
1004
+ return;
1005
+ }
1006
+ }
1007
+ await setSecret("search", next, key.trim());
1008
+ info(`saved ${next} ${maskSecret(key.trim())}`);
1009
+ }
1010
+ else {
1011
+ const next = assertProvider(providerVal);
1012
+ if (next === "ollama") {
1013
+ let key = keyVal;
1014
+ if (!key) {
1015
+ key = await requestSecret({
1016
+ title: "Ollama host URL",
1017
+ prompt: `Enter host URL for Ollama:`,
1018
+ });
1019
+ if (!key) {
1020
+ info("cancelled");
1021
+ return;
1022
+ }
1023
+ }
1024
+ updateConfig({ ollamaHost: key.trim() });
1025
+ info(`saved ollama host → ${key.trim()}`);
1026
+ return;
1027
+ }
1028
+ let key = keyVal;
1029
+ if (!key) {
1030
+ const secret = await getProviderSecret(next);
1031
+ if (secret.value) {
1032
+ const reset = await new Promise((resolveConfirm) => {
1033
+ confirmResolver.current = resolveConfirm;
1034
+ dispatch({
1035
+ type: "event",
1036
+ event: {
1037
+ type: "confirm-request",
1038
+ id: "c",
1039
+ kind: "reset",
1040
+ prompt: `${next} already has a key (${maskSecret(secret.value)}). Reset it?`,
1041
+ },
1042
+ });
1043
+ });
1044
+ if (!reset) {
1045
+ info("cancelled");
1046
+ return;
1047
+ }
1048
+ }
1049
+ key = await requestSecret({
1050
+ title: `${next} API key`,
1051
+ prompt: `Enter API key for ${next}:`,
1052
+ });
1053
+ if (!key) {
1054
+ info("cancelled");
1055
+ return;
1056
+ }
1057
+ }
1058
+ if (!getProvider(next).validateKey(key.trim())) {
1059
+ warn(`invalid API key format for ${next}`);
1060
+ return;
1061
+ }
1062
+ await setProviderSecret(next, key.trim());
1063
+ info(`saved ${next} ${maskSecret(key.trim())}`);
1064
+ }
1065
+ }
1066
+ catch (e) {
1067
+ warn(e.message);
1068
+ }
1069
+ }
1070
+ })();
1071
+ return true;
1072
+ }
1073
+ case "/unset": {
1074
+ const parts = arg.split(/\s+/).filter(Boolean);
1075
+ const providerVal = parts[0];
1076
+ void (async () => {
1077
+ if (!providerVal) {
1078
+ const llm = await listProviderStatuses(provider);
1079
+ const activeSearch = getConfig().activeSearchProvider;
1080
+ const search = await Promise.all(searchProviderIds.map(async (id) => {
1081
+ const secret = await getSearchProviderKey(id);
1082
+ const keyless = id === "duckduckgo";
1083
+ return {
1084
+ provider: id,
1085
+ configured: keyless || Boolean(secret.value),
1086
+ maskedKey: secret.value ? maskSecret(secret.value) : undefined,
1087
+ };
1088
+ }));
1089
+ setOverlay({
1090
+ kind: "picker",
1091
+ title: "Unset API key for provider",
1092
+ options: [
1093
+ ...llm.map((status) => ({
1094
+ value: `llm:${status.provider}`,
1095
+ label: `${status.provider.padEnd(12)} ${status.configured ? chalk.green("✓ ") + (status.maskedKey ?? "key set") : chalk.red("✗ no key")}${status.active ? " (active)" : ""}`,
1096
+ description: status.model,
1097
+ })),
1098
+ ...search.map((status) => ({
1099
+ value: `search:${status.provider}`,
1100
+ label: `${status.provider.padEnd(12)} ${status.configured ? chalk.green("✓ ") + (status.maskedKey ?? "keyless") : chalk.red("✗ no key")}`,
1101
+ description: `Search provider${status.provider === activeSearch ? " (active)" : ""}`,
1102
+ })),
1103
+ ],
1104
+ onSelect: (val) => {
1105
+ setOverlay({ kind: "none" });
1106
+ void (async () => {
1107
+ const isSearch = val.startsWith("search:");
1108
+ const id = val.split(":")[1];
1109
+ if (isSearch) {
1110
+ const next = id;
1111
+ if (next === "duckduckgo") {
1112
+ info("duckduckgo requires no credentials and cannot be unset");
1113
+ return;
1114
+ }
1115
+ const secret = await getSearchProviderKey(next);
1116
+ if (!secret.value) {
1117
+ warn(`${next} has no key to unset`);
1118
+ return;
1119
+ }
1120
+ const { unsetSearchProviderKey } = await import("../commands/search-providers.js");
1121
+ await unsetSearchProviderKey(next);
1122
+ info(`unset ${next}`);
1123
+ }
1124
+ else {
1125
+ const next = id;
1126
+ if (next === "ollama") {
1127
+ info("ollama does not store an API key");
1128
+ return;
1129
+ }
1130
+ const secret = await getProviderSecret(next);
1131
+ if (!secret.value) {
1132
+ warn(`${next} has no key to unset`);
1133
+ return;
1134
+ }
1135
+ await unsetProviderSecret(next);
1136
+ info(`unset ${next}`);
1137
+ }
1138
+ })();
1139
+ },
1140
+ });
1141
+ }
1142
+ else {
1143
+ try {
1144
+ const isSearch = ["brave", "tavily", "duckduckgo"].includes(providerVal);
1145
+ if (isSearch) {
1146
+ const next = providerVal;
1147
+ if (next === "duckduckgo") {
1148
+ info("duckduckgo requires no credentials and cannot be unset");
1149
+ return;
1150
+ }
1151
+ const secret = await getSearchProviderKey(next);
1152
+ if (!secret.value) {
1153
+ warn(`${next} has no key to unset`);
1154
+ return;
1155
+ }
1156
+ const { unsetSearchProviderKey } = await import("../commands/search-providers.js");
1157
+ await unsetSearchProviderKey(next);
1158
+ info(`unset ${next}`);
1159
+ }
1160
+ else {
1161
+ const next = assertProvider(providerVal);
1162
+ if (next === "ollama") {
1163
+ info("ollama does not store an API key");
1164
+ return;
1165
+ }
1166
+ const secret = await getProviderSecret(next);
1167
+ if (!secret.value) {
1168
+ warn(`${next} has no key to unset`);
1169
+ return;
1170
+ }
1171
+ await unsetProviderSecret(next);
1172
+ info(`unset ${next}`);
1173
+ }
1174
+ }
1175
+ catch (e) {
1176
+ warn(e.message);
1177
+ }
1178
+ }
1179
+ })();
1180
+ return true;
1181
+ }
741
1182
  case "/update":
742
- info(`${cmd} manages external credentials or updates; use the equivalent \`clai ${cmd.slice(1)}\` command outside the TUI`);
1183
+ info(`${cmd} manages updates; use the equivalent \`clai update\` command outside the TUI`);
743
1184
  return true;
744
1185
  case "/help":
745
1186
  setOverlay({
@@ -763,9 +1204,30 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
763
1204
  case "/quit":
764
1205
  exitTui();
765
1206
  return true;
766
- default: return false;
1207
+ default:
1208
+ return false;
767
1209
  }
768
- }, [activateProvider, activateSearchProvider, chooseModel, chooseProvider, chooseReasoning, chooseSearchProvider, compacting, exitTui, mouseMode, noHistory, provider, runner, runImplement, lastToolOutput, openToolOutput, setReasoning, state.items]);
1210
+ }, [
1211
+ activateProvider,
1212
+ activateSearchProvider,
1213
+ chooseModel,
1214
+ chooseProvider,
1215
+ chooseReasoning,
1216
+ chooseSearchProvider,
1217
+ compacting,
1218
+ exitTui,
1219
+ mouseMode,
1220
+ noHistory,
1221
+ provider,
1222
+ runner,
1223
+ runImplement,
1224
+ lastToolOutput,
1225
+ openToolOutput,
1226
+ setReasoning,
1227
+ state.items,
1228
+ requestSecret,
1229
+ setOverlay,
1230
+ ]);
769
1231
  const submitText = useCallback((text) => {
770
1232
  const trimmed = text.trim();
771
1233
  if (!trimmed)
@@ -780,7 +1242,11 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
780
1242
  // token is an actual clai command; file paths remain normal prompts.
781
1243
  if (trimmed.startsWith("/") && isKnownSlashCommand(trimmed)) {
782
1244
  if (!handleLocalSlash(trimmed))
783
- dispatch({ type: "notice", level: "warn", text: `unknown command: ${trimmed}` });
1245
+ dispatch({
1246
+ type: "notice",
1247
+ level: "warn",
1248
+ text: `unknown command: ${trimmed}`,
1249
+ });
784
1250
  return;
785
1251
  }
786
1252
  if (shouldStoreInPromptHistory(trimmed))
@@ -821,7 +1287,11 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
821
1287
  const statusH = state.pendingConfirm ? 6 : secretRequest ? 7 : 1;
822
1288
  const composerH = 3;
823
1289
  const maxMenuRows = Math.max(3, usableRows - headerH - statusH - composerH - 3);
824
- const menuH = slashMenuOpen ? Math.min(suggestions.length, maxMenuRows) : fileMenuOpen ? fileSuggestions.length : 0;
1290
+ const menuH = slashMenuOpen
1291
+ ? Math.min(suggestions.length, maxMenuRows)
1292
+ : fileMenuOpen
1293
+ ? fileSuggestions.length
1294
+ : 0;
825
1295
  const viewportH = Math.max(3, usableRows - headerH - statusH - composerH - menuH);
826
1296
  const slashWindowStart = slashMenuOpen
827
1297
  ? Math.min(Math.max(0, selected - Math.floor(menuH / 2)), Math.max(0, suggestions.length - menuH))
@@ -855,7 +1325,9 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
855
1325
  setScroll((value) => Math.max(0, value - 3));
856
1326
  };
857
1327
  stdin.on("data", onData);
858
- return () => { stdin.off("data", onData); };
1328
+ return () => {
1329
+ stdin.off("data", onData);
1330
+ };
859
1331
  }, [stdin, modalActive, maxOffset]);
860
1332
  // ── Key handling ────────────────────────────────────────────────────────────
861
1333
  useInput((ch, key) => {
@@ -870,9 +1342,9 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
870
1342
  return;
871
1343
  }
872
1344
  if (key.ctrl && ch === "o") {
873
- const last = lastToolOutput();
874
- if (last)
875
- void openToolOutput(last);
1345
+ const hasOutput = state.items.some((item) => item.kind === "tool" && Boolean(item.output));
1346
+ if (hasOutput)
1347
+ dispatch({ type: "toggle-output" });
876
1348
  else
877
1349
  dispatch({ type: "notice", level: "info", text: "no tool output yet" });
878
1350
  return;
@@ -881,9 +1353,17 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
881
1353
  void (async () => {
882
1354
  const plan = await loadPlan(runner.getSession().sessionId).catch(() => undefined);
883
1355
  if (plan)
884
- setOverlay({ kind: "pager", title: "Plan", body: renderPlanDocument(plan) });
1356
+ setOverlay({
1357
+ kind: "pager",
1358
+ title: "Plan",
1359
+ body: renderPlanDocument(plan),
1360
+ });
885
1361
  else
886
- dispatch({ type: "notice", level: "info", text: "no active plan yet" });
1362
+ dispatch({
1363
+ type: "notice",
1364
+ level: "info",
1365
+ text: "no active plan yet",
1366
+ });
887
1367
  })();
888
1368
  return;
889
1369
  }
@@ -917,7 +1397,9 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
917
1397
  return;
918
1398
  }
919
1399
  if (key.escape) {
920
- if (runner.isRunning())
1400
+ if (state.outputExpanded)
1401
+ dispatch({ type: "toggle-output" });
1402
+ else if (runner.isRunning())
921
1403
  runner.abort();
922
1404
  else if (input) {
923
1405
  setInput("");
@@ -984,7 +1466,9 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
984
1466
  return;
985
1467
  if (historyIdx.current < 0)
986
1468
  historyDraft.current = input;
987
- const idx = historyIdx.current < 0 ? history.current.length - 1 : Math.max(0, historyIdx.current - 1);
1469
+ const idx = historyIdx.current < 0
1470
+ ? history.current.length - 1
1471
+ : Math.max(0, historyIdx.current - 1);
988
1472
  historyIdx.current = idx;
989
1473
  const v = history.current[idx] ?? "";
990
1474
  setInput(v);
@@ -1044,17 +1528,31 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
1044
1528
  const before = input.slice(0, cursor);
1045
1529
  const at = input.slice(cursor, cursor + 1) || " ";
1046
1530
  const after = input.slice(cursor + 1);
1047
- return (_jsxs(Box, { flexDirection: "column", width: cols, height: usableRows, children: [_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", marginX: 2, paddingX: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { children: [_jsx(Text, { backgroundColor: "#2563EB", color: "#FFFFFF", bold: true, children: " \u25C6 clai " }), _jsxs(Text, { color: "#94A3B8", children: [" v", version] })] }), _jsxs(Text, { children: [_jsx(Text, { backgroundColor: "#854D0E", color: "#FFFFFF", bold: true, children: ` ${mode.toUpperCase()} ` }), _jsx(Text, { color: "#94A3B8", children: " MODE" })] })] }), _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { color: "green", children: provider }), _jsx(Text, { dimColor: true, children: " / " }), _jsx(Text, { color: "cyan", children: model }), _jsx(Text, { dimColor: true, children: ` · ${safeCwd()}` }), offset > 0 ? _jsx(Text, { color: "yellow", children: ` · ▲ ${offset}` }) : null] })] }), overlay.kind === "pager" ? (_jsx(Pager, { title: overlay.title, body: overlay.body, height: viewportH, onClose: closeOverlay })) : overlay.kind === "jobs" ? (_jsx(JobsPanel, { jobs: jobs, onClose: closeOverlay })) : overlay.kind === "picker" ? (_jsx(PickerPanel, { title: overlay.title, options: overlay.options, height: viewportH, onSelect: overlay.onSelect, onClose: closeOverlay })) : (_jsx(Box, { flexDirection: "column", height: viewportH, children: visible.map((line, i) => (_jsx(Text, { wrap: "wrap", children: line === "" ? " " : line }, i))) })), slashMenuOpen && !modalActive
1531
+ return (_jsxs(Box, { flexDirection: "column", width: cols, height: usableRows, children: [_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", marginX: 2, paddingX: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { children: [_jsxs(Text, { backgroundColor: "#2563EB", color: "#FFFFFF", bold: true, children: [" ", "\u25C6 clai", " "] }), _jsxs(Text, { color: "#94A3B8", children: [" v", version] })] }), _jsxs(Text, { children: [_jsx(Text, { backgroundColor: "#854D0E", color: "#FFFFFF", bold: true, children: ` ${mode.toUpperCase()} ` }), _jsx(Text, { color: "#94A3B8", children: " MODE" })] })] }), _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { color: "green", children: provider }), _jsx(Text, { dimColor: true, children: " / " }), _jsx(Text, { color: "cyan", children: model }), _jsx(Text, { dimColor: true, children: ` · ${safeCwd()}` }), offset > 0 ? _jsx(Text, { color: "yellow", children: ` · ▲ ${offset}` }) : null] })] }), overlay.kind === "pager" ? (_jsx(Pager, { title: overlay.title, body: overlay.body, height: viewportH, onClose: closeOverlay })) : overlay.kind === "jobs" ? (_jsx(JobsPanel, { jobs: jobs, onClose: closeOverlay })) : overlay.kind === "picker" ? (_jsx(PickerPanel, { title: overlay.title, options: overlay.options, height: viewportH, onSelect: overlay.onSelect, onClose: closeOverlay })) : (_jsx(Box, { flexDirection: "column", height: viewportH, children: visible.map((line, i) => (_jsx(Text, { wrap: "wrap", children: line === "" ? " " : line }, i))) })), slashMenuOpen && !modalActive
1048
1532
  ? visibleSlashSuggestions.map((cmd, i) => {
1049
1533
  const absoluteIndex = slashWindowStart + i;
1050
- return (_jsxs(Text, { wrap: "truncate-end", backgroundColor: absoluteIndex === selected ? "#2563EB" : absoluteIndex % 2 === 0 ? "#1E293B" : "#0F172A", children: [_jsxs(Text, { color: absoluteIndex === selected ? "#FFFFFF" : "#67E8F9", bold: true, children: [absoluteIndex === selected ? " ❯ " : " ", cmd.command.padEnd(14)] }), cmd.usage ? _jsxs(Text, { color: absoluteIndex === selected ? "#FFFFFF" : "#CBD5E1", children: [cmd.usage, " "] }) : null, _jsxs(Text, { color: "#F8FAFC", children: [" ", cmd.description] }), i === visibleSlashSuggestions.length - 1 && slashWindowStart + menuH < suggestions.length
1051
- ? _jsx(Text, { dimColor: true, children: ` · ${suggestions.length - slashWindowStart - menuH} more ↓` })
1052
- : null] }, cmd.command));
1534
+ return (_jsxs(Text, { wrap: "truncate-end", backgroundColor: absoluteIndex === selected
1535
+ ? "#2563EB"
1536
+ : absoluteIndex % 2 === 0
1537
+ ? "#1E293B"
1538
+ : "#0F172A", children: [_jsxs(Text, { color: absoluteIndex === selected ? "#FFFFFF" : "#67E8F9", bold: true, children: [absoluteIndex === selected ? " ❯ " : " ", cmd.command.padEnd(14)] }), cmd.usage ? (_jsxs(Text, { color: absoluteIndex === selected ? "#FFFFFF" : "#CBD5E1", children: [cmd.usage, " "] })) : null, _jsxs(Text, { color: "#F8FAFC", children: [" ", cmd.description] }), i === visibleSlashSuggestions.length - 1 &&
1539
+ slashWindowStart + menuH < suggestions.length ? (_jsx(Text, { dimColor: true, children: ` · ${suggestions.length - slashWindowStart - menuH} more ↓` })) : null] }, cmd.command));
1053
1540
  })
1054
1541
  : null, fileMenuOpen && !modalActive
1055
- ? fileSuggestions.map((file, i) => (_jsxs(Text, { wrap: "truncate-end", backgroundColor: i === selected ? "#2563EB" : i % 2 === 0 ? "#1E293B" : "#0F172A", children: [_jsxs(Text, { color: i === selected ? "#FFFFFF" : file.isDir ? "#67E8F9" : "#F8FAFC", bold: i === selected, children: [i === selected ? "❯ " : " ", file.isDir ? "▸ " : "· ", file.value] }), _jsx(Text, { dimColor: true, children: file.isDir ? " directory" : " attach file" })] }, file.value)))
1056
- : null, secretRequest ? (_jsx(SecretInputPanel, { title: secretRequest.title, prompt: secretRequest.prompt, onSubmit: (value) => answerSecret(value), onCancel: () => answerSecret(undefined) })) : state.pendingConfirm ? (_jsx(ConfirmModal, { confirm: state.pendingConfirm, onAnswer: answerConfirm })) : (_jsx(Box, { children: compacting ? (_jsxs(Text, { children: [_jsxs(Text, { color: "magenta", children: [spinner, " "] }), _jsx(Text, { color: "yellow", children: "compacting conversation\u2026" })] })) : state.status.running ? (_jsxs(Text, { children: [_jsxs(Text, { color: "magenta", children: [spinner, " "] }), _jsx(Text, { color: "yellow", children: state.status.activity || "working" }), state.status.step > 0 ? _jsx(Text, { dimColor: true, children: ` · step ${state.status.step}` }) : null, _jsx(Text, { dimColor: true, children: ` · ${elapsed}s · esc to cancel` }), state.queued.length > 0 ? _jsx(Text, { dimColor: true, children: ` · ${state.queued.length} queued` }) : null] })) : null })), _jsxs(Box, { borderStyle: "round", borderColor: state.pendingConfirm || secretRequest ? "yellow" : state.status.running ? "yellow" : "cyan", paddingX: 1, children: [_jsx(Text, { color: state.status.running ? "yellow" : "cyan", bold: true, children: state.pendingConfirm || secretRequest ? "! " : "❯ " }), secretRequest ? (_jsx(Text, { bold: true, children: "Input locked \u00B7 complete the secure input above" })) : state.pendingConfirm ? (_jsx(Text, { bold: true, children: "Input locked \u00B7 answer the confirmation above with Y or N" })) : input.length === 0 ? (_jsx(Text, { dimColor: true, children: state.status.running
1542
+ ? fileSuggestions.map((file, i) => (_jsxs(Text, { wrap: "truncate-end", backgroundColor: i === selected ? "#2563EB" : i % 2 === 0 ? "#1E293B" : "#0F172A", children: [_jsxs(Text, { color: i === selected
1543
+ ? "#FFFFFF"
1544
+ : file.isDir
1545
+ ? "#67E8F9"
1546
+ : "#F8FAFC", bold: i === selected, children: [i === selected ? "❯ " : " ", file.isDir ? "▸ " : "· ", file.value] }), _jsx(Text, { dimColor: true, children: file.isDir ? " directory" : " attach file" })] }, file.value)))
1547
+ : null, secretRequest ? (_jsx(SecretInputPanel, { title: secretRequest.title, prompt: secretRequest.prompt, onSubmit: (value) => answerSecret(value), onCancel: () => answerSecret(undefined) })) : state.pendingConfirm ? (_jsx(ConfirmModal, { confirm: state.pendingConfirm, onAnswer: answerConfirm })) : (_jsx(Box, { children: compacting ? (_jsxs(Text, { children: [_jsxs(Text, { color: "magenta", children: [spinner, " "] }), _jsx(Text, { color: "yellow", children: "compacting conversation\u2026" })] })) : state.status.running ? (_jsxs(Text, { children: [_jsxs(Text, { color: "magenta", children: [spinner, " "] }), _jsx(Text, { color: "yellow", children: state.status.activity || "working" }), state.status.step > 0 ? (_jsx(Text, { dimColor: true, children: ` · step ${state.status.step}` })) : null, _jsx(Text, { dimColor: true, children: ` · ${elapsed}s · esc to cancel` }), state.queued.length > 0 ? (_jsx(Text, { dimColor: true, children: ` · ${state.queued.length} queued` })) : null] })) : null })), _jsxs(Box, { borderStyle: "round", borderColor: state.pendingConfirm || secretRequest
1548
+ ? "yellow"
1549
+ : state.status.running
1550
+ ? "yellow"
1551
+ : "cyan", paddingX: 1, children: [_jsx(Text, { color: state.status.running ? "yellow" : "cyan", bold: true, children: state.pendingConfirm || secretRequest ? "! " : "❯ " }), secretRequest ? (_jsx(Text, { bold: true, children: "Input locked \u00B7 complete the secure input above" })) : state.pendingConfirm ? (_jsx(Text, { bold: true, children: "Input locked \u00B7 answer the confirmation above with Y or N" })) : input.length === 0 ? (_jsx(Text, { dimColor: true, children: state.status.running
1057
1552
  ? "type to queue a message…"
1058
- : "ask anything · / for commands · @file to attach · esc to cancel" })) : (_jsxs(Text, { wrap: "truncate-start", children: [_jsx(Text, { color: "#F8FAFC", children: before }), _jsx(Text, { backgroundColor: "#22D3EE", color: "#020617", bold: true, children: at }), _jsx(Text, { color: "#F8FAFC", children: after })] }))] }), !secretRequest && !state.pendingConfirm && !state.status.running && !compacting ? (_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { backgroundColor: "#166534", color: "#FFFFFF", bold: true, children: " READY " }), state.queued.length > 0 ? _jsx(Text, { backgroundColor: "#854D0E", color: "#FFFFFF", bold: true, children: ` ${state.queued.length} QUEUED ` }) : null, _jsx(Text, { children: " " }), _jsx(Text, { backgroundColor: "#334155", color: "#F8FAFC", children: " / COMMANDS " }), _jsx(Text, { children: " " }), _jsx(Text, { backgroundColor: "#334155", color: "#F8FAFC", children: " CTRL+T THINKING " }), _jsx(Text, { children: " " }), _jsx(Text, { backgroundColor: "#334155", color: "#F8FAFC", children: " CTRL+O OUTPUT " }), _jsx(Text, { children: " " }), _jsx(Text, { backgroundColor: mouseMode ? "#155E75" : "#334155", color: "#F8FAFC", children: ` MOUSE ${mouseMode ? "ON" : "OFF"} ` })] })) : null] }));
1553
+ : "ask anything · / for commands · @file to attach · esc to cancel" })) : (_jsxs(Text, { wrap: "truncate-start", children: [_jsx(Text, { color: "#F8FAFC", children: before }), _jsx(Text, { backgroundColor: "#22D3EE", color: "#020617", bold: true, children: at }), _jsx(Text, { color: "#F8FAFC", children: after })] }))] }), !secretRequest &&
1554
+ !state.pendingConfirm &&
1555
+ !state.status.running &&
1556
+ !compacting ? (_jsxs(Box, { paddingX: 1, justifyContent: "center", children: [_jsxs(Text, { backgroundColor: "#166534", color: "#FFFFFF", bold: true, children: [" ", "READY", " "] }), state.queued.length > 0 ? (_jsx(Text, { backgroundColor: "#854D0E", color: "#FFFFFF", bold: true, children: ` ${state.queued.length} QUEUED ` })) : null, _jsx(Text, { children: " " }), _jsxs(Text, { backgroundColor: "#334155", color: "#F8FAFC", children: [" ", "/ COMMANDS", " "] }), _jsx(Text, { children: " " }), _jsxs(Text, { backgroundColor: "#334155", color: "#F8FAFC", children: [" ", "CTRL+T THINKING", " "] }), _jsx(Text, { children: " " }), _jsxs(Text, { backgroundColor: "#334155", color: "#F8FAFC", children: [" ", "CTRL+O OUTPUT", " "] }), _jsx(Text, { children: " " }), _jsx(Text, { backgroundColor: mouseMode ? "#155E75" : "#334155", color: "#F8FAFC", children: ` MOUSE ${mouseMode ? "ON" : "OFF"} ` })] })) : null] }));
1059
1557
  }
1060
1558
  //# sourceMappingURL=App.js.map