xibecode 0.9.6 → 0.9.7

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 (46) hide show
  1. package/dist/commands/chat.d.ts.map +1 -1
  2. package/dist/commands/chat.js +26 -14
  3. package/dist/commands/chat.js.map +1 -1
  4. package/dist/commands/run-pr.d.ts.map +1 -1
  5. package/dist/commands/run-pr.js +22 -11
  6. package/dist/commands/run-pr.js.map +1 -1
  7. package/dist/commands/run.d.ts.map +1 -1
  8. package/dist/commands/run.js +24 -5
  9. package/dist/commands/run.js.map +1 -1
  10. package/dist/commands/skills.d.ts +6 -0
  11. package/dist/commands/skills.d.ts.map +1 -0
  12. package/dist/commands/skills.js +109 -0
  13. package/dist/commands/skills.js.map +1 -0
  14. package/dist/core/agent.d.ts +5 -1
  15. package/dist/core/agent.d.ts.map +1 -1
  16. package/dist/core/agent.js +35 -11
  17. package/dist/core/agent.js.map +1 -1
  18. package/dist/core/skills.d.ts +10 -0
  19. package/dist/core/skills.d.ts.map +1 -1
  20. package/dist/core/skills.js +37 -0
  21. package/dist/core/skills.js.map +1 -1
  22. package/dist/index.js +6 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/ink.d.ts +1 -1
  25. package/dist/ink.d.ts.map +1 -1
  26. package/dist/ink.js +1 -1
  27. package/dist/ink.js.map +1 -1
  28. package/dist/ui/claude-style-chat.d.ts.map +1 -1
  29. package/dist/ui/claude-style-chat.js +196 -80
  30. package/dist/ui/claude-style-chat.js.map +1 -1
  31. package/dist/ui/enhanced-tui.d.ts +0 -4
  32. package/dist/ui/enhanced-tui.d.ts.map +1 -1
  33. package/dist/ui/enhanced-tui.js +3 -8
  34. package/dist/ui/enhanced-tui.js.map +1 -1
  35. package/dist/utils/at-references.d.ts +14 -0
  36. package/dist/utils/at-references.d.ts.map +1 -0
  37. package/dist/utils/at-references.js +47 -0
  38. package/dist/utils/at-references.js.map +1 -0
  39. package/dist/utils/image-attachments.d.ts +12 -0
  40. package/dist/utils/image-attachments.d.ts.map +1 -0
  41. package/dist/utils/image-attachments.js +30 -0
  42. package/dist/utils/image-attachments.js.map +1 -0
  43. package/dist/webui/server.d.ts.map +1 -1
  44. package/dist/webui/server.js +32 -2
  45. package/dist/webui/server.js.map +1 -1
  46. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
3
  import TextInput from 'ink-text-input';
4
- import { Box, Text, createRoot, useApp, useInput } from '../ink.js';
4
+ import { Box, Static, Text, createRoot, useApp, useInput } from '../ink.js';
5
5
  import * as fs from 'fs/promises';
6
6
  import * as path from 'path';
7
7
  import { ConfigManager, PROVIDER_CONFIGS } from '../utils/config.js';
@@ -14,7 +14,9 @@ import { renderAndRun } from '../interactiveHelpers.js';
14
14
  import { AssistantMarkdown } from '../components/AssistantMarkdown.js';
15
15
  import { formatToolArgs, formatToolOutcome, formatRunSwarmDetailLines } from '../utils/tool-display.js';
16
16
  import { SPINNER_VERBS } from '../constants/spinnerVerbs.js';
17
- const APP_VERSION = '0.9.6';
17
+ import { extractAtReferences, splitAtReferences } from '../utils/at-references.js';
18
+ import { loadImageAttachment, mimeFromExtension } from '../utils/image-attachments.js';
19
+ const APP_VERSION = '0.9.7';
18
20
  const HERO_LOGO = [
19
21
  '██╗ ██╗██╗██████╗ ███████╗',
20
22
  '╚██╗██╔╝██║██╔══██╗██╔════╝',
@@ -58,6 +60,14 @@ function isAnthropicWireFormat(requestFormat, provider, customProviderFormat) {
58
60
  }
59
61
  return false;
60
62
  }
