praxis-agent 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +18 -4
  2. package/dist/application/session-service.d.ts +34 -0
  3. package/dist/application/session-service.d.ts.map +1 -1
  4. package/dist/application/session-service.js +646 -38
  5. package/dist/application/session-service.js.map +1 -1
  6. package/dist/cli/interactive.d.ts +68 -8
  7. package/dist/cli/interactive.d.ts.map +1 -1
  8. package/dist/cli/interactive.js +2617 -206
  9. package/dist/cli/interactive.js.map +1 -1
  10. package/dist/cli/protocol.d.ts +1 -0
  11. package/dist/cli/protocol.d.ts.map +1 -1
  12. package/dist/cli/protocol.js.map +1 -1
  13. package/dist/cli/tui/claude-style.d.ts +142 -3
  14. package/dist/cli/tui/claude-style.d.ts.map +1 -1
  15. package/dist/cli/tui/claude-style.js +377 -16
  16. package/dist/cli/tui/claude-style.js.map +1 -1
  17. package/dist/cli/tui/clipboard.d.ts +24 -0
  18. package/dist/cli/tui/clipboard.d.ts.map +1 -0
  19. package/dist/cli/tui/clipboard.js +153 -0
  20. package/dist/cli/tui/clipboard.js.map +1 -0
  21. package/dist/cli/tui/composer-editor.d.ts +19 -0
  22. package/dist/cli/tui/composer-editor.d.ts.map +1 -0
  23. package/dist/cli/tui/composer-editor.js +99 -0
  24. package/dist/cli/tui/composer-editor.js.map +1 -0
  25. package/dist/cli/tui/composer-images.d.ts +7 -0
  26. package/dist/cli/tui/composer-images.d.ts.map +1 -0
  27. package/dist/cli/tui/composer-images.js +48 -0
  28. package/dist/cli/tui/composer-images.js.map +1 -0
  29. package/dist/cli/tui/conversation-export.d.ts +6 -0
  30. package/dist/cli/tui/conversation-export.d.ts.map +1 -0
  31. package/dist/cli/tui/conversation-export.js +76 -0
  32. package/dist/cli/tui/conversation-export.js.map +1 -0
  33. package/dist/cli/tui/external-editor.d.ts +21 -0
  34. package/dist/cli/tui/external-editor.d.ts.map +1 -0
  35. package/dist/cli/tui/external-editor.js +144 -0
  36. package/dist/cli/tui/external-editor.js.map +1 -0
  37. package/dist/cli/tui/file-picker.d.ts +33 -0
  38. package/dist/cli/tui/file-picker.d.ts.map +1 -0
  39. package/dist/cli/tui/file-picker.js +117 -0
  40. package/dist/cli/tui/file-picker.js.map +1 -0
  41. package/dist/cli/tui/git-diff.d.ts +14 -0
  42. package/dist/cli/tui/git-diff.d.ts.map +1 -0
  43. package/dist/cli/tui/git-diff.js +55 -0
  44. package/dist/cli/tui/git-diff.js.map +1 -0
  45. package/dist/cli/tui/keybindings.d.ts +14 -0
  46. package/dist/cli/tui/keybindings.d.ts.map +1 -0
  47. package/dist/cli/tui/keybindings.js +430 -0
  48. package/dist/cli/tui/keybindings.js.map +1 -0
  49. package/dist/cli/tui/permission-settings.d.ts +18 -0
  50. package/dist/cli/tui/permission-settings.d.ts.map +1 -0
  51. package/dist/cli/tui/permission-settings.js +109 -0
  52. package/dist/cli/tui/permission-settings.js.map +1 -0
  53. package/dist/cli/tui/slash-commands.d.ts +11 -0
  54. package/dist/cli/tui/slash-commands.d.ts.map +1 -0
  55. package/dist/cli/tui/slash-commands.js +183 -0
  56. package/dist/cli/tui/slash-commands.js.map +1 -0
  57. package/dist/cli/tui/terminal-suspend.d.ts +5 -0
  58. package/dist/cli/tui/terminal-suspend.d.ts.map +1 -0
  59. package/dist/cli/tui/terminal-suspend.js +9 -0
  60. package/dist/cli/tui/terminal-suspend.js.map +1 -0
  61. package/dist/cli/tui/workspace-directories.d.ts +3 -0
  62. package/dist/cli/tui/workspace-directories.d.ts.map +1 -0
  63. package/dist/cli/tui/workspace-directories.js +55 -0
  64. package/dist/cli/tui/workspace-directories.js.map +1 -0
  65. package/dist/cli.d.ts +23 -2
  66. package/dist/cli.d.ts.map +1 -1
  67. package/dist/cli.js +45 -5
  68. package/dist/cli.js.map +1 -1
  69. package/dist/compatibility/claude/compaction.d.ts +8 -0
  70. package/dist/compatibility/claude/compaction.d.ts.map +1 -1
  71. package/dist/compatibility/claude/compaction.js +45 -8
  72. package/dist/compatibility/claude/compaction.js.map +1 -1
  73. package/dist/compatibility/claude/fork.d.ts.map +1 -1
  74. package/dist/compatibility/claude/fork.js +24 -6
  75. package/dist/compatibility/claude/fork.js.map +1 -1
  76. package/dist/compatibility/claude/history.d.ts.map +1 -1
  77. package/dist/compatibility/claude/history.js +41 -1
  78. package/dist/compatibility/claude/history.js.map +1 -1
  79. package/dist/compatibility/claude/projection.d.ts +29 -1
  80. package/dist/compatibility/claude/projection.d.ts.map +1 -1
  81. package/dist/compatibility/claude/projection.js +132 -2
  82. package/dist/compatibility/claude/projection.js.map +1 -1
  83. package/dist/compatibility/claude/schema.d.ts.map +1 -1
  84. package/dist/compatibility/claude/schema.js +34 -7
  85. package/dist/compatibility/claude/schema.js.map +1 -1
  86. package/dist/compatibility/claude/translation.d.ts +7 -0
  87. package/dist/compatibility/claude/translation.d.ts.map +1 -1
  88. package/dist/compatibility/claude/translation.js +20 -0
  89. package/dist/compatibility/claude/translation.js.map +1 -1
  90. package/dist/core/runtime.d.ts +19 -0
  91. package/dist/core/runtime.d.ts.map +1 -1
  92. package/dist/core/runtime.js +30 -11
  93. package/dist/core/runtime.js.map +1 -1
  94. package/dist/extensions/claude-extensions.d.ts +12 -0
  95. package/dist/extensions/claude-extensions.d.ts.map +1 -1
  96. package/dist/extensions/claude-extensions.js +49 -0
  97. package/dist/extensions/claude-extensions.js.map +1 -1
  98. package/dist/hooks/claude-hook-tools.js +2 -2
  99. package/dist/hooks/claude-hook-tools.js.map +1 -1
  100. package/dist/persistence/claude-transcript-store.d.ts.map +1 -1
  101. package/dist/persistence/claude-transcript-store.js +4 -3
  102. package/dist/persistence/claude-transcript-store.js.map +1 -1
  103. package/dist/tools/claude-interactive-tools.d.ts +1 -0
  104. package/dist/tools/claude-interactive-tools.d.ts.map +1 -1
  105. package/dist/tools/claude-interactive-tools.js +12 -0
  106. package/dist/tools/claude-interactive-tools.js.map +1 -1
  107. package/dist/tools/local-tools.d.ts.map +1 -1
  108. package/dist/tools/local-tools.js +11 -0
  109. package/dist/tools/local-tools.js.map +1 -1
  110. package/package.json +1 -1
