@thegitai/cli 1.0.0-preview.4 → 1.0.0-preview.41

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 (68) hide show
  1. package/README.md +49 -4
  2. package/dist/bin/ai.js +119 -111
  3. package/dist/bin/browser-host.js +265 -0
  4. package/dist/src/agent-mode.js +11 -6
  5. package/dist/src/api/auth.js +6 -4
  6. package/dist/src/api/browser-login.js +152 -37
  7. package/dist/src/api/chat.js +286 -44
  8. package/dist/src/api/contracts.js +55 -1
  9. package/dist/src/api/default-host.js +1 -0
  10. package/dist/src/api/http.js +127 -7
  11. package/dist/src/api/models.js +29 -10
  12. package/dist/src/background-jobs.js +4 -2
  13. package/dist/src/browser/bridge.js +232 -0
  14. package/dist/src/browser/framing.js +42 -0
  15. package/dist/src/browser/native-host.js +209 -0
  16. package/dist/src/browser/protocol.js +46 -0
  17. package/dist/src/browser/session-bridge.js +104 -0
  18. package/dist/src/cli-args.js +19 -5
  19. package/dist/src/client-environment.js +2 -0
  20. package/dist/src/core/clipboard.js +7 -13
  21. package/dist/src/core/image-limits.js +56 -0
  22. package/dist/src/core/image-path-extractor.js +70 -3
  23. package/dist/src/core/session-image-store.js +246 -0
  24. package/dist/src/edit-journal.js +33 -2
  25. package/dist/src/executor.js +25 -3
  26. package/dist/src/help-text.js +58 -17
  27. package/dist/src/patcher.js +43 -4
  28. package/dist/src/permissions.js +288 -0
  29. package/dist/src/session-safety.js +0 -12
  30. package/dist/src/session-store.js +121 -20
  31. package/dist/src/session.js +19 -4
  32. package/dist/src/signin.js +58 -0
  33. package/dist/src/tool-executor.js +12 -46
  34. package/dist/src/tools/browser.js +628 -0
  35. package/dist/src/tools/delete-file.js +20 -4
  36. package/dist/src/tools/index.js +32 -10
  37. package/dist/src/tools/patch-file.js +12 -26
  38. package/dist/src/tools/read-image-file.js +85 -0
  39. package/dist/src/tools/replace-document-text.js +28 -18
  40. package/dist/src/tools/restore-checkpoint.js +0 -1
  41. package/dist/src/tools/run-command.js +14 -71
  42. package/dist/src/tools/run-node-script.js +12 -81
  43. package/dist/src/tools/save-generated-image.js +120 -0
  44. package/dist/src/tools/str-replace.js +12 -26
  45. package/dist/src/tools/undo-edit.js +1 -6
  46. package/dist/src/tools/write-file.js +67 -11
  47. package/dist/src/ui/prompt-history-store.js +1 -1
  48. package/dist/src/ui/repl.js +802 -195
  49. package/dist/src/ui/tui/bridge.js +10 -0
  50. package/dist/src/ui/tui/build-frame.js +517 -165
  51. package/dist/src/ui/tui/composer-layout.js +76 -0
  52. package/dist/src/ui/tui/markdown-render.js +81 -73
  53. package/dist/src/ui/tui/shell-input.js +227 -67
  54. package/dist/src/ui/tui/terminal-theme.js +28 -0
  55. package/dist/src/ui/tui/terminal-title.js +3 -0
  56. package/dist/src/ui/tui/terminal-writes.js +48 -0
  57. package/dist/src/ui/tui/text.js +158 -4
  58. package/dist/src/ui/tui/user-input.js +568 -0
  59. package/dist/src/utils.js +9 -0
  60. package/package.json +20 -8
  61. package/dist/src/markdown-renderer.js +0 -112
  62. package/dist/src/project-index.js +0 -221
  63. package/dist/src/tools/code-intel.js +0 -472
  64. package/dist/src/tools/find-symbol.js +0 -70
  65. package/dist/src/tools/hover-symbol.js +0 -95
  66. package/dist/src/tools/list-symbols.js +0 -55
  67. package/dist/src/tools/search-code.js +0 -37
  68. package/dist/src/tools/signature-help.js +0 -118
