@abacus-ai/cli 1.106.25008 → 2.0.0-canary.1

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 (199) hide show
  1. package/.oxlintrc.json +8 -0
  2. package/dist/index.mjs +12823 -0
  3. package/package.json +7 -39
  4. package/resources/abacus.ico +0 -0
  5. package/resources/entitlements.plist +9 -0
  6. package/src/__e2e__/README.md +196 -0
  7. package/src/__e2e__/agent-interactions.e2e.test.tsx +61 -0
  8. package/src/__e2e__/cli-commands.e2e.test.tsx +77 -0
  9. package/src/__e2e__/conversation-throttle.e2e.test.ts +453 -0
  10. package/src/__e2e__/conversation.e2e.test.tsx +56 -0
  11. package/src/__e2e__/diff-preview.e2e.test.tsx +3399 -0
  12. package/src/__e2e__/file-creation.e2e.test.tsx +149 -0
  13. package/src/__e2e__/helpers/test-helpers.ts +449 -0
  14. package/src/__e2e__/keyboard-navigation.e2e.test.tsx +34 -0
  15. package/src/__e2e__/llm-models.e2e.test.ts +402 -0
  16. package/src/__e2e__/mcp/mcp-callback-flow.e2e.test.tsx +71 -0
  17. package/src/__e2e__/mcp/mcp-full-app-ui.e2e.test.tsx +167 -0
  18. package/src/__e2e__/mcp/mcp-ui-rendering.e2e.test.tsx +185 -0
  19. package/src/__e2e__/repl.e2e.test.tsx +78 -0
  20. package/src/__e2e__/shell-compatibility.e2e.test.tsx +76 -0
  21. package/src/__e2e__/theme-mcp.e2e.test.tsx +98 -0
  22. package/src/__e2e__/tool-permissions.e2e.test.tsx +66 -0
  23. package/src/args.ts +22 -0
  24. package/src/components/__tests__/react-compiler.test.tsx +78 -0
  25. package/src/components/__tests__/status-indicator.test.tsx +403 -0
  26. package/src/components/composer/__tests__/bash-runner.test.tsx +263 -0
  27. package/src/components/composer/agent-mode-indicator.tsx +63 -0
  28. package/src/components/composer/bash-runner.tsx +54 -0
  29. package/src/components/composer/commands/default-commands.tsx +615 -0
  30. package/src/components/composer/commands/handler.tsx +59 -0
  31. package/src/components/composer/commands/picker.tsx +273 -0
  32. package/src/components/composer/commands/registry.ts +233 -0
  33. package/src/components/composer/commands/types.ts +33 -0
  34. package/src/components/composer/context.tsx +88 -0
  35. package/src/components/composer/file-mention-picker.tsx +83 -0
  36. package/src/components/composer/help.tsx +44 -0
  37. package/src/components/composer/index.tsx +1007 -0
  38. package/src/components/composer/mentions.ts +57 -0
  39. package/src/components/composer/message-queue.tsx +70 -0
  40. package/src/components/composer/mode-panel.tsx +35 -0
  41. package/src/components/composer/modes/__tests__/bash-handler.test.tsx +755 -0
  42. package/src/components/composer/modes/__tests__/bash-renderer.test.tsx +1108 -0
  43. package/src/components/composer/modes/bash-handler.tsx +132 -0
  44. package/src/components/composer/modes/bash-renderer.tsx +175 -0
  45. package/src/components/composer/modes/default-handlers.tsx +33 -0
  46. package/src/components/composer/modes/index.ts +41 -0
  47. package/src/components/composer/modes/types.ts +21 -0
  48. package/src/components/composer/persistent-shell.ts +283 -0
  49. package/src/components/composer/process.ts +65 -0
  50. package/src/components/composer/types.ts +9 -0
  51. package/src/components/composer/use-mention-search.ts +68 -0
  52. package/src/components/error-boundry.tsx +60 -0
  53. package/src/components/exit-message.tsx +29 -0
  54. package/src/components/expanded-view.tsx +74 -0
  55. package/src/components/file-completion.tsx +127 -0
  56. package/src/components/header.tsx +47 -0
  57. package/src/components/logo.tsx +37 -0
  58. package/src/components/segments.tsx +356 -0
  59. package/src/components/status-indicator.tsx +306 -0
  60. package/src/components/tool-group-summary.tsx +263 -0
  61. package/src/components/tool-permissions/ask-user-question-permission-ui.tsx +319 -0
  62. package/src/components/tool-permissions/diff-preview.tsx +359 -0
  63. package/src/components/tool-permissions/index.ts +5 -0
  64. package/src/components/tool-permissions/permission-options.tsx +401 -0
  65. package/src/components/tool-permissions/permission-preview-header.tsx +57 -0
  66. package/src/components/tool-permissions/tool-permission-ui.tsx +420 -0
  67. package/src/components/tools/agent/ask-user-question.tsx +107 -0
  68. package/src/components/tools/agent/enter-plan-mode.tsx +55 -0
  69. package/src/components/tools/agent/exit-plan-mode.tsx +83 -0
  70. package/src/components/tools/agent/handoff-to-main.tsx +27 -0
  71. package/src/components/tools/agent/subagent.tsx +37 -0
  72. package/src/components/tools/agent/todo-write.tsx +104 -0
  73. package/src/components/tools/browser/close-tab.tsx +58 -0
  74. package/src/components/tools/browser/computer.tsx +70 -0
  75. package/src/components/tools/browser/get-interactive-elements.tsx +54 -0
  76. package/src/components/tools/browser/get-tab-content.tsx +51 -0
  77. package/src/components/tools/browser/navigate-to.tsx +59 -0
  78. package/src/components/tools/browser/new-tab.tsx +60 -0
  79. package/src/components/tools/browser/perform-action.tsx +63 -0
  80. package/src/components/tools/browser/refresh-tab.tsx +43 -0
  81. package/src/components/tools/browser/switch-tab.tsx +58 -0
  82. package/src/components/tools/filesystem/delete-file.tsx +104 -0
  83. package/src/components/tools/filesystem/edit.tsx +220 -0
  84. package/src/components/tools/filesystem/list-dir.tsx +78 -0
  85. package/src/components/tools/filesystem/read-file.tsx +180 -0
  86. package/src/components/tools/filesystem/upload-image.tsx +76 -0
  87. package/src/components/tools/ide/ide-diagnostics.tsx +62 -0
  88. package/src/components/tools/index.ts +91 -0
  89. package/src/components/tools/mcp/mcp-tool.tsx +158 -0
  90. package/src/components/tools/search/fetch-url.tsx +73 -0
  91. package/src/components/tools/search/file-search.tsx +78 -0
  92. package/src/components/tools/search/grep.tsx +90 -0
  93. package/src/components/tools/search/semantic-search.tsx +66 -0
  94. package/src/components/tools/search/web-search.tsx +71 -0
  95. package/src/components/tools/shared/index.tsx +48 -0
  96. package/src/components/tools/shared/zod-coercion.ts +35 -0
  97. package/src/components/tools/terminal/bash-tool-output.tsx +188 -0
  98. package/src/components/tools/terminal/get-terminal-output.tsx +91 -0
  99. package/src/components/tools/terminal/run-in-terminal.tsx +131 -0
  100. package/src/components/tools/types.ts +16 -0
  101. package/src/components/tools.tsx +68 -0
  102. package/src/components/ui/__tests__/divider.test.tsx +61 -0
  103. package/src/components/ui/__tests__/gradient.test.tsx +125 -0
  104. package/src/components/ui/__tests__/input.test.tsx +166 -0
  105. package/src/components/ui/__tests__/select.test.tsx +273 -0
  106. package/src/components/ui/__tests__/shimmer.test.tsx +99 -0
  107. package/src/components/ui/blinking-indicator.tsx +27 -0
  108. package/src/components/ui/divider.tsx +162 -0
  109. package/src/components/ui/gradient.tsx +56 -0
  110. package/src/components/ui/input.tsx +228 -0
  111. package/src/components/ui/select.tsx +151 -0
  112. package/src/components/ui/shimmer.tsx +76 -0
  113. package/src/context/agent-mode.tsx +95 -0
  114. package/src/context/extension-file.tsx +136 -0
  115. package/src/context/network-activity.tsx +45 -0
  116. package/src/context/notification.tsx +62 -0
  117. package/src/context/shell-size.tsx +49 -0
  118. package/src/context/shell-title.tsx +38 -0
  119. package/src/entrypoints/print-mode.ts +312 -0
  120. package/src/entrypoints/repl.tsx +389 -0
  121. package/src/hooks/use-agent.ts +15 -0
  122. package/src/hooks/use-api-client.ts +1 -0
  123. package/src/hooks/use-available-height.ts +8 -0
  124. package/src/hooks/use-cleanup.ts +29 -0
  125. package/src/hooks/use-interrupt-manager.ts +242 -0
  126. package/src/hooks/use-models.ts +22 -0
  127. package/src/index.ts +217 -0
  128. package/src/lib/__tests__/ansi.test.ts +255 -0
  129. package/src/lib/__tests__/cli.test.ts +122 -0
  130. package/src/lib/__tests__/commands.test.ts +325 -0
  131. package/src/lib/__tests__/constants.test.ts +15 -0
  132. package/src/lib/__tests__/focusables.test.ts +25 -0
  133. package/src/lib/__tests__/fs.test.ts +231 -0
  134. package/src/lib/__tests__/markdown.test.tsx +348 -0
  135. package/src/lib/__tests__/mcpCommandHandler.test.ts +173 -0
  136. package/src/lib/__tests__/mcpManagement.test.ts +38 -0
  137. package/src/lib/__tests__/path-paste.test.ts +144 -0
  138. package/src/lib/__tests__/path.test.ts +300 -0
  139. package/src/lib/__tests__/queries.test.ts +39 -0
  140. package/src/lib/__tests__/standaloneMcpService.test.ts +71 -0
  141. package/src/lib/__tests__/text-buffer.test.ts +328 -0
  142. package/src/lib/__tests__/text-utils.test.ts +32 -0
  143. package/src/lib/__tests__/timing.test.ts +78 -0
  144. package/src/lib/__tests__/utils.test.ts +238 -0
  145. package/src/lib/__tests__/vim-buffer-actions.test.ts +154 -0
  146. package/src/lib/ansi.ts +150 -0
  147. package/src/lib/cli-push-server.ts +112 -0
  148. package/src/lib/cli.ts +44 -0
  149. package/src/lib/clipboard.ts +226 -0
  150. package/src/lib/command-utils.ts +93 -0
  151. package/src/lib/commands.ts +270 -0
  152. package/src/lib/constants.ts +3 -0
  153. package/src/lib/extension-connection.ts +181 -0
  154. package/src/lib/focusables.ts +7 -0
  155. package/src/lib/fs.ts +533 -0
  156. package/src/lib/markdown/code-block.tsx +63 -0
  157. package/src/lib/markdown/index.ts +4 -0
  158. package/src/lib/markdown/link.tsx +19 -0
  159. package/src/lib/markdown/markdown.tsx +372 -0
  160. package/src/lib/markdown/types.ts +15 -0
  161. package/src/lib/mcpCommandHandler.ts +121 -0
  162. package/src/lib/mcpManagement.ts +44 -0
  163. package/src/lib/path-paste.ts +185 -0
  164. package/src/lib/path.ts +179 -0
  165. package/src/lib/queries.ts +15 -0
  166. package/src/lib/standaloneMcpService.ts +688 -0
  167. package/src/lib/status-utils.ts +237 -0
  168. package/src/lib/test-utils.tsx +72 -0
  169. package/src/lib/text-buffer.ts +2415 -0
  170. package/src/lib/text-utils.ts +272 -0
  171. package/src/lib/timing.ts +63 -0
  172. package/src/lib/types.ts +295 -0
  173. package/src/lib/utils.ts +182 -0
  174. package/src/lib/vim-buffer-actions.ts +732 -0
  175. package/src/providers/agent.tsx +1063 -0
  176. package/src/providers/api-client.tsx +43 -0
  177. package/src/services/logger.ts +85 -0
  178. package/src/terminal/detection.ts +187 -0
  179. package/src/terminal/exit.ts +279 -0
  180. package/src/terminal/notification.ts +83 -0
  181. package/src/terminal/progress.ts +201 -0
  182. package/src/terminal/setup.ts +797 -0
  183. package/src/terminal/types.ts +51 -0
  184. package/src/theme/context.tsx +57 -0
  185. package/src/theme/index.ts +4 -0
  186. package/src/theme/themed.tsx +35 -0
  187. package/src/theme/themes.json +546 -0
  188. package/src/theme/types.ts +110 -0
  189. package/src/tools/types.ts +59 -0
  190. package/src/tools/utils/__tests__/zod-coercion.test.ts +33 -0
  191. package/src/tools/utils/tool-ui-components.tsx +649 -0
  192. package/src/tools/utils/zod-coercion.ts +35 -0
  193. package/tsconfig.json +16 -0
  194. package/tsconfig.node.json +29 -0
  195. package/tsconfig.test.json +27 -0
  196. package/tsdown.config.ts +17 -0
  197. package/vitest.config.ts +76 -0
  198. package/README.md +0 -28
  199. package/dist/index.js +0 -26