@@ -1,6 +1,8 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
3
  import { Box, Text, useStdout } from 'ink';
4
+ import { composerEditorSegments } from './composer-editor.js';
5
+ import { visiblePatchLines } from './git-diff.js';
4
6
  const BRAND = '#D97757';
5
7
  const ACCENT = '#B8A1FF';
6
8
  const SPINNER = ['✳', '✢', '✣', '✤', '✥'];
@@ -43,9 +45,9 @@ function permissionLabel(mode) {
43
45
  }
44
46
  }
45
47
  export function WelcomePanel({ display, width, }) {
46
- const panelWidth = Math.min(80, Math.max(32, width));
48
+ const panelWidth = Math.min(100, Math.max(32, width));
47
49
  const wide = panelWidth >= 68;
48
- return (_jsxs(Box, { borderStyle: "round", borderColor: "gray", flexDirection: "column", width: panelWidth, paddingX: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: BRAND, bold: true, children: "Praxis" }), _jsxs(Text, { children: [" Code v", display.version, " "] }), _jsx(Text, { dimColor: true, children: '─'.repeat(Math.max(1, panelWidth - 24)) })] }), _jsxs(Box, { flexDirection: wide ? 'row' : 'column', marginTop: 1, children: [_jsxs(Box, { flexDirection: "column", width: wide ? '62%' : '100%', children: [_jsx(Text, { bold: true, children: "Welcome back!" }), _jsx(Text, { color: BRAND, bold: true, children: ' ◆' }), _jsx(Text, { color: BRAND, children: ' ◆ ◆ ◆' }), _jsx(Text, { color: BRAND, children: ' ◆' }), _jsxs(Text, { children: [display.model ?? 'provider default', display.effort ? (_jsxs(Text, { dimColor: true, children: [" \u00B7 ", display.effort, " effort"] })) : null] }), _jsx(Text, { dimColor: true, children: compactPath(display.cwd) })] }), _jsxs(Box, { flexDirection: "column", width: wide ? '38%' : '100%', marginTop: wide ? 0 : 1, children: [_jsx(Text, { bold: true, children: "Tips for getting started" }), _jsx(Text, { children: "Run /help for commands" }), _jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }), _jsx(Text, { bold: true, children: "Shared with Claude Code" }), _jsx(Text, { children: "Sessions, memory, skills" })] })] })] }));
50
+ return (_jsxs(Box, { borderStyle: "round", borderColor: "gray", flexDirection: "column", width: panelWidth, paddingX: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: BRAND, bold: true, children: "Praxis" }), _jsxs(Text, { children: [" Code v", display.version, " "] }), _jsx(Text, { dimColor: true, children: '─'.repeat(Math.max(1, panelWidth - 24)) })] }), _jsxs(Box, { flexDirection: wide ? 'row' : 'column', marginTop: 1, children: [_jsxs(Box, { alignItems: wide ? 'center' : undefined, flexDirection: "column", width: wide ? '50%' : '100%', children: [_jsx(Text, { bold: true, children: "Welcome back!" }), _jsx(Text, { color: BRAND, bold: true, children: "\u2590\u259B\u2588\u2588\u2588\u259C\u258C" }), _jsx(Text, { color: BRAND, children: "\u259D\u259C\u2588\u2588\u2588\u2588\u2588\u259B\u2598" }), _jsx(Text, { color: BRAND, children: " \u2598\u2598 \u259D\u259D" }), _jsxs(Text, { children: [display.model ?? 'provider default', display.effort ? (_jsxs(Text, { dimColor: true, children: [" \u00B7 ", display.effort, " effort"] })) : null] }), _jsx(Text, { dimColor: true, children: compactPath(display.cwd) })] }), _jsxs(Box, { flexDirection: "column", width: wide ? '50%' : '100%', marginTop: wide ? 0 : 1, children: [_jsx(Text, { bold: true, children: "Tips for getting started" }), _jsx(Text, { children: "Run /help for commands" }), _jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }), _jsx(Text, { bold: true, children: "Shared with Claude Code" }), _jsx(Text, { children: "Sessions, memory, skills" })] })] })] }));
49
51
  }
