@thegitai/cli 1.0.0-preview.5 → 1.0.0-preview.51

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 (76) hide show
  1. package/README.md +93 -4
  2. package/dist/bin/ai.js +142 -112
  3. package/dist/bin/browser-host.js +265 -0
  4. package/dist/src/agent-mode.js +32 -7
  5. package/dist/src/api/auth.js +10 -4
  6. package/dist/src/api/browser-login.js +149 -37
  7. package/dist/src/api/chat.js +680 -166
  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 +151 -7
  11. package/dist/src/api/models.js +29 -10
  12. package/dist/src/api/startup-handshake.js +36 -0
  13. package/dist/src/api/turn-recovery.js +189 -0
  14. package/dist/src/background-jobs.js +4 -2
  15. package/dist/src/browser/bridge.js +262 -0
  16. package/dist/src/browser/framing.js +42 -0
  17. package/dist/src/browser/native-host.js +219 -0
  18. package/dist/src/browser/protocol.js +46 -0
  19. package/dist/src/browser/session-bridge.js +130 -0
  20. package/dist/src/cli-args.js +19 -5
  21. package/dist/src/client-environment.js +2 -0
  22. package/dist/src/core/clipboard.js +7 -13
  23. package/dist/src/core/image-limits.js +56 -0
  24. package/dist/src/core/image-path-extractor.js +70 -3
  25. package/dist/src/core/session-image-store.js +246 -0
  26. package/dist/src/edit-journal.js +33 -2
  27. package/dist/src/executor.js +25 -3
  28. package/dist/src/help-text.js +112 -21
  29. package/dist/src/patcher.js +43 -4
  30. package/dist/src/permissions.js +288 -0
  31. package/dist/src/preferences.js +39 -0
  32. package/dist/src/quota-notice.js +35 -0
  33. package/dist/src/session-safety.js +0 -12
  34. package/dist/src/session-store.js +121 -20
  35. package/dist/src/session.js +19 -4
  36. package/dist/src/signin.js +58 -0
  37. package/dist/src/tool-executor.js +13 -46
  38. package/dist/src/tools/browser.js +641 -0
  39. package/dist/src/tools/delete-file.js +20 -4
  40. package/dist/src/tools/index.js +32 -10
  41. package/dist/src/tools/patch-file.js +12 -26
  42. package/dist/src/tools/read-file.js +5 -0
  43. package/dist/src/tools/read-image-file.js +85 -0
  44. package/dist/src/tools/replace-document-text.js +28 -18
  45. package/dist/src/tools/restore-checkpoint.js +0 -1
  46. package/dist/src/tools/run-command.js +14 -71
  47. package/dist/src/tools/run-node-script.js +12 -81
  48. package/dist/src/tools/save-generated-image.js +120 -0
  49. package/dist/src/tools/str-replace.js +12 -26
  50. package/dist/src/tools/undo-edit.js +1 -6
  51. package/dist/src/tools/write-file.js +67 -11
  52. package/dist/src/ui/prompt-history-store.js +1 -1
  53. package/dist/src/ui/repl.js +949 -279
  54. package/dist/src/ui/slash-commands.js +121 -0
  55. package/dist/src/ui/tui/attention.js +25 -0
  56. package/dist/src/ui/tui/bridge.js +38 -5
  57. package/dist/src/ui/tui/build-frame.js +686 -223
  58. package/dist/src/ui/tui/composer-layout.js +76 -0
  59. package/dist/src/ui/tui/desktop-notification.js +118 -0
  60. package/dist/src/ui/tui/markdown-render.js +11 -2
  61. package/dist/src/ui/tui/shell-input.js +256 -73
  62. package/dist/src/ui/tui/shortcuts.js +61 -0
  63. package/dist/src/ui/tui/terminal-theme.js +28 -0
  64. package/dist/src/ui/tui/user-input.js +593 -0
  65. package/dist/src/update-check.js +205 -0
  66. package/dist/src/usage.js +5 -2
  67. package/dist/src/utils.js +13 -0
  68. package/dist/src/version.js +52 -2
  69. package/package.json +22 -9
  70. package/dist/src/project-index.js +0 -233
  71. package/dist/src/tools/code-intel.js +0 -472
  72. package/dist/src/tools/find-symbol.js +0 -70
  73. package/dist/src/tools/hover-symbol.js +0 -95
  74. package/dist/src/tools/list-symbols.js +0 -55
  75. package/dist/src/tools/search-code.js +0 -37
  76. package/dist/src/tools/signature-help.js +0 -118
@@ -1,6 +1,8 @@
1
1
  import path from 'node:path';
2
2
  import { normalizeAgentMode, } from './agent-mode.js';
