@thegitai/cli 1.0.0-beta.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 (101) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +30 -0
  3. package/dist/bin/ai.js +438 -0
  4. package/dist/parsers/tree-sitter-c-sharp.wasm +0 -0
  5. package/dist/parsers/tree-sitter-c.wasm +0 -0
  6. package/dist/parsers/tree-sitter-cpp.wasm +0 -0
  7. package/dist/parsers/tree-sitter-css.wasm +0 -0
  8. package/dist/parsers/tree-sitter-go.wasm +0 -0
  9. package/dist/parsers/tree-sitter-html.wasm +0 -0
  10. package/dist/parsers/tree-sitter-java.wasm +0 -0
  11. package/dist/parsers/tree-sitter-javascript.wasm +0 -0
  12. package/dist/parsers/tree-sitter-objc.wasm +0 -0
  13. package/dist/parsers/tree-sitter-php.wasm +0 -0
  14. package/dist/parsers/tree-sitter-python.wasm +0 -0
  15. package/dist/parsers/tree-sitter-ruby.wasm +0 -0
  16. package/dist/parsers/tree-sitter-rust.wasm +0 -0
  17. package/dist/parsers/tree-sitter-tsx.wasm +0 -0
  18. package/dist/parsers/tree-sitter-typescript.wasm +0 -0
  19. package/dist/src/agent-mode.js +142 -0
  20. package/dist/src/api/auth.js +81 -0
  21. package/dist/src/api/browser-login.js +184 -0
  22. package/dist/src/api/chat.js +346 -0
  23. package/dist/src/api/contracts.js +1 -0
  24. package/dist/src/api/http.js +44 -0
  25. package/dist/src/api/index.js +11 -0
  26. package/dist/src/api/models.js +110 -0
  27. package/dist/src/api/sessions.js +72 -0
  28. package/dist/src/artifact-policy.js +207 -0
  29. package/dist/src/client-state.js +14 -0
  30. package/dist/src/core/clipboard.js +208 -0
  31. package/dist/src/core/open-url.js +32 -0
  32. package/dist/src/edit-journal.js +133 -0
  33. package/dist/src/executor.js +924 -0
  34. package/dist/src/extractors/cpp.js +18 -0
  35. package/dist/src/extractors/csharp.js +16 -0
  36. package/dist/src/extractors/css.js +12 -0
  37. package/dist/src/extractors/go.js +27 -0
  38. package/dist/src/extractors/index.js +52 -0
  39. package/dist/src/extractors/java.js +14 -0
  40. package/dist/src/extractors/javascript.js +33 -0
  41. package/dist/src/extractors/objc.js +14 -0
  42. package/dist/src/extractors/php.js +20 -0
  43. package/dist/src/extractors/python.js +11 -0
  44. package/dist/src/extractors/ruby.js +13 -0
  45. package/dist/src/extractors/rust.js +17 -0
  46. package/dist/src/extractors/utils.js +58 -0
  47. package/dist/src/help-text.js +125 -0
  48. package/dist/src/markdown-renderer.js +112 -0
  49. package/dist/src/patcher.js +279 -0
  50. package/dist/src/project-index.js +221 -0
  51. package/dist/src/repo-map-languages.js +100 -0
  52. package/dist/src/runtime-mode.js +35 -0
  53. package/dist/src/scanner.js +362 -0
  54. package/dist/src/secret-preview.js +137 -0
  55. package/dist/src/session-exit.js +17 -0
  56. package/dist/src/session-safety.js +1012 -0
  57. package/dist/src/session-store.js +266 -0
  58. package/dist/src/session.js +93 -0
  59. package/dist/src/tool-executor.js +188 -0
  60. package/dist/src/tools/code-intel.js +472 -0
  61. package/dist/src/tools/delete-file.js +27 -0
  62. package/dist/src/tools/exec-utils.js +17 -0
  63. package/dist/src/tools/find-symbol.js +70 -0
  64. package/dist/src/tools/get-diagnostics.js +22 -0
  65. package/dist/src/tools/grep-code.js +331 -0
  66. package/dist/src/tools/hover-symbol.js +95 -0
  67. package/dist/src/tools/index.js +73 -0
  68. package/dist/src/tools/list-checkpoints.js +11 -0
  69. package/dist/src/tools/list-directories.js +16 -0
  70. package/dist/src/tools/list-files.js +13 -0
  71. package/dist/src/tools/list-session-edits.js +9 -0
  72. package/dist/src/tools/list-symbols.js +55 -0
  73. package/dist/src/tools/patch-file.js +88 -0
  74. package/dist/src/tools/path-listing.js +83 -0
  75. package/dist/src/tools/read-document.js +111 -0
  76. package/dist/src/tools/read-file.js +109 -0
  77. package/dist/src/tools/restore-checkpoint.js +100 -0
  78. package/dist/src/tools/ripgrep.js +29 -0
  79. package/dist/src/tools/run-command.js +94 -0
  80. package/dist/src/tools/run-node-script.js +210 -0
  81. package/dist/src/tools/search-code.js +37 -0
  82. package/dist/src/tools/shell-diagnostics.js +707 -0
  83. package/dist/src/tools/signature-help.js +118 -0
  84. package/dist/src/tools/str-replace.js +193 -0
  85. package/dist/src/tools/types.js +1 -0
  86. package/dist/src/tools/undo-edit.js +202 -0
  87. package/dist/src/tools/write-file.js +59 -0
  88. package/dist/src/tree-sitter-runtime.js +135 -0
  89. package/dist/src/types.js +1 -0
  90. package/dist/src/ui/paste-collapse.js +22 -0
  91. package/dist/src/ui/prompt-history-store.js +96 -0
  92. package/dist/src/ui/repl.js +2238 -0
  93. package/dist/src/ui/tui/bridge.js +175 -0
  94. package/dist/src/ui/tui/build-frame.js +718 -0
  95. package/dist/src/ui/tui/markdown-render.js +455 -0
  96. package/dist/src/ui/tui/shell-input.js +488 -0
  97. package/dist/src/ui/tui/text.js +30 -0
  98. package/dist/src/ui/tui/types.js +1 -0
  99. package/dist/src/usage.js +47 -0
  100. package/dist/src/utils.js +38 -0
  101. package/package.json +38 -0