63
+ function computeWindowStart(total, selected, windowSize) {
64
+ if (total <= windowSize)
65
+ return 0;
66
+ const clampedSelected = Math.max(0, Math.min(selected, total - 1));
67
+ const maxStart = total - windowSize;
68
+ const start = clampedSelected - Math.floor(windowSize / 2);
69
+ return Math.max(0, Math.min(start, maxStart));
70
+ }
61
71
  function lineColorKey(type) {
62
72
  switch (type) {
63
73
  case 'user':
@@ -117,10 +127,8 @@ function XibeCodeChatApp(props) {
117
127
  const { exit } = useApp();
118
128
  const [input, setInput] = useState('');
119
129
  const [isRunning, setIsRunning] = useState(false);
120
- const [paused, setPaused] = useState(false);
121
130
  const [runStartedAt, setRunStartedAt] = useState(null);
122
131
  const [runElapsedMs, setRunElapsedMs] = useState(0);
123
- const [liveStats, setLiveStats] = useState(null);
124
132
  const [wireFormat, setWireFormat] = useState(props.initialRequestFormat);
125
133
  const [activeModel, setActiveModel] = useState(props.model);
126
134
  const [activeMode, setActiveMode] = useState(props.initialMode);
@@ -156,33 +164,28 @@ function XibeCodeChatApp(props) {
156
164
  const [configCostModeIndex, setConfigCostModeIndex] = useState(0);
157
165
  const [workSpinnerFrame, setWorkSpinnerFrame] = useState(0);
158
166
  const [workVerbIndex, setWorkVerbIndex] = useState(0);
167
+ const nextLineIdRef = useRef(1);
159
168
  const [lines, setLines] = useState([
160
169
  {
170
+ kind: 'line',
171
+ id: nextLineIdRef.current++,
161
172
  type: 'info',
162
173
  text: 'XibeCode interactive session. Type /exit to quit, /clear to reset the transcript.',
163
174
  },
164
- { type: 'info', text: 'Tip: press p to pause/resume live updates (for copying).' },
175
+ { kind: 'line', id: nextLineIdRef.current++, type: 'info', text: 'Type /help for shortcuts.' },
165
176
  ]);
166
- const pausedBuffer = useRef([]);
167
177
  const pushLine = useCallback((line) => {
178
+ const withId = { ...line, kind: 'line', id: nextLineIdRef.current++ };
168
179
  props.onUiLine?.(line);
169
- if (paused) {
170
- pausedBuffer.current.push(line);
171
- return;
172
- }
173
180
  // Keep a much larger in-memory transcript so context doesn't vanish quickly.
174
- setLines((prev) => [...prev.slice(-5000), line]);
175
- }, [paused, props]);
176
- const togglePaused = useCallback(() => {
177
- setPaused((prev) => {
178
- const next = !prev;
179
- if (prev && pausedBuffer.current.length > 0) {
180
- const buffered = pausedBuffer.current.splice(0, pausedBuffer.current.length);
181
- setLines((current) => [...current.slice(-5000), ...buffered].slice(-5000));
182
- }
183
- return next;
184
- });
185
- }, []);
181
+ setLines((prev) => [...prev.slice(-5000), withId]);
182
+ }, [props]);
183
+ const abortControllerRef = useRef(null);
184
+ const abortReasonRef = useRef('none');
185
+ const queuedPromptRef = useRef(null);
186
+ const lastVisibleOutputAtRef = useRef(Date.now());
187
+ const currentPromptRef = useRef(null);
188
+ const restartAttemptsRef = useRef(0);
186
189
  const lastBgLineByTask = useRef(new Map());
187
190
  useEffect(() => {
188
191
  if (!isRunning)
@@ -226,9 +229,27 @@ function XibeCodeChatApp(props) {
226
229
  }
227
230
  const id = setInterval(() => {
228
231
  setWorkSpinnerFrame((f) => (f + 1) % WORK_SPINNER_FRAMES.length);
229
- }, 90);
232
+ }, 120);
230
233
  return () => clearInterval(id);
231
234
  }, [isRunning]);
235
+ useEffect(() => {
236
+ if (!isRunning)
237
+ return;
238
+ const id = setInterval(() => {
239
+ // Silent means: no visible output (tool_call/tool_result/stream_text/response) for 90s.
240
+ if (Date.now() - lastVisibleOutputAtRef.current > 90_000) {
241
+ if (abortControllerRef.current && abortReasonRef.current === 'none') {
242
+ abortReasonRef.current = 'watchdog';
243
+ pushLine({
244
+ type: 'info',
245
+ text: `No output for 90s — restarting (attempt ${Math.min(restartAttemptsRef.current + 1, 2)}/2)…`,
246
+ });
247
+ abortControllerRef.current.abort();
248
+ }
249
+ }
250
+ }, 1000);
251
+ return () => clearInterval(id);
252
+ }, [isRunning, pushLine]);
232
253
  useEffect(() => {
233
254
  if (!isRunning) {
234
255
  setRunStartedAt(null);
@@ -242,19 +263,6 @@ function XibeCodeChatApp(props) {
242
263
  }, 250);
243
264
  return () => clearInterval(id);
244
265
  }, [isRunning]);