50
52
  function InlineText({ text }) {
51
53
  const tokens = text.split(/(`[^`]+`|\*\*[^*]+\*\*|\[[^\]]+\]\([^)]+\))/u);
@@ -84,6 +86,62 @@ function ToolResultText({ text }) {
84
86
  ? { color: 'cyan' }
85
87
  : { dimColor: true }), children: line || ' ' }, index))) }));
86
88
  }
89
+ function inputString(call, key) {
90
+ const value = call.input[key];
91
+ return typeof value === 'string' ? value : '';
92
+ }
93
+ function contentLines(text) {
94
+ const lines = text.split('\n');
95
+ if (lines.at(-1) === '')
96
+ lines.pop();
97
+ return lines;
98
+ }
99
+ function toolHeading(call) {
100
+ if (call.name === 'Edit')
101
+ return `Update(${inputString(call, 'file_path')})`;
102
+ if (call.name === 'Read')
103
+ return `Read(${inputString(call, 'file_path')})`;
104
+ if (call.name === 'Bash')
105
+ return `Bash(${inputString(call, 'command')})`;
106
+ return call.name;
107
+ }
108
+ function ToolTranscriptEntry({ call, detail, result, detailed, }) {
109
+ const displayResult = call.name === 'Read' &&
110
+ result?.text.startsWith('Wasted call — file unchanged since your last Read')
111
+ ? 'Unchanged since last read'
112
+ : (result?.text ?? '');
113
+ const resultLines = contentLines(displayResult);
114
+ if (call.name === 'Read' && result && !result.isError && !detailed) {
115
+ return (_jsxs(Text, { children: [' ', "Read 1 file ", _jsx(Text, { dimColor: true, children: "(ctrl+o to expand)" })] }));
116
+ }
117
+ const visible = detailed ? resultLines : resultLines.slice(0, 3);
118
+ const hidden = resultLines.length - visible.length;
119
+ const errorText = displayResult.length > 500
120
+ ? `${displayResult.slice(0, 497)}...`
121
+ : displayResult;
122
+ const oldLines = contentLines(inputString(call, 'old_string'));
123
+ const newLines = contentLines(inputString(call, 'new_string'));
124
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: ACCENT, children: "\u23FA" }), " ", _jsx(Text, { bold: true, children: toolHeading(call) })] }), !['Bash', 'Read', 'Edit'].includes(call.name) && detail ? (_jsxs(Text, { dimColor: true, children: [" ", detail] })) : null, result ? (_jsx(Box, { marginLeft: 2, flexDirection: "column", children: result.isError ? (_jsxs(Text, { color: "red", children: ["\u23BF Error: ", errorText] })) : call.name === 'Edit' ? (_jsxs(_Fragment, { children: [_jsxs(Text, { dimColor: true, children: ["\u23BF Added ", newLines.length, " line", newLines.length === 1 ? '' : 's', ", removed ", oldLines.length, " line", oldLines.length === 1 ? '' : 's'] }), oldLines.map((line, index) => (_jsxs(Text, { color: "red", children: [' ', index + 1, " -", line] }, `old-${index}`))), newLines.map((line, index) => (_jsxs(Text, { color: "green", children: [' ', index + 1, " +", line] }, `new-${index}`)))] })) : (_jsxs(_Fragment, { children: [visible.map((line, index) => (_jsxs(Text, { dimColor: true, children: [index === 0 ? '⎿ ' : ' ', line || ' '] }, index))), hidden > 0 ? (_jsxs(Text, { dimColor: true, children: [' ', "\u2026 +", hidden, " lines (ctrl+o to expand)"] })) : null] })) })) : null] }));
125
+ }
126
+ function ThinkingBlock({ text, active, expanded, screenReader, }) {
127
+ const summary = text.replace(/\s+/gu, ' ').trim();
128
+ const showFull = screenReader || active || expanded;
129
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { ...(!screenReader ? { color: ACCENT } : {}), dimColor: !screenReader, italic: !screenReader, children: [screenReader ? 'Thinking:' : '✻ ', active ? 'Thinking…' : 'Thought for a moment', !showFull && summary ? ` · ${summary.slice(0, 160)}` : ''] }), showFull && text ? (_jsx(Box, { marginLeft: screenReader ? 0 : 2, flexDirection: "column", children: _jsx(MarkdownText, { text: text }) })) : !screenReader && summary.length > 160 ? (_jsx(Text, { dimColor: true, children: " ctrl+o to expand thinking" })) : null] }));
130
+ }
131
+ function ContextUsageBlock({ usedTokens, contextWindowTokens, skills, screenReader, }) {
132
+ const totalTokens = Math.max(1, contextWindowTokens);
133
+ const compactBuffer = Math.round(totalTokens * 0.165);
134
+ const usable = Math.max(1, totalTokens - compactBuffer);
135
+ if (screenReader) {
136
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "Context Usage" }), _jsxs(Text, { children: [usedTokens.toLocaleString(), " of ", totalTokens.toLocaleString(), " tokens"] }), _jsxs(Text, { children: ["Autocompact buffer: ", compactBuffer.toLocaleString(), " tokens"] }), _jsxs(Text, { children: ["Skills: ", skills.map(({ name }) => name).join(', ') || 'none'] })] }));
137
+ }
138
+ const usedCells = Math.min(100, Math.ceil((usedTokens / totalTokens) * 100));
139
+ const bufferCells = Math.min(100 - usedCells, Math.ceil((compactBuffer / totalTokens) * 100));
140
+ const cells = Array.from({ length: 100 }, (_, index) => index < usedCells ? '⛁' : index >= 100 - bufferCells ? '⛝' : '⛶');
141
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, marginLeft: 2, children: [_jsx(Text, { bold: true, children: "Context Usage" }), _jsxs(Box, { children: [_jsx(Box, { flexDirection: "column", marginRight: 2, children: Array.from({ length: 10 }, (_, row) => (_jsx(Text, { children: cells.slice(row * 10, row * 10 + 10).join(' ') }, row))) }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, children: [usedTokens.toLocaleString(), "/", totalTokens.toLocaleString(), " tokens"] }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Estimated usage by category" }), _jsxs(Text, { children: ["\u26C1 Skills:", ' ', skills
142
+ .reduce((total, skill) => total + skill.tokens, 0)
143
+ .toLocaleString(), ' ', "tokens"] }), _jsxs(Text, { children: ["\u26F6 Free space: ", Math.max(0, usable - usedTokens).toLocaleString()] }), _jsxs(Text, { children: ["\u26DD Autocompact buffer: ", compactBuffer.toLocaleString(), " tokens"] })] })] }), _jsx(Text, { children: " " }), _jsxs(Text, { children: ["Auto-compact window: ", totalTokens.toLocaleString(), " tokens"] }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Skills \u00B7 /skills" }), skills.length === 0 ? (_jsx(Text, { dimColor: true, children: "\u2514 No skills loaded" })) : (skills.map((skill, index) => (_jsxs(Text, { dimColor: true, children: [index === skills.length - 1 ? '└' : '├', " ", skill.name, ": ~", skill.tokens, " tokens"] }, skill.name))))] }));
144
+ }
87
145
  export function MarkdownText({ text }) {
88
146
  const lines = text.split('\n');
89
147
  let code = false;
@@ -95,39 +153,328 @@ export function MarkdownText({ text }) {
95
153
  return code ? (_jsxs(Text, { color: "cyan", children: ["\u2502 ", line] }, index)) : (_jsx(MarkdownLine, { line: line }, index));
96
154
  }) }));
97
155
  }
98
- export function Transcript({ items, activeText, activeThinking = '', screenReader, }) {
156
+ export function Transcript({ items, activeText, activeThinking = '', thinkingExpanded = false, detailedTranscript = false, screenReader, }) {
157
+ const detailed = thinkingExpanded || detailedTranscript;
158
+ const results = new Map(items
159
+ .filter((item) => item.kind === 'tool-result')
160
+ .map((item) => [item.callId, item]));
161
+ const pairedResults = new Set(items
162
+ .filter((item) => item.kind === 'tool' && results.has(item.call.id))
163
+ .map((item) => item.call.id));
164
+ const shellResults = new Map(items
165
+ .filter((item) => item.kind === 'shell-result')
166
+ .map((item) => [item.callId, item]));
167
+ const pairedShellResults = new Set(items
168
+ .filter((item) => item.kind === 'shell' && shellResults.has(item.callId))
169
+ .map((item) => item.callId));
170
+ const readGroupStarts = new Map();
171
+ const groupedReadTools = new Set();
172
+ if (!detailed && !screenReader) {
173
+ for (let index = 0; index < items.length; index += 1) {
174
+ const first = items[index];
175
+ if (first?.kind !== 'tool' || first.call.name !== 'Read')
176
+ continue;
177
+ let cursor = index;
178
+ const memberIndexes = [];
179
+ const memberCallIds = new Set();
180
+ while (cursor < items.length) {
181
+ const candidate = items[cursor];
182
+ if (candidate?.kind === 'tool' && candidate.call.name === 'Read') {
183
+ const result = results.get(candidate.call.id);
184
+ if (!result || result.isError)
185
+ break;
186
+ memberIndexes.push(cursor);
187
+ memberCallIds.add(candidate.call.id);
188
+ cursor += 1;
189
+ continue;
190
+ }
191
+ if (candidate?.kind === 'tool-result' &&
192
+ memberCallIds.has(candidate.callId)) {
193
+ cursor += 1;
194
+ continue;
195
+ }
196
+ break;
197
+ }
198
+ if (memberIndexes.length === 0)
199
+ continue;
200
+ readGroupStarts.set(index, memberIndexes.length);
201
+ for (const memberIndex of memberIndexes.slice(1)) {
202
+ groupedReadTools.add(memberIndex);
203
+ }
204
+ index = cursor - 1;
205
+ }
206
+ }
99
207
  return (_jsxs(Box, { flexDirection: "column", children: [items.map((item, index) => {
208
+ const readCount = readGroupStarts.get(index);
209
+ if (readCount !== undefined) {
210
+ return (_jsxs(Text, { children: [' ', "Read ", readCount, " file", readCount === 1 ? '' : 's', ' ', _jsx(Text, { dimColor: true, children: "(ctrl+o to expand)" })] }, index));
211
+ }
212
+ if (groupedReadTools.has(index))
213
+ return null;
100
214
  if (item.kind === 'user') {
101
215
  return (_jsxs(Box, { marginTop: index === 0 ? 0 : 1, children: [_jsx(Text, { color: BRAND, bold: true, children: screenReader ? 'You: ' : '❯ ' }), _jsx(Text, { bold: true, children: item.text })] }, index));
102
216
  }
103
217
  if (item.kind === 'assistant') {
104
- return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [screenReader ? _jsx(Text, { children: "Praxis:" }) : null, _jsx(MarkdownText, { text: item.text })] }, index));
218
+ return (_jsxs(Box, { marginTop: 1, children: [screenReader ? _jsx(Text, { children: "Praxis:" }) : null, !screenReader ? _jsx(Text, { color: ACCENT, children: "\u23FA " }) : null, _jsx(MarkdownText, { text: item.text })] }, index));
105
219
  }
106
220
  if (item.kind === 'thinking') {
107
- return (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: ACCENT, children: "\u273B " }), _jsxs(Text, { dimColor: true, italic: true, children: ["Thought for a moment", item.text ? ` · ${item.text.slice(0, 160)}` : ''] })] }, index));
221
+ return (_jsx(ThinkingBlock, { text: item.text, active: false, expanded: detailed, screenReader: screenReader }, index));
222
+ }
223
+ if (item.kind === 'compact') {
224
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: ACCENT, italic: true, children: screenReader
225
+ ? 'Conversation compacted'
226
+ : '✻ Conversation compacted (ctrl+o for history)' }), detailed ? (_jsx(Box, { marginLeft: screenReader ? 0 : 2, children: _jsx(MarkdownText, { text: item.summary }) })) : null] }, index));
227
+ }
228
+ if (item.kind === 'context') {
229
+ return (_jsx(ContextUsageBlock, { ...item, screenReader: screenReader }, index));
108
230
  }
109
231
  if (item.kind === 'tool') {
110
- return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: ACCENT, children: "\u25CF" }), " ", _jsx(Text, { bold: true, children: item.call.name })] }), _jsxs(Text, { dimColor: true, children: [" ", item.detail] })] }, index));
232
+ const result = results.get(item.call.id);
233
+ return (_jsx(ToolTranscriptEntry, { call: item.call, detail: item.detail, ...(result ? { result } : {}), detailed: detailed || screenReader }, index));
111
234
  }
112
235
  if (item.kind === 'tool-result') {
236
+ if (pairedResults.has(item.callId))
237
+ return null;
113
238
  const text = item.text.length > 500 ? `${item.text.slice(0, 497)}...` : item.text;
114
239
  return (_jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsx(Text, { color: item.isError ? 'red' : 'gray', children: item.isError ? '└ Error' : '└ Result' }), item.isError ? (_jsx(Text, { color: "red", children: text })) : (_jsx(ToolResultText, { text: text }))] }, index));
115
240
  }
241
+ if (item.kind === 'shell') {
242
+ const result = shellResults.get(item.callId);
243
+ const output = result
244
+ ? [result.stdout, result.stderr]
245
+ .filter(Boolean)
246
+ .join(result.stdout &&
247
+ result.stderr &&
248
+ !result.stdout.endsWith('\n')
249
+ ? '\n'
250
+ : '')
251
+ : '';
252
+ const lines = contentLines(output);
253
+ const visible = detailed || screenReader ? lines : lines.slice(0, 3);
254
+ const hidden = lines.length - visible.length;
255
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { children: _jsxs(Text, { bold: true, children: [screenReader ? 'Shell command: ' : '! ', item.command] }) }), result ? (_jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [lines.length === 0 ? (_jsx(Text, { dimColor: true, children: "\u23BF " })) : (visible.map((line, lineIndex) => (_jsxs(Text, { ...(result.isError ? { color: 'red' } : {}), dimColor: !result.isError, children: [lineIndex === 0 ? '⎿ ' : ' ', line || ' '] }, lineIndex)))), hidden > 0 ? (_jsxs(Text, { dimColor: true, children: [' ', "\u2026 +", hidden, " lines (ctrl+o to expand)"] })) : null] })) : null] }, index));
256
+ }
257
+ if (item.kind === 'shell-result') {
258
+ if (pairedShellResults.has(item.callId))
259
+ return null;
260
+ const output = [item.stdout, item.stderr].filter(Boolean).join('\n');
261
+ return (_jsxs(Text, { ...(item.isError ? { color: 'red' } : {}), children: ["\u23BF ", output] }, index));
262
+ }
263
+ if (item.kind === 'local-result') {
264
+ return (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { dimColor: true, children: ["\u23BF ", item.text] }) }, index));
265
+ }
116
266
  return (_jsxs(Text, { ...(item.kind === 'warning' ? { color: 'red' } : {}), dimColor: item.kind === 'notice', children: [item.kind === 'warning' ? '⚠ ' : '· ', item.text] }, index));
117
- }), activeThinking ? (_jsxs(Box, { marginTop: 1, children: [screenReader ? (_jsx(Text, { children: "Thinking: " })) : (_jsx(Text, { color: ACCENT, children: "\u273B " })), _jsxs(Text, { dimColor: true, italic: true, children: ["Thinking\u2026 ", activeThinking.slice(-160)] })] })) : activeText ? (_jsxs(Box, { marginTop: 1, children: [screenReader ? (_jsx(Text, { children: "Praxis: " })) : (_jsx(Text, { color: ACCENT, children: "\u2733 " })), _jsx(MarkdownText, { text: activeText })] })) : null] }));
267
+ }), activeThinking ? (_jsx(ThinkingBlock, { text: activeThinking, active: true, expanded: detailed, screenReader: screenReader })) : activeText ? (_jsxs(Box, { marginTop: 1, children: [screenReader ? (_jsx(Text, { children: "Praxis: " })) : (_jsx(Text, { color: ACCENT, children: "\u2733 " })), _jsx(MarkdownText, { text: activeText })] })) : null] }));
268
+ }
269
+ export function DiffDashboard({ snapshots, sourceIndex, selectedIndex, viewingFile, scrollOffset, width, screenReader, }) {
270
+ const source = snapshots[sourceIndex];
271
+ const snapshot = source?.snapshot ?? { files: [], additions: 0, deletions: 0 };
272
+ const selected = snapshot.files[selectedIndex];
273
+ const panelWidth = Math.max(32, Math.min(100, width));
274
+ const line = '─'.repeat(panelWidth);
275
+ if (viewingFile && selected) {
276
+ const lines = visiblePatchLines(selected.patch).slice(scrollOffset, scrollOffset + 18);
277
+ return (_jsxs(Box, { flexDirection: "column", children: [!screenReader ? _jsx(Text, { dimColor: true, children: line }) : null, _jsx(Text, { bold: true, children: " Uncommitted changes (git diff HEAD)" }), _jsxs(Text, { children: [' ', snapshots.map((item, index) => (_jsxs(Text, { inverse: index === sourceIndex, children: [' ', item.label, ' '] }, item.label)))] }), _jsx(Text, { children: " " }), _jsxs(Text, { bold: true, children: [" ", selected.path] }), _jsxs(Text, { dimColor: true, children: [" ", '─'.repeat(Math.max(1, panelWidth - 4))] }), lines.map((patchLine, index) => (_jsxs(Text, { ...(patchLine.startsWith('+')
278
+ ? { color: 'green' }
279
+ : patchLine.startsWith('-')
280
+ ? { color: 'red' }
281
+ : { dimColor: true }), children: [' ', patchLine] }, `${scrollOffset}-${index}`))), _jsx(Text, { dimColor: true, children: " \u2191/\u2193 to scroll \u00B7 Esc to back" })] }));
282
+ }
283
+ return (_jsxs(Box, { flexDirection: "column", children: [!screenReader ? _jsx(Text, { dimColor: true, children: line }) : null, _jsx(Text, { bold: true, children: " Uncommitted changes (git diff HEAD)" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [' ', snapshots.map((item, index) => (_jsxs(Text, { inverse: index === sourceIndex, children: [' ', item.label, ' '] }, item.label)))] }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [' ', snapshot.files.length, " file", snapshot.files.length === 1 ? '' : 's', ' ', "changed ", _jsxs(Text, { color: "green", children: ["+", snapshot.additions] }), ' ', _jsxs(Text, { color: "red", children: ["-", snapshot.deletions] })] }), _jsx(Text, { children: " " }), snapshot.files.length === 0 ? (_jsx(Text, { dimColor: true, children: " No uncommitted changes." })) : (snapshot.files.map((file, index) => (_jsxs(Text, { inverse: index === selectedIndex, children: [index === selectedIndex ? '❯ ' : ' ', file.path, " ", _jsxs(Text, { color: "green", children: ["+", file.additions] }), ' ', _jsxs(Text, { color: "red", children: ["-", file.deletions] })] }, file.path)))), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "\u2190/\u2192 to switch source \u00B7 \u2191/\u2193 to select \u00B7 Enter to view \u00B7 Esc to close" })] }));
284
+ }
285
+ export function PermissionDashboard({ tabIndex, selectedIndex, query, rules, recentDenied, workspaceDirectories, width, screenReader, }) {
286
+ const tabs = ['Recently denied', 'Allow', 'Ask', 'Deny', 'Workspace'];
287
+ const behavior = ['allow', 'ask', 'deny'][tabIndex - 1];
288
+ const normalizedQuery = query.trim().toLowerCase();
289
+ const matchingRules = behavior
290
+ ? rules.filter((rule) => rule.behavior === behavior &&
291
+ (!normalizedQuery ||
292
+ rule.rule.toLowerCase().includes(normalizedQuery) ||
293
+ rule.scope.includes(normalizedQuery)))
294
+ : [];
295
+ const rows = tabIndex === 0 ? recentDenied : matchingRules.map((rule) => rule.rule);
296
+ const originalWorkspace = workspaceDirectories.find((directory) => directory.original);
297
+ const additionalWorkspaces = workspaceDirectories.filter((directory) => !directory.original);
298
+ const description = tabIndex === 0
299
+ ? recentDenied.length === 0
300
+ ? 'No recent denials. Commands denied by the auto mode classifier will appear here.'
301
+ : 'Commands denied by the auto mode classifier appear here.'
302
+ : tabIndex === 1
303
+ ? "Praxis Code won't ask before using allowed tools."
304
+ : tabIndex === 2
305
+ ? 'Praxis Code will always ask for confirmation before using these tools.'
306
+ : tabIndex === 3
307
+ ? 'Praxis Code will reject requests to use denied tools.'
308
+ : 'Praxis Code can read files in the workspace, and make edits when auto-accept edits is on.';
309
+ return (_jsxs(Box, { flexDirection: "column", width: Math.min(100, width), children: [!screenReader ? (_jsx(Text, { dimColor: true, children: '─'.repeat(Math.min(100, width)) })) : null, _jsx(Text, { bold: true, children: " Permissions" }), _jsxs(Text, { children: [' ', tabs.map((tab, index) => (_jsxs(Text, { inverse: index === tabIndex, children: [' ', tab, ' '] }, tab)))] }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [" ", description] }), tabIndex >= 1 && tabIndex <= 3 ? (_jsx(Box, { borderStyle: screenReader ? undefined : 'round', paddingX: 1, marginY: 1, children: _jsxs(Text, { ...(query ? {} : { dimColor: true }), children: ["\u2315 ", query || 'Search…'] }) })) : (_jsx(Text, { children: " " })), tabIndex >= 1 && tabIndex <= 3 ? (['Add a new rule…', ...rows].map((row, index) => (_jsxs(Text, { inverse: index === selectedIndex, children: [index === selectedIndex ? '❯ ' : ' ', index + 1, ". ", row] }, `${index}-${row}`)))) : tabIndex === 4 ? (_jsxs(_Fragment, { children: [originalWorkspace ? (_jsxs(Text, { children: [' - ', originalWorkspace.path, " (Original working directory)"] })) : null, additionalWorkspaces.map((directory, index) => (_jsxs(Text, { inverse: selectedIndex === index, children: [selectedIndex === index ? '❯ ' : ' ', index + 1, ". ", directory.path] }, directory.path))), _jsxs(Text, { inverse: selectedIndex === additionalWorkspaces.length, children: [selectedIndex === additionalWorkspaces.length ? '❯ ' : ' ', additionalWorkspaces.length + 1, ". Add directory\u2026"] })] })) : rows.length > 0 ? (rows.map((row, index) => (_jsxs(Text, { inverse: index === selectedIndex, children: [index === selectedIndex ? '❯ ' : ' ', index + 1, ". ", row] }, `${index}-${row}`)))) : null, _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: selectedIndex >= 0
310
+ ? '↑/↓ navigate · Enter to select · ←/→ to switch · Esc to cancel'
311
+ : '←/→ to switch · ↓ to select · Esc to cancel' })] }));
118
312
  }
119
- export function SessionPicker({ sessions, selectedIndex, screenReader, }) {
313
+ export function StatusDashboard({ tabIndex, version, sessionId, display, usage, costUsd, turnCount, toolCount, commandCount, detailedTranscript, width, screenReader, }) {
314
+ const tabs = ['Settings', 'Status', 'Config', 'Usage', 'Stats'];
315
+ const rows = tabIndex === 0
316
+ ? [
317
+ ['Thinking mode', 'provider controlled'],
318
+ ['Verbose output', detailedTranscript ? 'true' : 'false'],
319
+ ['Default permission mode', permissionLabel(display.permissionMode)],
320
+ ['Context compaction', 'automatic'],
321
+ ['Shared Claude data', 'enabled'],
322
+ ]
323
+ : tabIndex === 1
324
+ ? [
325
+ ['Version', version],
326
+ ['Session ID', sessionId ?? 'new session'],
327
+ ['cwd', display.cwd],
328
+ ['Model', display.model ?? 'provider default'],
329
+ ['Permission mode', permissionLabel(display.permissionMode)],
330
+ ]
331
+ : tabIndex === 2
332
+ ? [
333
+ ['Model', display.model ?? 'provider default'],
334
+ ['Effort', display.effort ?? 'high'],
335
+ [
336
+ 'Context window',
337
+ String(display.contextWindowTokens ?? 'provider default'),
338
+ ],
339
+ ['Available commands', String(commandCount)],
340
+ ]
341
+ : tabIndex === 3
342
+ ? [
343
+ ['Input tokens', String(usage?.inputTokens ?? 0)],
344
+ ['Output tokens', String(usage?.outputTokens ?? 0)],
345
+ [
346
+ 'Session cost',
347
+ costUsd === undefined
348
+ ? 'unavailable'
349
+ : `$${costUsd.toFixed(4)}`,
350
+ ],
351
+ ]
352
+ : [
353
+ ['Turns', String(turnCount)],
354
+ ['Tool calls', String(toolCount)],
355
+ [
356
+ 'Context used',
357
+ String((usage?.inputTokens ?? 0) + (usage?.outputTokens ?? 0)),
358
+ ],
359
+ ];
360
+ return (_jsxs(Box, { flexDirection: "column", width: Math.min(100, width), children: [!screenReader ? (_jsx(Text, { dimColor: true, children: '─'.repeat(Math.min(100, width)) })) : null, _jsxs(Text, { children: [' ', tabs.map((tab, index) => (_jsxs(Text, { inverse: index === tabIndex, children: [' ', tab, ' '] }, tab)))] }), _jsx(Text, { children: " " }), rows.map(([label, value]) => (_jsxs(Box, { children: [_jsx(Box, { width: 28, children: _jsxs(Text, { children: [label, ":"] }) }), _jsx(Text, { children: value })] }, label))), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "\u2190/\u2192/tab to switch \u00B7 Esc to close" })] }));
361
+ }
362
+ export function ListDashboard({ title, rows, emptyText, selectedIndex, width, screenReader, }) {
363
+ return (_jsxs(Box, { flexDirection: "column", width: Math.min(100, width), children: [!screenReader ? (_jsx(Text, { dimColor: true, children: '─'.repeat(Math.min(100, width)) })) : null, _jsxs(Text, { bold: true, children: [" ", title] }), _jsx(Text, { children: " " }), rows.length === 0 ? (_jsxs(Text, { dimColor: true, children: [" ", emptyText] })) : (rows.map((row, index) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { inverse: index === selectedIndex, children: [index === selectedIndex ? '❯ ' : ' ', row.label] }), row.description ? _jsxs(Text, { dimColor: true, children: [" ", row.description] }) : null] }, `${index}-${row.label}`)))), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "\u2191/\u2193 to select \u00B7 Esc to close" })] }));
364
+ }
365
+ export function SessionPicker({ sessions, selectedIndex, screenReader, query = '', }) {
120
366
  const maxVisible = 8;
121
367
  const start = Math.max(0, Math.min(selectedIndex - Math.floor(maxVisible / 2), sessions.length - maxVisible));
122
368
  const visible = sessions.slice(start, start + maxVisible);
123
- return (_jsxs(Box, { flexDirection: "column", borderStyle: screenReader ? undefined : 'round', borderColor: "gray", paddingX: screenReader ? 0 : 1, children: [_jsx(Text, { bold: true, children: "Resume a session" }), !screenReader ? (_jsx(Text, { dimColor: true, children: "\u2191/\u2193 to move \u00B7 Enter to select \u00B7 Esc to cancel" })) : null, start > 0 ? _jsxs(Text, { dimColor: true, children: [" \u2191 ", start, " earlier"] }) : null, visible.map((session, visibleIndex) => {
369
+ return (_jsxs(Box, { flexDirection: "column", children: [!screenReader ? _jsx(Text, { dimColor: true, children: '─'.repeat(80) }) : null, _jsx(Text, { bold: true, children: " Resume session" }), _jsx(Box, { borderStyle: screenReader ? undefined : 'round', borderColor: "gray", paddingX: screenReader ? 0 : 1, marginY: 1, children: _jsxs(Text, { ...(query ? {} : { dimColor: true }), children: ["\u2315 ", query || 'Search…'] }) }), start > 0 ? _jsxs(Text, { dimColor: true, children: [" \u2191 ", start, " earlier"] }) : null, visible.length === 0 ? _jsx(Text, { dimColor: true, children: "No sessions found." }) : null, visible.map((session, visibleIndex) => {
124
370
  const index = start + visibleIndex;
125
371
  return (_jsxs(Box, { children: [_jsxs(Text, { ...(index === selectedIndex ? { color: BRAND } : {}), bold: index === selectedIndex, children: [index === selectedIndex ? '❯ ' : ' ', session
126
372
  ? (session.name ?? session.lastPrompt ?? 'Untitled')
127
373
  : 'Start a new session'] }), session ? (_jsxs(Text, { dimColor: true, children: [' ', "\u00B7 ", session.sessionId, " \u00B7 ", session.status] })) : null] }, session?.sessionId ?? 'new'));
128
- }), start + visible.length < sessions.length ? (_jsxs(Text, { dimColor: true, children: [" \u2193 ", sessions.length - start - visible.length, " more"] })) : null] }));
374
+ }), start + visible.length < sessions.length ? (_jsxs(Text, { dimColor: true, children: [" \u2193 ", sessions.length - start - visible.length, " more"] })) : null, !screenReader ? (_jsx(Text, { dimColor: true, children: "\u2191/\u2193 to navigate \u00B7 Enter to select \u00B7 Type to search \u00B7 Esc to cancel" })) : null] }));
375
+ }
376
+ export function CommandPalette({ commands, selectedIndex, width, screenReader, }) {
377
+ const maxVisible = 12;
378
+ const start = Math.max(0, Math.min(selectedIndex - Math.floor(maxVisible / 2), Math.max(0, commands.length - maxVisible)));
379
+ const visible = commands.slice(start, start + maxVisible);
380
+ if (screenReader) {
381
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "Commands" }), visible.length === 0 ? (_jsx(Text, { children: "No matching commands." })) : (visible.map((command) => (_jsxs(Text, { children: ["/", command.name, ": ", command.description] }, command.name))))] }));
382
+ }
383
+ return (_jsx(Box, { flexDirection: "column", width: Math.min(100, width), children: visible.length === 0 ? (_jsx(Text, { dimColor: true, children: "No matching commands." })) : (visible.map((command, visibleIndex) => {
384
+ const index = start + visibleIndex;
385
+ const selected = index === selectedIndex;
386
+ return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 30, children: _jsxs(Text, { ...(selected ? { color: BRAND, bold: true } : {}), children: ["/", command.name] }) }), _jsx(Box, { flexGrow: 1, children: _jsx(Text, { dimColor: true, children: command.description }) })] }, command.name));
387
+ })) }));
388
+ }
389
+ export function FilePicker({ entries, selectedIndex, width, screenReader, }) {
390
+ const maxVisible = 12;
391
+ const start = Math.max(0, Math.min(selectedIndex - Math.floor(maxVisible / 2), Math.max(0, entries.length - maxVisible)));
392
+ const visible = entries.slice(start, start + maxVisible);
393
+ return (_jsx(Box, { flexDirection: "column", width: Math.min(100, width), children: visible.length === 0 ? (_jsx(Text, { dimColor: true, children: "No matching files." })) : (visible.map((entry, visibleIndex) => {
394
+ const index = start + visibleIndex;
395
+ return (_jsxs(Text, { inverse: !screenReader && index === selectedIndex, children: [screenReader && index === selectedIndex ? 'Selected: ' : '+ ', entry.path] }, entry.path));
396
+ })) }));
397
+ }
398
+ export function MentionPicker({ entries, selectedIndex, width, screenReader, }) {
399
+ const maxVisible = 12;
400
+ const start = Math.max(0, Math.min(selectedIndex - Math.floor(maxVisible / 2), Math.max(0, entries.length - maxVisible)));
401
+ const visible = entries.slice(start, start + maxVisible);
402
+ return (_jsx(Box, { flexDirection: "column", width: Math.min(100, width), children: visible.length === 0 ? (_jsx(Text, { dimColor: true, children: "No matching files or agents." })) : (visible.map((entry, visibleIndex) => {
403
+ const index = start + visibleIndex;
404
+ const selected = index === selectedIndex;
405
+ if (entry.kind === 'agent') {
406
+ return (_jsxs(Text, { inverse: !screenReader && selected, ...(screenReader ? {} : { wrap: 'truncate-end' }), children: [screenReader && selected ? 'Selected agent: ' : '* ', entry.name, screenReader ? '' : ' (agent)', entry.description ? ` – ${entry.description}` : ''] }, `agent:${entry.name}`));
407
+ }
408
+ return (_jsxs(Text, { inverse: !screenReader && selected, ...(screenReader ? {} : { wrap: 'truncate-end' }), children: [screenReader && selected ? 'Selected: ' : '+ ', entry.path] }, `file:${entry.path}`));
409
+ })) }));
410
+ }
411
+ const SHORTCUT_ROWS = [
412
+ [
413
+ '! for shell mode',
414
+ 'double tap esc to clear input',
415
+ 'ctrl + shift + _ to undo',
416
+ ],
417
+ ['/ for commands', 'shift + tab to cycle permissions', 'ctrl + z to suspend'],
418
+ [
419
+ '@ for file paths',
420
+ 'ctrl + o for verbose output',
421
+ 'ctrl + v to paste images',
422
+ ],
423
+ [
424
+ '/btw for side question',
425
+ 'ctrl + t to toggle tasks',
426
+ 'opt + p to switch model',
427
+ ],
428
+ ['', 'backslash (\\) + return (⏎) for newline', 'ctrl + s to stash prompt'],
429
+ ['', '', 'ctrl + g to edit in $EDITOR'],
430
+ ['', '', '/keybindings to customize'],
431
+ ];
432
+ export function ShortcutHelp({ width }) {
433
+ const wide = width >= 78;
434
+ return (_jsx(Box, { flexDirection: "column", width: Math.min(100, width), children: SHORTCUT_ROWS.map((row, rowIndex) => wide ? (_jsx(Box, { flexDirection: "row", children: row.map((cell, cellIndex) => (_jsx(Box, { width: cellIndex === 2 ? undefined : '33%', children: _jsx(Text, { dimColor: true, children: cell }) }, cellIndex))) }, rowIndex)) : (row.filter(Boolean).map((cell, cellIndex) => (_jsx(Text, { dimColor: true, children: cell }, `${rowIndex}-${cellIndex}`))))) }));
435
+ }
436
+ export function HelpMenu({ tabIndex, selectedIndex, builtinCommands, customCommands, width, screenReader, }) {
437
+ const tabs = ['General', 'Commands', 'Custom commands'];
438
+ const commands = tabIndex === 1 ? builtinCommands : customCommands;
439
+ const maxVisible = 10;
440
+ const start = Math.max(0, Math.min(selectedIndex - Math.floor(maxVisible / 2), Math.max(0, commands.length - maxVisible)));
441
+ const visible = commands.slice(start, start + maxVisible);
442
+ const line = '─'.repeat(Math.max(12, Math.min(100, width)));
443
+ return (_jsxs(Box, { flexDirection: "column", width: Math.min(100, width), children: [!screenReader ? _jsx(Text, { dimColor: true, children: line }) : null, _jsxs(Box, { children: [_jsx(Text, { bold: true, children: " Help " }), tabs.map((tab, index) => (_jsxs(Text, { ...(index === tabIndex ? { color: BRAND, bold: true } : {}), children: [' ', tab, ' '] }, tab)))] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: tabIndex === 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: "Praxis understands your codebase, makes edits with your permission, and executes commands from your terminal." }), _jsx(Text, { bold: true, children: "Shortcuts" }), _jsx(ShortcutHelp, { width: width })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: tabIndex === 1
444
+ ? 'Browse default commands'
445
+ : 'Browse shared commands and skills' }), visible.length === 0 ? (_jsx(Text, { dimColor: true, children: "No commands found." })) : (visible.map((command, visibleIndex) => {
446
+ const index = start + visibleIndex;
447
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { ...(index === selectedIndex
448
+ ? { color: BRAND, bold: true }
449
+ : {}), children: [index === selectedIndex ? '↓ ' : ' ', "/", command.name] }), _jsxs(Text, { dimColor: true, children: [" ", command.description] })] }, command.name));
450
+ }))] })) }), _jsx(Text, { dimColor: true, children: "\u2190/\u2192 to switch \u00B7 \u2191/\u2193 to navigate \u00B7 Esc to cancel" })] }));
451
+ }
452
+ export function SelectionMenu({ title, description, options, selectedIndex, footer, width, screenReader, }) {
453
+ const maxVisible = 7;
454
+ const start = Math.max(0, Math.min(selectedIndex - Math.floor(maxVisible / 2), Math.max(0, options.length - maxVisible)));
455
+ const visible = options.slice(start, start + maxVisible);
456
+ return (_jsxs(Box, { borderStyle: screenReader ? undefined : 'round', borderColor: "gray", flexDirection: "column", marginTop: 1, paddingX: screenReader ? 0 : 1, width: screenReader ? undefined : Math.min(80, width), children: [_jsx(Text, { bold: true, children: title }), description ? _jsx(Text, { dimColor: true, children: description }) : null, visible.map((option, visibleIndex) => {
457
+ const index = start + visibleIndex;
458
+ const selected = index === selectedIndex;
459
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { ...(selected ? { color: BRAND, bold: true } : {}), children: [selected ? '❯ ' : ' ', index + 1, ". ", option.label, option.selected ? ' ✔' : ''] }), option.description ? (_jsxs(Text, { dimColor: true, children: [" ", option.description] })) : null] }, `${index}-${option.label}`));
460
+ }), start > 0 || start + visible.length < options.length ? (_jsxs(Text, { dimColor: true, children: [start > 0 ? `↑ ${start} earlier` : '', start > 0 && start + visible.length < options.length ? ' · ' : '', start + visible.length < options.length
461
+ ? `↓ ${options.length - start - visible.length} more`
462
+ : ''] })) : null, _jsx(Text, { dimColor: true, children: footer })] }));
463
+ }
464
+ function ComposerInput({ input, cursor }) {
465
+ const { before, current, after } = composerEditorSegments({
466
+ text: input,
467
+ cursor,
468
+ });
469
+ const cursorText = current === '\n' ? '↵' : (current ?? ' ');
470
+ return (_jsxs(Text, { children: [before, _jsx(Text, { color: "black", backgroundColor: ACCENT, children: cursorText }), after] }));
471
+ }
472
+ export function ExternalEditorWait({ screenReader, }) {
473
+ if (screenReader)
474
+ return _jsx(Text, { children: "External editor open. Save and close it to continue." });
475
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }), _jsx(Text, { children: "Save and close editor to continue..." }), _jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" })] }));
129
476
  }
130
- export function Composer({ input, busy, status, display, usage, width, screenReader, }) {
477
+ export function Composer({ input, cursor, busy, clipboardBusy = false, status, display, usage, costUsd, width, screenReader, hasThinking = false, thinkingExpanded = false, shortcutsVisible = false, shellMode = false, footerMessage, }) {
131
478
  const [spinnerIndex, setSpinnerIndex] = useState(0);
132
479
  useEffect(() => {
133
480
  if (!busy || screenReader)
@@ -136,11 +483,25 @@ export function Composer({ input, busy, status, display, usage, width, screenRea
136
483
  return () => clearInterval(timer);
137
484
  }, [busy, screenReader]);
138
485
  if (screenReader)
139
- return _jsx(Text, { children: busy ? `Status: ${status}` : `Prompt: ${input}` });
140
- const line = ''.repeat(Math.max(12, Math.min(80, width)));
141
- return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [display.effort ? (_jsxs(Box, { justifyContent: "flex-end", width: Math.min(80, width), children: [_jsxs(Text, { color: ACCENT, children: ["\u25C9 ", display.effort] }), _jsx(Text, { dimColor: true, children: " \u00B7 /effort" })] })) : null, usage ? (_jsxs(Text, { dimColor: true, children: ["Context \u00B7 ", usage.inputTokens + usage.outputTokens, " tokens", usage.cacheReadInputTokens
486
+ return (_jsx(Text, { children: clipboardBusy
487
+ ? 'Pasting…'
488
+ : busy
489
+ ? `Status: ${status}`
490
+ : shellMode
491
+ ? `Shell command: ${input}`
492
+ : `Prompt: ${input}` }));
493
+ const line = '─'.repeat(Math.max(12, Math.min(100, width)));
494
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [usage ? (_jsxs(Text, { dimColor: true, children: ["Context \u00B7 ", usage.inputTokens + usage.outputTokens, " tokens", display.contextWindowTokens
495
+ ? ` / ${display.contextWindowTokens} (${Math.min(100, Math.round(((usage.inputTokens + usage.outputTokens) /
496
+ display.contextWindowTokens) *
497
+ 100))}%)`
498
+ : '', usage.cacheReadInputTokens
142
499
  ? ` · ${usage.cacheReadInputTokens} cached`
143
- : ''] })) : null, _jsx(Text, { dimColor: true, children: line }), busy ? (_jsxs(Text, { children: [_jsx(Text, { color: ACCENT, children: SPINNER[spinnerIndex] }), " ", status, "\u2026", ' ', _jsx(Text, { dimColor: true, children: "\u00B7 esc to interrupt" })] })) : (_jsxs(Text, { children: [_jsxs(Text, { color: BRAND, bold: true, children: ["\u276F", ' '] }), input || _jsx(Text, { dimColor: true, children: "Try \"review this project\"" })] })), _jsx(Text, { dimColor: true, children: line }), _jsxs(Text, { dimColor: true, children: ["\u23F5\u23F5 ", permissionLabel(display.permissionMode), " \u00B7 /help for shortcuts"] }), _jsx(Text, { dimColor: true, children: "/new \u00B7 /sessions \u00B7 /workflows \u00B7 /exit" })] }));
500
+ : '', costUsd === undefined ? '' : ` · $${costUsd.toFixed(6)}`] })) : null, _jsx(Text, { dimColor: true, children: line }), clipboardBusy ? (_jsx(Text, { children: "Pasting\u2026" })) : busy ? (_jsxs(Text, { children: [_jsx(Text, { color: ACCENT, children: SPINNER[spinnerIndex] }), " ", status, "\u2026", ' ', _jsx(Text, { dimColor: true, children: "\u00B7 esc to interrupt" })] })) : (_jsxs(Text, { children: [_jsx(Text, { ...(shellMode ? {} : { color: BRAND }), bold: true, children: shellMode ? '! ' : '❯ ' }), input ? (_jsx(ComposerInput, { cursor: cursor ?? Array.from(input).length, input: input })) : (_jsx(Text, { dimColor: true, children: shellMode
501
+ ? 'Enter a shell command'
502
+ : 'Try "review this project"' }))] })), _jsx(Text, { dimColor: true, children: line }), shortcutsVisible ? (_jsx(ShortcutHelp, { width: width })) : (_jsxs(Box, { width: Math.min(100, width), children: [_jsx(Text, { dimColor: true, children: shellMode ? ('! for shell mode') : (_jsxs(_Fragment, { children: ["\u23F5\u23F5 ", permissionLabel(display.permissionMode), " \u00B7", ' ', busy ? 'esc to interrupt' : '? for shortcuts', " \u00B7 \u2190 for agents", hasThinking
503
+ ? ` · ctrl+o ${thinkingExpanded ? 'collapse' : 'expand'}`
504
+ : ''] })) }), _jsx(Box, { flexGrow: 1 }), footerMessage ? (footerMessage.isError ? (_jsx(Text, { color: "red", children: footerMessage.text })) : (_jsx(Text, { dimColor: true, children: footerMessage.text }))) : display.effort ? (_jsxs(Text, { children: [_jsxs(Text, { color: ACCENT, children: ["\u25CF ", display.effort] }), _jsx(Text, { dimColor: true, children: " \u00B7 /effort" })] })) : null] }))] }));
144
505
  }
145
506
  export function DialogFrame({ title, children, screenReader, }) {
146
507
  return (_jsxs(Box, { flexDirection: "column", borderStyle: screenReader ? undefined : 'round', borderColor: "yellow", paddingX: screenReader ? 0 : 1, marginTop: 1, children: [_jsx(Text, { color: "yellow", bold: true, children: title }), children] }));