@@ -0,0 +1,488 @@
1
+ import { readClipboardImage, readClipboardText } from '../../core/clipboard.js';
2
+ import { applySlashCommandSuggestion, buildModelPickerOptions, deleteAtCursor, deleteBeforeCursor, getApprovalChoiceForCursor, getInputCommandToken, getNextApprovalCursor, getNextModelPickerIndex, getSlashCommandSuggestions, insertAtCursor, isExactSlashCommandToken, navigatePromptHistory, resolveApprovalChoiceFromInput, shouldRemountLiveFrameForComposerInputChange, } from '../repl.js';
3
+ import { buildPastePlaceholder, shouldCollapsePaste, } from '../paste-collapse.js';
4
+ function shouldShowCommandPalette(state) {
5
+ if (state.busy ||
6
+ state.exiting ||
7
+ state.modelPickerOpen ||
8
+ state.resumePickerOpen) {
9
+ return false;
10
+ }
11
+ const token = getInputCommandToken(state.input);
12
+ return token.startsWith('/') && !token.includes(' ');
13
+ }
14
+ function insertPastedText(store, handlers, text) {
15
+ const current = store.getState();
16
+ if (current.exiting || !text)
17
+ return;
18
+ if (shouldCollapsePaste(text)) {
19
+ store.update((state) => {
20
+ const id = state.pastedChunks.length + 1;
21
+ const placeholder = buildPastePlaceholder(text, id);
22
+ const chunk = { placeholder, text };
23
+ const nextInput = insertAtCursor(state, placeholder);
24
+ if (shouldRemountLiveFrameForComposerInputChange(state, nextInput.input)) {
25
+ handlers.onLiveFrameShapeChange();
26
+ }
27
+ return {
28
+ ...state,
29
+ commandCursor: 0,
30
+ pastedChunks: [...state.pastedChunks, chunk],
31
+ ...nextInput,
32
+ };
33
+ });
34
+ return;
35
+ }
36
+ store.update((state) => {
37
+ const nextInput = insertAtCursor(state, text);
38
+ if (shouldRemountLiveFrameForComposerInputChange(state, nextInput.input)) {
39
+ handlers.onLiveFrameShapeChange();
40
+ }
41
+ return {
42
+ ...state,
43
+ commandCursor: 0,
44
+ ...nextInput,
45
+ };
46
+ });
47
+ }
48
+ function pasteTextFromClipboard(store, handlers) {
49
+ const current = store.getState();
50
+ if (current.exiting ||
51
+ current.approvalPrompt ||
52
+ current.modelPickerOpen ||
53
+ current.resumePickerOpen ||
54
+ current.sudoPrompt) {
55
+ return;
56
+ }
57
+ const text = (handlers.readClipboardText ?? readClipboardText)();
58
+ if (!text) {
59
+ store.update((state) => ({
60
+ ...state,
61
+ status: 'Clipboard has no text to paste',
62
+ }));
63
+ return;
64
+ }
65
+ insertPastedText(store, handlers, text);
66
+ }
67
+ function filterResumeSessionsLocal(sessions, filter, serverModels) {
68
+ const q = filter.trim().toLowerCase();
69
+ if (!q)
70
+ return sessions;
71
+ return sessions.filter((session) => {
72
+ const branch = (session.branch ?? '').toLowerCase();
73
+ const model = serverModels.find((entry) => entry.id === session.modelId)?.label.toLowerCase() ??
74
+ '';
75
+ const conv = session.lastUserMessage.toLowerCase();
76
+ return branch.includes(q) || model.includes(q) || conv.includes(q);
77
+ });
78
+ }
79
+ export function handleShellKeyEvent(store, handlers, event) {
80
+ if (event.kind === 'paste') {
81
+ insertPastedText(store, handlers, event.text);
82
+ return;
83
+ }
84
+ if (event.kind === 'selectionCopy') {
85
+ if (event.text.trim()) {
86
+ void handlers.onSelectionCopy?.(event.text);
87
+ }
88
+ return;
89
+ }
90
+ if (event.kind === 'linkCopy') {
91
+ if (event.url.trim()) {
92
+ void handlers.onLinkCopy?.(event.url);
93
+ }
94
+ return;
95
+ }
96
+ if (event.kind === 'linkOpen') {
97
+ if (event.url.trim()) {
98
+ void handlers.onLinkOpen?.(event.url);
99
+ }
100
+ return;
101
+ }
102
+ if (event.kind === 'contextMenu') {
103
+ pasteTextFromClipboard(store, handlers);
104
+ return;
105
+ }
106
+ if (event.kind === 'transcriptScroll') {
107
+ const delta = Math.trunc(event.deltaLines);
108
+ if (delta !== 0) {
109
+ store.update((current) => {
110
+ const limit = handlers.getTranscriptScrollLimit?.() ??
111
+ current.transcript.reduce((total, entry) => total + 2 + (entry.body ? entry.body.split('\n').length : 0), 0);
112
+ const next = current.transcriptScrollOffset + delta;
113
+ return {
114
+ ...current,
115
+ transcriptScrollOffset: Math.max(0, Math.min(next, limit)),
116
+ };
117
+ });
118
+ }
119
+ return;
120
+ }
121
+ if (event.kind !== 'key')
122
+ return;
123
+ const key = event;
124
+ const state = store.getState();
125
+ if (key.ctrl && key.input === 'c') {
126
+ if (state.sudoPrompt) {
127
+ handlers.onSudoPasswordInput({ kind: 'cancel' });
128
+ return;
129
+ }
130
+ if (handlers.onCtrlC) {
131
+ handlers.onCtrlC();
132
+ return;
133
+ }
134
+ handlers.onRequestExit();
135
+ return;
136
+ }
137
+ const commandPaletteActive = shouldShowCommandPalette(state);
138
+ const commandSuggestions = commandPaletteActive
139
+ ? getSlashCommandSuggestions(state.input)
140
+ : [];
141
+ const prepareForComposerInputChange = (current, nextInput) => {
142
+ if (shouldRemountLiveFrameForComposerInputChange(current, nextInput)) {
143
+ handlers.onLiveFrameShapeChange();
144
+ }
145
+ };
146
+ if (state.sudoPrompt) {
147
+ if (key.escape) {
148
+ handlers.onSudoPasswordInput({ kind: 'cancel' });
149
+ return;
150
+ }
151
+ if (key.returnKey) {
152
+ handlers.onSudoPasswordInput({ kind: 'submit' });
153
+ return;
154
+ }
155
+ if (key.backspace || key.delete) {
156
+ handlers.onSudoPasswordInput({ kind: 'backspace' });
157
+ return;
158
+ }
159
+ if (!key.ctrl &&
160
+ !key.meta &&
161
+ key.input &&
162
+ key.input.length === 1 &&
163
+ key.input >= ' ') {
164
+ handlers.onSudoPasswordInput({ kind: 'char', char: key.input });
165
+ }
166
+ return;
167
+ }
168
+ if (state.approvalPrompt) {
169
+ const directChoice = resolveApprovalChoiceFromInput(key.input);
170
+ if (directChoice) {
171
+ void handlers.onResolveApproval(directChoice);
172
+ return;
173
+ }
174
+ if (key.escape) {
175
+ void handlers.onResolveApproval('n');
176
+ return;
177
+ }
178
+ if (key.upArrow || key.downArrow) {
179
+ store.update((current) => ({
180
+ ...current,
181
+ approvalCursor: getNextApprovalCursor(current.approvalCursor, key.upArrow ? -1 : 1),
182
+ }));
183
+ return;
184
+ }
185
+ if (key.returnKey) {
186
+ void handlers.onResolveApproval(getApprovalChoiceForCursor(state.approvalCursor));
187
+ }
188
+ return;
189
+ }
190
+ if (state.exiting) {
191
+ return;
192
+ }
193
+ if (key.pageUp || key.pageDown) {
194
+ store.update((current) => {
195
+ const transcriptLines = handlers.getTranscriptScrollLimit?.() ??
196
+ current.transcript.reduce((total, entry) => total + 2 + (entry.body ? entry.body.split('\n').length : 0), 0);
197
+ const next = current.transcriptScrollOffset + (key.pageUp ? 8 : -8);
198
+ return {
199
+ ...current,
200
+ transcriptScrollOffset: Math.max(0, Math.min(next, transcriptLines)),
201
+ };
202
+ });
203
+ return;
204
+ }
205
+ if (state.modelPickerOpen) {
206
+ if (key.escape) {
207
+ store.update((current) => ({ ...current, modelPickerOpen: false }));
208
+ return;
209
+ }
210
+ if (key.upArrow || key.downArrow) {
211
+ store.update((current) => {
212
+ const options = buildModelPickerOptions(Number(current.currentModelId ?? 0), current.serverModels ?? []);
213
+ return {
214
+ ...current,
215
+ modelPickerIndex: getNextModelPickerIndex(options, current.modelPickerIndex, key.upArrow ? -1 : 1),
216
+ };
217
+ });
218
+ return;
219
+ }
220
+ if (key.returnKey) {
221
+ void handlers.onSelectModel();
222
+ }
223
+ return;
224
+ }
225
+ if (state.resumePickerOpen) {
226
+ if (key.escape) {
227
+ store.update((current) => ({
228
+ ...current,
229
+ resumePickerFilter: '',
230
+ resumePickerIndex: 0,
231
+ resumePickerOpen: false,
232
+ status: 'Ready',
233
+ }));
234
+ return;
235
+ }
236
+ if (key.upArrow || key.downArrow) {
237
+ store.update((current) => {
238
+ const count = filterResumeSessionsLocal(current.resumePickerSessions, current.resumePickerFilter, current.serverModels ?? []).length;
239
+ const next = current.resumePickerIndex + (key.upArrow ? -1 : 1);
240
+ return {
241
+ ...current,
242
+ resumePickerIndex: Math.max(0, Math.min(next, count - 1)),
243
+ };
244
+ });
245
+ return;
246
+ }
247
+ if (key.returnKey) {
248
+ void handlers.onResumeSession();
249
+ return;
250
+ }
251
+ if (key.backspace || key.delete) {
252
+ store.update((current) => ({
253
+ ...current,
254
+ resumePickerFilter: current.resumePickerFilter.slice(0, -1),
255
+ resumePickerIndex: 0,
256
+ }));
257
+ return;
258
+ }
259
+ if (!key.ctrl && !key.meta && key.input && key.input.length === 1 && key.input >= ' ') {
260
+ store.update((current) => ({
261
+ ...current,
262
+ resumePickerFilter: current.resumePickerFilter + key.input,
263
+ resumePickerIndex: 0,
264
+ }));
265
+ }
266
+ return;
267
+ }
268
+ if (key.escape) {
269
+ if (state.busy) {
270
+ // With a queued message, Esc clears the queue only (turn keeps running).
271
+ // With nothing queued, Esc cancels the turn (Ctrl+C also cancels).
272
+ if (state.queuedMessage) {
273
+ handlers.onLiveFrameShapeChange();
274
+ store.update((current) => ({ ...current, queuedMessage: null }));
275
+ return;
276
+ }
277
+ handlers.onCancelTurn();
278
+ return;
279
+ }
280
+ store.update((current) => {
281
+ if (!current.input &&
282
+ current.cursor === 0 &&
283
+ current.pastedChunks.length === 0 &&
284
+ current.promptHistoryCursor === null) {
285
+ return current;
286
+ }
287
+ prepareForComposerInputChange(current, '');
288
+ return {
289
+ ...current,
290
+ commandCursor: 0,
291
+ cursor: 0,
292
+ input: '',
293
+ pastedChunks: [],
294
+ promptHistoryCursor: null,
295
+ promptHistoryDraft: '',
296
+ };
297
+ });
298
+ return;
299
+ }
300
+ if (key.tab && key.shift) {
301
+ handlers.onCycleAgentMode();
302
+ return;
303
+ }
304
+ if (commandPaletteActive && (key.upArrow || key.downArrow)) {
305
+ store.update((current) => ({
306
+ ...current,
307
+ commandCursor: Math.min(Math.max(current.commandCursor + (key.upArrow ? -1 : 1), 0), Math.max(commandSuggestions.length - 1, 0)),
308
+ }));
309
+ return;
310
+ }
311
+ if (key.upArrow) {
312
+ // While busy with an empty composer, Up recalls and dequeues the queued
313
+ // message for editing; otherwise it walks prompt history as usual.
314
+ if (state.busy && state.input.trim() === '' && state.queuedMessage) {
315
+ handlers.onLiveFrameShapeChange();
316
+ store.update((current) => {
317
+ const queued = current.queuedMessage;
318
+ if (!queued)
319
+ return current;
320
+ return {
321
+ ...current,
322
+ cursor: queued.body.length,
323
+ imageAttachments: queued.imageAttachments,
324
+ input: queued.body,
325
+ pastedChunks: queued.pastedChunks,
326
+ promptHistoryCursor: null,
327
+ promptHistoryDraft: '',
328
+ queuedMessage: null,
329
+ };
330
+ });
331
+ return;
332
+ }
333
+ if (state.busy)
334
+ return;
335
+ store.update((current) => {
336
+ const next = navigatePromptHistory(current, 'previous');
337
+ prepareForComposerInputChange(current, next.input);
338
+ return next;
339
+ });
340
+ return;
341
+ }
342
+ if (key.downArrow) {
343
+ if (state.busy)
344
+ return;
345
+ store.update((current) => {
346
+ const next = navigatePromptHistory(current, 'next');
347
+ prepareForComposerInputChange(current, next.input);
348
+ return next;
349
+ });
350
+ return;
351
+ }
352
+ if (commandPaletteActive && key.tab) {
353
+ const suggestion = commandSuggestions[Math.min(state.commandCursor, commandSuggestions.length - 1)];
354
+ if (!suggestion)
355
+ return;
356
+ store.update((current) => {
357
+ const nextInput = applySlashCommandSuggestion(current.input, suggestion);
358
+ prepareForComposerInputChange(current, nextInput.input);
359
+ return { ...current, commandCursor: 0, ...nextInput };
360
+ });
361
+ return;
362
+ }
363
+ if (key.returnKey) {
364
+ const token = getInputCommandToken(state.input);
365
+ if (commandPaletteActive && !isExactSlashCommandToken(token)) {
366
+ const suggestion = commandSuggestions[Math.min(state.commandCursor, commandSuggestions.length - 1)];
367
+ if (!suggestion)
368
+ return;
369
+ store.update((current) => {
370
+ const nextInput = applySlashCommandSuggestion(current.input, suggestion);
371
+ prepareForComposerInputChange(current, nextInput.input);
372
+ return { ...current, commandCursor: 0, ...nextInput };
373
+ });
374
+ return;
375
+ }
376
+ void handlers.onSubmit(state.input);
377
+ return;
378
+ }
379
+ if (key.leftArrow) {
380
+ store.update((current) => ({
381
+ ...current,
382
+ cursor: Math.max(current.cursor - 1, 0),
383
+ }));
384
+ return;
385
+ }
386
+ if (key.rightArrow) {
387
+ store.update((current) => ({
388
+ ...current,
389
+ cursor: Math.min(current.cursor + 1, current.input.length),
390
+ }));
391
+ return;
392
+ }
393
+ if (key.home) {
394
+ store.update((current) => ({ ...current, commandCursor: 0, cursor: 0 }));
395
+ return;
396
+ }
397
+ if (key.end) {
398
+ store.update((current) => ({
399
+ ...current,
400
+ commandCursor: 0,
401
+ cursor: current.input.length,
402
+ }));
403
+ return;
404
+ }
405
+ if (key.backspace) {
406
+ store.update((current) => {
407
+ const next = deleteBeforeCursor(current);
408
+ if (next)
409
+ prepareForComposerInputChange(current, next.input);
410
+ return next ? { ...current, commandCursor: 0, ...next } : current;
411
+ });
412
+ return;
413
+ }
414
+ if (key.delete) {
415
+ store.update((current) => {
416
+ const next = deleteAtCursor(current);
417
+ if (next)
418
+ prepareForComposerInputChange(current, next.input);
419
+ return next ? { ...current, commandCursor: 0, ...next } : current;
420
+ });
421
+ return;
422
+ }
423
+ if (key.tab) {
424
+ store.update((current) => {
425
+ const nextInput = insertAtCursor(current, ' ');
426
+ prepareForComposerInputChange(current, nextInput.input);
427
+ return { ...current, commandCursor: 0, ...nextInput };
428
+ });
429
+ return;
430
+ }
431
+ if (key.ctrl && key.input === 'v') {
432
+ const current = store.getState();
433
+ if (current.busy)
434
+ return;
435
+ const liveAttachments = current.imageAttachments.filter((attachment) => current.input.includes(`[Image #${attachment.index}]`));
436
+ if (liveAttachments.length >= 2) {
437
+ store.appendEntry({
438
+ body: 'Maximum of 2 images per message. Send the current message first.',
439
+ kind: 'error',
440
+ title: 'Image',
441
+ });
442
+ return;
443
+ }
444
+ try {
445
+ const clipResult = readClipboardImage();
446
+ const idx = liveAttachments.length === 0
447
+ ? 1
448
+ : Math.max(...liveAttachments.map((attachment) => attachment.index)) + 1;
449
+ store.update((shellState) => ({
450
+ ...shellState,
451
+ imageAttachments: [
452
+ ...liveAttachments,
453
+ {
454
+ index: idx,
455
+ mimeType: clipResult.mimeType,
456
+ base64Data: clipResult.base64Data,
457
+ source: 'clipboard',
458
+ },
459
+ ],
460
+ ...insertAtCursor(shellState, `[Image #${idx}] `),
461
+ }));
462
+ }
463
+ catch (error) {
464
+ if (error?.code === 'NO_IMAGE') {
465
+ store.appendEntry({
466
+ body: 'No image found on clipboard. Use Ctrl+Shift+V to paste text.',
467
+ kind: 'error',
468
+ title: 'Image',
469
+ });
470
+ }
471
+ else if (error?.code === 'NO_TOOL' || error?.code === 'READ_FAILED') {
472
+ store.appendEntry({
473
+ body: error.message,
474
+ kind: 'error',
475
+ title: 'Image Error',
476
+ });
477
+ }
478
+ }
479
+ return;
480
+ }
481
+ if (!key.ctrl && !key.meta && key.input) {
482
+ store.update((current) => {
483
+ const nextInput = insertAtCursor(current, key.input);
484
+ prepareForComposerInputChange(current, nextInput.input);
485
+ return { ...current, commandCursor: 0, ...nextInput };
486
+ });
487
+ }
488
+ }
@@ -0,0 +1,30 @@
1
+ export function span(text, style = {}) {
2
+ return { text, ...style };
3
+ }
4
+ export function line(...spans) {
5
+ return { spans };
6
+ }
7
+ export function plainLine(text, style = {}) {
8
+ return line(span(text, style));
9
+ }
10
+ export function wrapText(text, width) {
11
+ const safeWidth = Math.max(1, width);
12
+ if (!text)
13
+ return [''];
14
+ const lines = [];
15
+ for (const rawLine of text.split('\n')) {
16
+ let remaining = rawLine;
17
+ while (remaining.length > safeWidth) {
18
+ let breakAt = remaining.lastIndexOf(' ', safeWidth);
19
+ if (breakAt <= 0)
20
+ breakAt = safeWidth;
21
+ lines.push(remaining.slice(0, breakAt).trimEnd());
22
+ remaining = remaining.slice(breakAt).trimStart();
23
+ }
24
+ lines.push(remaining);
25
+ }
26
+ return lines;
27
+ }
28
+ export function joinLines(blocks) {
29
+ return blocks.flat();
30
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,47 @@
1
+ const USAGE_WINDOWS = [
2
+ ['5-hour', '5-hourly'],
3
+ ['Weekly', 'weekly'],
4
+ ['Monthly', 'monthly'],
5
+ ];
6
+ function formatUtcMinute(value) {
7
+ if (!value)
8
+ return 'no active usage';
9
+ const date = new Date(value);
10
+ if (!Number.isFinite(date.getTime()))
11
+ return 'unknown';
12
+ const pad = (part) => String(part).padStart(2, '0');
13
+ return [
14
+ date.getUTCFullYear(),
15
+ '-',
16
+ pad(date.getUTCMonth() + 1),
17
+ '-',
18
+ pad(date.getUTCDate()),
19
+ ' ',
20
+ pad(date.getUTCHours()),
21
+ ':',
22
+ pad(date.getUTCMinutes()),
23
+ ' UTC',
24
+ ].join('');
25
+ }
26
+ function formatPercentLeft(value) {
27
+ const percent = Number.isFinite(value)
28
+ ? Math.max(0, Math.min(100, value))
29
+ : 0;
30
+ return `${new Intl.NumberFormat('en-US', {
31
+ maximumFractionDigits: 1,
32
+ }).format(percent)}% left`;
33
+ }
34
+ export function formatUsageText(response) {
35
+ const { customer, usage } = response;
36
+ const lines = [`Usage for ${customer.email}`, ''];
37
+ if (!usage) {
38
+ lines.push('Usage is not available for this account yet.');
39
+ return lines.join('\n');
40
+ }
41
+ lines.push('Window Left Resets');
42
+ for (const [label, key] of USAGE_WINDOWS) {
43
+ const value = formatPercentLeft(Number(usage.percentLeft[key] ?? 0));
44
+ lines.push(`${label.padEnd(10)} ${value.padEnd(11)} ${formatUtcMinute(usage.resetsAt[key])}`);
45
+ }
46
+ return lines.join('\n');
47
+ }
@@ -0,0 +1,38 @@
1
+ export function cosineSimilarity(a, b) {
2
+ let dot = 0;
3
+ let magA = 0;
4
+ let magB = 0;
5
+ for (let i = 0; i < a.length; i++) {
6
+ const valA = a[i] ?? 0;
7
+ const valB = b[i] ?? 0;
8
+ dot += valA * valB;
9
+ magA += valA * valA;
10
+ magB += valB * valB;
11
+ }
12
+ const denom = Math.sqrt(magA) * Math.sqrt(magB);
13
+ return denom === 0 ? 0 : dot / denom;
14
+ }
15
+ export function truncate(text, maxChars = 4000) {
16
+ if (text.length <= maxChars)
17
+ return text;
18
+ return `${text.slice(0, maxChars)}\n... (truncated)`;
19
+ }
20
+ export function clampInteger(value, fallback, max) {
21
+ const numeric = Number(value);
22
+ if (!Number.isFinite(numeric) || numeric <= 0) {
23
+ return fallback;
24
+ }
25
+ return Math.min(Math.floor(numeric), max);
26
+ }
27
+ export function readFileRange(content, startLine, endLine) {
28
+ const lines = content.split('\n');
29
+ const safeStart = startLine ? Math.max(1, startLine) : 1;
30
+ const safeEnd = endLine ? Math.max(safeStart, endLine) : lines.length;
31
+ const selected = lines.slice(safeStart - 1, safeEnd);
32
+ return {
33
+ totalLines: lines.length,
34
+ startLine: safeStart,
35
+ endLine: safeStart + selected.length - 1,
36
+ content: selected.join('\n'),
37
+ };
38
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@thegitai/cli",
3
+ "version": "1.0.0-beta.1",
4
+ "description": "TheGitAI CLI client (source-visible, proprietary)",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "homepage": "https://thegit.ai",
7
+ "type": "module",
8
+ "engines": {
9
+ "node": ">=24.14.1 <25"
10
+ },
11
+ "bin": {
12
+ "ai": "./dist/bin/ai.js"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "LICENSE",
17
+ "README.md"
18
+ ],
19
+ "dependencies": {
20
+ "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1",
21
+ "@mozilla/readability": "0.6.0",
22
+ "@vscode/ripgrep": "1.17.1",
23
+ "@vscode/tree-sitter-wasm": "^0.3.0",
24
+ "chalk": "^5.4.1",
25
+ "glob": "^11.0.1",
26
+ "jsdom": "29.1.1",
27
+ "web-tree-sitter": "^0.26.6"
28
+ },
29
+ "optionalDependencies": {
30
+ "@thegitai/tui-darwin-arm64": "1.0.0-beta.1",
31
+ "@thegitai/tui-darwin-x64": "1.0.0-beta.1",
32
+ "@thegitai/tui-linux-x64": "1.0.0-beta.1",
33
+ "@thegitai/tui-win32-x64": "1.0.0-beta.1"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ }
38
+ }