@ebowwa/coder 0.7.64 → 0.7.65

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 (101) hide show
  1. package/dist/index.js +36168 -32
  2. package/dist/interfaces/ui/terminal/cli/index.js +34253 -158
  3. package/dist/interfaces/ui/terminal/native/README.md +53 -0
  4. package/dist/interfaces/ui/terminal/native/claude_code_native.darwin-x64.node +0 -0
  5. package/dist/interfaces/ui/terminal/native/claude_code_native.dylib +0 -0
  6. package/dist/interfaces/ui/terminal/native/index.d.ts +0 -0
  7. package/dist/interfaces/ui/terminal/native/index.darwin-arm64.node +0 -0
  8. package/dist/interfaces/ui/terminal/native/index.js +43 -0
  9. package/dist/interfaces/ui/terminal/native/index.node +0 -0
  10. package/dist/interfaces/ui/terminal/native/package.json +34 -0
  11. package/dist/native/README.md +53 -0
  12. package/dist/native/claude_code_native.darwin-x64.node +0 -0
  13. package/dist/native/claude_code_native.dylib +0 -0
  14. package/dist/native/index.d.ts +0 -480
  15. package/dist/native/index.darwin-arm64.node +0 -0
  16. package/dist/native/index.js +43 -1625
  17. package/dist/native/index.node +0 -0
  18. package/dist/native/package.json +34 -0
  19. package/native/index.darwin-arm64.node +0 -0
  20. package/native/index.js +33 -19
  21. package/package.json +3 -2
  22. package/packages/src/core/agent-loop/__tests__/compaction.test.ts +17 -14
  23. package/packages/src/core/agent-loop/compaction.ts +6 -2
  24. package/packages/src/core/agent-loop/index.ts +2 -0
  25. package/packages/src/core/agent-loop/loop-state.ts +1 -1
  26. package/packages/src/core/agent-loop/turn-executor.ts +4 -0
  27. package/packages/src/core/agent-loop/types.ts +4 -0
  28. package/packages/src/core/api-client-impl.ts +283 -173
  29. package/packages/src/core/cognitive-security/hooks.ts +2 -1
  30. package/packages/src/core/config/todo +7 -0
  31. package/packages/src/core/context/__tests__/integration.test.ts +334 -0
  32. package/packages/src/core/context/compaction.ts +170 -0
  33. package/packages/src/core/context/constants.ts +58 -0
  34. package/packages/src/core/context/extraction.ts +85 -0
  35. package/packages/src/core/context/index.ts +66 -0
  36. package/packages/src/core/context/summarization.ts +251 -0
  37. package/packages/src/core/context/token-estimation.ts +98 -0
  38. package/packages/src/core/context/types.ts +59 -0
  39. package/packages/src/core/models.ts +81 -4
  40. package/packages/src/core/normalizers/todo +5 -1
  41. package/packages/src/core/providers/README.md +230 -0
  42. package/packages/src/core/providers/__tests__/providers.test.ts +135 -0
  43. package/packages/src/core/providers/index.ts +419 -0
  44. package/packages/src/core/providers/types.ts +132 -0
  45. package/packages/src/core/retry.ts +10 -0
  46. package/packages/src/ecosystem/tools/index.ts +174 -0
  47. package/packages/src/index.ts +23 -2
  48. package/packages/src/interfaces/ui/index.ts +17 -20
  49. package/packages/src/interfaces/ui/spinner.ts +2 -2
  50. package/packages/src/interfaces/ui/terminal/bridge/index.ts +370 -0
  51. package/packages/src/interfaces/ui/terminal/bridge/ipc.ts +829 -0
  52. package/packages/src/interfaces/ui/terminal/bridge/screen-export.ts +968 -0
  53. package/packages/src/interfaces/ui/terminal/bridge/types.ts +226 -0
  54. package/packages/src/interfaces/ui/terminal/bridge/useBridge.ts +210 -0
  55. package/packages/src/interfaces/ui/terminal/cli/bootstrap.ts +132 -0
  56. package/packages/src/interfaces/ui/terminal/cli/index.ts +200 -13
  57. package/packages/src/interfaces/ui/terminal/cli/interactive/index.ts +110 -0
  58. package/packages/src/interfaces/ui/terminal/cli/interactive/input-handler.ts +393 -0
  59. package/packages/src/interfaces/ui/terminal/cli/interactive/interactive-runner.ts +820 -0
  60. package/packages/src/interfaces/ui/terminal/cli/interactive/message-store.ts +299 -0
  61. package/packages/src/interfaces/ui/terminal/cli/interactive/types.ts +274 -0
  62. package/packages/src/interfaces/ui/terminal/shared/index.ts +13 -0
  63. package/packages/src/interfaces/ui/terminal/shared/query.ts +9 -3
  64. package/packages/src/interfaces/ui/terminal/shared/setup.ts +5 -1
  65. package/packages/src/interfaces/ui/terminal/shared/spinner-frames.ts +73 -0
  66. package/packages/src/interfaces/ui/terminal/shared/status-line.ts +10 -2
  67. package/packages/src/native/index.ts +404 -27
  68. package/packages/src/native/tui_v2_types.ts +39 -0
  69. package/packages/src/teammates/coordination.test.ts +279 -0
  70. package/packages/src/teammates/coordination.ts +646 -0
  71. package/packages/src/teammates/index.ts +95 -25
  72. package/packages/src/teammates/integration.test.ts +272 -0
  73. package/packages/src/teammates/runner.test.ts +235 -0
  74. package/packages/src/teammates/runner.ts +750 -0
  75. package/packages/src/teammates/schemas.ts +673 -0
  76. package/packages/src/types/index.ts +1 -0
  77. package/packages/src/core/context-compaction.ts +0 -578
  78. package/packages/src/interfaces/ui/Screenshot 2026-03-02 at 9.23.10/342/200/257PM.png +0 -0
  79. package/packages/src/interfaces/ui/Screenshot 2026-03-03 at 10.55.11/342/200/257AM.png +0 -0
  80. package/packages/src/interfaces/ui/terminal/tui/HelpPanel.tsx +0 -262
  81. package/packages/src/interfaces/ui/terminal/tui/InputContext.tsx +0 -232
  82. package/packages/src/interfaces/ui/terminal/tui/InputField.tsx +0 -62
  83. package/packages/src/interfaces/ui/terminal/tui/InteractiveTUI.tsx +0 -537
  84. package/packages/src/interfaces/ui/terminal/tui/MessageArea.tsx +0 -107
  85. package/packages/src/interfaces/ui/terminal/tui/MessageStore.tsx +0 -240
  86. package/packages/src/interfaces/ui/terminal/tui/StatusBar.tsx +0 -54
  87. package/packages/src/interfaces/ui/terminal/tui/commands.ts +0 -438
  88. package/packages/src/interfaces/ui/terminal/tui/components/InteractiveElements.tsx +0 -584
  89. package/packages/src/interfaces/ui/terminal/tui/components/MultilineInput.tsx +0 -614
  90. package/packages/src/interfaces/ui/terminal/tui/components/PaneManager.tsx +0 -333
  91. package/packages/src/interfaces/ui/terminal/tui/components/Sidebar.tsx +0 -604
  92. package/packages/src/interfaces/ui/terminal/tui/components/index.ts +0 -118
  93. package/packages/src/interfaces/ui/terminal/tui/console.ts +0 -49
  94. package/packages/src/interfaces/ui/terminal/tui/index.ts +0 -90
  95. package/packages/src/interfaces/ui/terminal/tui/run.tsx +0 -42
  96. package/packages/src/interfaces/ui/terminal/tui/spinner.ts +0 -69
  97. package/packages/src/interfaces/ui/terminal/tui/tui-app.tsx +0 -390
  98. package/packages/src/interfaces/ui/terminal/tui/tui-footer.ts +0 -422
  99. package/packages/src/interfaces/ui/terminal/tui/types.ts +0 -186
  100. package/packages/src/interfaces/ui/terminal/tui/useInputHandler.ts +0 -104
  101. package/packages/src/interfaces/ui/terminal/tui/useNativeInput.ts +0 -239
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Spinner Frames
3
+ * Single source of truth for spinner animation frames
4
+ *
5
+ * These are pure data arrays with no dependencies, suitable for any UI context.
6
+ */
7
+
8
+ /**
9
+ * Default braille spinner frames
10
+ */
11
+ export const spinnerFrames: string[] = [
12
+ "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏",
13
+ ];
14
+
15
+ /**
16
+ * Dot-based spinner frames (works in more terminals)
17
+ */
18
+ export const dotSpinnerFrames: string[] = [
19
+ "⠁", "⠈", "⠐", "⠠", "⢀", "⣀", "⣄", "⣤", "⣦", "⣶", "⣷", "⣸", "⣼", "⣾", "⣽", "⣻",
20
+ ];
21
+
22
+ /**
23
+ * ASCII-only spinner frames (maximum compatibility)
24
+ */
25
+ export const asciiSpinnerFrames: string[] = [
26
+ "|", "/", "-", "\\",
27
+ ];
28
+
29
+ /**
30
+ * Arrow spinner frames
31
+ */
32
+ export const arrowSpinnerFrames: string[] = [
33
+ "→", "↘", "↓", "↙", "←", "↖", "↑", "↗",
34
+ ];
35
+
36
+ /**
37
+ * Simple dot progress frames
38
+ */
39
+ export const simpleDotFrames: string[] = [
40
+ ". ", ".. ", "...", " ..", " .", " ",
41
+ ];
42
+
43
+ /**
44
+ * Tool activity spinner frames
45
+ */
46
+ export const toolSpinnerFrames: string[] = [
47
+ "⚙ ", "⚙⚙", " ⚙",
48
+ ];
49
+
50
+ /**
51
+ * Get next frame in sequence
52
+ */
53
+ export function nextFrame(frames: string[], index: number): string {
54
+ return frames[index % frames.length] ?? frames[0] ?? "";
55
+ }
56
+
57
+ /**
58
+ * Create a spinner iterator
59
+ */
60
+ export function* createSpinnerIterator(frames: string[]): Generator<string, never, never> {
61
+ let index = 0;
62
+ while (true) {
63
+ yield frames[index % frames.length] ?? frames[0] ?? "";
64
+ index++;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Get frame at specific index
70
+ */
71
+ export function getFrame(frames: string[], index: number): string {
72
+ return frames[index % frames.length] ?? frames[0] ?? "";
73
+ }
@@ -203,6 +203,7 @@ export function renderStatusLine(options: StatusLineOptions): string {
203
203
  model,
204
204
  permissionMode,
205
205
  isLoading = false,
206
+ verbose = false,
206
207
  } = options;
207
208
 
208
209
  const contextInfo = calculateContextInfo(tokensUsed, model);
@@ -223,6 +224,11 @@ export function renderStatusLine(options: StatusLineOptions): string {
223
224
  // 2. Add permission mode
224
225
  parts.push(formatPermissionMode(permissionMode));
225
226
 
227
+ // 3. Add version in verbose mode
228
+ if (verbose) {
229
+ parts.push(`currentVersion: ${VERSION}`);
230
+ }
231
+
226
232
  // Join with comma-space
227
233
  let statusLine = parts.join(", ");
228
234
 
@@ -238,13 +244,15 @@ export function renderStatusLine(options: StatusLineOptions): string {
238
244
  * Render a compact status line for narrow displays
239
245
  */
240
246
  export function renderCompactStatusLine(options: StatusLineOptions): string {
241
- const { tokensUsed, model, isLoading = false } = options;
247
+ const { tokensUsed, model, permissionMode, isLoading = false } = options;
242
248
  const contextInfo = calculateContextInfo(tokensUsed, model);
249
+ const permDisplay = getPermissionModeDisplay(permissionMode);
243
250
 
244
- // Compact: "8% | 0 tokens"
251
+ // Compact: "8% | 5.0k | bypass"
245
252
  const parts = [
246
253
  formatContextPercent(contextInfo.percentRemaining),
247
254
  contextInfo.tokenDisplay.split(" ")[0] ?? "0", // Just the number
255
+ permDisplay.label.split(" ")[0] ?? permDisplay.label, // First word of label (e.g., "bypass" from "bypass permissions")
248
256
  ];
249
257
 
250
258
  let line = parts.join(chalk.dim(" | "));
@@ -70,9 +70,138 @@ export interface MultiEditPreviewEntry {
70
70
  replacementCount: number;
71
71
  }
72
72
 
73
+ // ===== Grep Types =====
74
+
75
+ /** Options for grep search operations */
76
+ export interface GrepQueryOptions {
77
+ /** Case insensitive matching */
78
+ caseInsensitive?: boolean;
79
+ /** Maximum number of results to return */
80
+ maxResults?: number;
81
+ /** Glob patterns for files to include */
82
+ includePatterns?: string[];
83
+ /** Glob patterns for files/dirs to exclude */
84
+ excludePatterns?: string[];
85
+ /** Treat pattern as literal string (no regex) */
86
+ literal?: boolean;
87
+ /** Match whole words only */
88
+ wholeWord?: boolean;
89
+ /** Invert match (show non-matching lines) */
90
+ invert?: boolean;
91
+ /** Enable multiline matching */
92
+ multiline?: boolean;
93
+ /** Number of context lines (sets both before and after) */
94
+ contextLines?: number;
95
+ /** Number of lines to show before match */
96
+ contextBefore?: number;
97
+ /** Number of lines to show after match */
98
+ contextAfter?: number;
99
+ /** Maximum directory depth */
100
+ maxDepth?: number;
101
+ /** Follow symbolic links */
102
+ followSymlinks?: boolean;
103
+ /** Skip hidden files and directories */
104
+ skipHidden?: boolean;
105
+ /** Respect .gitignore rules */
106
+ respectGitignore?: boolean;
107
+ /** Skip binary files */
108
+ skipBinary?: boolean;
109
+ /** Maximum file size in bytes */
110
+ maxFilesize?: number;
111
+ /** File extensions to include */
112
+ extensions?: string[];
113
+ }
114
+
115
+ /** A single grep match result */
116
+ export interface GrepMatch {
117
+ /** File path containing the match */
118
+ path: string;
119
+ /** 1-based line number */
120
+ line: number;
121
+ /** 1-based column number */
122
+ column: number;
123
+ /** The matched line content */
124
+ content: string;
125
+ /** Length of the match in bytes */
126
+ matchLength: number;
127
+ /** Lines before the match (for context) */
128
+ contextBefore: string[];
129
+ /** Lines after the match (for context) */
130
+ contextAfter: string[];
131
+ }
132
+
133
+ /** Result of a grep search operation */
134
+ export interface GrepResult {
135
+ /** Total number of matches found */
136
+ totalCount: number;
137
+ /** All matches found */
138
+ matches: GrepMatch[];
139
+ /** Number of files searched */
140
+ filesSearched: number;
141
+ /** Number of files skipped */
142
+ filesSkipped: number;
143
+ /** Time spent searching in milliseconds */
144
+ durationMs: number;
145
+ }
146
+
147
+ /** Count result per file */
148
+ export interface GrepCountResult {
149
+ /** File path */
150
+ path: string;
151
+ /** Number of matches in this file */
152
+ count: number;
153
+ /** Number of lines in this file */
154
+ lineCount: number;
155
+ }
156
+
157
+ /** Result for files-with-matches mode */
158
+ export interface GrepFilesResult {
159
+ /** List of file paths containing matches */
160
+ files: string[];
161
+ /** Total number of files with matches */
162
+ totalMatches: number;
163
+ }
164
+
73
165
  // ===== Quant Types =====
74
166
 
75
167
  /** OHLCV candlestick data */
168
+ // ===== TUI v2 Types =====
169
+
170
+ export interface RenderMessage {
171
+ role: string;
172
+ content: string;
173
+ }
174
+
175
+ export interface RenderState {
176
+ messages: RenderMessage[];
177
+ inputValue: string;
178
+ cursorPos: number;
179
+ statusText: string;
180
+ isLoading: boolean;
181
+ streamingText: string;
182
+ model: string;
183
+ showHelp: boolean;
184
+ helpText: string;
185
+ searchMode: boolean;
186
+ searchQuery: string;
187
+ searchResults: SearchResult[];
188
+ searchSelected: number;
189
+ }
190
+
191
+ export interface InputEvent {
192
+ eventType: "key" | "resize" | "none";
193
+ key?: string;
194
+ modifiers?: string;
195
+ newWidth?: number;
196
+ newHeight?: number;
197
+ }
198
+
199
+ export interface SearchResult {
200
+ filePath: string;
201
+ lineNumber: number;
202
+ content: string;
203
+ }
204
+
76
205
  export interface OHLCV {
77
206
  timestamp: number;
78
207
  open: number;
@@ -246,6 +375,9 @@ export interface NativeModule {
246
375
  /** Highlight a diff with ANSI colors */
247
376
  highlight_diff: (oldText: string, newText: string, options?: DiffOptions) => HighlightDiffResult;
248
377
 
378
+ // ===== Grep Functions =====
379
+
380
+ /** Legacy search_files (deprecated, use grep_search instead) */
249
381
  search_files: (
250
382
  pattern: string,
251
383
  path: string,
@@ -267,6 +399,17 @@ export interface NativeModule {
267
399
  files_searched: number;
268
400
  };
269
401
 
402
+ /** Search for pattern in files with full options */
403
+ grep_search: (pattern: string, path: string, options?: GrepQueryOptions) => Promise<GrepResult>;
404
+
405
+ /** Count matches per file */
406
+ grep_count: (pattern: string, path: string, options?: GrepQueryOptions) => Promise<GrepCountResult[]>;
407
+
408
+ /** List files containing matches */
409
+ grep_files: (pattern: string, path: string, options?: GrepQueryOptions) => Promise<GrepFilesResult>;
410
+
411
+ // ===== Token Functions =====
412
+
270
413
  count_tokens: (text: string) => number;
271
414
 
272
415
  calculate_diff: (
@@ -539,6 +682,11 @@ export function loadNative(): NativeModule {
539
682
  preview_multi_edits: native.previewMultiEdits || native.preview_multi_edits,
540
683
  apply_multi_edits: native.applyMultiEdits || native.apply_multi_edits,
541
684
 
685
+ // Grep functions (NAPI exports camelCase for async functions)
686
+ grep_search: native.grepSearch,
687
+ grep_count: native.grepCount,
688
+ grep_files: native.grepFiles,
689
+
542
690
  // Cognitive Security - Action Module (NAPI exports camelCase)
543
691
  classify_operation: native.classifyOperation,
544
692
  get_action_types: native.getActionTypes,
@@ -985,9 +1133,42 @@ function getFallbackModule(): NativeModule {
985
1133
  };
986
1134
  },
987
1135
 
988
- count_tokens: (text) => {
989
- // Approximate token count: ~4 chars per token
990
- return Math.ceil(text.length / 4);
1136
+ count_tokens: (text: string): number => {
1137
+ // Sophisticated fallback matching Rust implementation
1138
+ if (!text || text.length === 0) return 0;
1139
+
1140
+ const bytes = text.length;
1141
+
1142
+ // Count whitespace
1143
+ const whitespace = text.split('').filter(c => /\s/.test(c)).length;
1144
+
1145
+ // Count punctuation
1146
+ const punctuation = text.split('').filter(c => /[^\w\s]/.test(c)).length;
1147
+
1148
+ // Detect if this is code (heuristic)
1149
+ const isCode = text.includes('{') ||
1150
+ text.includes('}') ||
1151
+ text.includes(';') ||
1152
+ text.includes('fn ') ||
1153
+ text.includes('function') ||
1154
+ text.includes('const ') ||
1155
+ text.includes('let ') ||
1156
+ text.includes('import ');
1157
+
1158
+ // Base estimate: ~4 characters per token
1159
+ const baseEstimate = Math.floor(bytes / 4);
1160
+
1161
+ // Adjust for code (typically more tokens)
1162
+ const codeAdjustment = isCode ? Math.floor(bytes / 10) : 0;
1163
+
1164
+ // Adjust for whitespace (tokens are often split on whitespace)
1165
+ const whitespaceAdjustment = Math.floor(whitespace * 0.2);
1166
+
1167
+ // Adjust for punctuation
1168
+ const punctuationAdjustment = Math.floor(punctuation * 0.5);
1169
+
1170
+ // Combine estimates, minimum 1
1171
+ return Math.max(1, baseEstimate + codeAdjustment + whitespaceAdjustment + punctuationAdjustment);
991
1172
  },
992
1173
 
993
1174
  calculate_diff: (oldText, newText) => {
@@ -1025,34 +1206,102 @@ function getFallbackModule(): NativeModule {
1025
1206
  }
1026
1207
 
1027
1208
  switch (strategy) {
1028
- case "truncate":
1209
+ case "truncate": {
1210
+ // Smart truncation: find paragraph or sentence boundary
1211
+ const half = Math.floor(maxChars / 2);
1212
+ const searchStart = Math.max(0, half - 100);
1213
+ const searchEnd = Math.min(content.length, half + 100);
1214
+
1215
+ // Try to find paragraph break
1216
+ let breakPoint = half;
1217
+ for (let i = searchEnd; i >= searchStart; i--) {
1218
+ if (content[i] === '\n' && content[i + 1] === '\n') {
1219
+ breakPoint = i;
1220
+ break;
1221
+ }
1222
+ }
1223
+
1029
1224
  return (
1030
- content.slice(0, maxChars / 2) +
1031
- "\n\n... [truncated] ...\n\n" +
1032
- content.slice(-maxChars / 2)
1225
+ content.slice(0, breakPoint) +
1226
+ "\n\n... [content truncated: " + Math.floor((content.length - maxChars) / charsPerToken) + " tokens omitted] ...\n\n" +
1227
+ content.slice(-half)
1033
1228
  );
1034
- case "summarize":
1035
- // Return first and last sections
1229
+ }
1230
+ case "summarize": {
1231
+ // Return beginning, key sections, and end
1232
+ const lines = content.split("\n");
1233
+ const firstCount = Math.min(20, Math.floor(lines.length / 4));
1234
+ const lastCount = Math.min(20, Math.floor(lines.length / 4));
1036
1235
  const quarter = Math.floor(maxChars / 4);
1037
- return (
1038
- "=== BEGINNING ===\n" +
1039
- content.slice(0, quarter) +
1040
- "\n\n=== END ===\n" +
1041
- content.slice(-quarter)
1042
- );
1043
- case "extract":
1044
- // Return important lines only
1236
+
1237
+ let result = "=== BEGINNING ===\n";
1238
+ result += lines.slice(0, firstCount).join("\n").slice(0, quarter) + "\n\n";
1239
+
1240
+ result += "=== KEY LINES ===\n";
1241
+ const importantLines = lines.filter(line => {
1242
+ const t = line.trim();
1243
+ return t.startsWith('#') ||
1244
+ t.startsWith('function') ||
1245
+ t.startsWith('const') ||
1246
+ t.startsWith('class') ||
1247
+ t.startsWith('export') ||
1248
+ t.startsWith('import') ||
1249
+ t.startsWith('async ') ||
1250
+ t.startsWith('pub fn') ||
1251
+ t.startsWith('fn ');
1252
+ });
1253
+ result += importantLines.slice(0, 20).join("\n").slice(0, quarter) + "\n\n";
1254
+
1255
+ result += "=== END ===\n";
1256
+ result += lines.slice(-lastCount).join("\n").slice(0, quarter);
1257
+
1258
+ return result;
1259
+ }
1260
+ case "extract": {
1261
+ // Extract structure (headings, declarations, etc.)
1045
1262
  const lines = content.split("\n");
1046
- const important = lines.filter(
1047
- (line) =>
1048
- line.trim().startsWith("#") ||
1049
- line.trim().startsWith("function") ||
1050
- line.trim().startsWith("const") ||
1051
- line.trim().startsWith("class") ||
1052
- line.trim().startsWith("export") ||
1053
- line.trim().startsWith("import")
1054
- );
1055
- return important.slice(0, maxChars / 50).join("\n");
1263
+ const result: string[] = [];
1264
+ let currentChars = 0;
1265
+ let inCodeBlock = false;
1266
+
1267
+ for (const line of lines) {
1268
+ // Track code blocks
1269
+ if (line.trim().startsWith('```')) {
1270
+ inCodeBlock = !inCodeBlock;
1271
+ }
1272
+
1273
+ const trimmed = line.trim();
1274
+
1275
+ // Include important lines
1276
+ const isImportant =
1277
+ trimmed.startsWith('#') ||
1278
+ trimmed.startsWith('##') ||
1279
+ trimmed.startsWith('###') ||
1280
+ trimmed.startsWith('function ') ||
1281
+ trimmed.startsWith('const ') ||
1282
+ trimmed.startsWith('let ') ||
1283
+ trimmed.startsWith('class ') ||
1284
+ trimmed.startsWith('interface ') ||
1285
+ trimmed.startsWith('type ') ||
1286
+ trimmed.startsWith('export ') ||
1287
+ trimmed.startsWith('import ') ||
1288
+ trimmed.startsWith('async ') ||
1289
+ trimmed.startsWith('pub fn') ||
1290
+ trimmed.startsWith('fn ') ||
1291
+ trimmed.startsWith('struct ') ||
1292
+ trimmed.startsWith('impl ') ||
1293
+ trimmed.startsWith('// TODO') ||
1294
+ trimmed.startsWith('// FIXME') ||
1295
+ trimmed.startsWith('// NOTE');
1296
+
1297
+ if (isImportant && currentChars + line.length < maxChars) {
1298
+ result.push(line);
1299
+ currentChars += line.length + 1;
1300
+ }
1301
+ }
1302
+
1303
+ return result.length > 0 ? result.join("\n") : content.slice(0, maxChars);
1304
+ }
1056
1305
  default:
1057
1306
  return content.slice(0, maxChars);
1058
1307
  }
@@ -1862,6 +2111,17 @@ function getFallbackModule(): NativeModule {
1862
2111
  throw new Error("Native TUI not available in fallback mode. Build the Rust native module.");
1863
2112
  },
1864
2113
  is_native_tui_available: () => false,
2114
+
2115
+ // Grep functions (fallback - use Grep tool instead)
2116
+ grep_search: async (_pattern: string, _path: string, _options?: GrepQueryOptions): Promise<GrepResult> => {
2117
+ throw new Error("grep_search not available in fallback mode. Build the Rust native module or use the Grep tool.");
2118
+ },
2119
+ grep_count: async (_pattern: string, _path: string, _options?: GrepQueryOptions): Promise<GrepCountResult[]> => {
2120
+ throw new Error("grep_count not available in fallback mode. Build the Rust native module or use the Grep tool.");
2121
+ },
2122
+ grep_files: async (_pattern: string, _path: string, _options?: GrepQueryOptions): Promise<GrepFilesResult> => {
2123
+ throw new Error("grep_files not available in fallback mode. Build the Rust native module or use the Grep tool.");
2124
+ },
1865
2125
  };
1866
2126
  }
1867
2127
 
@@ -2343,3 +2603,120 @@ export function quantCorrelation(x: number[], y: number[]): number {
2343
2603
  return quant.quant_correlation(ptrX, ptrY, x.length);
2344
2604
  }
2345
2605
 
2606
+
2607
+
2608
+
2609
+ // ===== TUI v2 Native Renderer Re-export =====
2610
+ // Re-export the NativeRenderer class directly from the native module (the .node binary)
2611
+ // Use lazy loading to avoid circular dependency issues during module initialization
2612
+
2613
+ import * as path from "path";
2614
+
2615
+ let _NativeRendererClass: any = null;
2616
+
2617
+ /**
2618
+ * Find the native module directory at runtime
2619
+ * Works both in development and when bundled
2620
+ */
2621
+ function findNativeDir(): string | null {
2622
+ // Try multiple possible locations for the native directory
2623
+ const possiblePaths = [
2624
+ // When running from dist/ (bundled)
2625
+ path.resolve(__dirname, "..", "..", "..", "native"),
2626
+ // When running from dist/native/ (native loader location)
2627
+ path.resolve(__dirname, "..", "..", "native"),
2628
+ // When running from packages/src/native/ (development)
2629
+ path.resolve(__dirname, "..", "..", "..", "..", "..", "native"),
2630
+ // Try from current working directory
2631
+ path.resolve(process.cwd(), "native"),
2632
+ // Try from dist directory relative to executable
2633
+ path.resolve(path.dirname(process.execPath), "..", "native"),
2634
+ ];
2635
+
2636
+ for (const p of possiblePaths) {
2637
+ try {
2638
+ const files = require("fs").readdirSync(p);
2639
+ if (files.some((f: string) => f.endsWith(".node"))) {
2640
+ return p;
2641
+ }
2642
+ } catch {
2643
+ // Directory doesn't exist or can't be read, try next
2644
+ }
2645
+ }
2646
+
2647
+ return null;
2648
+ }
2649
+
2650
+ /**
2651
+ * Get the NativeRenderer class (lazy-loaded to avoid circular dependencies)
2652
+ */
2653
+ function getNativeRendererClass(): any {
2654
+ if (!_NativeRendererClass) {
2655
+ const { platform, arch } = process;
2656
+
2657
+ // Find the native directory
2658
+ const nativeDir = findNativeDir();
2659
+ if (!nativeDir) {
2660
+ throw new Error("Could not find native module directory");
2661
+ }
2662
+
2663
+ // Determine the correct native module file based on platform
2664
+ let nativeFile: string;
2665
+ if (platform === "darwin" && arch === "arm64") {
2666
+ nativeFile = "index.darwin-arm64.node";
2667
+ } else if (platform === "darwin" && arch === "x64") {
2668
+ nativeFile = "claude_code_native.darwin-x64.node";
2669
+ } else if (platform === "linux" && arch === "x64") {
2670
+ nativeFile = "claude_code_native.linux-x64-gnu.node";
2671
+ } else if (platform === "win32" && arch === "x64") {
2672
+ nativeFile = "claude_code_native.win32-x64-msvc.node";
2673
+ } else {
2674
+ throw new Error(`Unsupported platform: ${platform}-${arch}`);
2675
+ }
2676
+
2677
+ const nativePath = path.join(nativeDir, nativeFile);
2678
+ const nativeModule = require(nativePath);
2679
+ _NativeRendererClass = nativeModule.NativeRenderer;
2680
+ }
2681
+ return _NativeRendererClass;
2682
+ }
2683
+
2684
+ /**
2685
+ * Lazy-loading wrapper for the NativeRenderer class
2686
+ * This allows us to defer loading the native module until it's actually needed
2687
+ */
2688
+ export const NativeRenderer = new Proxy(
2689
+ // Use a function as the target so it can be called with `new`
2690
+ function NativeRenderer(this: any, ...args: any[]) {
2691
+ const ActualClass = getNativeRendererClass();
2692
+ const instance = new ActualClass(...args);
2693
+ // Copy instance to `this` if called with new
2694
+ Object.assign(this, instance);
2695
+ return instance;
2696
+ } as any,
2697
+ {
2698
+ // Proxy property access to the actual class
2699
+ get(_target, prop) {
2700
+ const ActualClass = getNativeRendererClass();
2701
+ return Reflect.get(ActualClass, prop, ActualClass);
2702
+ },
2703
+ // Proxy static method calls
2704
+ apply(_target, _thisArg, args) {
2705
+ const ActualClass = getNativeRendererClass();
2706
+ return ActualClass(...args);
2707
+ },
2708
+ }
2709
+ ) as any;
2710
+
2711
+ // Type alias for the NativeRenderer instance type
2712
+ // Use typeof since NativeRenderer is a value (Proxy)
2713
+ export type NativeRendererType = typeof NativeRenderer;
2714
+
2715
+ // Note: RenderState, RenderMessage, InputEvent, SearchResult types are already
2716
+ // defined as interfaces in this file (lines 78-107).
2717
+ // Type aliases for the v2 TUI using the already-defined types
2718
+ export type NativeRenderState = RenderState;
2719
+ export type NativeRenderMessage = RenderMessage;
2720
+ export type NativeInputEvent = InputEvent;
2721
+ export type NativeSearchResult = SearchResult;
2722
+
@@ -0,0 +1,39 @@
1
+ /**
2
+ * TUI v2 Type definitions
3
+ * Re-exported from the native module for TypeScript consumption
4
+ */
5
+
6
+ export interface RenderMessage {
7
+ role: string;
8
+ content: string;
9
+ }
10
+
11
+ export interface RenderState {
12
+ messages: RenderMessage[];
13
+ input_value: string;
14
+ cursor_pos: number;
15
+ status_text: string;
16
+ is_loading: boolean;
17
+ streaming_text: string;
18
+ model: string;
19
+ show_help: boolean;
20
+ help_text: string;
21
+ search_mode: boolean;
22
+ search_query: string;
23
+ search_results: SearchResult[];
24
+ search_selected: number;
25
+ }
26
+
27
+ export interface InputEvent {
28
+ event_type: "key" | "resize" | "none";
29
+ key?: string;
30
+ modifiers?: string;
31
+ new_width?: number;
32
+ new_height?: number;
33
+ }
34
+
35
+ export interface SearchResult {
36
+ file_path: string;
37
+ line_number: number;
38
+ content: string;
39
+ }