3
+ import { createSessionGrants, } from './permissions.js';
3
4
  import { createSessionSafetyState, } from './session-safety.js';
5
+ import { closeBrowserBridge } from './browser/session-bridge.js';
4
6
  import { clampInteger } from './utils.js';
5
7
  const DEFAULT_MAX_TOOL_STEPS = 32;
6
8
  function defaultStatus(message) {
@@ -31,7 +33,7 @@ function preserveProviderSelection(serverState) {
31
33
  : null;
32
34
  return providerSelection ? { providerSelection } : {};
33
35
  }
34
- export function createSession({ rootDir, autoYes = false, agentMode, modelId, maxToolSteps = DEFAULT_MAX_TOOL_STEPS, confirmCommand = null, confirmPatch = null, requestSudoPassword = null, onStatus = null, onContextLog = null, onToolEvent = null, env = process.env, sessionId = createSessionId(), sessionName = null, history = [], serverState = null, editJournal = [], stickyFilePaths = [], editCounter = 0, safety = createSessionSafetyState(), }) {
36
+ export function createSession({ rootDir, autoYes = false, agentMode, modelId, maxToolSteps = DEFAULT_MAX_TOOL_STEPS, maxToolStepsWasExplicit = false, requestPermission = null, requestSudoPassword = null, requestUserInput = null, onStatus = null, onContextLog = null, onToolEvent = null, env = process.env, sessionId = createSessionId(), sessionName = null, history = [], serverState = null, editJournal = [], stickyFilePaths = [], editCounter = 0, safety = createSessionSafetyState(), }) {
35
37
  const createdAt = new Date().toISOString();
36
38
  const initialAgentMode = normalizeAgentMode(agentMode ?? (autoYes ? 'auto-accept' : 'default'));
37
39
  return {
@@ -40,12 +42,14 @@ export function createSession({ rootDir, autoYes = false, agentMode, modelId, ma
40
42
  autoYes: initialAgentMode === 'auto-accept',
41
43
  agentMode: initialAgentMode,
42
44
  maxToolSteps: clampInteger(maxToolSteps, DEFAULT_MAX_TOOL_STEPS, 128),
45
+ maxToolStepsWasExplicit: maxToolStepsWasExplicit === true,
43
46
  onStatus: onStatus ?? defaultStatus,
44
47
  onContextLog: onContextLog ?? defaultContextLog,
45
48
  onToolEvent,
46
- confirmCommand,
47
- confirmPatch,
49
+ grants: createSessionGrants(),
50
+ requestPermission,
48
51
  requestSudoPassword,
52
+ requestUserInput,
49
53
  history: JSON.parse(JSON.stringify(history)),
50
54
  initialized: true,
51
55
  sessionId,
@@ -69,8 +73,17 @@ export function createSession({ rootDir, autoYes = false, agentMode, modelId, ma
69
73
  serverState: cloneOpaqueState(serverState),
70
74
  };
71
75
  }
76
+ export function startNewConversation(session) {
77
+ clearConversation(session);
78
+ const createdAt = new Date().toISOString();
79
+ session.sessionId = createSessionId();
80
+ session.sessionName = null;
81
+ session.sessionCreatedAt = createdAt;
82
+ session.sessionUpdatedAt = createdAt;
83
+ }
72
84
  export function clearConversation(session) {
73
85
  session.history = [];
86
+ session.grants = createSessionGrants();
74
87
  session.serverState = preserveProviderSelection(session.serverState);
75
88
  session.turnState = {
76
89
  id: null,
@@ -86,7 +99,9 @@ export function clearConversation(session) {
86
99
  safety: createSessionSafetyState(),
87
100
  };
88
101
  }
89
- export async function disposeSession(_session) { }
102
+ export async function disposeSession(_session) {
103
+ closeBrowserBridge();
104
+ }
90
105
  export function switchModel(session, modelId) {
91
106
  session.modelId = Number(modelId);
92
107
  return { id: session.modelId };
@@ -0,0 +1,58 @@
1
+ import { stdin as input, stdout as output } from 'node:process';
2
+ import readline from 'node:readline/promises';
3
+ import chalk from './colors.js';
4
+ import { auth } from './api/index.js';
5
+ import { DEFAULT_THEGITAI_HOST } from './api/default-host.js';
6
+ import { loginViaBrowser } from './api/browser-login.js';
7
+ function hyperlink(url, label) {
8
+ if (process.stdout.isTTY !== true)
9
+ return label;
10
+ return `\x1b]8;;${url}\x07${label}\x1b]8;;\x07`;
11
+ }
12
+ export function formatSignInScreen({ url }) {
13
+ return [
14
+ '',
15
+ ` ${chalk.bold.cyan('TheGitAI')}`,
16
+ '',
17
+ ` ${chalk.bold('Sign in to continue.')}`,
18
+ ' Your browser should open automatically. If not, copy this URL:',
19
+ '',
20
+ ` ${chalk.cyan(url)}`,
21
+ '',
22
+ ` ${hyperlink(url, chalk.cyan('→ Click here to authenticate'))}`,
23
+ '',
24
+ ' If this terminal is not on the computer where that page opened, choose',
25
+ ` ${chalk.bold('"On another computer"')} there and paste the code it gives you below.`,
26
+ '',
27
+ ].join('\n');
28
+ }
29
+ async function promptForCode(signal) {
30
+ const rl = readline.createInterface({ input, output });
31
+ try {
32
+ return await rl.question(` ${chalk.bold('Authorization code:')} `, { signal });
33
+ }
34
+ finally {
35
+ rl.close();
36
+ }
37
+ }
38
+ export async function runSignIn({ env = process.env, login = loginViaBrowser, write = auth.writeCliAuthConfig, log = (line) => console.log(line), } = {}) {
39
+ const result = await login({
40
+ serverUrl: DEFAULT_THEGITAI_HOST,
41
+ deviceName: env.THEGITAI_DEVICE_NAME?.trim() || undefined,
42
+ onUrl: (url) => log(formatSignInScreen({ url })),
43
+ onBrowserOpen: (opened) => {
44
+ if (!opened) {
45
+ log(chalk.dim(' (no browser opened — use the URL or the code box above)'));
46
+ }
47
+ },
48
+ promptCode: promptForCode,
49
+ onPasteRejected: (message) => log(` ${chalk.red('✖')} ${message}\n` +
50
+ ' Codes last 10 minutes. If that page has been open a while, run `ai`\n' +
51
+ ' again for a fresh one.'),
52
+ });
53
+ write(result, env);
54
+ log('');
55
+ log(chalk.green(` ✓ Signed in as ${result.customer.email}`));
56
+ log('');
57
+ return result;
58
+ }
@@ -4,7 +4,6 @@ import { clearEditFailure, collectCommandMutations, captureMutationBaseline, ens
4
4
  import { buildAgentModeToolBlockedResult, } from './agent-mode.js';
5
5
  import { classifyProjectPath } from './patcher.js';
6
6
  import { dispatchTool } from './tools/index.js';
7
- import { syncIndexFromDisk } from './project-index.js';
8
7
  import { PATH_REPAIRING_EDIT_TOOLS, repairFilePath } from './tools/path-suggest.js';
9
8
  import { invalidateShellDiagnosticsCache, runShellDiagnostics, } from './tools/shell-diagnostics.js';
10
9
  import { extractTodosArg } from './todo-list.js';
@@ -69,7 +68,7 @@ function editToolWritesSeparateOutput(call) {
69
68
  const output = args.outputPath ?? args.output_path;
70
69
  return typeof output === 'string' && output.trim().length > 0;
71
70
  }
72
- async function collectTrackedCommandMutations({ session, projectIndex, result, tracker, toolName, toolCallId, turnId, }) {
71
+ async function collectTrackedCommandMutations({ session, result, tracker, toolName, toolCallId, turnId, }) {
73
72
  const checkpoint = ensureActiveCheckpoint(session.clientState.safety, turnId);
74
73
  const records = collectCommandMutations({
75
74
  state: session.clientState.safety,
@@ -84,40 +83,11 @@ async function collectTrackedCommandMutations({ session, projectIndex, result, t
84
83
  return;
85
84
  invalidateShellDiagnosticsCache(session.rootDir);
86
85
  rememberCheckpointFiles(session.clientState.safety, session.rootDir, records.map((record) => record.filePath), turnId);
87
- const priorSync = result.repoSync;
88
- if (!(priorSync && (priorSync.added || priorSync.modified || priorSync.removed))) {
89
- const mutationCounts = {
90
- added: records.filter((record) => record.operation === 'create').length,
91
- modified: records.filter((record) => record.operation === 'update').length,
92
- removed: records.filter((record) => record.operation === 'delete').length,
93
- indexedChunks: 0,
94
- retrievalTokensUsed: 0,
95
- };
96
- if (priorSync?.error) {
97
- result.repoSync = {
98
- ...mutationCounts,
99
- indexSyncError: priorSync.error,
100
- skipped: true,
101
- reason: 'local index sync failed after command execution',
102
- };
103
- }
104
- else {
105
- try {
106
- const repoSync = await syncIndexFromDisk(projectIndex);
107
- result.repoSync = {
108
- ...repoSync,
109
- added: Math.max(repoSync.added, mutationCounts.added),
110
- modified: Math.max(repoSync.modified, mutationCounts.modified),
111
- removed: Math.max(repoSync.removed, mutationCounts.removed),
112
- };
113
- }
114
- catch (error) {
115
- const indexSyncError = error instanceof Error ? error.message : String(error);
116
- result.repoSync = { ...mutationCounts, indexSyncError };
117
- session.onStatus(`Repository changes were recorded, but local index sync failed: ${indexSyncError}`);
118
- }
119
- }
120
- }
86
+ result.repoSync = {
87
+ added: records.filter((record) => record.operation === 'create').length,
88
+ modified: records.filter((record) => record.operation === 'update').length,
89
+ removed: records.filter((record) => record.operation === 'delete').length,
90
+ };
121
91
  result.sessionEdits = records.map((record) => ({
122
92
  id: record.id,
123
93
  filePath: record.filePath,
@@ -127,7 +97,7 @@ async function collectTrackedCommandMutations({ session, projectIndex, result, t
127
97
  }));
128
98
  result.diagnostics = runShellDiagnostics(session.rootDir);
129
99
  }
130
- export async function collectBackgroundJobUiKillMutations({ session, projectIndex, jobId, result, }) {
100
+ export async function collectBackgroundJobUiKillMutations({ session, jobId, result, }) {
131
101
  const normalizedJobId = String(jobId ?? result.snapshot?.id ?? '').trim();
132
102
  if (!normalizedJobId || !result.ok)
133
103
  return;
@@ -137,7 +107,6 @@ export async function collectBackgroundJobUiKillMutations({ session, projectInde
137
107
  const mutationResult = result;
138
108
  await collectTrackedCommandMutations({
139
109
  session,
140
- projectIndex,
141
110
  result: mutationResult,
142
111
  tracker: tracked.tracker,
143
112
  toolName: tracked.toolName,
@@ -151,7 +120,7 @@ export async function collectBackgroundJobUiKillMutations({ session, projectInde
151
120
  backgroundCommandTrackers.delete(normalizedJobId);
152
121
  }
153
122
  }
154
- export async function collectBackgroundJobUiOutputMutations({ session, projectIndex, jobId, }) {
123
+ export async function collectBackgroundJobUiOutputMutations({ session, jobId, }) {
155
124
  const normalizedJobId = String(jobId ?? '').trim();
156
125
  if (!normalizedJobId)
157
126
  return;
@@ -165,7 +134,6 @@ export async function collectBackgroundJobUiOutputMutations({ session, projectIn
165
134
  return;
166
135
  await collectTrackedCommandMutations({
167
136
  session,
168
- projectIndex,
169
137
  result: {},
170
138
  tracker: tracked.tracker,
171
139
  toolName: tracked.toolName,
@@ -244,7 +212,8 @@ function recordAssistantEdit(session, call, result, before) {
244
212
  });
245
213
  clearEditFailure(session.clientState.safety, filePath);
246
214
  }
247
- export async function executeLocalToolCall(toolContext, session, call) {
215
+ export async function executeLocalToolCall(session, call) {
216
+ session.turnAbortSignal?.throwIfAborted();
248
217
  session.onStatus(formatToolCallForStatus(call));
249
218
  try {
250
219
  const agentModeBlocked = buildAgentModeToolBlockedResult(session.agentMode, call);
@@ -275,10 +244,10 @@ export async function executeLocalToolCall(toolContext, session, call) {
275
244
  const context = {
276
245
  rootDir: session.rootDir,
277
246
  sessionId: session.sessionId,
278
- projectIndex: toolContext.projectIndex,
279
247
  autoYes: session.autoYes,
280
- confirmCommand: session.confirmCommand,
281
- confirmPatch: session.confirmPatch,
248
+ agentMode: session.agentMode,
249
+ grants: session.grants,
250
+ requestPermission: session.requestPermission,
282
251
  requestSudoPassword: session.requestSudoPassword,
283
252
  onStatus: session.onStatus,
284
253
  editJournal: session.clientState.editJournal,
@@ -299,7 +268,6 @@ export async function executeLocalToolCall(toolContext, session, call) {
299
268
  if (result && typeof result === 'object' && commandTracker) {
300
269
  await collectTrackedCommandMutations({
301
270
  session,
302
- projectIndex: toolContext.projectIndex,
303
271
  result,
304
272
  tracker: commandTracker,
305
273
  toolName: call.name,
@@ -326,7 +294,6 @@ export async function executeLocalToolCall(toolContext, session, call) {
326
294
  if (tracked) {
327
295
  await collectTrackedCommandMutations({
328
296
  session,
329
- projectIndex: toolContext.projectIndex,
330
297
  result,
331
298
  tracker: tracked.tracker,
332
299
  toolName: tracked.toolName,