apex-dev 3.0.2 → 3.1.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 (153) hide show
  1. package/dist/highlights-eq9cgrbb.scm +604 -0
  2. package/dist/highlights-ghv9g403.scm +205 -0
  3. package/dist/highlights-hk7bwhj4.scm +284 -0
  4. package/dist/highlights-r812a2qc.scm +150 -0
  5. package/dist/highlights-x6tmsnaa.scm +115 -0
  6. package/dist/index.js +62590 -0
  7. package/dist/injections-73j83es3.scm +27 -0
  8. package/dist/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
  9. package/dist/tree-sitter-markdown-411r6y9b.wasm +0 -0
  10. package/dist/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
  11. package/dist/tree-sitter-typescript-zxjzwt75.wasm +0 -0
  12. package/dist/tree-sitter-zig-e78zbjpm.wasm +0 -0
  13. package/package.json +10 -4
  14. package/.config/amp/settings.json +0 -3
  15. package/.config/opencode/oh-my-opencode.json +0 -58
  16. package/.config/opencode/opencode.json +0 -6
  17. package/.local/share/amp/device-id.json +0 -3
  18. package/.local/share/amp/history.jsonl +0 -78
  19. package/.local/share/amp/session.json +0 -6
  20. package/.local/share/amp/threads/T-019c93b8-fce7-7083-aab9-d5f1c88a9545.json +0 -2528
  21. package/.local/share/amp/threads/T-019c93c8-4b7a-71df-94ac-867d8236a288.json +0 -7
  22. package/.local/share/amp/threads/T-019c93cd-5a7d-728e-8289-02e0ef4ca2ff.json +0 -680
  23. package/.local/share/amp/threads/T-019c93e7-83ca-7633-9eed-12bdcc118163.json +0 -873
  24. package/.local/share/amp/threads/T-019c93ea-ccd3-765a-88c9-42d7b631e977.json +0 -620
  25. package/.local/share/amp/threads/T-019c93ee-5977-71af-9ab7-c4611004b703.json +0 -1000
  26. package/.local/share/amp/threads/T-019c93f0-8328-71ed-a250-6da169cebfe1.json +0 -829
  27. package/.local/share/amp/threads/T-019c93f5-7bdd-703b-b2cd-0a04da64441a.json +0 -459
  28. package/.local/share/amp/threads/T-019c93f8-2b2e-733b-8249-9876546d9b5b.json +0 -764
  29. package/.local/share/amp/threads/T-019c93fd-fade-7195-a3b7-358f180d40b8.json +0 -7
  30. package/.local/share/amp/threads/T-019c93fe-2e56-705e-827e-eb99bd02e257.json +0 -3593
  31. package/.local/share/amp/threads/T-019c9408-6e64-77e1-9519-b913e3b24a03.json +0 -1559
  32. package/.local/share/amp/threads/T-019c9409-feeb-736d-b92c-4f7a263a643c.json +0 -7
  33. package/.local/share/amp/threads/T-019c940b-8d11-755b-b9e1-f923d8a5e6ba.json +0 -7
  34. package/.local/share/amp/threads/T-019c943a-6c5e-76a5-bf4e-170f7ad452ce.json +0 -979
  35. package/.local/share/amp/threads/T-019c94b2-1c8f-76d8-96d0-82449a028849.json +0 -1584
  36. package/.local/share/amp/threads/T-019c94b6-68f0-726e-92dd-90c5411ca28c.json +0 -7
  37. package/.local/share/amp/threads/T-019c94bf-a589-72a3-b3c2-a81359d9e0a6.json +0 -7
  38. package/.local/share/amp/threads/T-019c94e1-1bd9-70ab-b6f2-abd5cab4f4ce.json +0 -1035
  39. package/.local/share/amp/threads/T-019c94fd-cc4a-714b-896a-74f94020f6eb.json +0 -1310
  40. package/.local/share/amp/threads/T-019c9501-8976-7138-aca6-245a01a8fe9b.json +0 -7
  41. package/.local/share/amp/threads/T-019c9504-4b51-763e-8a9f-5d4cdfcf0cfa.json +0 -496
  42. package/.local/share/amp/threads/T-019c9506-4e3b-74fd-8eda-cedbf3793598.json +0 -2679
  43. package/.local/share/amp/threads/T-019c9508-178c-718c-88d2-caf816d64f65.json +0 -965
  44. package/.local/share/amp/threads/T-019c9509-2812-71fd-8fd2-923e29ad34fa.json +0 -7
  45. package/.local/share/amp/threads/T-019c950e-69fe-77d6-9854-fc73b77a3148.json +0 -4570
  46. package/.local/share/amp/threads/T-019c9707-6e2b-741c-b4d4-117026a78449.json +0 -2899
  47. package/.local/share/amp/threads/T-019c971b-6bc0-77b8-8868-f8956d3e71a8.json +0 -7
  48. package/.local/share/amp/threads/T-019c971b-c87c-75f3-a61f-beb18a1cb25f.json +0 -474
  49. package/.local/share/amp/threads/T-019c971d-d371-70ac-9805-5c739908e73b.json +0 -802
  50. package/.local/share/amp/threads/T-019c9722-d73d-74f1-9d1d-8fafaad0ede7.json +0 -7
  51. package/.local/share/amp/threads/T-019c9761-858c-719b-911f-bc2e4c8cbdde.json +0 -188
  52. package/.local/share/amp/threads/T-019c9761-f5f3-7606-a900-ebe7f10d6e37.json +0 -121
  53. package/.local/share/amp/threads/T-019c9763-b1ae-729d-90aa-f59938ce912e.json +0 -799
  54. package/.local/share/amp/threads/T-019c9769-4a8a-77b8-beab-f48973276f9a.json +0 -1541
  55. package/.local/share/amp/threads/T-019c9772-edac-7075-b26e-0ada1f8697d2.json +0 -7
  56. package/.local/share/amp/threads/T-019c97e8-a9ab-71a1-a8f9-109c540c98bf.json +0 -111
  57. package/.local/share/amp/threads/T-019c97e9-2277-753c-8c5d-df745fa6cfff.json +0 -7
  58. package/.local/share/amp/threads/T-019c97e9-f28e-758d-9663-e37047a8ed95.json +0 -111
  59. package/.local/share/amp/threads/T-019c97ea-17c7-77b8-92b2-f641c069bcc9.json +0 -71
  60. package/.local/share/amp/threads/T-019c97ea-44c6-75b8-88bc-d88113194f6a.json +0 -1611
  61. package/.local/share/amp/threads/T-019c97ec-abae-7251-a5f6-693adf496a1c.json +0 -7
  62. package/.local/share/amp/threads/T-019c97f5-8e61-73ad-8c5d-2637abedcde6.json +0 -1341
  63. package/.local/share/amp/threads/T-019c989d-4f4e-7249-bde0-21d19455ccae.json +0 -163
  64. package/.local/share/amp/threads/T-019c989d-9024-73c4-bee8-e2ae45028a39.json +0 -124
  65. package/.local/share/amp/threads/T-019c989e-1394-74ad-8234-ac573fcdb4c7.json +0 -1260
  66. package/.local/share/amp/threads/T-019c989f-e3dd-772e-85ac-525d0fc88fda.json +0 -403
  67. package/.local/share/amp/threads/T-019c98a1-7b0c-778a-b311-2e1cff85d710.json +0 -3422
  68. package/.local/share/amp/threads/T-019c98c5-4b7f-7284-99e9-88aa8c18ba66.json +0 -1830
  69. package/.local/share/amp/threads/T-019c98d0-f27f-76ec-be10-6df96f22be99.json +0 -4061
  70. package/.local/share/amp/threads/T-019c98f9-d031-704d-a0c2-f2f395f68f2b.json +0 -509
  71. package/.local/share/amp/threads/T-019c9919-f9ee-766c-90be-af7a07f6a4c6.json +0 -2075
  72. package/.local/share/amp/threads/T-019c991c-b98b-7158-9083-cc52408beb13.json +0 -7
  73. package/.local/share/amp/threads/T-019c991d-66d6-72aa-a9a1-105f7df0ea06.json +0 -7
  74. package/.local/share/amp/threads/T-019c9c2e-71a4-77ff-bd7f-b053da7f9000.json +0 -1637
  75. package/.local/share/amp/threads/T-019c9c45-27ca-728b-ba77-835115dfa9b2.json +0 -3893
  76. package/.local/share/amp/threads/T-019c9c48-45dc-736a-9752-e4119fe698f9.json +0 -7
  77. package/.local/share/amp/threads/T-019c9c4d-266b-72d0-b56e-74a5777e6583.json +0 -7
  78. package/.local/share/amp/threads/T-019c9c52-ab89-758f-9178-bda99c39d10b.json +0 -7
  79. package/.local/share/amp/threads/T-019c9c56-5715-72e2-b8b4-87711a842dd1.json +0 -1799
  80. package/.local/share/amp/threads/T-019c9c5b-88b1-74cb-97e9-16b23e03daa2.json +0 -727
  81. package/.local/share/amp/threads/T-019c9c5c-3b3e-721c-ad2e-a2ef245dce3f.json +0 -738
  82. package/.local/share/amp/threads/T-019c9c5c-fd78-736f-9d29-a66d23839d40.json +0 -256
  83. package/.local/share/amp/threads/T-019c9c5d-db4a-74cd-ad2a-925fac87131d.json +0 -1859
  84. package/.local/share/kilo/kilo.db +0 -0
  85. package/.local/share/kilo/kilo.db-shm +0 -0
  86. package/.local/share/kilo/kilo.db-wal +0 -0
  87. package/.local/share/kilo/storage/migration +0 -1
  88. package/.local/share/kilo/storage/session_diff/ses_36bea4cb9ffe1b0j5HEL14KEaU.json +0 -1
  89. package/.local/share/kilo/storage/session_diff/ses_36beaa8f2ffeeZ3Y39SQ9UDWQQ.json +0 -1
  90. package/.local/share/kilo/telemetry-id +0 -1
  91. package/.local/share/opencode/auth.json +0 -6
  92. package/.local/share/opencode/opencode.db +0 -0
  93. package/.local/share/opencode/opencode.db-shm +0 -0
  94. package/.local/share/opencode/opencode.db-wal +0 -0
  95. package/.local/share/opencode/storage/agent-usage-reminder/ses_36870ea98ffe8S5ZOCE4F11yFh.json +0 -6
  96. package/.local/share/opencode/storage/agent-usage-reminder/ses_3687a3e9affewUnHBzvpiPR6df.json +0 -6
  97. package/.local/share/opencode/storage/agent-usage-reminder/ses_36886e68dffeKVgUWf6lzXdEEt.json +0 -6
  98. package/.local/share/opencode/storage/agent-usage-reminder/ses_36bee9f1effeJbiHHLWLR6O3WJ.json +0 -6
  99. package/.local/share/opencode/storage/agent-usage-reminder/ses_36c25e50affef2nhaXq9aSgKH3.json +0 -6
  100. package/.local/share/opencode/storage/agent-usage-reminder/ses_36c260708ffel4wG4yhdo0knDD.json +0 -6
  101. package/.local/share/opencode/storage/agent-usage-reminder/ses_36c261531ffeoVcvqXxry2bN9H.json +0 -6
  102. package/.local/share/opencode/storage/agent-usage-reminder/ses_36c291bddffePWRiaFLLJAC1y7.json +0 -6
  103. package/.local/share/opencode/storage/migration +0 -1
  104. package/.local/share/opencode/storage/session_diff/ses_36870ea98ffe8S5ZOCE4F11yFh.json +0 -1
  105. package/.local/share/opencode/storage/session_diff/ses_3687a3e9affewUnHBzvpiPR6df.json +0 -1
  106. package/.local/share/opencode/storage/session_diff/ses_36886e68dffeKVgUWf6lzXdEEt.json +0 -1
  107. package/.local/share/opencode/storage/session_diff/ses_36bee9f1effeJbiHHLWLR6O3WJ.json +0 -1
  108. package/.local/share/opencode/storage/session_diff/ses_36c25e50affef2nhaXq9aSgKH3.json +0 -1
  109. package/.local/share/opencode/storage/session_diff/ses_36c260708ffel4wG4yhdo0knDD.json +0 -1
  110. package/.local/share/opencode/storage/session_diff/ses_36c261531ffeoVcvqXxry2bN9H.json +0 -1
  111. package/.local/share/opencode/storage/session_diff/ses_36c291bddffePWRiaFLLJAC1y7.json +0 -1
  112. package/.local/share/opencode/storage/session_diff/ses_36c2af1c5ffegxEaOZOGcVykyy.json +0 -1
  113. package/.local/share/opencode/storage/session_diff/ses_36c2be235ffeOa6x8UCk1HW4kU.json +0 -1
  114. package/.local/share/opencode/tool-output/tool_c93da840c0016GrdyAkOnHGezU +0 -2330
  115. package/.local/share/opencode/tool-output/tool_c9411e784001cRoQqwVDb1a6lY +0 -1017
  116. package/.local/state/replit/log-query.db +0 -0
  117. package/.local/state/replit/log-query.db-shm +0 -0
  118. package/.local/state/replit/log-query.db-wal +0 -0
  119. package/.replit +0 -41
  120. package/.upm/store.json +0 -1
  121. package/AGENTS.md +0 -28
  122. package/bun.lock +0 -271
  123. package/generated-icon.png +0 -0
  124. package/hello.txt +0 -1
  125. package/index.jsx +0 -24
  126. package/src/agent.js +0 -504
  127. package/src/app.jsx +0 -96
  128. package/src/commands.js +0 -133
  129. package/src/components/AssistantMessage.jsx +0 -83
  130. package/src/components/ChatArea.jsx +0 -84
  131. package/src/components/DiffView.jsx +0 -26
  132. package/src/components/Divider.jsx +0 -8
  133. package/src/components/Header.jsx +0 -44
  134. package/src/components/HelpModal.jsx +0 -81
  135. package/src/components/InputBar.jsx +0 -32
  136. package/src/components/Spinner.jsx +0 -23
  137. package/src/components/StatusBar.jsx +0 -44
  138. package/src/components/SystemMessage.jsx +0 -31
  139. package/src/components/ThinkBlock.jsx +0 -29
  140. package/src/components/ToolCallItem.jsx +0 -43
  141. package/src/components/UserMessage.jsx +0 -11
  142. package/src/components/Welcome.jsx +0 -14
  143. package/src/config.js +0 -196
  144. package/src/hooks/useLayout.js +0 -15
  145. package/src/hooks/useStore.js +0 -6
  146. package/src/prompt.js +0 -101
  147. package/src/store.js +0 -99
  148. package/src/theme.js +0 -19
  149. package/src/thinking.js +0 -54
  150. package/src/toolExecutors.js +0 -853
  151. package/src/tools.js +0 -335
  152. package/src/utils.js +0 -32
  153. package/tsconfig.json +0 -10
