@thegitai/cli 1.0.0-preview.5 → 1.0.0-preview.50
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.
- package/README.md +93 -4
- package/dist/bin/ai.js +142 -112
- package/dist/bin/browser-host.js +265 -0
- package/dist/src/agent-mode.js +32 -7
- package/dist/src/api/auth.js +10 -4
- package/dist/src/api/browser-login.js +149 -37
- package/dist/src/api/chat.js +680 -166
- package/dist/src/api/contracts.js +55 -1
- package/dist/src/api/default-host.js +1 -0
- package/dist/src/api/http.js +151 -7
- package/dist/src/api/models.js +29 -10
- package/dist/src/api/startup-handshake.js +36 -0
- package/dist/src/api/turn-recovery.js +189 -0
- package/dist/src/background-jobs.js +4 -2
- package/dist/src/browser/bridge.js +262 -0
- package/dist/src/browser/framing.js +42 -0
- package/dist/src/browser/native-host.js +219 -0
- package/dist/src/browser/protocol.js +46 -0
- package/dist/src/browser/session-bridge.js +130 -0
- package/dist/src/cli-args.js +19 -5
- package/dist/src/client-environment.js +2 -0
- package/dist/src/core/clipboard.js +7 -13
- package/dist/src/core/image-limits.js +56 -0
- package/dist/src/core/image-path-extractor.js +70 -3
- package/dist/src/core/session-image-store.js +246 -0
- package/dist/src/edit-journal.js +33 -2
- package/dist/src/executor.js +25 -3
- package/dist/src/help-text.js +112 -21
- package/dist/src/patcher.js +43 -4
- package/dist/src/permissions.js +288 -0
- package/dist/src/preferences.js +39 -0
- package/dist/src/quota-notice.js +35 -0
- package/dist/src/session-safety.js +0 -12
- package/dist/src/session-store.js +121 -20
- package/dist/src/session.js +19 -4
- package/dist/src/signin.js +58 -0
- package/dist/src/tool-executor.js +13 -46
- package/dist/src/tools/browser.js +628 -0
- package/dist/src/tools/delete-file.js +20 -4
- package/dist/src/tools/index.js +32 -10
- package/dist/src/tools/patch-file.js +12 -26
- package/dist/src/tools/read-image-file.js +85 -0
- package/dist/src/tools/replace-document-text.js +28 -18
- package/dist/src/tools/restore-checkpoint.js +0 -1
- package/dist/src/tools/run-command.js +14 -71
- package/dist/src/tools/run-node-script.js +12 -81
- package/dist/src/tools/save-generated-image.js +120 -0
- package/dist/src/tools/str-replace.js +12 -26
- package/dist/src/tools/undo-edit.js +1 -6
- package/dist/src/tools/write-file.js +67 -11
- package/dist/src/ui/prompt-history-store.js +1 -1
- package/dist/src/ui/repl.js +949 -279
- package/dist/src/ui/slash-commands.js +121 -0
- package/dist/src/ui/tui/attention.js +25 -0
- package/dist/src/ui/tui/bridge.js +38 -5
- package/dist/src/ui/tui/build-frame.js +686 -223
- package/dist/src/ui/tui/composer-layout.js +76 -0
- package/dist/src/ui/tui/desktop-notification.js +118 -0
- package/dist/src/ui/tui/markdown-render.js +11 -2
- package/dist/src/ui/tui/shell-input.js +256 -73
- package/dist/src/ui/tui/shortcuts.js +61 -0
- package/dist/src/ui/tui/terminal-theme.js +28 -0
- package/dist/src/ui/tui/user-input.js +593 -0
- package/dist/src/update-check.js +205 -0
- package/dist/src/usage.js +5 -2
- package/dist/src/utils.js +13 -0
- package/dist/src/version.js +52 -2
- package/package.json +22 -9
- package/dist/src/project-index.js +0 -233
- package/dist/src/tools/code-intel.js +0 -472
- package/dist/src/tools/find-symbol.js +0 -70
- package/dist/src/tools/hover-symbol.js +0 -95
- package/dist/src/tools/list-symbols.js +0 -55
- package/dist/src/tools/search-code.js +0 -37
- package/dist/src/tools/signature-help.js +0 -118
package/dist/src/edit-journal.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import { existsSync, lstatSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { closeSync, existsSync, lstatSync, openSync, readFileSync, readSync, } from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { BINARY_ARTIFACT_EXTENSIONS } from './artifact-policy.js';
|
|
6
6
|
import { resolveProjectPath } from './patcher.js';
|
|
@@ -30,13 +30,33 @@ function snapshotEncoding(filePath, content) {
|
|
|
30
30
|
return 'base64';
|
|
31
31
|
return 'utf8';
|
|
32
32
|
}
|
|
33
|
+
const MAX_SNAPSHOT_READ_BYTES = MAX_STORED_CONTENT_CHARS;
|
|
34
|
+
const SNAPSHOT_HASH_CHUNK_BYTES = 1024 * 1024;
|
|
35
|
+
function hashFileInChunks(absPath) {
|
|
36
|
+
const hash = createHash('sha256');
|
|
37
|
+
const chunk = Buffer.allocUnsafe(SNAPSHOT_HASH_CHUNK_BYTES);
|
|
38
|
+
const fd = openSync(absPath, 'r');
|
|
39
|
+
try {
|
|
40
|
+
for (;;) {
|
|
41
|
+
const read = readSync(fd, chunk, 0, chunk.length, null);
|
|
42
|
+
if (read <= 0)
|
|
43
|
+
break;
|
|
44
|
+
hash.update(chunk.subarray(0, read));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
closeSync(fd);
|
|
49
|
+
}
|
|
50
|
+
return `sha256:${hash.digest('hex')}`;
|
|
51
|
+
}
|
|
33
52
|
export function readFileEditSnapshot(rootDir, filePath) {
|
|
34
53
|
try {
|
|
35
54
|
const absPath = resolveProjectPath(rootDir, filePath);
|
|
36
55
|
if (!existsSync(absPath)) {
|
|
37
56
|
return { exists: false, content: null, contentEncoding: 'utf8', hash: null };
|
|
38
57
|
}
|
|
39
|
-
|
|
58
|
+
const stat = lstatSync(absPath);
|
|
59
|
+
if (stat.isSymbolicLink()) {
|
|
40
60
|
return {
|
|
41
61
|
exists: true,
|
|
42
62
|
content: null,
|
|
@@ -45,6 +65,17 @@ export function readFileEditSnapshot(rootDir, filePath) {
|
|
|
45
65
|
error: `Refusing to snapshot symbolic link: ${filePath}`,
|
|
46
66
|
};
|
|
47
67
|
}
|
|
68
|
+
if (stat.isFile() && stat.size > MAX_SNAPSHOT_READ_BYTES) {
|
|
69
|
+
return {
|
|
70
|
+
exists: true,
|
|
71
|
+
content: null,
|
|
72
|
+
contentEncoding: BINARY_ARTIFACT_EXTENSIONS.has(path.extname(filePath).toLowerCase())
|
|
73
|
+
? 'base64'
|
|
74
|
+
: 'utf8',
|
|
75
|
+
hash: hashFileInChunks(absPath),
|
|
76
|
+
error: `File is too large to snapshot (${stat.size} bytes).`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
48
79
|
const content = readFileSync(absPath);
|
|
49
80
|
const encoding = snapshotEncoding(filePath, content);
|
|
50
81
|
return {
|
package/dist/src/executor.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk from './colors.js';
|
|
2
2
|
import { execFileSync, spawn } from 'child_process';
|
|
3
|
-
import { existsSync, statSync } from 'fs';
|
|
3
|
+
import { accessSync, constants as fsConstants, existsSync, statSync } from 'fs';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
5
|
import os from 'os';
|
|
6
6
|
import path from 'path';
|
|
@@ -596,7 +596,7 @@ export function commandUsesSudo(command) {
|
|
|
596
596
|
}
|
|
597
597
|
export function sudoPromptFromTail(text) {
|
|
598
598
|
const tail = text.slice(-1000).replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '');
|
|
599
|
-
const match = tail.match(/(?:\[sudo\][^\r\n]*password[^\r\n]*:
|
|
599
|
+
const match = tail.match(/(?:\[sudo\][^\r\n]*password[^\r\n]*: ?|\[?sudo[^\r\n]*password[^\r\n]*: ?|password[^\r\n]*: ?)$/i);
|
|
600
600
|
return match?.[0] ?? null;
|
|
601
601
|
}
|
|
602
602
|
function isSudoPromptLine(text) {
|
|
@@ -637,6 +637,28 @@ export function buildCommandEnv(cwd) {
|
|
|
637
637
|
THEGITAI_SCRATCH_DIR: ensureSessionScratchDir(),
|
|
638
638
|
};
|
|
639
639
|
}
|
|
640
|
+
let cachedCommandShell;
|
|
641
|
+
function findBashPath() {
|
|
642
|
+
const fromPath = (process.env.PATH ?? '')
|
|
643
|
+
.split(path.delimiter)
|
|
644
|
+
.filter(Boolean)
|
|
645
|
+
.map((dir) => path.join(dir, 'bash'));
|
|
646
|
+
for (const candidate of [...fromPath, '/bin/bash', '/usr/bin/bash']) {
|
|
647
|
+
try {
|
|
648
|
+
accessSync(candidate, fsConstants.X_OK);
|
|
649
|
+
return candidate;
|
|
650
|
+
}
|
|
651
|
+
catch { }
|
|
652
|
+
}
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
export function resolveCommandShell() {
|
|
656
|
+
if (cachedCommandShell !== undefined)
|
|
657
|
+
return cachedCommandShell;
|
|
658
|
+
cachedCommandShell =
|
|
659
|
+
process.platform === 'win32' ? true : (findBashPath() ?? true);
|
|
660
|
+
return cachedCommandShell;
|
|
661
|
+
}
|
|
640
662
|
function sanitizePtyOutput(command, output, cwd, secrets) {
|
|
641
663
|
return sanitizeCommandText(command, stripSudoPromptText(redactSecrets(output, secrets)), cwd);
|
|
642
664
|
}
|
|
@@ -822,7 +844,7 @@ export async function runCommand(command, cwd, { requestSudoPassword, timeout, }
|
|
|
822
844
|
let killTimer = null;
|
|
823
845
|
const child = spawn(command, {
|
|
824
846
|
cwd,
|
|
825
|
-
shell:
|
|
847
|
+
shell: resolveCommandShell(),
|
|
826
848
|
detached: process.platform !== 'win32',
|
|
827
849
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
828
850
|
env: buildCommandEnv(cwd),
|
package/dist/src/help-text.js
CHANGED
|
@@ -14,7 +14,7 @@ const PASTE_SHORTCUT = pasteShortcutForPlatform();
|
|
|
14
14
|
const HELP_MARKDOWN = [
|
|
15
15
|
'# TheGitAI',
|
|
16
16
|
'',
|
|
17
|
-
'Interactive terminal coding agent. Local repo
|
|
17
|
+
'Interactive terminal coding agent. Local repo search, file edits, and',
|
|
18
18
|
'shell commands run on your machine. Model inference and server-executed',
|
|
19
19
|
'tools run on the server.',
|
|
20
20
|
'',
|
|
@@ -26,8 +26,12 @@ const HELP_MARKDOWN = [
|
|
|
26
26
|
'',
|
|
27
27
|
'## Auth',
|
|
28
28
|
'',
|
|
29
|
-
'- `ai
|
|
30
|
-
'- `ai login
|
|
29
|
+
'- `ai` — signs you in if you are not already, then starts a session',
|
|
30
|
+
'- `ai login` — the same thing; kept for muscle memory',
|
|
31
|
+
'- Signing in opens your browser. When it cannot open one — over SSH, in a',
|
|
32
|
+
' container, or when the browser you want is on another device — the same',
|
|
33
|
+
' screen prints a URL you can open anywhere and takes the code that page',
|
|
34
|
+
' gives you back. There is no mode to pick and no flag to remember.',
|
|
31
35
|
'- `ai whoami` — show the signed-in account',
|
|
32
36
|
'- `ai --usage` — show account usage percentage and reset times',
|
|
33
37
|
'- `ai logout` — sign out',
|
|
@@ -39,25 +43,60 @@ const HELP_MARKDOWN = [
|
|
|
39
43
|
'- Sessions are stored locally and can be listed or resumed in the same repo.',
|
|
40
44
|
' Continuing one requires the TheGitAI account used for that session.',
|
|
41
45
|
'',
|
|
46
|
+
'## Updates',
|
|
47
|
+
'',
|
|
48
|
+
'- On start, `ai` says when a newer release is published and offers to install',
|
|
49
|
+
' it. TheGitAI works best on the latest version, and an older CLI may not',
|
|
50
|
+
' work optimally.',
|
|
51
|
+
'- **Update now** is selected when the offer appears, so **Enter** takes the',
|
|
52
|
+
' update; **↑/↓** moves to **Skip**, and the number keys still jump straight',
|
|
53
|
+
' to a row. The CLI installs the update and asks you to start `ai` again, so',
|
|
54
|
+
' the whole session runs one version.',
|
|
55
|
+
'- Skipping starts the session on the version you have and asks again next',
|
|
56
|
+
' time. A release cannot be silenced for good — that is what keeps a broken',
|
|
57
|
+
' combination from lasting.',
|
|
58
|
+
'- **Ctrl+C** at the prompt quits without installing anything and without',
|
|
59
|
+
' starting the session.',
|
|
60
|
+
'',
|
|
42
61
|
'## Options',
|
|
43
62
|
'',
|
|
44
|
-
'- `-y, --yes` — start in
|
|
63
|
+
'- `-y, --yes` — start in Bypass all permissions mode',
|
|
45
64
|
'- `-h, --help` — show this help',
|
|
46
65
|
'- `-v, --version` — print the version and exit',
|
|
47
66
|
'',
|
|
48
67
|
'## Modes',
|
|
49
68
|
'',
|
|
50
|
-
'- Default — asks before
|
|
51
|
-
'
|
|
69
|
+
'- Default · Ask for Approval — asks before creating, editing or deleting',
|
|
70
|
+
' files, before running commands, and before touching a website. Each answer',
|
|
71
|
+
' can be remembered for the rest of the session, and once it is, Default',
|
|
72
|
+
' stops asking for that action; see Safety & approvals.',
|
|
73
|
+
'- Bypass all permissions — approves every command, file edit, deletion and',
|
|
74
|
+
' browser action for the rest of the session, without asking. Shown in red',
|
|
75
|
+
' in the footer. Blocked commands stay blocked.',
|
|
52
76
|
'- Plan — read-only; the agent can inspect, ask typed questions, and plan,',
|
|
53
77
|
' with file-reading shell commands only. It will not edit files or run tests,',
|
|
54
78
|
' builds, diagnostics, package managers, project code, or network probes.',
|
|
55
|
-
'- In the TUI, Shift+Tab cycles
|
|
79
|
+
'- In the TUI, press **?** on an empty composer for the keyboard-and-mouse panel. Shift+Tab cycles Default → Bypass → Plan (refused while the agent is working), mouse drag copies transcript/footer text, left-click opens rendered links, right-click copies rendered links or pastes in the sticky composer/footer when no selection is active, double-click copies a word, and triple-click copies a row.',
|
|
56
80
|
'',
|
|
57
81
|
'## Keys & clipboard',
|
|
58
82
|
'',
|
|
59
|
-
'-
|
|
60
|
-
'
|
|
83
|
+
'- **?** on an empty composer opens the keyboard-and-mouse panel.',
|
|
84
|
+
'- **Enter** sends • **Shift+Tab** cycles Default → Bypass → Plan (only',
|
|
85
|
+
' between turns) • **Esc** cancels the turn •',
|
|
86
|
+
' **Ctrl+C** clears the composer or the queued message, and quits once there',
|
|
87
|
+
' is nothing left to clear. These are the same on macOS, Linux, and Windows.',
|
|
88
|
+
'- **While the agent is working**, Enter queues your message and locks the',
|
|
89
|
+
' composer. Press **Enter** again to send it into the running turn: the agent',
|
|
90
|
+
' picks it up at its next step instead of waiting for the turn to finish, and',
|
|
91
|
+
' the Your messages panel tracks it from queued to delivered. **↑** brings it',
|
|
92
|
+
' back for editing and **Esc**/**Ctrl+C** discards it; both unlock the',
|
|
93
|
+
' composer for the next message. **Ctrl+V** pastes a screenshot onto the',
|
|
94
|
+
' queued message, and it is delivered into the running turn with the text.',
|
|
95
|
+
'- **Ctrl+G** turns desktop notifications on or off. One is raised when a turn',
|
|
96
|
+
' ends and when a permission prompt or a question is waiting for you, but',
|
|
97
|
+
' only while the terminal is in the background — if you are looking at the',
|
|
98
|
+
' session you are told nothing. Coming back clears it. The choice is',
|
|
99
|
+
' remembered between sessions.',
|
|
61
100
|
`- **Paste** into the composer with your terminal's paste shortcut (\`${PASTE_SHORTCUT}\``,
|
|
62
101
|
' on this system) or by right-clicking the composer.',
|
|
63
102
|
'- **Copy** from the transcript by dragging to select; double-click copies a',
|
|
@@ -66,26 +105,68 @@ const HELP_MARKDOWN = [
|
|
|
66
105
|
? ['- On Mac laptops without dedicated keys, **Home/End** are `Fn+←` / `Fn+→`.']
|
|
67
106
|
: []),
|
|
68
107
|
'',
|
|
108
|
+
'## Questions',
|
|
109
|
+
'',
|
|
110
|
+
'- The agent can pause a turn to ask up to four questions in one form.',
|
|
111
|
+
' **↑/↓** moves, the option number or **Enter** chooses, **←/→** moves',
|
|
112
|
+
' between questions, and the last question has a **Submit answers** row.',
|
|
113
|
+
'- Only the option you are on shows its description, so the whole form fits',
|
|
114
|
+
' on screen. Move to an option to read what it means.',
|
|
115
|
+
'- **Something else / add details** is the numbered final option: focus it',
|
|
116
|
+
' and type. **Enter** commits the note and moves to the next question, or to',
|
|
117
|
+
' Submit on the last one — it never submits the form for you.',
|
|
118
|
+
'- **Esc** closes an open note first, and otherwise cancels the form.',
|
|
119
|
+
'',
|
|
69
120
|
'## Chat commands',
|
|
70
121
|
'',
|
|
122
|
+
'- Type `/` to open the command picker. It ranks what you type, so a typo or',
|
|
123
|
+
' a stray plural (`/moddel`, `/models`) still finds the command, and a word',
|
|
124
|
+
' from the description does too (`/bug` finds `/report`). It shows the five',
|
|
125
|
+
' best matches and counts the rest; **↑/↓** chooses, **Tab** or **Enter**',
|
|
126
|
+
' accepts, **Esc** closes it.',
|
|
71
127
|
'- `/help` — show this help',
|
|
72
128
|
'- `/about` — show version and platform info',
|
|
129
|
+
'- `/report` — file a bug privately through the TheGitAI website in your',
|
|
130
|
+
' browser (sign in with your existing account)',
|
|
73
131
|
'- `/usage` — show account usage percentage and reset times',
|
|
74
132
|
'- `/model` — list supported models and pick one',
|
|
75
133
|
'- `/model <id>` — switch the active model without clearing history',
|
|
76
134
|
'- `/resume` — resume a saved session for this repo',
|
|
77
135
|
'- `/jobs` — manage long-running commands like dev servers and watchers:',
|
|
78
136
|
' browse them, press Enter to expand one and read its output, k to stop it',
|
|
137
|
+
'- Ctrl+B — open the same jobs picker, and the only way in while the agent',
|
|
138
|
+
' is working; press it again or Esc to close without touching the turn',
|
|
79
139
|
'- `/jobs output <id>` — print one job\'s full captured output',
|
|
80
140
|
'- `/jobs kill <id>` — stop one background job',
|
|
81
|
-
'- `/
|
|
141
|
+
'- `/notify` — turn desktop notifications on or off (same as Ctrl+G)',
|
|
142
|
+
'- `/new` — start a new conversation; this session remains saved',
|
|
143
|
+
'- `/logout` — sign out and quit',
|
|
82
144
|
'- `/exit` — quit the session',
|
|
83
145
|
'',
|
|
84
146
|
'## Safety & approvals',
|
|
85
147
|
'',
|
|
86
|
-
'- TheGitAI asks before
|
|
87
|
-
'
|
|
88
|
-
'
|
|
148
|
+
'- In Default mode TheGitAI asks before it creates a file, edits an existing',
|
|
149
|
+
' file, deletes a file, or runs a command. These are four separate',
|
|
150
|
+
' permissions: allowing edits does not allow deletions, and allowing file',
|
|
151
|
+
' changes does not allow commands.',
|
|
152
|
+
'- At each prompt: **↑/↓** moves between choices, **Enter** confirms the',
|
|
153
|
+
' highlighted one, and **Esc** denies. Deny is selected by default.',
|
|
154
|
+
' Single-letter shortcuts were removed on purpose: a prompt can appear',
|
|
155
|
+
' while you are typing, and a stray letter must never approve anything.',
|
|
156
|
+
'- Every prompt offers Approve once, an "Always allow" for that kind of',
|
|
157
|
+
' action, and Deny. A command also offers to remember just its prefix — for',
|
|
158
|
+
' example approving `npm test -- --watch=false` can allow `npm test`.',
|
|
159
|
+
'- A prefix is only offered when the command shows a plain verb, as in',
|
|
160
|
+
' `npm test` or `git status`. Commands with no verb (`ls -la`), interpreters',
|
|
161
|
+
' and wrappers (`bash -c`, `python -c`, `env`, `timeout`, `make`), and',
|
|
162
|
+
' destructive or network binaries (`rm`, `sudo`, `curl`) are never offered',
|
|
163
|
+
' one — a bare binary grant would mean "anything this program can do".',
|
|
164
|
+
' `npm run` must name its script, so the grant is `npm run build`.',
|
|
165
|
+
'- A remembered prefix never covers a command that chains, pipes, redirects,',
|
|
166
|
+
' or substitutes, so allowing `npm test` can never green-light',
|
|
167
|
+
' `npm test && rm -rf build`.',
|
|
168
|
+
'- Everything you allow lasts for the current session only and is forgotten',
|
|
169
|
+
' when it ends. `/new` clears it immediately.',
|
|
89
170
|
'- If an approved `sudo` command needs a password, the TUI shows the exact',
|
|
90
171
|
' command and keeps the password masked and local.',
|
|
91
172
|
'- `-y` / `--yes` at startup auto-approves every shell command and file',
|
|
@@ -94,24 +175,34 @@ const HELP_MARKDOWN = [
|
|
|
94
175
|
' jobs after the same approval as any other command. Background output',
|
|
95
176
|
' stays quiet once the model has responded; the footer shows only a compact',
|
|
96
177
|
' shell-running indicator, `/jobs` lists, inspects, and kills jobs, and',
|
|
97
|
-
' killed jobs disappear immediately.
|
|
98
|
-
'
|
|
178
|
+
' killed jobs disappear immediately. While a turn is running the indicator',
|
|
179
|
+
' points at Ctrl+B instead, which opens the same picker without cancelling',
|
|
180
|
+
' or interrupting the turn. Every job is killed when the session ends.',
|
|
99
181
|
'- File and shell operations are confined to the target repo root.',
|
|
100
182
|
'- Sensitive directories (`.git`, `node_modules`, build output) are',
|
|
101
|
-
'
|
|
183
|
+
' excluded from search and listing.',
|
|
102
184
|
'',
|
|
103
185
|
'## Troubleshooting',
|
|
104
186
|
'',
|
|
105
|
-
'- "Not logged in" → run `ai login`.',
|
|
106
187
|
'- Auth or permission errors → run `ai whoami` to confirm the signed-in',
|
|
107
188
|
' account.',
|
|
108
189
|
'- Usage or quota errors → run `ai --usage`.',
|
|
109
|
-
'- Signed in with the wrong
|
|
110
|
-
' the account you intended to use.',
|
|
190
|
+
'- Signed in with the wrong account → `/logout` (or `ai logout`), then run',
|
|
191
|
+
' `ai` and sign in as the account you intended to use.',
|
|
192
|
+
'- The browser did not open, or opened on the wrong machine → open the URL',
|
|
193
|
+
' printed on the sign-in screen anywhere you like, choose "On another',
|
|
194
|
+
' computer" on that page, and paste the code back into the terminal.',
|
|
111
195
|
'- A local session was used with a different sign-in → sign in with the',
|
|
112
196
|
' account you used for that session or start a new session.',
|
|
113
|
-
'-
|
|
114
|
-
'
|
|
197
|
+
'- The connection drops mid-turn → nothing to do: the CLI reconnects to the',
|
|
198
|
+
' same turn up to three times, ten seconds apart, without running anything',
|
|
199
|
+
' twice. Esc stops it. If it cannot reconnect it says so; some actions may',
|
|
200
|
+
' already have completed, and a queued message waits in the composer.',
|
|
201
|
+
' If the service restarted mid-turn, the turn continues from the steps that',
|
|
202
|
+
' had finished. Starting `ai` while the service is briefly unavailable waits',
|
|
203
|
+
' the same way.',
|
|
204
|
+
'- For anything else, re-run the command and file the printed error',
|
|
205
|
+
' message with `/report` — there is no client-side debug mode by design.',
|
|
115
206
|
].join('\n');
|
|
116
207
|
export function formatAboutCard() {
|
|
117
208
|
return [
|
package/dist/src/patcher.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from './colors.js';
|
|
2
|
-
import { chmodSync, closeSync, constants, existsSync, fchmodSync, fstatSync, ftruncateSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, unlinkSync, writeFileSync, } from 'fs';
|
|
2
|
+
import { chmodSync, closeSync, constants, existsSync, fchmodSync, fstatSync, ftruncateSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, realpathSync, unlinkSync, writeFileSync, } from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { createInterface } from 'readline';
|
|
5
5
|
import { runCommand } from './executor.js';
|
|
@@ -271,22 +271,61 @@ export function writeProjectFileBuffer(rootDir, filePath, content) {
|
|
|
271
271
|
}
|
|
272
272
|
return { absPath, changed: true };
|
|
273
273
|
}
|
|
274
|
+
const MAX_DELETED_CONTENT_BYTES = 64 * 1024;
|
|
275
|
+
const DELETED_BINARY_SNIFF_BYTES = 4096;
|
|
276
|
+
function headHasNulByte(absPath) {
|
|
277
|
+
const chunk = Buffer.allocUnsafe(DELETED_BINARY_SNIFF_BYTES);
|
|
278
|
+
let fd = null;
|
|
279
|
+
try {
|
|
280
|
+
fd = openSync(absPath, 'r');
|
|
281
|
+
const read = readSync(fd, chunk, 0, chunk.length, 0);
|
|
282
|
+
return read > 0 && chunk.subarray(0, read).includes(0);
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
finally {
|
|
288
|
+
if (fd !== null)
|
|
289
|
+
closeSync(fd);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
274
292
|
export function deleteProjectFile(rootDir, filePath) {
|
|
275
293
|
const absPath = resolveProjectPath(rootDir, filePath);
|
|
276
294
|
if (!existsSync(absPath)) {
|
|
277
295
|
return { deleted: false, absPath };
|
|
278
296
|
}
|
|
297
|
+
let bytes;
|
|
279
298
|
let content;
|
|
299
|
+
let contentOmitted;
|
|
280
300
|
try {
|
|
281
|
-
|
|
282
|
-
|
|
301
|
+
const stat = lstatSync(absPath);
|
|
302
|
+
if (!stat.isSymbolicLink()) {
|
|
303
|
+
bytes = stat.size;
|
|
304
|
+
if (stat.size > MAX_DELETED_CONTENT_BYTES) {
|
|
305
|
+
contentOmitted = headHasNulByte(absPath) ? 'binary' : 'too_large';
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
const buffer = readFileSync(absPath);
|
|
309
|
+
if (buffer.includes(0))
|
|
310
|
+
contentOmitted = 'binary';
|
|
311
|
+
else
|
|
312
|
+
content = buffer.toString('utf-8');
|
|
313
|
+
}
|
|
283
314
|
}
|
|
284
315
|
}
|
|
285
316
|
catch {
|
|
317
|
+
bytes = undefined;
|
|
286
318
|
content = undefined;
|
|
319
|
+
contentOmitted = undefined;
|
|
287
320
|
}
|
|
288
321
|
unlinkSync(absPath);
|
|
289
|
-
return {
|
|
322
|
+
return {
|
|
323
|
+
deleted: true,
|
|
324
|
+
absPath,
|
|
325
|
+
...(bytes === undefined ? {} : { bytes }),
|
|
326
|
+
...(content === undefined ? {} : { content }),
|
|
327
|
+
...(contentOmitted === undefined ? {} : { contentOmitted }),
|
|
328
|
+
};
|
|
290
329
|
}
|
|
291
330
|
export function readProjectFile(rootDir, filePath) {
|
|
292
331
|
const absPath = resolveProjectPath(rootDir, filePath);
|