245
- useEffect(() => {
246
- if (!isRunning || !props.getLiveStats)
247
- return;
248
- const id = setInterval(() => {
249
- try {
250
- setLiveStats(props.getLiveStats());
251
- }
252
- catch {
253
- // ignore
254
- }
255
- }, 1000);
256
- return () => clearInterval(id);
257
- }, [isRunning, props]);
258
266
  useEffect(() => {
259
267
  if (!isRunning)
260
268
  return;
@@ -359,8 +367,14 @@ function XibeCodeChatApp(props) {
359
367
  }, [activeMode, props, pushLine]);
360
368
  const onSubmit = useCallback(async (value) => {
361
369
  const trimmed = value.trim();
362
- if (!trimmed || isRunning)
370
+ if (!trimmed)
371
+ return;
372
+ if (isRunning) {
373
+ queuedPromptRef.current = trimmed;
374
+ setInput('');
375
+ pushLine({ type: 'info', text: 'Queued message — will run next.' });
363
376
  return;
377
+ }
364
378
  const commandMatches = CHAT_COMMANDS.filter((command) => command.name.toLowerCase().startsWith(trimmed.toLowerCase()));
365
379
  const exactMatch = CHAT_COMMANDS.some((command) => command.name.toLowerCase() === trimmed.toLowerCase());
366
380
  const resolvedInput = trimmed.startsWith('/') && !exactMatch && commandMatches[selectedCommandIndex]
@@ -464,14 +478,21 @@ function XibeCodeChatApp(props) {
464
478
  return;
465
479
  }
466
480
  if (resolvedInput === '/clear') {
467
- setLines([{ type: 'info', text: 'Cleared transcript.' }]);
481
+ pushLine({ type: 'info', text: '──────────── transcript cleared ────────────' });
468
482
  return;
469
483
  }
470
484
  if (resolvedInput === '/help') {
471
485
  setLines((prev) => [
472
486
  ...prev,
473
- { type: 'info', text: `Shortcuts: ${QUICK_HELP.join(' · ')}` },
474
487
  {
488
+ kind: 'line',
489
+ id: nextLineIdRef.current++,
490
+ type: 'info',
491
+ text: `Shortcuts: ${QUICK_HELP.join(' · ')}`,
492
+ },
493
+ {
494
+ kind: 'line',
495
+ id: nextLineIdRef.current++,
475
496
  type: 'info',
476
497
  text: 'Press Ctrl+C to quit. Type any prompt and XibeCode will run agent mode.',
477
498
  },
@@ -579,26 +600,94 @@ function XibeCodeChatApp(props) {
579
600
  }
580
601
  return;
581
602
  }
582
- pushLine({ type: 'user', text: resolvedInput });
583
- setIsRunning(true);
584
- try {
585
- const startedAt = Date.now();
586
- const stats = await props.runPrompt(resolvedInput, pushLine);
587
- const elapsedMs = Date.now() - startedAt;
588
- const seconds = (elapsedMs / 1000).toFixed(1);
589
- pushLine({
590
- type: 'info',
591
- text: `Done in ${seconds}s · ` +
592
- `tokens ${stats.inputTokens} in / ${stats.outputTokens} out / ${stats.totalTokens} total` +
593
- (stats.costLabel ? ` · cost ${stats.costLabel}` : ''),
594
- });
595
- }
596
- catch (error) {
597
- const message = error instanceof Error ? error.message : 'Unknown error';
598
- pushLine({ type: 'error', text: message });
599
- }
600
- finally {
601
- setIsRunning(false);
603
+ const runOne = async (prompt) => {
604
+ currentPromptRef.current = prompt;
605
+ abortReasonRef.current = 'none';
606
+ lastVisibleOutputAtRef.current = Date.now();
607
+ abortControllerRef.current = new AbortController();
608
+ pushLine({ type: 'user', text: prompt });
609
+ setIsRunning(true);
610
+ try {
611
+ const startedAt = Date.now();
612
+ const refs = extractAtReferences(prompt, process.cwd());
613
+ const { image: imageRefs } = splitAtReferences(refs);
614
+ const images = [];
615
+ for (const ref of imageRefs) {
616
+ try {
617
+ const mime = mimeFromExtension(ref.extension);
618
+ if (!mime)
619
+ continue;
620
+ const attachment = await loadImageAttachment(ref.resolvedPath, { mime });
621
+ images.push(attachment);
622
+ }
623
+ catch (err) {
624
+ const message = err instanceof Error ? err.message : 'Failed to load image';
625
+ pushLine({ type: 'error', text: message });
626
+ }
627
+ }
628
+ const stats = await props.runPrompt(prompt, pushLine, {
629
+ images: images.length ? images : undefined,
630
+ signal: abortControllerRef.current.signal,
631
+ onVisibleOutput: () => {
632
+ lastVisibleOutputAtRef.current = Date.now();
633
+ },
634
+ });
635
+ const elapsedMs = Date.now() - startedAt;
636
+ const seconds = (elapsedMs / 1000).toFixed(1);
637
+ pushLine({
638
+ type: 'info',
639
+ text: `Done in ${seconds}s` + (stats.costLabel ? ` · cost ${stats.costLabel}` : ''),
640
+ });
641
+ }
642
+ finally {
643
+ abortControllerRef.current = null;
644
+ currentPromptRef.current = null;
645
+ setIsRunning(false);
646
+ }
647
+ };
648
+ const isAbortError = (err) => {
649
+ if (!err || typeof err !== 'object')
650
+ return false;
651
+ const anyErr = err;
652
+ return anyErr.name === 'AbortError' || String(anyErr.message || '').toLowerCase().includes('aborted');
653
+ };
654
+ // Watchdog auto-restart loop (up to 2 restarts)
655
+ restartAttemptsRef.current = 0;
656
+ let promptToRun = resolvedInput;
657
+ while (promptToRun) {
658
+ try {
659
+ await runOne(promptToRun);
660
+ break;
661
+ }
662
+ catch (err) {
663
+ if (isAbortError(err) && abortReasonRef.current === 'watchdog') {
664
+ restartAttemptsRef.current += 1;
665
+ if (restartAttemptsRef.current <= 2) {
666
+ // Restart same prompt
667
+ continue;
668
+ }
669
+ pushLine({ type: 'error', text: 'Restart limit reached (2). Stopping.' });
670
+ break;
671
+ }
672
+ if (isAbortError(err) && abortReasonRef.current === 'user') {
673
+ pushLine({ type: 'info', text: 'Cancelled.' });
674
+ break;
675
+ }
676
+ const message = err instanceof Error ? err.message : 'Unknown error';
677
+ pushLine({ type: 'error', text: message });
678
+ break;
679
+ }
680
+ finally {
681
+ // If a message was queued during this run, send it next.
682
+ if (!isRunning && queuedPromptRef.current) {
683
+ const q = queuedPromptRef.current;
684
+ queuedPromptRef.current = null;
685
+ promptToRun = q;
686
+ }
687
+ else {
688
+ promptToRun = null;
689
+ }
690
+ }
602
691
  }
603
692
  }, [
604
693
  applyModel,
@@ -617,6 +706,7 @@ function XibeCodeChatApp(props) {
617
706
  wireFormat,
618
707
  applyMode,
619
708
  startSetupWizard,
709
+ isRunning,
620
710
  ]);
621
711
  const normalizedInput = input.trim().toLowerCase();
622
712
  const isSlashMode = input.startsWith('/');
@@ -631,13 +721,21 @@ function XibeCodeChatApp(props) {
631
721
  mode.label.toLowerCase().includes(modeFilter) ||
632
722
  mode.description.toLowerCase().includes(modeFilter));
633
723
  const filteredCommands = CHAT_COMMANDS.filter((command) => command.name.toLowerCase().startsWith(normalizedInput || '/'));
724
+ const MODEL_PICKER_WINDOW = 14;
725
+ const modelPickerStart = computeWindowStart(filteredModels.length, selectedModelIndex, MODEL_PICKER_WINDOW);
726
+ const visibleModelOptions = filteredModels.slice(modelPickerStart, modelPickerStart + MODEL_PICKER_WINDOW);
727
+ const setupModelPickerStart = computeWindowStart(setupModels.length, setupSelectedModelIndex, MODEL_PICKER_WINDOW);
728
+ const visibleSetupModelOptions = setupModels.slice(setupModelPickerStart, setupModelPickerStart + MODEL_PICKER_WINDOW);
634
729
  useInput((inputKey, key) => {
635
730
  if (key.ctrl && inputKey === 'c') {
636
731
  exit();
637
732
  return;
638
733
  }
639
- if (!key.ctrl && !key.meta && !key.shift && (inputKey === 'p' || inputKey === 'P')) {
640
- togglePaused();
734
+ if (isRunning && key.escape) {
735
+ if (abortControllerRef.current && abortReasonRef.current === 'none') {
736
+ abortReasonRef.current = 'user';
737
+ abortControllerRef.current.abort();
738
+ }
641
739
  return;
642
740
  }
643
741
  if (configMenuOpen && !isRunning) {
@@ -817,9 +915,8 @@ function XibeCodeChatApp(props) {
817
915
  const picked = setupModels[setupSelectedModelIndex];
818
916
  if (!picked)
819
917
  return;
820
- const config = new ConfigManager(props.profile);
821
- config.set('model', picked);
822
- pushLine({ type: 'info', text: `Model set to: ${picked}` });
918
+ // Use the same path as /model so the live status line updates immediately.
919
+ void applyModel(picked);
823
920
  setSetupModelPickerOpen(false);
824
921
  setSetupStep('idle');
825
922
  pushLine({ type: 'info', text: 'Setup complete. Type a prompt to start.' });
@@ -907,12 +1004,8 @@ function XibeCodeChatApp(props) {
907
1004
  ? `${workVerbPhrase.slice(0, 30)}…`
908
1005
  : workVerbPhrase;
909
1006
  const tail = `working ${WORK_SPINNER_FRAMES[workSpinnerFrame]} · ${shortVerb}`;
910
- const pauseLabel = paused ? ' | PAUSED' : '';
911
1007
  const elapsed = runElapsedMs ? ` | elapsed ${(runElapsedMs / 1000).toFixed(1)}s` : '';
912
- const tokens = liveStats
913
- ? ` | tokens ${liveStats.inputTokens}/${liveStats.outputTokens}/${liveStats.totalTokens}`
914
- : '';
915
- return `model: ${activeModel} | format: ${wireFormat} | mode: ${activeMode} | provider: ${props.provider || 'auto'} | ${tail}${pauseLabel}${elapsed}${tokens}`;
1008
+ return `model: ${activeModel} | format: ${wireFormat} | mode: ${activeMode} | provider: ${props.provider || 'auto'} | ${tail}${elapsed}`;
916
1009
  }, [
917
1010
  activeModel,
918
1011
  activeMode,
@@ -921,17 +1014,34 @@ function XibeCodeChatApp(props) {
921
1014
  wireFormat,
922
1015
  workSpinnerFrame,
923
1016
  workVerbPhrase,
924
- paused,
925
1017
  runElapsedMs,
926
- liveStats,
927
1018
  ]);
928
- const showWelcome = lines.length <= 1;
1019
+ /** Taller transcript area once there is real chat; hero stays visible for the whole session. */
1020
+ const hasChatContent = lines.some((l) => l.kind === 'line' &&
1021
+ (l.type === 'user' ||
1022
+ l.type === 'assistant' ||
1023
+ l.type === 'tool' ||
1024
+ l.type === 'tool_out' ||
1025
+ l.type === 'error'));
929
1026
  const providerName = props.provider ? props.provider.toUpperCase() : 'AUTO';
930
1027
  const divider = '─'.repeat(98);
931
- const chatPanelHeight = showWelcome ? 12 : 22;
932
- return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [showWelcome && (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [HERO_LOGO.map((line, idx) => (_jsx(React.Fragment, { children: _jsx(Text, { bold: true, color: idx < 6 ? 'claude' : 'suggestion', children: line }) }, `logo-${idx}`))), _jsx(Text, { color: "suggestion", children: "\u2726 Any model, Every tool, Zero limits. \u2726" }), _jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "claude", flexDirection: "column", paddingX: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: "inactive", children: "Provider " }), _jsx(Text, { color: "claude", bold: true, children: providerName })] }), _jsxs(Text, { children: [_jsx(Text, { color: "inactive", children: "Model " }), _jsx(Text, { children: activeModel })] }), _jsxs(Text, { children: [_jsx(Text, { color: "inactive", children: "Endpoint " }), _jsx(Text, { children: props.baseUrl || 'provider default' })] }), _jsxs(Text, { children: [_jsx(Text, { color: "inactive", children: "Format " }), _jsxs(Text, { children: [wireFormat, ' ', _jsxs(Text, { dimColor: true, children: ["(", isAnthropicWireFormat(wireFormat, props.provider, props.customProviderFormat)
933
- ? 'Anthropic Messages'
934
- : 'OpenAI chat', ")"] })] })] })] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "suggestion", children: "\u25C8 cloud" }), _jsx(Text, { color: "inactive", children: " Ready \u2014 type " }), _jsx(Text, { color: "claude", children: "/help" }), _jsx(Text, { color: "inactive", children: " to begin" })] }), _jsxs(Text, { color: "inactive", children: ["xibecode ", _jsxs(Text, { color: "claude", children: ["v", APP_VERSION] })] })] })), _jsx(Text, { dimColor: true, children: status }), _jsx(Text, { color: "subtle", children: divider }), _jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "promptBorder", flexDirection: "column", paddingX: 1, minHeight: chatPanelHeight, children: lines.slice(-chatPanelHeight + 2).map((line, index) => line.type === 'assistant' ? (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { bold: true, color: prefixColorKey('assistant'), children: [prefixForType('assistant'), ":"] }), _jsx(Box, { marginLeft: 2, flexDirection: "column", children: _jsx(AssistantMarkdown, { content: line.text }) })] }, `${index}-assistant-${line.text.length}-${line.text.slice(0, 12)}`)) : (_jsx(React.Fragment, { children: _jsxs(Text, { children: [_jsxs(Text, { bold: true, color: prefixColorKey(line.type), children: [prefixForType(line.type), ":", ' '] }), _jsx(Text, { color: lineColorKey(line.type), children: line.text })] }) }, `${index}-${line.type}-${line.text.slice(0, 24)}`))) }), _jsx(Text, { color: "subtle", children: divider }), isRunning && (_jsx(Box, { marginTop: 1, paddingX: 1, borderStyle: "round", borderColor: "claudeBlue_FOR_SYSTEM_SPINNER", flexDirection: "column", children: _jsxs(Text, { wrap: "wrap", children: [_jsxs(Text, { bold: true, color: "claudeBlue_FOR_SYSTEM_SPINNER", children: [WORK_SPINNER_FRAMES[workSpinnerFrame], ' '] }), _jsx(Text, { bold: true, color: "briefLabelClaude", children: workVerbPhrase })] }) })), _jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "claude", paddingX: 1, children: [_jsx(Text, { color: "claude", children: '> ' }), _jsx(TextInput, { value: input, onChange: setInput, onSubmit: onSubmit, placeholder: isRunning ? 'Waiting for response…' : 'Message XibeCode…' })] }), isSlashMode && (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "suggestion", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { color: "suggestion", bold: true, children: "Commands" }), filteredCommands.length === 0 ? (_jsxs(Text, { color: "inactive", children: ["No commands match \"", input, "\""] })) : (filteredCommands.map((command, index) => (_jsx(React.Fragment, { children: _jsxs(Text, { children: [_jsx(Text, { color: index === selectedCommandIndex ? 'claude' : 'inactive', children: index === selectedCommandIndex ? '▸ ' : ' ' }), _jsx(Text, { bold: true, color: index === selectedCommandIndex ? 'claude' : 'text', children: command.name }), _jsxs(Text, { color: "inactive", children: [" \u2014 ", command.description] })] }) }, command.name)))), _jsx(Text, { color: "subtle", children: "Use \u2191/\u2193 to navigate, Tab to autocomplete." })] })), modelPickerOpen && (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "claude", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "claude", children: "Select model" }), isModelListLoading ? (_jsx(Text, { color: "inactive", children: "Loading models from provider..." })) : filteredModels.length === 0 ? (_jsx(Text, { color: "inactive", children: "No models matched current filter." })) : (filteredModels.slice(0, 14).map((modelName, index) => (_jsx(React.Fragment, { children: _jsxs(Text, { children: [_jsx(Text, { color: index === selectedModelIndex ? 'claude' : 'inactive', children: index === selectedModelIndex ? '▸ ' : ' ' }), _jsx(Text, { color: index === selectedModelIndex ? 'claude' : 'text', children: modelName })] }) }, modelName)))), _jsx(Text, { color: "subtle", children: "\u2191/\u2193 navigate \u2022 Enter apply \u2022 Esc close" })] })), setupModelPickerOpen && (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "claude", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "claude", children: "Setup: select model" }), setupModels.length === 0 ? (_jsx(Text, { color: "inactive", children: "No models loaded." })) : (setupModels.slice(0, 14).map((modelName, index) => (_jsx(React.Fragment, { children: _jsxs(Text, { children: [_jsx(Text, { color: index === setupSelectedModelIndex ? 'claude' : 'inactive', children: index === setupSelectedModelIndex ? '▸ ' : ' ' }), _jsx(Text, { color: index === setupSelectedModelIndex ? 'claude' : 'text', children: modelName })] }) }, modelName)))), _jsx(Text, { color: "subtle", children: "\u2191/\u2193 navigate \u2022 Enter apply \u2022 Esc cancel" })] })), configMenuOpen && (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "suggestion", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "suggestion", children: "Config" }), CONFIG_MENU.map((item, index) => (_jsx(React.Fragment, { children: _jsxs(Text, { children: [_jsx(Text, { color: index === configSelectedIndex ? 'claude' : 'inactive', children: index === configSelectedIndex ? '▸ ' : ' ' }), _jsx(Text, { bold: true, color: index === configSelectedIndex ? 'claude' : 'text', children: item.label }), _jsxs(Text, { color: "inactive", children: [" \u2014 ", item.description] })] }) }, item.value))), _jsx(Text, { color: "subtle", children: "\u2191/\u2193 navigate \u2022 Enter select \u2022 Esc close" })] })), configProviderPickerOpen && (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "suggestion", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "suggestion", children: "Provider" }), [
1028
+ const staticItems = useMemo(() => [{ kind: 'hero', id: 0 }, ...lines], [lines]);
1029
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { dimColor: true, children: status }), _jsx(Text, { color: "subtle", children: divider }), _jsx(Static, { items: staticItems, children: (item) => {
1030
+ if (item.kind === 'hero') {
1031
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [HERO_LOGO.map((line, idx) => (_jsx(React.Fragment, { children: _jsx(Text, { bold: true, color: idx < 6 ? 'claude' : 'suggestion', children: line }) }, `logo-${idx}`))), _jsx(Text, { color: "suggestion", children: "\u2726 Any model, Every tool, Zero limits. \u2726" }), _jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "claude", flexDirection: "column", paddingX: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: "inactive", children: "Provider " }), _jsx(Text, { color: "claude", bold: true, children: providerName })] }), _jsxs(Text, { children: [_jsx(Text, { color: "inactive", children: "Model " }), _jsx(Text, { children: activeModel })] }), _jsxs(Text, { children: [_jsx(Text, { color: "inactive", children: "Endpoint " }), _jsx(Text, { children: props.baseUrl || 'provider default' })] }), _jsxs(Text, { children: [_jsx(Text, { color: "inactive", children: "Format " }), _jsxs(Text, { children: [wireFormat, ' ', _jsxs(Text, { dimColor: true, children: ["(", isAnthropicWireFormat(wireFormat, props.provider, props.customProviderFormat)
1032
+ ? 'Anthropic Messages'
1033
+ : 'OpenAI chat', ")"] })] })] })] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "suggestion", children: "\u25C8 cloud" }), _jsx(Text, { color: "inactive", children: " Ready \u2014 type " }), _jsx(Text, { color: "claude", children: "/help" }), _jsx(Text, { color: "inactive", children: " to begin" })] }), _jsxs(Text, { color: "inactive", children: ["xibecode ", _jsxs(Text, { color: "claude", children: ["v", APP_VERSION] })] }), _jsx(Text, { color: "subtle", children: '─'.repeat(98) }), _jsx(Text, { color: "inactive", children: "Agent transcript" })] }));
1034
+ }
1035
+ return item.type === 'assistant' ? (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { bold: true, color: prefixColorKey('assistant'), children: [prefixForType('assistant'), ":"] }), _jsx(Box, { marginLeft: 2, flexDirection: "column", children: _jsx(AssistantMarkdown, { content: item.text }) })] })) : (_jsxs(Text, { children: [_jsxs(Text, { bold: true, color: prefixColorKey(item.type), children: [prefixForType(item.type), ":", ' '] }), _jsx(Text, { color: lineColorKey(item.type), children: item.text })] }));
1036
+ } }), !hasChatContent && (_jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(Text, { color: "inactive", children: "(send a message to start)" }) })), _jsx(Text, { color: "subtle", children: divider }), isRunning && (_jsx(Box, { marginTop: 1, paddingX: 1, borderStyle: "round", borderColor: "claudeBlue_FOR_SYSTEM_SPINNER", flexDirection: "column", children: _jsxs(Text, { wrap: "wrap", children: [_jsxs(Text, { bold: true, color: "claudeBlue_FOR_SYSTEM_SPINNER", children: [WORK_SPINNER_FRAMES[workSpinnerFrame], ' '] }), _jsx(Text, { bold: true, color: "briefLabelClaude", children: workVerbPhrase })] }) })), _jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "claude", paddingX: 1, children: [_jsx(Text, { color: "claude", children: '> ' }), _jsx(TextInput, { value: input, onChange: setInput, onSubmit: onSubmit, placeholder: isRunning ? 'Waiting for response…' : 'Message XibeCode…' })] }), isSlashMode && (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "suggestion", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { color: "suggestion", bold: true, children: "Commands" }), filteredCommands.length === 0 ? (_jsxs(Text, { color: "inactive", children: ["No commands match \"", input, "\""] })) : (filteredCommands.map((command, index) => (_jsx(React.Fragment, { children: _jsxs(Text, { children: [_jsx(Text, { color: index === selectedCommandIndex ? 'claude' : 'inactive', children: index === selectedCommandIndex ? '▸ ' : ' ' }), _jsx(Text, { bold: true, color: index === selectedCommandIndex ? 'claude' : 'text', children: command.name }), _jsxs(Text, { color: "inactive", children: [" \u2014 ", command.description] })] }) }, command.name)))), _jsx(Text, { color: "subtle", children: "Use \u2191/\u2193 to navigate, Tab to autocomplete." })] })), modelPickerOpen && (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "claude", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "claude", children: "Select model" }), isModelListLoading ? (_jsx(Text, { color: "inactive", children: "Loading models from provider..." })) : filteredModels.length === 0 ? (_jsx(Text, { color: "inactive", children: "No models matched current filter." })) : (visibleModelOptions.map((modelName, index) => {
1037
+ const absoluteIndex = modelPickerStart + index;
1038
+ const isSelected = absoluteIndex === selectedModelIndex;
1039
+ return (_jsx(React.Fragment, { children: _jsxs(Text, { children: [_jsx(Text, { color: isSelected ? 'claude' : 'inactive', children: isSelected ? '▸ ' : ' ' }), _jsx(Text, { color: isSelected ? 'claude' : 'text', children: modelName })] }) }, modelName));
1040
+ })), _jsx(Text, { color: "subtle", children: "\u2191/\u2193 navigate \u2022 Enter apply \u2022 Esc close" })] })), setupModelPickerOpen && (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "claude", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "claude", children: "Setup: select model" }), setupModels.length === 0 ? (_jsx(Text, { color: "inactive", children: "No models loaded." })) : (visibleSetupModelOptions.map((modelName, index) => {
1041
+ const absoluteIndex = setupModelPickerStart + index;
1042
+ const isSelected = absoluteIndex === setupSelectedModelIndex;
1043
+ return (_jsx(React.Fragment, { children: _jsxs(Text, { children: [_jsx(Text, { color: isSelected ? 'claude' : 'inactive', children: isSelected ? '▸ ' : ' ' }), _jsx(Text, { color: isSelected ? 'claude' : 'text', children: modelName })] }) }, modelName));
1044
+ })), _jsx(Text, { color: "subtle", children: "\u2191/\u2193 navigate \u2022 Enter apply \u2022 Esc cancel" })] })), configMenuOpen && (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "suggestion", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "suggestion", children: "Config" }), CONFIG_MENU.map((item, index) => (_jsx(React.Fragment, { children: _jsxs(Text, { children: [_jsx(Text, { color: index === configSelectedIndex ? 'claude' : 'inactive', children: index === configSelectedIndex ? '▸ ' : ' ' }), _jsx(Text, { bold: true, color: index === configSelectedIndex ? 'claude' : 'text', children: item.label }), _jsxs(Text, { color: "inactive", children: [" \u2014 ", item.description] })] }) }, item.value))), _jsx(Text, { color: "subtle", children: "\u2191/\u2193 navigate \u2022 Enter select \u2022 Esc close" })] })), configProviderPickerOpen && (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "suggestion", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "suggestion", children: "Provider" }), [
935
1045
  'auto-detect',
936
1046
  'openai',
937
1047
  'anthropic',
@@ -1027,7 +1137,7 @@ export async function launchClaudeStyleChat(options) {
1027
1137
  }
1028
1138
  return unique;
1029
1139
  };