@@ -0,0 +1,76 @@
1
+ export const COMPOSER_PROMPT_LABEL_IDLE = '❯ ';
2
+ export const COMPOSER_PROMPT_LABEL_BUSY = 'queue> ';
3
+ export function composerPromptLabel(state) {
4
+ return state.busy ? COMPOSER_PROMPT_LABEL_BUSY : COMPOSER_PROMPT_LABEL_IDLE;
5
+ }
6
+ export function frameContentWidth(cols) {
7
+ return Math.max(20, Math.floor(cols * 0.95) - 2);
8
+ }
9
+ export function composerInputWidth(cols, state) {
10
+ return Math.max(1, frameContentWidth(cols) - composerPromptLabel(state).length);
11
+ }
12
+ function buildComposerLayout(input, cursor, width) {
13
+ const safeWidth = Math.max(1, width);
14
+ const normalizedCursor = Math.min(Math.max(cursor, 0), input.length);
15
+ const rows = [{ cursorOffsets: [0], text: '' }];
16
+ let cursorRow = 0;
17
+ let cursorCol = 0;
18
+ let capturedCursor = false;
19
+ const currentRow = () => rows[rows.length - 1];
20
+ const addRow = (offset) => {
21
+ rows.push({ cursorOffsets: [offset], text: '' });
22
+ };
23
+ const captureCursor = () => {
24
+ if (capturedCursor)
25
+ return;
26
+ if (currentRow().text.length >= safeWidth) {
27
+ addRow(normalizedCursor);
28
+ }
29
+ cursorRow = rows.length - 1;
30
+ cursorCol = currentRow().text.length;
31
+ capturedCursor = true;
32
+ };
33
+ for (let index = 0; index < input.length; index += 1) {
34
+ const char = input[index] ?? '';
35
+ if (char !== '\n' &&
36
+ char !== '\r' &&
37
+ currentRow().text.length >= safeWidth) {
38
+ addRow(index);
39
+ }
40
+ if (index === normalizedCursor) {
41
+ captureCursor();
42
+ }
43
+ if (char === '\r') {
44
+ currentRow().cursorOffsets[currentRow().text.length] = index + 1;
45
+ continue;
46
+ }
47
+ if (char === '\n') {
48
+ currentRow().cursorOffsets[currentRow().text.length] =
49
+ input[index - 1] === '\r' ? index - 1 : index;
50
+ addRow(index + 1);
51
+ continue;
52
+ }
53
+ currentRow().text += char;
54
+ currentRow().cursorOffsets.push(index + 1);
55
+ }
56
+ if (!capturedCursor) {
57
+ captureCursor();
58
+ }
59
+ return { cursorCol, cursorRow, rows };
60
+ }
61
+ export function splitComposerInput(input, cursor, width) {
62
+ const layout = buildComposerLayout(input, cursor, width);
63
+ return {
64
+ cursorCol: layout.cursorCol,
65
+ cursorRow: layout.cursorRow,
66
+ rows: layout.rows.map((row) => row.text),
67
+ };
68
+ }
69
+ export function moveComposerCursorVertically(input, cursor, width, direction) {
70
+ const layout = buildComposerLayout(input, cursor, width);
71
+ const targetRow = layout.rows[layout.cursorRow + direction];
72
+ if (!targetRow)
73
+ return null;
74
+ const targetCol = Math.min(layout.cursorCol, targetRow.text.length);
75
+ return targetRow.cursorOffsets[targetCol] ?? null;
76
+ }
@@ -1,6 +1,8 @@
1
- import { line, plainLine, span, wrapText } from './text.js';
2
- const TABLE_BORDER_WIDTH = 2;
3
- const TABLE_CELL_OVERHEAD_WIDTH = 3;
1
+ import { displayWidth, line, padToWidth, plainLine, sliceToWidth, span, wrapText, wrapToWidth, } from './text.js';
2
+ const MIN_TABLE_COLUMN_WIDTH = 3;
3
+ function tableRowOverhead(columnCount) {
4
+ return 3 * columnCount + 1;
5
+ }
4
6
  function parseInlineSegments(text) {
5
7
  const source = String(text ?? '');
6
8
  const segments = [];
@@ -110,45 +112,37 @@ function stripInlineFormattingForWidth(text) {
110
112
  .replace(/\*\*([^*]+)\*\*/g, '$1');
111
113
  }
112
114
  function fitTableColumnWidths(columnWidths, maxWidth) {
113
- const widths = columnWidths.map((width) => Math.max(3, Math.floor(width)));
114
- if (widths.length === 0)
115
- return widths;
116
- const overhead = TABLE_BORDER_WIDTH + widths.length * TABLE_CELL_OVERHEAD_WIDTH;
117
- const budget = Math.max(widths.length * 3, maxWidth - overhead);
118
- const total = widths.reduce((sum, width) => sum + width, 0);
119
- if (total <= budget)
120
- return widths;
121
- let remaining = budget;
122
- const scaled = widths.map((width) => {
123
- const next = Math.max(3, Math.floor((width / total) * budget));
124
- remaining -= next;
125
- return next;
126
- });
127
- while (remaining > 0) {
128
- let targetIndex = 0;
129
- for (let index = 1; index < widths.length; index++) {
130
- if (widths[index] - scaled[index] > widths[targetIndex] - scaled[targetIndex]) {
131
- targetIndex = index;
132
- }
133
- }
134
- scaled[targetIndex]++;
135
- remaining--;
115
+ const columnCount = columnWidths.length;
116
+ if (columnCount === 0)
117
+ return [];
118
+ const natural = columnWidths.map((width) => Math.max(1, Math.floor(width)));
119
+ const budget = Math.floor(maxWidth) - tableRowOverhead(columnCount);
120
+ const total = natural.reduce((sum, width) => sum + width, 0);
121
+ if (budget >= total)
122
+ return natural;
123
+ const floorWidth = Math.max(1, Math.min(MIN_TABLE_COLUMN_WIDTH, Math.floor(budget / columnCount)));
124
+ const widths = natural.map((width) => Math.min(width, floorWidth));
125
+ let used = widths.reduce((sum, width) => sum + width, 0);
126
+ if (used > budget) {
127
+ const share = Math.max(1, Math.floor(budget / columnCount));
128
+ for (let index = 0; index < columnCount; index++)
129
+ widths[index] = share;
130
+ used = share * columnCount;
136
131
  }
137
- while (remaining < 0) {
138
- let targetIndex = -1;
139
- for (let index = 0; index < scaled.length; index++) {
140
- if (scaled[index] <= 3)
132
+ let remaining = budget - used;
133
+ while (remaining > 0) {
134
+ let grew = false;
135
+ for (let index = 0; index < columnCount && remaining > 0; index++) {
136
+ if (widths[index] >= natural[index])
141
137
  continue;
142
- if (targetIndex === -1 || scaled[index] > scaled[targetIndex]) {
143
- targetIndex = index;
144
- }
138
+ widths[index]++;
139
+ remaining--;
140
+ grew = true;
145
141
  }
146
- if (targetIndex === -1)
142
+ if (!grew)
147
143
  break;
148
- scaled[targetIndex]--;
149
- remaining++;
150
144
  }
151
- return scaled;
145
+ return widths;
152
146
  }
153
147
  function normalizeMarkdownTableCells(cells, columnCount) {
154
148
  return Array.from({ length: columnCount }, (_, index) => cells[index] ?? '');
@@ -176,7 +170,7 @@ function parseMarkdownTableBlock(lines, startIndex) {
176
170
  const normalizedHeaders = normalizeMarkdownTableCells(headers, columnCount);
177
171
  const columnWidths = normalizedHeaders.map((header, columnIndex) => {
178
172
  const values = [header, ...rows.map((row) => row[columnIndex] ?? '')];
179
- return Math.max(3, ...values.map((value) => stripInlineFormattingForWidth(value).length));
173
+ return Math.max(MIN_TABLE_COLUMN_WIDTH, ...values.map((value) => displayWidth(stripInlineFormattingForWidth(value))));
180
174
  });
181
175
  return {
182
176
  nextIndex,
@@ -280,24 +274,42 @@ function wrapInlineToLines(text, width, bodyColor, prefix = '') {
280
274
  rowWidth = 0;
281
275
  };
282
276
  const appendSpan = (part) => {
283
- const current = rows[rows.length - 1];
284
- const limit = safeWidth - (indent ? prefix.length : 0);
277
+ let current = rows[rows.length - 1];
285
278
  let remaining = part.text;
286
279
  while (remaining.length > 0) {
280
+ const limit = safeWidth - (rows.length === 1 && indent ? displayWidth(prefix) : 0);
287
281
  const room = limit - rowWidth;
288
282
  if (room <= 0) {
289
283
  startRow();
284
+ current = rows[rows.length - 1];
290
285
  continue;
291
286
  }
292
- if (remaining.length <= room) {
287
+ const width = displayWidth(remaining);
288
+ if (width <= room) {
293
289
  current.push(span(remaining, styleFromSpan(part)));
294
- rowWidth += remaining.length;
290
+ rowWidth += width;
295
291
  remaining = '';
296
292
  break;
297
293
  }
298
- current.push(span(remaining.slice(0, room), styleFromSpan(part)));
299
- remaining = remaining.slice(room);
294
+ if (rowWidth > 0) {
295
+ if (/^\s+$/.test(remaining)) {
296
+ remaining = '';
297
+ break;
298
+ }
299
+ startRow();
300
+ current = rows[rows.length - 1];
301
+ continue;
302
+ }
303
+ const head = sliceToWidth(remaining, room);
304
+ if (!head || (displayWidth(head) > room && current.length > 0)) {
305
+ startRow();
306
+ current = rows[rows.length - 1];
307
+ continue;
308
+ }
309
+ current.push(span(head, styleFromSpan(part)));
310
+ remaining = remaining.slice(head.length);
300
311
  startRow();
312
+ current = rows[rows.length - 1];
301
313
  }
302
314
  };
303
315
  for (const segment of segments) {
@@ -316,36 +328,35 @@ function wrapInlineToLines(text, width, bodyColor, prefix = '') {
316
328
  return line(...bodySpans);
317
329
  });
318
330
  }
319
- function padCell(text, width) {
320
- const plain = stripInlineFormattingForWidth(text);
321
- if (plain.length >= width)
322
- return plain.slice(0, width);
323
- return plain + ' '.repeat(width - plain.length);
324
- }
325
331
  function renderTableLines(table, width) {
326
332
  const columnWidths = fitTableColumnWidths(table.columnWidths, width);
333
+ const border = (left, joint, right) => line(span(left +
334
+ columnWidths.map((colWidth) => '─'.repeat(colWidth + 2)).join(joint) +
335
+ right, { color: 'cyan', dim: true }));
327
336
  const renderRow = (cells, bold) => {
328
- const parts = columnWidths.map((colWidth, index) => span(` ${padCell(cells[index] ?? '', colWidth)} `, {
329
- color: 'cyan',
330
- bold,
331
- }));
332
- return line(span('│', { color: 'cyan' }), ...parts, span('│', { color: 'cyan' }));
337
+ const wrapped = columnWidths.map((colWidth, index) => wrapToWidth(stripInlineFormattingForWidth(cells[index] ?? ''), colWidth));
338
+ const height = Math.max(1, ...wrapped.map((cellLines) => cellLines.length));
339
+ const rows = [];
340
+ for (let row = 0; row < height; row++) {
341
+ const spans = [span('│', { color: 'cyan' })];
342
+ for (let column = 0; column < columnWidths.length; column++) {
343
+ const text = wrapped[column]?.[row] ?? '';
344
+ spans.push(span(` ${padToWidth(text, columnWidths[column])} `, {
345
+ color: 'cyan',
346
+ bold,
347
+ }));
348
+ spans.push(span('│', { color: 'cyan' }));
349
+ }
350
+ rows.push(line(...spans));
351
+ }
352
+ return rows;
333
353
  };
334
- const separator = line(span('├' +
335
- columnWidths.map((colWidth) => '─'.repeat(colWidth + 2)).join('┼') +
336
- '┤', { color: 'cyan', dim: true }));
337
- const top = line(span('┌' +
338
- columnWidths.map((colWidth) => '─'.repeat(colWidth + 2)).join('┬') +
339
- '┐', { color: 'cyan', dim: true }));
340
- const bottom = line(span('└' +
341
- columnWidths.map((colWidth) => '─'.repeat(colWidth + 2)).join('┴') +
342
- '┘', { color: 'cyan', dim: true }));
343
354
  return [
344
- top,
345
- renderRow(table.headers, true),
346
- separator,
347
- ...table.rows.map((row) => renderRow(row, false)),
348
- bottom,
355
+ border('┌', '┬', '┐'),
356
+ ...renderRow(table.headers, true),
357
+ border('├', '┼', '┤'),
358
+ ...table.rows.flatMap((row) => renderRow(row, false)),
359
+ border('└', '┴', '┘'),
349
360
  ];
350
361
  }
351
362
  function getEntryColor(kind) {
@@ -410,10 +421,7 @@ export function renderFormattedBodyLines(body, width, kind) {
410
421
  }
411
422
  if (formattedLine.kind === 'table' && formattedLine.table) {
412
423
  output.push(...renderTableLines(formattedLine.table, bodyWidth).map((tableLine) => {
413
- const spans = tableLine.spans.map((part) => ({
414
- ...part,
415
- text: ` ${part.text}`,
416
- }));
424
+ const spans = tableLine.spans.map((part, index) => index === 0 ? { ...part, text: ` ${part.text}` } : part);
417
425
  return { spans };
418
426
  }));
419
427
  continue;