@@ -0,0 +1,272 @@
1
+ import { stripVTControlCharacters } from "node:util";
2
+ import stringWidth from "string-width";
3
+
4
+ import { stripAnsi, ansiRegex } from "./ansi.js";
5
+
6
+ /**
7
+ * Dangerous ANSI sequences that affect parent terminal.
8
+ * These are stripped. Color codes (SGR - \x1b[...m) are preserved.
9
+ */
10
+ const ESC = String.fromCharCode(27);
11
+ const BEL = String.fromCharCode(7);
12
+ const DANGEROUS_SEQUENCES: RegExp[] = [
13
+ new RegExp(ESC + "\\[2J", "g"), // Clear screen (ED - Erase Display)
14
+ new RegExp(ESC + "\\[H", "g"), // Cursor home
15
+ new RegExp(ESC + "\\[\\d+(;\\d+)?H", "g"), // Cursor position (CUP: row or row;col)
16
+ new RegExp(ESC + "c", "g"), // Reset terminal (RIS)
17
+ new RegExp(ESC + "\\[!p", "g"), // Soft reset (DECSTR)
18
+ new RegExp(ESC + "\\[\\?1049[hl]", "g"), // Alternate screen buffer
19
+ new RegExp(ESC + "\\[\\d*[ABCD]", "g"), // Cursor movement (up/down/forward/back)
20
+ new RegExp(ESC + "\\[s", "g"), // Save cursor position (SCP)
21
+ new RegExp(ESC + "\\[u", "g"), // Restore cursor position (RCP)
22
+ new RegExp(ESC + "\\[\\d*J", "g"), // Erase display variants (except colors)
23
+ new RegExp(ESC + "\\[\\d*K", "g"), // Erase line variants
24
+ new RegExp(ESC + "\\[\\?25[hl]", "g"), // Show/hide cursor
25
+ new RegExp(BEL, "g"), // Bell (BEL)
26
+ ];
27
+
28
+ /**
29
+ * Strip dangerous terminal sequences but preserve colors.
30
+ *
31
+ * Preserved: \x1b[...m (SGR color codes like \x1b[31m for red, \x1b[0m for reset)
32
+ * Stripped: cursor movement, screen clearing, terminal resets, bell
33
+ */
34
+ export function stripDangerousSequences(str: string): string {
35
+ let result = str;
36
+ for (const pattern of DANGEROUS_SEQUENCES) {
37
+ result = result.replace(pattern, "");
38
+ }
39
+ return result;
40
+ }
41
+
42
+ /**
43
+ * Trim leading and trailing empty lines.
44
+ */
45
+ export function trimWhitespace(str: string): string {
46
+ const lines = str.split("\n");
47
+ let start = 0;
48
+ while (start < lines.length && lines[start]?.trim() === "") {
49
+ start++;
50
+ }
51
+ let end = lines.length - 1;
52
+ while (end >= 0 && lines[end]?.trim() === "") {
53
+ end--;
54
+ }
55
+ if (start > end) {
56
+ return "";
57
+ }
58
+ return lines.slice(start, end + 1).join("\n");
59
+ }
60
+
61
+ /**
62
+ * Calculates the maximum width of a multi-line ASCII art string.
63
+ * @param asciiArt The ASCII art string.
64
+ * @returns The length of the longest line in the ASCII art.
65
+ */
66
+ export const getAsciiArtWidth = (asciiArt: string): number => {
67
+ if (!asciiArt) {
68
+ return 0;
69
+ }
70
+ const lines = asciiArt.split("\n");
71
+ return Math.max(...lines.map((line) => line.length));
72
+ };
73
+
74
+ /*
75
+ * -------------------------------------------------------------------------
76
+ * Unicode‑aware helpers (work at the code‑point level rather than UTF‑16
77
+ * code units so that surrogate‑pair emoji count as one "column".)
78
+ * ---------------------------------------------------------------------- */
79
+
80
+ // Cache for code points to reduce GC pressure
81
+ const codePointsCache = new Map<string, string[]>();
82
+ const MAX_STRING_LENGTH_TO_CACHE = 1000;
83
+
84
+ export function toCodePoints(str: string): string[] {
85
+ // ASCII fast path - check if all chars are ASCII (0-127)
86
+ let isAscii = true;
87
+ for (let i = 0; i < str.length; i++) {
88
+ if (str.charCodeAt(i) > 127) {
89
+ isAscii = false;
90
+ break;
91
+ }
92
+ }
93
+ if (isAscii) {
94
+ return str.split("");
95
+ }
96
+
97
+ // Cache short strings
98
+ if (str.length <= MAX_STRING_LENGTH_TO_CACHE) {
99
+ const cached = codePointsCache.get(str);
100
+ if (cached) {
101
+ return cached;
102
+ }
103
+ }
104
+
105
+ const result = Array.from(str);
106
+
107
+ // Cache result (unlimited like Ink)
108
+ if (str.length <= MAX_STRING_LENGTH_TO_CACHE) {
109
+ codePointsCache.set(str, result);
110
+ }
111
+
112
+ return result;
113
+ }
114
+
115
+ export function cpLen(str: string): number {
116
+ return toCodePoints(str).length;
117
+ }
118
+
119
+ export function cpSlice(str: string, start: number, end?: number): string {
120
+ // Slice by code‑point indices and re‑join.
121
+ const arr = toCodePoints(str).slice(start, end);
122
+ return arr.join("");
123
+ }
124
+
125
+ /**
126
+ * Strip characters that can break terminal rendering.
127
+ *
128
+ * Uses Node.js built-in stripVTControlCharacters to handle VT sequences,
129
+ * then filters remaining control characters that can disrupt display.
130
+ *
131
+ * Characters stripped:
132
+ * - ANSI escape sequences (via strip-ansi)
133
+ * - VT control sequences (via Node.js util.stripVTControlCharacters)
134
+ * - C0 control chars (0x00-0x1F) except CR/LF which are handled elsewhere
135
+ * - C1 control chars (0x80-0x9F) that can cause display issues
136
+ *
137
+ * Characters preserved:
138
+ * - All printable Unicode including emojis
139
+ * - DEL (0x7F) - handled functionally by applyOperations, not a display issue
140
+ * - CR/LF (0x0D/0x0A) - needed for line breaks
141
+ * - TAB (0x09) - preserve tabs
142
+ */
143
+ export function stripUnsafeCharacters(str: string): string {
144
+ const strippedAnsi = stripAnsi(str);
145
+ const strippedVT = stripVTControlCharacters(strippedAnsi);
146
+
147
+ return toCodePoints(strippedVT)
148
+ .filter((char) => {
149
+ const code = char.codePointAt(0);
150
+ if (code === undefined) {
151
+ return false;
152
+ }
153
+
154
+ // Preserve CR/LF/TAB for line handling
155
+ if (code === 0x0a || code === 0x0d || code === 0x09) {
156
+ return true;
157
+ }
158
+
159
+ // Remove C0 control chars (except CR/LF) that can break display
160
+ // Examples: BELL(0x07) makes noise, BS(0x08) moves cursor, VT(0x0B), FF(0x0C)
161
+ if (code >= 0x00 && code <= 0x1f) {
162
+ return false;
163
+ }
164
+
165
+ // Remove C1 control chars (0x80-0x9f) - legacy 8-bit control codes
166
+ if (code >= 0x80 && code <= 0x9f) {
167
+ return false;
168
+ }
169
+
170
+ // Preserve DEL (0x7f) - it's handled functionally by applyOperations as backspace
171
+ // and doesn't cause rendering issues when displayed
172
+
173
+ // Preserve all other characters including Unicode/emojis
174
+ return true;
175
+ })
176
+ .join("");
177
+ }
178
+
179
+ // String width caching for performance optimization
180
+ const stringWidthCache = new Map<string, number>();
181
+
182
+ /**
183
+ * Cached version of stringWidth function for better performance
184
+ * Follows Ink's approach with unlimited cache (no eviction)
185
+ */
186
+ export const getCachedStringWidth = (str: string): number => {
187
+ // ASCII printable chars have width 1
188
+ if (/^[\u0020-\u007E]*$/.test(str)) {
189
+ return str.length;
190
+ }
191
+
192
+ if (stringWidthCache.has(str)) {
193
+ return stringWidthCache.get(str)!;
194
+ }
195
+
196
+ const width = stringWidth(str);
197
+ stringWidthCache.set(str, width);
198
+
199
+ return width;
200
+ };
201
+
202
+ /**
203
+ * Clear the string width cache
204
+ */
205
+ export const clearStringWidthCache = (): void => {
206
+ stringWidthCache.clear();
207
+ };
208
+
209
+ const regex = ansiRegex();
210
+
211
+ /* Recursively traverses a JSON-like structure (objects, arrays, primitives)
212
+ * and escapes all ANSI control characters found in any string values.
213
+ *
214
+ * This function is designed to be robust, handling deeply nested objects and
215
+ * arrays. It applies a regex-based replacement to all string values to
216
+ * safely escape control characters.
217
+ *
218
+ * To optimize performance, this function uses a "copy-on-write" strategy.
219
+ * It avoids allocating new objects or arrays if no nested string values
220
+ * required escaping, returning the original object reference in such cases.
221
+ *
222
+ * @param obj The JSON-like value (object, array, string, etc.) to traverse.
223
+ * @returns A new value with all nested string fields escaped, or the
224
+ * original `obj` reference if no changes were necessary.
225
+ */
226
+ export function escapeAnsiCtrlCodes<T>(obj: T): T {
227
+ if (typeof obj === "string") {
228
+ if (obj.search(regex) === -1) {
229
+ return obj; // No changes return original string
230
+ }
231
+
232
+ regex.lastIndex = 0; // needed for global regex
233
+ return obj.replace(regex, (match) => JSON.stringify(match).slice(1, -1)) as T;
234
+ }
235
+
236
+ if (obj === null || typeof obj !== "object") {
237
+ return obj;
238
+ }
239
+
240
+ if (Array.isArray(obj)) {
241
+ let newArr: unknown[] | null = null;
242
+
243
+ for (let i = 0; i < obj.length; i++) {
244
+ const value = obj[i];
245
+ const escapedValue = escapeAnsiCtrlCodes(value);
246
+ if (escapedValue !== value) {
247
+ if (newArr === null) {
248
+ newArr = [...obj];
249
+ }
250
+ newArr[i] = escapedValue;
251
+ }
252
+ }
253
+ return (newArr !== null ? newArr : obj) as T;
254
+ }
255
+
256
+ let newObj: T | null = null;
257
+ const keys = Object.keys(obj);
258
+
259
+ for (const key of keys) {
260
+ const value = (obj as Record<string, unknown>)[key];
261
+ const escapedValue = escapeAnsiCtrlCodes(value);
262
+
263
+ if (escapedValue !== value) {
264
+ if (newObj === null) {
265
+ newObj = { ...obj };
266
+ }
267
+ (newObj as Record<string, unknown>)[key] = escapedValue;
268
+ }
269
+ }
270
+
271
+ return newObj !== null ? newObj : obj;
272
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Shared timing utilities for performance tracking
3
+ */
4
+
5
+ export class Timing {
6
+ private startTime: number;
7
+
8
+ constructor() {
9
+ this.startTime = performance.now();
10
+ }
11
+
12
+ /**
13
+ * Get elapsed time in seconds
14
+ */
15
+ getElapsedSeconds(): number {
16
+ return (performance.now() - this.startTime) / 1000;
17
+ }
18
+
19
+ /**
20
+ * Get elapsed time in milliseconds
21
+ */
22
+ getElapsedMs(): number {
23
+ return performance.now() - this.startTime;
24
+ }
25
+
26
+ /**
27
+ * Format duration for display in human-readable format (hours, minutes, seconds)
28
+ */
29
+ getFormattedDuration(): string {
30
+ return formatDurationMs(this.getElapsedMs());
31
+ }
32
+
33
+ /**
34
+ * Reset the timer
35
+ */
36
+ reset(): void {
37
+ this.startTime = performance.now();
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Format a duration in milliseconds to human-readable format
43
+ * Standalone utility version of Timing.getFormattedDuration()
44
+ */
45
+ export function formatDurationMs(ms: number): string {
46
+ const totalSeconds = Math.floor(ms / 1000);
47
+ const hours = Math.floor(totalSeconds / 3600);
48
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
49
+ const seconds = totalSeconds % 60;
50
+
51
+ const parts: string[] = [];
52
+ if (hours > 0) {
53
+ parts.push(`${hours}h`);
54
+ }
55
+ if (minutes > 0) {
56
+ parts.push(`${minutes}m`);
57
+ }
58
+ if (seconds > 0 || parts.length === 0) {
59
+ parts.push(`${seconds}s`);
60
+ }
61
+
62
+ return parts.join(" ");
63
+ }
@@ -0,0 +1,295 @@
1
+ import type { ParsedToolResult, ToolDisplayData } from "@codellm/agent";
2
+ import type {
3
+ ComputerToolUseRequest,
4
+ IResponseChatHistory,
5
+ IResponseChatHistorySegment,
6
+ ToolUseResult,
7
+ } from "@codellm/api";
8
+
9
+ export enum Status {
10
+ Idle = "idle",
11
+ Loading = "loading",
12
+ Error = "error",
13
+ }
14
+
15
+ export type SegmentStatus = "transient" | "completed";
16
+
17
+ export interface HistorySegment extends Omit<IResponseChatHistorySegment, "type"> {
18
+ id: string; // Unique segment ID
19
+ status: SegmentStatus; // transient or completed
20
+ source: "user" | "bot"; // Who generated this segment
21
+ /** SDK-native types + all API types */
22
+ type?:
23
+ | IResponseChatHistorySegment["type"]
24
+ | "thinking" // Active/completed thinking block (SDK-native)
25
+ | "code_llm_tool_call"; // Tool call + result (SDK-native)
26
+ // client only fields
27
+ toolUseResult?: ToolUseResult;
28
+ parsedResult?: ParsedToolResult;
29
+ thinkingStartTime?: number;
30
+ thinkingEndTime?: number;
31
+ toolDisplayData?: ToolDisplayData;
32
+ /** Incremented to trigger a flash/attention effect on duplicate notifications */
33
+ flashKey?: number;
34
+ }
35
+
36
+ export interface HistoryItem extends Omit<IResponseChatHistory, "segments"> {
37
+ id?: string;
38
+ processingStartTime?: number;
39
+ segments?: HistorySegment[];
40
+ isToolResult?: boolean;
41
+ selectionContext?: {
42
+ file: string;
43
+ startLine: number;
44
+ endLine: number;
45
+ };
46
+ }
47
+
48
+ export interface FileAttachment {
49
+ doc_id?: string;
50
+ filename?: string;
51
+ filePath: string;
52
+ fileName: string;
53
+ content: string | Buffer;
54
+ mimeType: string;
55
+ mime_type?: string;
56
+ size?: number;
57
+ metadata?: Record<string, unknown>;
58
+ }
59
+
60
+ // Copied from src/vs/workbench/contrib/abacus/common/fileSystemTools.ts
61
+
62
+ export type UndoableAction = {
63
+ filePath: string;
64
+ } & (
65
+ | {
66
+ type: "mkdir" | "create";
67
+ }
68
+ | {
69
+ type: "str_replace";
70
+ oldStr: string;
71
+ newStr: string;
72
+ previousNewStrings: number;
73
+ }
74
+ | {
75
+ type: "insert";
76
+ insertLine: number;
77
+ newStr: string;
78
+ }
79
+ );
80
+
81
+ export type GrepResult = {
82
+ /** The matching file paths with context */
83
+ matches: string[];
84
+ /** Metadata about the search results */
85
+ metadata: {
86
+ /** Starting offset position */
87
+ offset: number;
88
+ /** Number of results requested */
89
+ limit: number;
90
+ /** Total number of matches found across all files */
91
+ totalMatches: number;
92
+ /** Whether there are more results available */
93
+ hasMore: boolean;
94
+ };
95
+ };
96
+
97
+ export type GrepCommonOpts = {
98
+ /** The maximum number of results to return. @default MAX_RESULTS_GREP */
99
+ maxResults?: number;
100
+ /** The glob patterns for folders to exclude. @default [] */
101
+ folderExcludes?: string[];
102
+ /** The glob patterns for folders to include. @default [] */
103
+ folderIncludes?: string[];
104
+ /** Starting offset position for pagination. @default 0 */
105
+ offset?: number;
106
+ };
107
+
108
+ export type GrepOpts = GrepCommonOpts & {
109
+ /** The regex pattern to search for */
110
+ query: string;
111
+ /** Whether to use case sensitive search. @default false */
112
+ isCaseSensitive?: boolean;
113
+ /** The maximum file size to search in characters. @default MAX_FILE_SIZE */
114
+ maxFileSize?: number;
115
+ /** File or directory to search in (relative to workspace). @default workspace root */
116
+ path?: string;
117
+ /** Output mode: "content" shows matching lines, "files_with_matches" shows file paths, "count" shows match counts. @default "files_with_matches" */
118
+ outputMode?: "content" | "files_with_matches" | "count";
119
+ /** Number of lines to show before each match (ripgrep -B) */
120
+ contextBefore?: number;
121
+ /** Number of lines to show after each match (ripgrep -A) */
122
+ contextAfter?: number;
123
+ /** Number of lines to show before and after each match (ripgrep -C) */
124
+ contextLines?: number;
125
+ /** Show line numbers in output (ripgrep -n). @default true for content mode */
126
+ showLineNumbers?: boolean;
127
+ /** File type to search (ripgrep --type). Common types: js, py, rust, go, java, etc. */
128
+ fileType?: string;
129
+ /** Limit output to first N lines/entries. @default based on maxResults */
130
+ headLimit?: number;
131
+ /** Enable multiline mode where . matches newlines (ripgrep -U --multiline-dotall). @default false */
132
+ multiline?: boolean;
133
+ };
134
+
135
+ export type GrepFileOpts = GrepCommonOpts & {
136
+ /** The glob or fuzzy pattern to search for */
137
+ query: string;
138
+ /** Whether to use fuzzy match instead of glob (* patterns will not work) @default false */
139
+ fuzzyMatch?: boolean;
140
+ /** The cache key to use for the search (e.g. time based cache). @default undefined */
141
+ cacheKey?: string;
142
+ };
143
+
144
+ export interface FileSystemTools {
145
+ ls: (path: string, showHidden?: boolean) => Promise<string>;
146
+ mkdir: (path: string) => Promise<UndoableAction>;
147
+ view: (path: string, viewRange?: [number, number]) => Promise<string>;
148
+ readRawFile: (path: string) => Promise<string>;
149
+ create: (path: string, content: string) => Promise<UndoableAction>;
150
+ readFile: (
151
+ path: string,
152
+ startLineOneIndexed: number,
153
+ endLineOneIndexed: number,
154
+ shouldReadEntireFile: boolean,
155
+ ) => Promise<string>;
156
+ deleteFile: (path: string) => Promise<void>;
157
+ listDir: (path: string) => Promise<string>;
158
+ strReplace: (
159
+ path: string,
160
+ oldStr: string,
161
+ newStr: string,
162
+ similarityThreshold?: number,
163
+ startLine?: number,
164
+ endLine?: number,
165
+ ) => Promise<UndoableAction>;
166
+ grep(params: GrepOpts): Promise<GrepResult>;
167
+ grepFile: (params: GrepFileOpts) => Promise<string[]>;
168
+ grepTextFile: (params: GrepFileOpts) => Promise<string[]>;
169
+ }
170
+
171
+ // Note: AbacusEditingService removed - TUI uses direct file operations for better simplicity and reliability
172
+
173
+ export type ICommandResult = {
174
+ id: number;
175
+ command: string;
176
+ output: string;
177
+ exitCode?: number;
178
+ cwd?: string;
179
+ duration?: number;
180
+ };
181
+
182
+ export interface ICommand {
183
+ running?: Promise<void>;
184
+ result: {
185
+ get(): ICommandResult;
186
+ };
187
+ }
188
+
189
+ export interface ITerminalService {
190
+ executeCommand(
191
+ command: string,
192
+ options?: {
193
+ terminalId?: number;
194
+ revealTerminal?: boolean;
195
+ alwaysCreateNew?: boolean;
196
+ interactiveMode?: boolean;
197
+ },
198
+ ): Promise<ICommand>;
199
+ getOutput(id: number): ICommandResult[];
200
+ stopCommand(id: number): Promise<boolean>;
201
+ closeTerminal?(id: number): Promise<void>;
202
+ dispose(): void;
203
+ }
204
+
205
+ // MCP Configuration Types
206
+ export interface IMcpConfiguration {
207
+ inputs?: unknown[];
208
+ servers?: Record<string, IMcpServerConfig>;
209
+ }
210
+
211
+ export type IMcpServerConfig = IMcpStdioServerConfig | IMcpHttpServerConfig;
212
+
213
+ export interface IMcpStdioServerConfig {
214
+ type?: "stdio";
215
+ command: string;
216
+ args?: readonly string[];
217
+ env?: Record<string, string | number | null>;
218
+ envFile?: string;
219
+ cwd?: string;
220
+ }
221
+
222
+ export interface IMcpHttpServerConfig {
223
+ type?: "http";
224
+ url: string;
225
+ headers?: Record<string, string>;
226
+ }
227
+
228
+ export interface IMcpServerInfo {
229
+ id: string;
230
+ name: string;
231
+ config: IMcpServerConfig;
232
+ status?: "running" | "stopped" | "error";
233
+ description?: string;
234
+ }
235
+
236
+ // Minimal interface for MCP management in TUI
237
+ export interface IMcpManagementService {
238
+ listServers(): Promise<IMcpServerInfo[]>;
239
+ addServer(name: string, config: IMcpServerConfig): Promise<void>;
240
+ removeServer(name: string): Promise<void>;
241
+ getServer(name: string): Promise<IMcpServerInfo | undefined>;
242
+ addServerFromJson(name: string, jsonConfig: string): Promise<void>;
243
+ addServerFromUrl(name: string, url: string, args?: string[]): Promise<void>;
244
+ formatServerInfo(server: IMcpServerInfo): string;
245
+ formatServerList(servers: IMcpServerInfo[]): string;
246
+ }
247
+
248
+ // Interface for MCP service with tool execution capabilities (like GUI's AbacusMcpService)
249
+ export interface ITuiMcpService {
250
+ listAllToolDefinitions(): Promise<AbacusMcpTool[]>;
251
+ callTool(serverId: string, toolName: string, params: Record<string, unknown>): Promise<any>;
252
+ reloadConnections(): Promise<void>;
253
+ getServerStatus(serverId: string): "running" | "stopped" | "error";
254
+ }
255
+
256
+ // MCP tool type for TUI (matching API client's AbacusMcpTool)
257
+ export interface AbacusMcpTool {
258
+ name: string;
259
+ description?: string;
260
+ inputSchema: any;
261
+ serverId: string;
262
+ originalName?: string;
263
+ }
264
+ export interface ElementAction {
265
+ action: "click" | "type" | "hover" | "scroll" | "focus" | "select";
266
+ text?: string;
267
+ scroll_direction?: "up" | "down" | "left" | "right";
268
+ scroll_amount?: number;
269
+ held_keys?: string;
270
+ }
271
+
272
+ export type EncodedId = `${number}-${number}`;
273
+
274
+ export interface IAbacusBrowserService {
275
+ navigateTo(url?: string): Promise<void>;
276
+ openTab(url?: string): Promise<number>;
277
+ switchActiveTab(id: number): Promise<void>;
278
+ closeTab(id: number): Promise<void>;
279
+ refreshTab(id?: number): Promise<void>;
280
+ getTabContent(id?: number): Promise<{
281
+ text: string;
282
+ metadata: { offset: number; limit: number; totalLength: number; hasMore: boolean };
283
+ }>;
284
+ do(
285
+ input: AbacusBrowserIO,
286
+ ): Promise<{ content: { text?: string; images?: { base64: string; mime_type: string }[] } }>;
287
+ getInteractiveElements(tabId?: number): Promise<string>;
288
+ doOnElementId(
289
+ id: EncodedId,
290
+ action: ElementAction,
291
+ tabId?: number,
292
+ ): Promise<{ content: { text?: string; images?: { base64: string; mime_type: string }[] } }>;
293
+ }
294
+
295
+ export type AbacusBrowserIO = ComputerToolUseRequest["input"];