1030
- const runPrompt = async (prompt, onLine) => {
1140
+ const runPrompt = async (prompt, onLine, opts) => {
1031
1141
  activeAgent.removeAllListeners('event');
1032
1142
  let streamedBuffer = '';
1033
1143
  activeAgent.on('event', (event) => {
@@ -1039,6 +1149,7 @@ export async function launchClaudeStyleChat(options) {
1039
1149
  });
1040
1150
  break;
1041
1151
  case 'tool_call': {
1152
+ opts?.onVisibleOutput?.();
1042
1153
  const name = String(event.data?.name ?? 'tool');
1043
1154
  const input = event.data?.input;
1044
1155
  const args = formatToolArgs(name, input);
@@ -1049,6 +1160,7 @@ export async function launchClaudeStyleChat(options) {
1049
1160
  break;
1050
1161
  }
1051
1162
  case 'tool_result': {
1163
+ opts?.onVisibleOutput?.();
1052
1164
  const name = String(event.data?.name ?? 'tool');
1053
1165
  const result = event.data?.result;
1054
1166
  const success = event.data?.success !== false;
@@ -1070,6 +1182,7 @@ export async function launchClaudeStyleChat(options) {
1070
1182
  break;
1071
1183
  }
1072
1184
  case 'stream_text':
1185
+ opts?.onVisibleOutput?.();
1073
1186
  streamedBuffer += event.data?.text || '';
1074
1187
  break;
1075
1188
  case 'stream_end':
@@ -1079,6 +1192,7 @@ export async function launchClaudeStyleChat(options) {
1079
1192
  streamedBuffer = '';
1080
1193
  break;
1081
1194
  case 'response':
1195
+ opts?.onVisibleOutput?.();
1082
1196
  onLine({ type: 'assistant', text: event.data?.text || '' });
1083
1197
  break;
1084
1198
  case 'error':
@@ -1093,12 +1207,14 @@ export async function launchClaudeStyleChat(options) {
1093
1207
  break;
1094
1208
  }
1095
1209
  });
1096
- await activeAgent.run(prompt, toolExecutor.getTools(), toolExecutor);
1210
+ await activeAgent.run(prompt, toolExecutor.getTools(), toolExecutor, {
1211
+ images: opts?.images,
1212
+ signal: opts?.signal,
1213
+ });
1097
1214
  activeMode = activeAgent.getMode();
1098
1215
  toolExecutor.setMode(activeMode);
1099
1216
  return activeAgent.getStats();
1100
1217
  };
1101
- const getLiveStats = () => activeAgent.getStats();
1102
1218
  const listBackgroundTasks = async () => {
1103
1219
  const result = await toolExecutor.execute('list_background_tasks', {});
1104
1220
  if (result?.success && Array.isArray(result.tasks)) {
@@ -1129,6 +1245,6 @@ export async function launchClaudeStyleChat(options) {
1129
1245
  logChain = logChain.then(() => fs.appendFile(chatLogPath, rendered, 'utf8')).catch(() => { });
1130
1246
  };
1131
1247
  const root = createRoot({ exitOnCtrlC: true });
1132
- await renderAndRun(root, _jsx(XibeCodeChatApp, { model: model, initialMode: activeMode, provider: provider, baseUrl: baseUrl, defaultModel: model, modeOptions: modeOptions, initialRequestFormat: wireFormat, customProviderFormat: customProviderFormat, profile: options.profile, runPrompt: runPrompt, getLiveStats: getLiveStats, listBackgroundTasks: listBackgroundTasks, checkBackgroundTask: checkBackgroundTask, onUiLine: appendLogLine, loadModels: loadModels, onModelChange: onModelChange, onModeChange: onModeChange, onWireFormatChange: onWireFormatChange }));
1248
+ await renderAndRun(root, _jsx(XibeCodeChatApp, { model: model, initialMode: activeMode, provider: provider, baseUrl: baseUrl, defaultModel: model, modeOptions: modeOptions, initialRequestFormat: wireFormat, customProviderFormat: customProviderFormat, profile: options.profile, runPrompt: runPrompt, listBackgroundTasks: listBackgroundTasks, checkBackgroundTask: checkBackgroundTask, onUiLine: appendLogLine, loadModels: loadModels, onModelChange: onModelChange, onModeChange: onModeChange, onWireFormatChange: onWireFormatChange }));
1133
1249
  }
1134
1250
  //# sourceMappingURL=claude-style-chat.js.map