package/src/commands.js DELETED
@@ -1,133 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { execSync } = require('child_process');
6
- const { PROJECT_ROOT, session, resolvePath, getMode, setMode } = require('./config');
7
- const { executeTool } = require('./toolExecutors');
8
- const store = require('./store');
9
-
10
- async function handleSlashCommand(input) {
11
- const [cmd, ...rest] = input.split(' ');
12
- const arg = rest.join(' ');
13
-
14
- switch (cmd) {
15
- case '/help':
16
- store.setState({ showHelp: true });
17
- break;
18
-
19
- case '/clear':
20
- session.conversationHistory = [];
21
- store.clearMessages();
22
- store.addMessage({ role: 'system', content: 'Conversation cleared.' });
23
- break;
24
-
25
- case '/files':
26
- case '/ls': {
27
- const dirPath = arg ? resolvePath(arg) : PROJECT_ROOT;
28
- store.addMessage({ role: 'system', content: 'Loading file tree...', label: 'Project Files' });
29
- const result = await executeTool('ListDir', { path: dirPath, recursive: true });
30
- store.addMessage({ role: 'system', content: result, label: 'Project Files' });
31
- break;
32
- }
33
-
34
- case '/cost':
35
- case '/status': {
36
- const elapsed = ((Date.now() - session.startTime) / 1000 / 60).toFixed(1);
37
- const parts = [
38
- `Session: ${elapsed} min`,
39
- `Turns: ${session.turnCount}`,
40
- `Tools: ${session.toolCallCount}`,
41
- `Tokens: ${session.totalTokens.toLocaleString()}`,
42
- `Cost: $${session.totalCost.toFixed(4)}`,
43
- ];
44
- if (session.filesModified.size > 0) parts.push(`Files modified: ${session.filesModified.size}`);
45
- if (session.commandsRun.length > 0) parts.push(`Commands: ${session.commandsRun.length}`);
46
- store.addMessage({ role: 'system', content: parts.join('\n'), label: 'Session Stats' });
47
- break;
48
- }
49
-
50
- case '/undo': {
51
- if (session.editHistory.length === 0) {
52
- store.addMessage({ role: 'system', content: 'No edits to undo.' });
53
- } else {
54
- const last = session.editHistory[session.editHistory.length - 1];
55
- fs.writeFileSync(last.path, last.before, 'utf-8');
56
- session.editHistory.pop();
57
- store.addMessage({ role: 'system', content: `Undone last edit to ${path.basename(last.path)}` });
58
- }
59
- break;
60
- }
61
-
62
- case '/diff': {
63
- try {
64
- const diff = execSync('git diff --stat 2>/dev/null', { encoding: 'utf-8', cwd: PROJECT_ROOT });
65
- store.addMessage({ role: 'system', content: diff || '(no changes)', label: 'Git Diff' });
66
- } catch {
67
- store.addMessage({ role: 'system', content: 'Not a git repository.' });
68
- }
69
- break;
70
- }
71
-
72
- case '/git': {
73
- if (!arg) {
74
- store.addMessage({ role: 'system', content: 'Usage: /git <command>' });
75
- break;
76
- }
77
- try {
78
- const output = execSync(`git ${arg}`, { encoding: 'utf-8', cwd: PROJECT_ROOT });
79
- store.addMessage({ role: 'system', content: output || '(no output)', label: `git ${arg}` });
80
- } catch (err) {
81
- store.addMessage({ role: 'system', content: err.stderr || err.message });
82
- }
83
- break;
84
- }
85
-
86
- case '/mode': {
87
- if (!arg) {
88
- const current = getMode();
89
- const modes = ['default', 'max', 'lite'];
90
- const modeDescriptions = {
91
- default: 'Single agent pass with auto code review',
92
- max: 'Multi-strategy editing, best-of-N thinking, multi-perspective review, auto context pruning',
93
- lite: 'Fast mode — skips validation and review steps',
94
- };
95
- const lines = modes.map(m =>
96
- `${m === current ? '→ ' : ' '}${m.padEnd(10)} ${modeDescriptions[m]}`
97
- );
98
- store.addMessage({ role: 'system', content: `Current mode: ${current}\n\n${lines.join('\n')}`, label: 'Mode' });
99
- } else {
100
- const success = setMode(arg.trim());
101
- if (success) {
102
- store.addMessage({ role: 'system', content: `Mode set to: ${arg.trim()}`, label: 'Mode' });
103
- } else {
104
- store.addMessage({ role: 'system', content: `Invalid mode: ${arg}. Use default, max, or lite.` });
105
- }
106
- }
107
- break;
108
- }
109
-
110
- case '/compact': {
111
- const pruneId = store.addMessage({ role: 'system', content: 'Compacting conversation...', label: 'Context Pruner' });
112
- try {
113
- const result = await executeTool('ContextPruner', {}, (partial) => {
114
- store.updateMessage(pruneId, { content: partial, label: 'Context Pruner' });
115
- });
116
- store.updateMessage(pruneId, { content: result, label: 'Context Pruner' });
117
- } catch (err) {
118
- store.updateMessage(pruneId, { content: `Compaction failed: ${err.message}` });
119
- }
120
- break;
121
- }
122
-
123
- case '/quit':
124
- return { action: 'quit' };
125
-
126
- default:
127
- store.addMessage({ role: 'system', content: `Unknown command: ${cmd}. Type /help for available commands.` });
128
- }
129
-
130
- return null;
131
- }
132
-
133
- module.exports = { handleSlashCommand };
@@ -1,83 +0,0 @@
1
- import { TextAttributes } from '@opentui/core';
2
- import { colors } from '../theme.js';
3
- import { useLayout } from '../hooks/useLayout.js';
4
-
5
- export default function AssistantMessage({ content, isStreaming }) {
6
- const { indent, isNarrow, width } = useLayout();
7
- const codeIndent = isNarrow ? 1 : 2;
8
- const separatorWidth = Math.min(width - indent - codeIndent, isNarrow ? 40 : 60);
9
- if (!content) return null;
10
-
11
- const lines = content.split('\n');
12
- const rendered = [];
13
- let inCodeBlock = false;
14
- let codeLines = [];
15
- let codeLang = '';
16
-
17
- for (let i = 0; i < lines.length; i++) {
18
- const line = lines[i];
19
-
20
- if (line.startsWith('```') && !inCodeBlock) {
21
- inCodeBlock = true;
22
- codeLang = line.slice(3).trim() || 'code';
23
- codeLines = [];
24
- } else if (line.startsWith('```') && inCodeBlock) {
25
- inCodeBlock = false;
26
- rendered.push(
27
- <box key={`code-${i}`} style={{ flexDirection: 'column', paddingLeft: codeIndent, marginTop: 0 }}>
28
- <text fg={colors.dim} content={`── ${codeLang} ──`} />
29
- {codeLines.map((cl, j) => (
30
- <text key={j}>
31
- <span fg={colors.dim}>{String(j + 1).padStart(isNarrow ? 2 : 3) + ' │ '}</span>
32
- <span fg={colors.text}>{cl}</span>
33
- </text>
34
- ))}
35
- <text fg={colors.dim} content={'─'.repeat(Math.max(separatorWidth, 10))} />
36
- </box>
37
- );
38
- } else if (inCodeBlock) {
39
- codeLines.push(line);
40
- } else {
41
- const processed = line.replace(/`([^`]+)`/g, '«$1»');
42
- if (processed.includes('«')) {
43
- const parts = processed.split(/«|»/);
44
- rendered.push(
45
- <text key={`line-${i}`}>
46
- {parts.map((part, j) =>
47
- j % 2 === 0
48
- ? <span key={j} fg={colors.text}>{part}</span>
49
- : <span key={j} fg={colors.cyan}>{part}</span>
50
- )}
51
- </text>
52
- );
53
- } else {
54
- rendered.push(
55
- <text key={`line-${i}`}>
56
- <span fg={colors.text}>{line}</span>
57
- </text>
58
- );
59
- }
60
- }
61
- }
62
-
63
- if (inCodeBlock && codeLines.length > 0) {
64
- rendered.push(
65
- <box key="code-tail" style={{ flexDirection: 'column', paddingLeft: codeIndent }}>
66
- <text fg={colors.dim} content={`── ${codeLang} ──`} />
67
- {codeLines.map((cl, j) => (
68
- <text key={j}>
69
- <span fg={colors.dim}>{String(j + 1).padStart(isNarrow ? 2 : 3) + ' │ '}</span>
70
- <span fg={colors.text}>{cl}</span>
71
- </text>
72
- ))}
73
- </box>
74
- );
75
- }
76
-
77
- return (
78
- <box style={{ flexDirection: 'column', paddingLeft: indent }}>
79
- {rendered}
80
- {isStreaming ? <text fg={colors.accent} content="▊" /> : null}
81
- </box>
82
- );
83
- }
@@ -1,84 +0,0 @@
1
- import { TextAttributes } from '@opentui/core';
2
- import { colors } from '../theme.js';
3
- import { useLayout } from '../hooks/useLayout.js';
4
- import Welcome from './Welcome.jsx';
5
- import UserMessage from './UserMessage.jsx';
6
- import AssistantMessage from './AssistantMessage.jsx';
7
- import ThinkBlock from './ThinkBlock.jsx';
8
- import ToolCallItem from './ToolCallItem.jsx';
9
- import DiffView from './DiffView.jsx';
10
- import SystemMessage from './SystemMessage.jsx';
11
- import Spinner from './Spinner.jsx';
12
- import { toggleMessageExpanded } from '../store.js';
13
-
14
- function MessageItem({ message }) {
15
- const { width } = useLayout();
16
- switch (message.role) {
17
- case 'user':
18
- return <UserMessage content={message.content} />;
19
- case 'assistant':
20
- return (
21
- <box style={{ flexDirection: 'column', marginTop: 1 }}>
22
- <text fg={colors.primary} attributes={TextAttributes.BOLD} style={{ paddingLeft: 1 }} content="Apex" />
23
- <AssistantMessage content={message.content} />
24
- </box>
25
- );
26
- case 'thinking':
27
- return (
28
- <ThinkBlock
29
- content={message.content}
30
- expanded={message.expanded}
31
- onToggle={() => toggleMessageExpanded(message.id)}
32
- />
33
- );
34
- case 'tool':
35
- return <ToolCallItem message={message} />;
36
- case 'diff':
37
- return <DiffView filename={message.filename} content={message.content} />;
38
- case 'system':
39
- return <SystemMessage message={message} />;
40
- case 'divider':
41
- return <text fg={colors.dim} style={{ paddingLeft: 1 }} content={'─'.repeat(Math.max(width - 2, 10))} />;
42
- default:
43
- return null;
44
- }
45
- }
46
-
47
- export default function ChatArea({ messages, streamingContent, streamingThinking, isProcessing }) {
48
- const { indent } = useLayout();
49
- return (
50
- <scrollbox
51
- style={{ flexGrow: 1 }}
52
- focused
53
- stickyScroll
54
- stickyStart="bottom"
55
- scrollY
56
- >
57
- <box style={{ flexDirection: 'column' }}>
58
- <Welcome />
59
- {messages.map(msg => (
60
- <MessageItem key={msg.id} message={msg} />
61
- ))}
62
- {streamingThinking ? (
63
- <box style={{ paddingLeft: 2, marginTop: 0 }}>
64
- <text fg={colors.dim} attributes={TextAttributes.ITALIC}>
65
- <span fg={colors.dim}>{'▸ Thinking: '}</span>
66
- <span fg={colors.dim}>{streamingThinking.slice(-100)}</span>
67
- </text>
68
- </box>
69
- ) : null}
70
- {streamingContent ? (
71
- <box style={{ flexDirection: 'column', marginTop: 0 }}>
72
- <AssistantMessage content={streamingContent} isStreaming />
73
- </box>
74
- ) : null}
75
- {isProcessing && !streamingContent && !streamingThinking ? (
76
- <box style={{ paddingLeft: indent, marginTop: 1 }}>
77
- <Spinner label="Reasoning..." />
78
- </box>
79
- ) : null}
80
- <box style={{ height: 1 }} />
81
- </box>
82
- </scrollbox>
83
- );
84
- }
@@ -1,26 +0,0 @@
1
- import { TextAttributes } from '@opentui/core';
2
- import { colors } from '../theme.js';
3
- import { useLayout } from '../hooks/useLayout.js';
4
-
5
- const path = require('path');
6
-
7
- export default function DiffView({ filename, content }) {
8
- const { indent } = useLayout();
9
- if (!content) return null;
10
- const lines = content.split('\n');
11
-
12
- return (
13
- <box style={{ flexDirection: 'column', paddingLeft: indent }}>
14
- <text fg={colors.text} attributes={TextAttributes.BOLD} content={path.basename(filename || '')} />
15
- {lines.map((line, i) => {
16
- if (line.startsWith('+')) {
17
- return <text key={i} fg={colors.green} content={line} />;
18
- }
19
- if (line.startsWith('-')) {
20
- return <text key={i} fg={colors.red} content={line} />;
21
- }
22
- return null;
23
- })}
24
- </box>
25
- );
26
- }
@@ -1,8 +0,0 @@
1
- import { colors } from '../theme.js';
2
- import { useLayout } from '../hooks/useLayout.js';
3
-
4
- export default function Divider() {
5
- const { width } = useLayout();
6
- const cols = Math.min(width, 120);
7
- return <text fg={colors.dim} content={'─'.repeat(cols)} />;
8
- }
@@ -1,44 +0,0 @@
1
- import { useState, useEffect } from 'react';
2
- import { TextAttributes } from '@opentui/core';
3
- import { colors } from '../theme.js';
4
- import { PROJECT_ROOT, getMode } from '../config.js';
5
- import { useLayout } from '../hooks/useLayout.js';
6
-
7
- const path = require('path');
8
- const { execSync } = require('child_process');
9
-
10
- export default function Header() {
11
- const [branch, setBranch] = useState('');
12
- const { isNarrow } = useLayout();
13
- const cwd = path.basename(PROJECT_ROOT);
14
- const mode = getMode();
15
- const modeColors = { default: colors.dim, max: colors.accent, lite: colors.muted };
16
-
17
- useEffect(() => {
18
- try {
19
- const b = execSync('git rev-parse --abbrev-ref HEAD 2>/dev/null', {
20
- encoding: 'utf-8',
21
- cwd: PROJECT_ROOT,
22
- }).trim();
23
- setBranch(b);
24
- } catch {}
25
- }, []);
26
-
27
- return (
28
- <box style={{ flexDirection: 'row', paddingLeft: 1, paddingRight: 1 }}>
29
- <text>
30
- <span fg={colors.primary} attributes={TextAttributes.BOLD}>⚡ Apex</span>
31
- <span fg={colors.dim}>{' '}</span>
32
- <span fg={modeColors[mode] || colors.dim}>[{mode}]</span>
33
- <span fg={colors.dim}>{' '}</span>
34
- <span fg={colors.muted}>{isNarrow && cwd.length > 12 ? cwd.slice(0, 12) + '…' : cwd}</span>
35
- {branch && !isNarrow ? (
36
- <>
37
- <span fg={colors.dim}>{' on '}</span>
38
- <span fg={colors.text}>{branch}</span>
39
- </>
40
- ) : null}
41
- </text>
42
- </box>
43
- );
44
- }
@@ -1,81 +0,0 @@
1
- import { TextAttributes } from '@opentui/core';
2
- import { useKeyboard } from '@opentui/react';
3
- import { colors } from '../theme.js';
4
- import { useLayout } from '../hooks/useLayout.js';
5
-
6
- const COMMANDS = [
7
- { cmd: '/help', desc: 'Show this menu' },
8
- { cmd: '/mode', desc: 'Show/set mode (default, max, lite)' },
9
- { cmd: '/compact', desc: 'Compact conversation context' },
10
- { cmd: '/files', desc: 'Show project file tree' },
11
- { cmd: '/clear', desc: 'Clear conversation' },
12
- { cmd: '/cost', desc: 'Show session stats' },
13
- { cmd: '/undo', desc: 'Undo last edit' },
14
- { cmd: '/diff', desc: 'Show git diff' },
15
- { cmd: '/git <cmd>', desc: 'Run a git command' },
16
- { cmd: '/quit', desc: 'Exit' },
17
- ];
18
-
19
- const TOOLS = [
20
- 'Read', 'Write', 'Edit', 'Patch', 'Bash', 'Grep', 'Glob', 'ListDir', 'UndoEdit', 'Task', 'CodeReview',
21
- ];
22
-
23
- const SUBAGENTS = [
24
- 'FilePickerMax', 'Thinker', 'ThinkerBestOfN*', 'EditorMultiPrompt*', 'CodeReviewMulti*', 'Commander', 'ContextPruner',
25
- ];
26
-
27
- export default function HelpModal({ onClose, onCommand }) {
28
- const { isNarrow } = useLayout();
29
-
30
- useKeyboard((key) => {
31
- if (key.name === 'escape' || key.name === 'q') {
32
- onClose();
33
- }
34
- });
35
-
36
- return (
37
- <box
38
- zIndex={100}
39
- border
40
- borderColor={colors.primary}
41
- backgroundColor="#0d0d1a"
42
- title=" Help "
43
- titleAlignment="center"
44
- style={{
45
- position: 'absolute',
46
- top: 2,
47
- left: isNarrow ? 1 : 4,
48
- bottom: 2,
49
- right: isNarrow ? 1 : 4,
50
- padding: 1,
51
- flexDirection: 'column',
52
- }}
53
- >
54
- <text fg={colors.white} attributes={TextAttributes.BOLD} content="Commands" />
55
- <box style={{ flexDirection: 'column', marginTop: 0 }}>
56
- {COMMANDS.map(({ cmd, desc }) => (
57
- <box
58
- key={cmd}
59
- style={{ flexDirection: 'row' }}
60
- onMouseDown={() => {
61
- const slashCmd = cmd.split(' ')[0];
62
- if (onCommand && !cmd.includes('<')) onCommand(slashCmd);
63
- onClose();
64
- }}
65
- >
66
- <text>
67
- <span fg={colors.accent}>{cmd.padEnd(isNarrow ? 10 : 14)}</span>
68
- <span fg={colors.text}>{desc}</span>
69
- </text>
70
- </box>
71
- ))}
72
- </box>
73
- <text fg={colors.white} attributes={TextAttributes.BOLD} style={{ marginTop: 1 }} content="Tools" />
74
- <text fg={colors.dim} content={TOOLS.join(', ')} />
75
- <text fg={colors.white} attributes={TextAttributes.BOLD} style={{ marginTop: 1 }} content="Sub-Agents" />
76
- <text fg={colors.dim} content={SUBAGENTS.join(', ')} />
77
- <text fg={colors.dim} content=" * = MAX mode only" />
78
- <text fg={colors.dim} style={{ marginTop: 1 }} content="Press ESC or q to close" />
79
- </box>
80
- );
81
- }
@@ -1,32 +0,0 @@
1
- import { useRef } from 'react';
2
- import { colors } from '../theme.js';
3
- import { useLayout } from '../hooks/useLayout.js';
4
-
5
- export default function InputBar({ disabled, onSubmit }) {
6
- const inputRef = useRef(null);
7
- const { isNarrow } = useLayout();
8
-
9
- const handleSubmit = (value) => {
10
- const trimmed = value.trim();
11
- if (!trimmed) return;
12
- if (inputRef.current) inputRef.current.value = '';
13
- onSubmit(trimmed);
14
- };
15
-
16
- return (
17
- <box style={{ flexDirection: 'column' }}>
18
- <text fg={colors.dim} style={{ paddingLeft: isNarrow ? 1 : 2 }} content={isNarrow ? '^C exit · /help' : 'Ctrl+C to exit · /help for commands'} />
19
- <box style={{ flexDirection: 'row', paddingLeft: 1 }}>
20
- <text fg={colors.primary} content="❯ " />
21
- <input
22
- ref={inputRef}
23
- focused={!disabled}
24
- placeholder={disabled ? 'Processing...' : 'Type a message...'}
25
- onSubmit={handleSubmit}
26
- fg={colors.text}
27
- style={{ flexGrow: 1 }}
28
- />
29
- </box>
30
- </box>
31
- );
32
- }
@@ -1,23 +0,0 @@
1
- import { useState, useEffect, useRef } from 'react';
2
- import { colors } from '../theme.js';
3
-
4
- const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
5
-
6
- export default function Spinner({ label }) {
7
- const [frame, setFrame] = useState(0);
8
- const timerRef = useRef(null);
9
-
10
- useEffect(() => {
11
- timerRef.current = setInterval(() => {
12
- setFrame(f => (f + 1) % FRAMES.length);
13
- }, 80);
14
- return () => clearInterval(timerRef.current);
15
- }, []);
16
-
17
- return (
18
- <text>
19
- <span fg={colors.accent}>{FRAMES[frame]}</span>
20
- {label ? <span fg={colors.dim}>{' ' + label}</span> : null}
21
- </text>
22
- );
23
- }
@@ -1,44 +0,0 @@
1
- import { colors } from '../theme.js';
2
- import { session } from '../config.js';
3
- import { useLayout } from '../hooks/useLayout.js';
4
-
5
- export default function StatusBar({ isProcessing }) {
6
- const { isNarrow } = useLayout();
7
- const elapsed = ((Date.now() - session.startTime) / 1000 / 60).toFixed(1);
8
-
9
- return (
10
- <box style={{ flexDirection: 'row', paddingLeft: isNarrow ? 1 : 2, paddingRight: isNarrow ? 1 : 2 }}>
11
- <text>
12
- <span fg={colors.dim}>{elapsed}min</span>
13
- <span fg={colors.dim}>{' · '}</span>
14
- <span fg={colors.dim}>{session.turnCount} turns</span>
15
- {!isNarrow ? (
16
- <>
17
- <span fg={colors.dim}>{' · '}</span>
18
- <span fg={colors.dim}>{session.toolCallCount} tools</span>
19
- </>
20
- ) : null}
21
- {!isNarrow ? (
22
- <>
23
- <span fg={colors.dim}>{' · '}</span>
24
- <span fg={colors.dim}>{session.totalTokens.toLocaleString()} tok</span>
25
- </>
26
- ) : null}
27
- <span fg={colors.dim}>{' · '}</span>
28
- <span fg={colors.dim}>{'$' + session.totalCost.toFixed(4)}</span>
29
- {session.filesModified.size > 0 && !isNarrow ? (
30
- <>
31
- <span fg={colors.dim}>{' · '}</span>
32
- <span fg={colors.yellow}>{session.filesModified.size} files modified</span>
33
- </>
34
- ) : null}
35
- {isProcessing ? (
36
- <>
37
- <span fg={colors.dim}>{' · '}</span>
38
- <span fg={colors.accent}>{isNarrow ? '...' : 'processing'}</span>
39
- </>
40
- ) : null}
41
- </text>
42
- </box>
43
- );
44
- }
@@ -1,31 +0,0 @@
1
- import { TextAttributes } from '@opentui/core';
2
- import { colors } from '../theme.js';
3
- import { useLayout } from '../hooks/useLayout.js';
4
- import { toggleMessageExpanded } from '../store.js';
5
-
6
- export default function SystemMessage({ message }) {
7
- const { isNarrow, smallIndent } = useLayout();
8
- const { id, content = '', label, expanded } = message;
9
- if (!content) return null;
10
- const lines = content.split('\n');
11
- const maxPreview = isNarrow ? 3 : 6;
12
- const displayLines = expanded ? lines : lines.slice(0, maxPreview);
13
- const isTruncated = !expanded && lines.length > maxPreview;
14
-
15
- return (
16
- <box
17
- style={{ flexDirection: 'column', paddingLeft: smallIndent, marginTop: 1 }}
18
- onMouseDown={() => toggleMessageExpanded(id)}
19
- >
20
- {label ? (
21
- <text fg={colors.accent} attributes={TextAttributes.BOLD} content={label} />
22
- ) : null}
23
- {displayLines.map((line, i) => (
24
- <text key={i} fg={colors.muted} content={line} />
25
- ))}
26
- {isTruncated ? (
27
- <text fg={colors.dim} content={isNarrow ? `+${lines.length - maxPreview} more (tap)` : `... +${lines.length - maxPreview} more lines (click to expand)`} />
28
- ) : null}
29
- </box>
30
- );
31
- }
@@ -1,29 +0,0 @@
1
- import { TextAttributes } from '@opentui/core';
2
- import { colors } from '../theme.js';
3
- import { useLayout } from '../hooks/useLayout.js';
4
-
5
- export default function ThinkBlock({ content, expanded, onToggle }) {
6
- const { isNarrow, smallIndent } = useLayout();
7
- if (!content) return null;
8
- const lines = content.split('\n');
9
- const maxPreview = isNarrow ? 2 : 4;
10
- const displayLines = expanded ? lines : lines.slice(0, maxPreview);
11
- const isTruncated = !expanded && lines.length > maxPreview;
12
-
13
- return (
14
- <box
15
- style={{ flexDirection: 'column', paddingLeft: smallIndent, marginTop: 0 }}
16
- onMouseDown={onToggle}
17
- >
18
- <text fg={colors.dim} attributes={TextAttributes.ITALIC} content="▸ Thinking" />
19
- {displayLines.map((line, i) => (
20
- line.trim() ? (
21
- <text key={i} fg={colors.dim} attributes={TextAttributes.ITALIC} style={{ paddingLeft: smallIndent }} content={line} />
22
- ) : null
23
- ))}
24
- {isTruncated ? (
25
- <text fg={colors.dim} attributes={TextAttributes.ITALIC} style={{ paddingLeft: smallIndent }} content={isNarrow ? `+${lines.length - maxPreview} more (tap)` : `... +${lines.length - maxPreview} more lines (click to expand)`} />
26
- ) : null}
27
- </box>
28
- );
29
- }
@@ -1,43 +0,0 @@
1
- import { colors } from '../theme.js';
2
- import { useLayout } from '../hooks/useLayout.js';
3
- import { toggleMessageExpanded } from '../store.js';
4
- import Spinner from './Spinner.jsx';
5
-
6
- function formatElapsed(ms) {
7
- return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;
8
- }
9
-
10
- function truncate(str, len) {
11
- return str.length > len ? str.slice(0, len - 3) + '...' : str;
12
- }
13
-
14
- export default function ToolCallItem({ message }) {
15
- const { indent, isNarrow } = useLayout();
16
- const truncLen = isNarrow ? 30 : 50;
17
- const { id, name, detail, status, success, elapsed, output, expanded } = message;
18
- const isRunning = status === 'running' || status === 'pending';
19
-
20
- return (
21
- <box style={{ flexDirection: 'column', paddingLeft: indent }} onMouseDown={() => toggleMessageExpanded(id)}>
22
- <box style={{ flexDirection: 'row' }}>
23
- {isRunning ? (
24
- <Spinner label={`[${name}] ${truncate(detail || '...', truncLen)}`} />
25
- ) : (
26
- <text>
27
- <span fg={success ? colors.green : colors.red}>{success ? '✓' : '✗'}</span>
28
- <span fg={colors.dim}>{' ['}</span>
29
- <span fg={colors.accent}>{name}</span>
30
- <span fg={colors.dim}>{'] '}</span>
31
- <span fg={colors.dim}>{truncate(detail || '', truncLen)}</span>
32
- {elapsed != null ? <span fg={colors.dim}>{' ' + formatElapsed(elapsed)}</span> : null}
33
- </text>
34
- )}
35
- </box>
36
- {expanded && output ? (
37
- <box style={{ paddingLeft: indent, marginTop: 0 }}>
38
- <text fg={colors.dim} content={truncate(output, 500)} wrapMode="char" />
39
- </box>
40
- ) : null}
41
- </box>
42
- );
43
- }