@thegitai/cli 1.0.0-preview.44 → 1.0.0-preview.46
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 +12 -6
- package/dist/src/agent-mode.js +1 -4
- package/dist/src/help-text.js +27 -8
- package/dist/src/ui/repl.js +5 -62
- package/dist/src/ui/slash-commands.js +121 -0
- package/dist/src/ui/tui/build-frame.js +18 -51
- package/dist/src/ui/tui/shell-input.js +8 -6
- package/dist/src/ui/tui/user-input.js +38 -13
- package/dist/src/update-check.js +92 -8
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -17,10 +17,10 @@ Supports Node.js 22 and newer. Node.js 24 is recommended.
|
|
|
17
17
|
## Updates
|
|
18
18
|
|
|
19
19
|
`ai` checks for a newer release each time it starts, and offers to install it
|
|
20
|
-
for you. TheGitAI
|
|
21
|
-
|
|
22
|
-
you to start `ai` again, so the whole session runs
|
|
23
|
-
**Skip** to go on with the version you have. Skipping lasts for that session
|
|
20
|
+
for you. TheGitAI works best on the latest version, and an older CLI may not
|
|
21
|
+
work optimally. **Update now** is already selected, so Enter takes it — the CLI
|
|
22
|
+
installs the update and asks you to start `ai` again, so the whole session runs
|
|
23
|
+
one version — while ↑/↓ moves to **Skip** to go on with the version you have. Skipping lasts for that session
|
|
24
24
|
only; the next start asks again. Ctrl+C quits without installing anything.
|
|
25
25
|
|
|
26
26
|
## Usage
|
|
@@ -78,12 +78,18 @@ The agent can pause its current turn to ask up to four related questions in one
|
|
|
78
78
|
form. Use **↑ / ↓**, the displayed option number, or **Enter** to choose, and
|
|
79
79
|
**← / →** to move between questions. **Something else / add details** is the
|
|
80
80
|
numbered final option; focus it and type, paste, or press **Enter** to add a
|
|
81
|
-
single-line note
|
|
82
|
-
|
|
81
|
+
single-line note; **Enter** commits it and moves on the way any other choice
|
|
82
|
+
does — to the next question, or to **Submit answers** on the last one — without
|
|
83
|
+
submitting the form. On single-select questions it is mutually exclusive with
|
|
84
|
+
the listed choices, and **↑ / ↓** leaves its editor when the note is empty.
|
|
83
85
|
Multi-select questions toggle choices. The last question has an explicit
|
|
84
86
|
**Submit answers** row. **Esc** closes an open note first and otherwise cancels
|
|
85
87
|
the form; **Ctrl+C** cancels the whole turn.
|
|
86
88
|
|
|
89
|
+
Only the option you are on shows its description, so the whole form — every
|
|
90
|
+
option, the note row, **Submit answers**, and the key hints — stays on screen
|
|
91
|
+
without scrolling. Move between options to read the others.
|
|
92
|
+
|
|
87
93
|
Answers return to the same turn, and completed question/answer records remain
|
|
88
94
|
readable when the session is resumed. Default, Bypass all permissions, and Plan
|
|
89
95
|
modes all support the form; Bypass all permissions does not choose answers for
|
package/dist/src/agent-mode.js
CHANGED
|
@@ -148,12 +148,9 @@ export function agentModeLabel(mode) {
|
|
|
148
148
|
return 'Bypass all permissions';
|
|
149
149
|
if (mode === 'plan')
|
|
150
150
|
return 'Plan · read-only';
|
|
151
|
-
return 'Default ·
|
|
151
|
+
return 'Default · Ask for Approval';
|
|
152
152
|
}
|
|
153
153
|
export function agentModeLabelTiers(mode) {
|
|
154
|
-
if (mode === 'default') {
|
|
155
|
-
return [agentModeLabel(mode), 'Default · asks to edit & run'];
|
|
156
|
-
}
|
|
157
154
|
return [agentModeLabel(mode)];
|
|
158
155
|
}
|
|
159
156
|
export function agentModeShortLabel(mode) {
|
package/dist/src/help-text.js
CHANGED
|
@@ -46,10 +46,12 @@ const HELP_MARKDOWN = [
|
|
|
46
46
|
'## Updates',
|
|
47
47
|
'',
|
|
48
48
|
'- On start, `ai` says when a newer release is published and offers to install',
|
|
49
|
-
' it. TheGitAI
|
|
50
|
-
'
|
|
51
|
-
'-
|
|
52
|
-
'
|
|
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.',
|
|
53
55
|
'- Skipping starts the session on the version you have and asks again next',
|
|
54
56
|
' time. A release cannot be silenced for good — that is what keeps a broken',
|
|
55
57
|
' combination from lasting.',
|
|
@@ -64,10 +66,10 @@ const HELP_MARKDOWN = [
|
|
|
64
66
|
'',
|
|
65
67
|
'## Modes',
|
|
66
68
|
'',
|
|
67
|
-
'- Default ·
|
|
68
|
-
'
|
|
69
|
-
'
|
|
70
|
-
'
|
|
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.',
|
|
71
73
|
'- Bypass all permissions — approves every command, file edit, deletion and',
|
|
72
74
|
' browser action for the rest of the session, without asking. Shown in red',
|
|
73
75
|
' in the footer. Blocked commands stay blocked.',
|
|
@@ -103,8 +105,25 @@ const HELP_MARKDOWN = [
|
|
|
103
105
|
? ['- On Mac laptops without dedicated keys, **Home/End** are `Fn+←` / `Fn+→`.']
|
|
104
106
|
: []),
|
|
105
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
|
+
'',
|
|
106
120
|
'## Chat commands',
|
|
107
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.',
|
|
108
127
|
'- `/help` — show this help',
|
|
109
128
|
'- `/about` — show version and platform info',
|
|
110
129
|
'- `/report` — file a bug privately through the TheGitAI website in your',
|
package/dist/src/ui/repl.js
CHANGED
|
@@ -7,7 +7,8 @@ import { backendFor, createNotificationQueue, } from './tui/desktop-notification
|
|
|
7
7
|
import { readPreferences, writePreferences } from '../preferences.js';
|
|
8
8
|
import { createTerminalTitleController } from './tui/terminal-title.js';
|
|
9
9
|
import { captureTerminalWrites, releaseTerminalWrites, } from './tui/terminal-writes.js';
|
|
10
|
-
|
|
10
|
+
import { getInputCommandToken, getSlashCommandSuggestions, SLASH_COMMANDS, slashCommandMatchesToken, } from './slash-commands.js';
|
|
11
|
+
export { getInputCommandToken, getSlashCommandSuggestions, SLASH_COMMANDS, slashCommandMatchesToken, };
|
|
11
12
|
import { composerInputWidth, frameContentWidth } from './tui/composer-layout.js';
|
|
12
13
|
import { agentModeLabel, nextAgentMode, } from '../agent-mode.js';
|
|
13
14
|
import { chat, models } from '../api/index.js';
|
|
@@ -110,52 +111,6 @@ export function buildTurnFailureHintEntries(cancelled) {
|
|
|
110
111
|
},
|
|
111
112
|
];
|
|
112
113
|
}
|
|
113
|
-
export const SLASH_COMMANDS = [
|
|
114
|
-
{
|
|
115
|
-
command: '/help',
|
|
116
|
-
description: 'Show available chat commands',
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
command: '/about',
|
|
120
|
-
description: 'Show version and platform info',
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
command: '/report',
|
|
124
|
-
description: 'Report a bug privately on the TheGitAI website',
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
command: '/usage',
|
|
128
|
-
description: 'Show account usage percentage and reset times',
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
command: '/model',
|
|
132
|
-
description: 'Switch the active model',
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
command: '/resume',
|
|
136
|
-
description: 'Open the session picker',
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
command: '/jobs',
|
|
140
|
-
description: 'Background jobs: pick to view output or kill',
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
command: '/notify',
|
|
144
|
-
description: 'Toggle desktop notifications when a turn ends (Ctrl+G)',
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
command: '/new',
|
|
148
|
-
description: 'start a new conversation; this session remains saved',
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
command: '/logout',
|
|
152
|
-
description: 'Sign out and quit',
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
command: '/exit',
|
|
156
|
-
description: 'Quit the current session',
|
|
157
|
-
},
|
|
158
|
-
];
|
|
159
114
|
export function composerAttachmentsAfterCancel(queuedAttachments, inFlightAttachments) {
|
|
160
115
|
const seen = new Set();
|
|
161
116
|
const kept = [];
|
|
@@ -1255,16 +1210,6 @@ export const EXIT_CTRL_C_CONFIRM_MS = 3000;
|
|
|
1255
1210
|
export function isExitConfirmActive(exitConfirmUntil, now = Date.now()) {
|
|
1256
1211
|
return exitConfirmUntil != null && now < exitConfirmUntil;
|
|
1257
1212
|
}
|
|
1258
|
-
export function getInputCommandToken(input) {
|
|
1259
|
-
const trimmed = String(input ?? '').trim();
|
|
1260
|
-
if (!trimmed.startsWith('/'))
|
|
1261
|
-
return '';
|
|
1262
|
-
const firstSpaceIndex = trimmed.indexOf(' ');
|
|
1263
|
-
const token = firstSpaceIndex === -1 ? trimmed : trimmed.slice(0, firstSpaceIndex);
|
|
1264
|
-
if (token.indexOf('/', 1) !== -1)
|
|
1265
|
-
return '';
|
|
1266
|
-
return token;
|
|
1267
|
-
}
|
|
1268
1213
|
function shouldShowCommandPalette(state) {
|
|
1269
1214
|
const trimmed = String(state.input ?? '').trim();
|
|
1270
1215
|
return (!state.busy &&
|
|
@@ -1272,8 +1217,9 @@ function shouldShowCommandPalette(state) {
|
|
|
1272
1217
|
!state.modelPickerOpen &&
|
|
1273
1218
|
!state.resumePickerOpen &&
|
|
1274
1219
|
trimmed.startsWith('/') &&
|
|
1275
|
-
!
|
|
1276
|
-
getInputCommandToken(trimmed) !== ''
|
|
1220
|
+
!String(state.input ?? '').includes(' ') &&
|
|
1221
|
+
getInputCommandToken(trimmed) !== '' &&
|
|
1222
|
+
getSlashCommandSuggestions(trimmed).length > 0);
|
|
1277
1223
|
}
|
|
1278
1224
|
export function shouldRemountLiveFrameForComposerInputChange(current, nextInput) {
|
|
1279
1225
|
const currentShowsCommands = shouldShowCommandPalette(current);
|
|
@@ -1296,9 +1242,6 @@ export function applySlashCommandSuggestion(currentInput, suggestion) {
|
|
|
1296
1242
|
input: `${before}${suggestion.command}${after}`,
|
|
1297
1243
|
};
|
|
1298
1244
|
}
|
|
1299
|
-
export function isExactSlashCommandToken(token) {
|
|
1300
|
-
return SLASH_COMMANDS.some((command) => command.command === token);
|
|
1301
|
-
}
|
|
1302
1245
|
export function insertAtCursor(state, text) {
|
|
1303
1246
|
const before = state.input.slice(0, state.cursor);
|
|
1304
1247
|
const after = state.input.slice(state.cursor);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
export const SLASH_COMMANDS = [
|
|
2
|
+
{ command: '/model', description: 'Switch the active model' },
|
|
3
|
+
{ command: '/help', description: 'Show available chat commands' },
|
|
4
|
+
{ command: '/usage', description: 'Show account usage percentage and reset times' },
|
|
5
|
+
{
|
|
6
|
+
command: '/resume',
|
|
7
|
+
description: 'Open the session picker to resume a previous session',
|
|
8
|
+
},
|
|
9
|
+
{ command: '/new', description: 'start a new conversation; this session remains saved' },
|
|
10
|
+
{ command: '/jobs', description: 'Background jobs: pick to view output or kill' },
|
|
11
|
+
{
|
|
12
|
+
command: '/notify',
|
|
13
|
+
description: 'Toggle desktop notifications when a turn ends (Ctrl+G)',
|
|
14
|
+
},
|
|
15
|
+
{ command: '/report', description: 'Report a bug privately on the TheGitAI website' },
|
|
16
|
+
{ command: '/about', description: 'Show version and platform info' },
|
|
17
|
+
{ command: '/logout', description: 'Sign out and quit' },
|
|
18
|
+
{ command: '/exit', description: 'Quit the current session', aliases: ['/quit'] },
|
|
19
|
+
];
|
|
20
|
+
export const SLASH_COMMAND_SUGGESTION_LIMIT = 5;
|
|
21
|
+
export function getInputCommandToken(input) {
|
|
22
|
+
const trimmed = String(input ?? '').trimStart();
|
|
23
|
+
const match = trimmed.match(/^\/[^\s]*/);
|
|
24
|
+
const token = match?.[0] ?? '';
|
|
25
|
+
if (token.indexOf('/', 1) !== -1)
|
|
26
|
+
return '';
|
|
27
|
+
return token;
|
|
28
|
+
}
|
|
29
|
+
export function slashCommandMatchesToken(option, token) {
|
|
30
|
+
return option.command === token || (option.aliases?.includes(token) ?? false);
|
|
31
|
+
}
|
|
32
|
+
const MATCH_EXACT = 0;
|
|
33
|
+
const MATCH_PREFIX = 100;
|
|
34
|
+
const MATCH_OVERTYPED = 200;
|
|
35
|
+
const MATCH_TYPO = 300;
|
|
36
|
+
const MATCH_IN_NAME = 400;
|
|
37
|
+
const MATCH_IN_DESCRIPTION = 500;
|
|
38
|
+
const MATCH_NONE = 600;
|
|
39
|
+
function typoBudget(body) {
|
|
40
|
+
if (body.length <= 3)
|
|
41
|
+
return 0;
|
|
42
|
+
if (body.length <= 5)
|
|
43
|
+
return 1;
|
|
44
|
+
return 2;
|
|
45
|
+
}
|
|
46
|
+
function editDistanceWithin(a, b, max) {
|
|
47
|
+
if (a === b)
|
|
48
|
+
return 0;
|
|
49
|
+
if (max <= 0)
|
|
50
|
+
return null;
|
|
51
|
+
if (Math.abs(a.length - b.length) > max)
|
|
52
|
+
return null;
|
|
53
|
+
const width = b.length + 1;
|
|
54
|
+
let beforePrevious = [];
|
|
55
|
+
let previous = Array.from({ length: width }, (_, index) => index);
|
|
56
|
+
for (let i = 1; i <= a.length; i += 1) {
|
|
57
|
+
const current = new Array(width);
|
|
58
|
+
current[0] = i;
|
|
59
|
+
let best = i;
|
|
60
|
+
for (let j = 1; j < width; j += 1) {
|
|
61
|
+
const substitution = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
62
|
+
let cost = Math.min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + substitution);
|
|
63
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
64
|
+
cost = Math.min(cost, beforePrevious[j - 2] + 1);
|
|
65
|
+
}
|
|
66
|
+
current[j] = cost;
|
|
67
|
+
if (cost < best)
|
|
68
|
+
best = cost;
|
|
69
|
+
}
|
|
70
|
+
if (best > max)
|
|
71
|
+
return null;
|
|
72
|
+
beforePrevious = previous;
|
|
73
|
+
previous = current;
|
|
74
|
+
}
|
|
75
|
+
const distance = previous[b.length];
|
|
76
|
+
return distance <= max ? distance : null;
|
|
77
|
+
}
|
|
78
|
+
function scoreSlashCommand(option, token) {
|
|
79
|
+
const command = option.command;
|
|
80
|
+
const body = token.slice(1);
|
|
81
|
+
if (!body)
|
|
82
|
+
return MATCH_PREFIX;
|
|
83
|
+
const spellings = [command, ...(option.aliases ?? [])];
|
|
84
|
+
if (slashCommandMatchesToken(option, token))
|
|
85
|
+
return MATCH_EXACT;
|
|
86
|
+
if (spellings.some((spelling) => spelling.startsWith(token)))
|
|
87
|
+
return MATCH_PREFIX;
|
|
88
|
+
const overtyped = spellings.find((spelling) => token.startsWith(spelling));
|
|
89
|
+
if (overtyped) {
|
|
90
|
+
return MATCH_OVERTYPED + Math.min(token.length - overtyped.length, 99);
|
|
91
|
+
}
|
|
92
|
+
const distances = spellings
|
|
93
|
+
.map((spelling) => editDistanceWithin(body, spelling.slice(1), typoBudget(body)))
|
|
94
|
+
.filter((distance) => distance !== null);
|
|
95
|
+
if (distances.length)
|
|
96
|
+
return MATCH_TYPO + Math.min(...distances);
|
|
97
|
+
if (command.includes(body))
|
|
98
|
+
return MATCH_IN_NAME;
|
|
99
|
+
if (option.description.toLowerCase().includes(body))
|
|
100
|
+
return MATCH_IN_DESCRIPTION;
|
|
101
|
+
return MATCH_NONE;
|
|
102
|
+
}
|
|
103
|
+
function rankSlashCommands(input) {
|
|
104
|
+
const token = getInputCommandToken(input).toLowerCase();
|
|
105
|
+
if (!token)
|
|
106
|
+
return [];
|
|
107
|
+
return SLASH_COMMANDS.map((option, order) => ({
|
|
108
|
+
option,
|
|
109
|
+
order,
|
|
110
|
+
score: scoreSlashCommand(option, token),
|
|
111
|
+
}))
|
|
112
|
+
.filter((entry) => entry.score < MATCH_NONE)
|
|
113
|
+
.sort((a, b) => a.score - b.score || a.order - b.order)
|
|
114
|
+
.map((entry) => entry.option);
|
|
115
|
+
}
|
|
116
|
+
export function getSlashCommandSuggestions(input) {
|
|
117
|
+
return rankSlashCommands(input).slice(0, SLASH_COMMAND_SUGGESTION_LIMIT);
|
|
118
|
+
}
|
|
119
|
+
export function getHiddenSlashCommandCount(input) {
|
|
120
|
+
return Math.max(0, rankSlashCommands(input).length - SLASH_COMMAND_SUGGESTION_LIMIT);
|
|
121
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { agentModeColor, agentModeLabelTiers, agentModeShortLabel, } from '../../agent-mode.js';
|
|
2
2
|
import { singleLinePreview, truncate } from '../../utils.js';
|
|
3
3
|
import { formatClientTokenUsage } from '../repl.js';
|
|
4
|
+
import { getHiddenSlashCommandCount, getSlashCommandSuggestions, } from '../slash-commands.js';
|
|
4
5
|
import { renderFormattedBodyLines, renderPreformattedBodyLines, } from './markdown-render.js';
|
|
5
6
|
import { displayWidth, line, padToWidth, plainLine, sliceToWidth, span, wrapText, } from './text.js';
|
|
6
7
|
import { isDarkTerminalBackground, mutedColor, mutedStyle } from './terminal-theme.js';
|
|
@@ -44,6 +45,7 @@ export function footerNotificationHint(status) {
|
|
|
44
45
|
}
|
|
45
46
|
const FOOTER_CYCLE_HINT = ' (shift+tab to cycle)';
|
|
46
47
|
const OVERLAY_PANEL_MAX_WIDTH = 86;
|
|
48
|
+
const USER_INPUT_PANEL_MAX_WIDTH = 120;
|
|
47
49
|
const OVERLAY_PANEL_MARGIN_LINES = 2;
|
|
48
50
|
const OVERLAY_PANEL_MARGIN_MIN_ROWS = 30;
|
|
49
51
|
const OVERLAY_BORDER_COLOR = 'yellow';
|
|
@@ -188,19 +190,6 @@ export function formatPromptDirectoryLabel(projectRoot, homeDir = process.env.HO
|
|
|
188
190
|
export function formatPromptSessionIdLabel(showSessionId, sessionId) {
|
|
189
191
|
return showSessionId ? String(sessionId ?? '').trim() : '';
|
|
190
192
|
}
|
|
191
|
-
const CLIENT_SLASH_COMMANDS = [
|
|
192
|
-
{ command: '/help', description: 'Show available chat commands' },
|
|
193
|
-
{ command: '/about', description: 'Show version and platform info' },
|
|
194
|
-
{ command: '/usage', description: 'Show account usage percentage and reset times' },
|
|
195
|
-
{ command: '/model', description: 'Switch the active model' },
|
|
196
|
-
{ command: '/resume', description: 'Open the session picker to resume a previous session' },
|
|
197
|
-
{ command: '/jobs', description: 'Background jobs: pick to view output or kill' },
|
|
198
|
-
{ command: '/notify', description: 'Toggle desktop notifications when a turn ends (Ctrl+G)' },
|
|
199
|
-
{ command: '/report', description: 'Report a bug privately on the TheGitAI website' },
|
|
200
|
-
{ command: '/new', description: 'start a new conversation; this session remains saved' },
|
|
201
|
-
{ command: '/logout', description: 'Sign out and quit' },
|
|
202
|
-
{ command: '/exit', description: 'Quit the current session' },
|
|
203
|
-
];
|
|
204
193
|
function buildModelPickerOptions(currentModelId, serverModels) {
|
|
205
194
|
return serverModels.map((model) => ({
|
|
206
195
|
id: model.id,
|
|
@@ -212,38 +201,7 @@ function buildModelPickerOptions(currentModelId, serverModels) {
|
|
|
212
201
|
note: model.description,
|
|
213
202
|
}));
|
|
214
203
|
}
|
|
215
|
-
|
|
216
|
-
const trimmed = String(input ?? '').trimStart();
|
|
217
|
-
const match = trimmed.match(/^\/[^\s]*/);
|
|
218
|
-
const token = match?.[0] ?? '';
|
|
219
|
-
if (token.indexOf('/', 1) !== -1)
|
|
220
|
-
return '';
|
|
221
|
-
return token;
|
|
222
|
-
}
|
|
223
|
-
function scoreSlashCommand(option, token) {
|
|
224
|
-
if (option.command === token)
|
|
225
|
-
return 0;
|
|
226
|
-
if (option.command.startsWith(token))
|
|
227
|
-
return 1;
|
|
228
|
-
if (option.command.includes(token.slice(1)))
|
|
229
|
-
return 2;
|
|
230
|
-
if (option.description.toLowerCase().includes(token.slice(1)))
|
|
231
|
-
return 3;
|
|
232
|
-
return 4;
|
|
233
|
-
}
|
|
234
|
-
export function getSlashCommandSuggestions(input) {
|
|
235
|
-
const token = getInputCommandToken(input).toLowerCase();
|
|
236
|
-
if (!token)
|
|
237
|
-
return [];
|
|
238
|
-
const ranked = [...CLIENT_SLASH_COMMANDS].sort((a, b) => {
|
|
239
|
-
const scoreDiff = scoreSlashCommand(a, token) - scoreSlashCommand(b, token);
|
|
240
|
-
if (scoreDiff !== 0)
|
|
241
|
-
return scoreDiff;
|
|
242
|
-
return a.command.localeCompare(b.command);
|
|
243
|
-
});
|
|
244
|
-
const matches = ranked.filter((option) => scoreSlashCommand(option, token) < 4);
|
|
245
|
-
return (matches.length ? matches : ranked).slice(0, 5);
|
|
246
|
-
}
|
|
204
|
+
export { getSlashCommandSuggestions };
|
|
247
205
|
function formatModelLabel(modelId, serverModels) {
|
|
248
206
|
return serverModels.find((model) => model.id === modelId)?.label ?? 'Unknown model';
|
|
249
207
|
}
|
|
@@ -817,8 +775,8 @@ function overlayPanelContentBudget(height) {
|
|
|
817
775
|
const margins = overlayPanelMarginLineCount(height) * 2;
|
|
818
776
|
return Math.max(1, Math.floor(height) - margins - 2);
|
|
819
777
|
}
|
|
820
|
-
function buildOverlayPanel(rows, width, color, height = Number.POSITIVE_INFINITY) {
|
|
821
|
-
const panelWidth = Math.max(24, Math.min(width,
|
|
778
|
+
function buildOverlayPanel(rows, width, color, height = Number.POSITIVE_INFINITY, maxWidth = OVERLAY_PANEL_MAX_WIDTH) {
|
|
779
|
+
const panelWidth = Math.max(24, Math.min(width, maxWidth));
|
|
822
780
|
const innerWidth = Math.max(1, panelWidth - 4);
|
|
823
781
|
const margin = Array.from({
|
|
824
782
|
length: overlayPanelMarginLineCount(height),
|
|
@@ -1060,7 +1018,7 @@ function commandPaletteItemLines(option, selected, innerWidth) {
|
|
|
1060
1018
|
modelPickerPanelSideLine(line(span(`${MODEL_PICKER_META_INDENT}${option.description}`, { color: 'gray' })), innerWidth),
|
|
1061
1019
|
];
|
|
1062
1020
|
}
|
|
1063
|
-
function buildCommandPalettePanel(suggestions, selectedIndex, width) {
|
|
1021
|
+
function buildCommandPalettePanel(suggestions, selectedIndex, hiddenCount, width) {
|
|
1064
1022
|
const panelWidth = Math.max(28, Math.min(width, MODEL_PICKER_PANEL_MAX_WIDTH));
|
|
1065
1023
|
const innerWidth = Math.max(1, panelWidth - 4);
|
|
1066
1024
|
const margin = Array.from({ length: MODEL_PICKER_PANEL_MARGIN_LINES }, () => plainLine(''));
|
|
@@ -1079,6 +1037,12 @@ function buildCommandPalettePanel(suggestions, selectedIndex, width) {
|
|
|
1079
1037
|
body.push(commandPaletteSeparatorLine(innerWidth));
|
|
1080
1038
|
}
|
|
1081
1039
|
});
|
|
1040
|
+
if (hiddenCount > 0) {
|
|
1041
|
+
body.push(commandPaletteSeparatorLine(innerWidth), modelPickerPanelSideLine(plainLine(` ${hiddenCount} more • keep typing to narrow`, {
|
|
1042
|
+
color: 'gray',
|
|
1043
|
+
dim: true,
|
|
1044
|
+
}), innerWidth));
|
|
1045
|
+
}
|
|
1082
1046
|
body.push(modelPickerPanelSideLine(plainLine(''), innerWidth), modelPickerPanelSideLine(line(span('─'.repeat(innerWidth), { color: MODEL_PICKER_BORDER_COLOR })), innerWidth), modelPickerPanelSideLine(plainLine('↑/↓ choose • Tab or Enter accept • Esc cancel', { color: 'gray' }), innerWidth), plainLine(`╰${'─'.repeat(panelWidth - 2)}╯`, { color: MODEL_PICKER_BORDER_COLOR }));
|
|
1083
1047
|
return [...margin, ...body, ...margin];
|
|
1084
1048
|
}
|
|
@@ -1104,6 +1068,9 @@ export function buildWorkingClockLine(state, elapsedSeconds) {
|
|
|
1104
1068
|
export function approvalPanelInnerWidth(width) {
|
|
1105
1069
|
return Math.max(1, Math.max(24, Math.min(width, OVERLAY_PANEL_MAX_WIDTH)) - 4);
|
|
1106
1070
|
}
|
|
1071
|
+
export function userInputPanelInnerWidth(width) {
|
|
1072
|
+
return Math.max(1, Math.max(24, Math.min(width, USER_INPUT_PANEL_MAX_WIDTH)) - 4);
|
|
1073
|
+
}
|
|
1107
1074
|
export function approvalPaddingEnabled(height) {
|
|
1108
1075
|
return height >= APPROVAL_PADDING_MIN_ROWS;
|
|
1109
1076
|
}
|
|
@@ -1212,7 +1179,7 @@ function buildOverlayLines(state, width, height, nowMs) {
|
|
|
1212
1179
|
if (height < 3) {
|
|
1213
1180
|
return [];
|
|
1214
1181
|
}
|
|
1215
|
-
return buildOverlayPanel(buildUserInputOverlayLines(state.userInputPrompt,
|
|
1182
|
+
return buildOverlayPanel(buildUserInputOverlayLines(state.userInputPrompt, userInputPanelInnerWidth(width), overlayPanelContentBudget(height)), width, USER_INPUT_BORDER_COLOR, height, USER_INPUT_PANEL_MAX_WIDTH);
|
|
1216
1183
|
}
|
|
1217
1184
|
if (state.approvalPrompt) {
|
|
1218
1185
|
const prompt = state.approvalPrompt;
|
|
@@ -1325,7 +1292,7 @@ function buildOverlayLines(state, width, height, nowMs) {
|
|
|
1325
1292
|
state.input.trimStart().startsWith('/') &&
|
|
1326
1293
|
!state.input.includes(' ') &&
|
|
1327
1294
|
suggestions.length > 0) {
|
|
1328
|
-
lines.push(...buildCommandPalettePanel(suggestions, state.commandCursor, width));
|
|
1295
|
+
lines.push(...buildCommandPalettePanel(suggestions, state.commandCursor, getHiddenSlashCommandCount(state.input), width));
|
|
1329
1296
|
}
|
|
1330
1297
|
return lines;
|
|
1331
1298
|
}
|
|
@@ -1337,7 +1304,7 @@ export function userInputViewportForFrame(state, cols, rows, spinnerFrame, elaps
|
|
|
1337
1304
|
const liveRows = buildLiveLines(state, contentWidth, spinnerFrame, elapsedSeconds, nowMs).length;
|
|
1338
1305
|
const overlayHeight = Math.max(0, rows - liveRows - composerFooterLines(state, contentWidth).length);
|
|
1339
1306
|
return {
|
|
1340
|
-
width:
|
|
1307
|
+
width: userInputPanelInnerWidth(contentWidth),
|
|
1341
1308
|
maxRows: overlayPanelContentBudget(overlayHeight),
|
|
1342
1309
|
};
|
|
1343
1310
|
}
|
|
@@ -3,7 +3,7 @@ import { moveComposerCursorVertically } from './composer-layout.js';
|
|
|
3
3
|
import { readClipboardImage, readClipboardText } from '../../core/clipboard.js';
|
|
4
4
|
import { MAX_IMAGES_PER_MESSAGE, MAX_TOTAL_IMAGE_BYTES_PER_MESSAGE, approximateBase64DecodedBytes, totalAttachmentBytes, } from '../../core/image-limits.js';
|
|
5
5
|
import { tryCacheAttachmentBytes } from '../../core/session-image-store.js';
|
|
6
|
-
import { applySlashCommandSuggestion, buildModelPickerOptions, deleteAtCursor, deleteBeforeCursor, getInputCommandToken, getNextApprovalCursor, getNextModelPickerIndex, getSlashCommandSuggestions, insertAtCursor,
|
|
6
|
+
import { applySlashCommandSuggestion, buildModelPickerOptions, deleteAtCursor, deleteBeforeCursor, getInputCommandToken, getNextApprovalCursor, getNextModelPickerIndex, getSlashCommandSuggestions, insertAtCursor, navigatePromptHistory, shouldRemountLiveFrameForComposerInputChange, slashCommandMatchesToken, } from '../repl.js';
|
|
7
7
|
import { buildPastePlaceholder, shouldCollapsePaste, } from '../paste-collapse.js';
|
|
8
8
|
import { handleUserInputPromptEvent, } from './user-input.js';
|
|
9
9
|
const APPROVAL_PREVIEW_PAGE_ROWS = 3;
|
|
@@ -56,7 +56,9 @@ function shouldShowCommandPalette(state) {
|
|
|
56
56
|
return false;
|
|
57
57
|
}
|
|
58
58
|
const token = getInputCommandToken(state.input);
|
|
59
|
-
|
|
59
|
+
if (!token.startsWith('/') || state.input.includes(' '))
|
|
60
|
+
return false;
|
|
61
|
+
return getSlashCommandSuggestions(state.input).length > 0;
|
|
60
62
|
}
|
|
61
63
|
function insertPastedText(store, handlers, text) {
|
|
62
64
|
const current = store.getState();
|
|
@@ -549,10 +551,10 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
549
551
|
}
|
|
550
552
|
if (key.returnKey) {
|
|
551
553
|
const token = getInputCommandToken(state.input);
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
554
|
+
const suggestion = commandSuggestions[Math.min(state.commandCursor, commandSuggestions.length - 1)];
|
|
555
|
+
if (commandPaletteActive &&
|
|
556
|
+
suggestion &&
|
|
557
|
+
!slashCommandMatchesToken(suggestion, token)) {
|
|
556
558
|
store.update((current) => {
|
|
557
559
|
const nextInput = applySlashCommandSuggestion(current.input, suggestion);
|
|
558
560
|
prepareForComposerInputChange(current, nextInput.input);
|
|
@@ -139,9 +139,19 @@ function editOpenNote(state, event) {
|
|
|
139
139
|
const question = currentQuestion(state);
|
|
140
140
|
const current = state.customText[question.id] ?? '';
|
|
141
141
|
const cursor = Math.max(0, Math.min(state.noteCursor, current.length));
|
|
142
|
-
if (event.escape
|
|
142
|
+
if (event.escape) {
|
|
143
143
|
return { ...state, noteOpen: false };
|
|
144
144
|
}
|
|
145
|
+
if (event.returnKey) {
|
|
146
|
+
const closed = { ...state, noteOpen: false };
|
|
147
|
+
if (question.multiSelect || !current.trim()) {
|
|
148
|
+
return closed;
|
|
149
|
+
}
|
|
150
|
+
const index = questionIndex(closed);
|
|
151
|
+
return index < closed.questions.length - 1
|
|
152
|
+
? setQuestion(closed, index + 1)
|
|
153
|
+
: setCursor(closed, question, SUBMIT_CURSOR);
|
|
154
|
+
}
|
|
145
155
|
if (event.tab)
|
|
146
156
|
return state;
|
|
147
157
|
if ((event.upArrow || event.downArrow) && !current.trim()) {
|
|
@@ -433,8 +443,10 @@ function buildUserInputContent(state, width) {
|
|
|
433
443
|
}), ...(recommendation
|
|
434
444
|
? [span(recommendation, { color: 'green' })]
|
|
435
445
|
: [])));
|
|
436
|
-
|
|
437
|
-
|
|
446
|
+
if (focused) {
|
|
447
|
+
for (const description of wrapText(option.description, Math.max(8, width - 5))) {
|
|
448
|
+
lines.push(plainLine(` ${description}`, { color: 'gray' }));
|
|
449
|
+
}
|
|
438
450
|
}
|
|
439
451
|
if (state.contentScrollAnchor === option.id) {
|
|
440
452
|
anchorRange = { start: blockStart, end: lines.length - 1 };
|
|
@@ -490,23 +502,38 @@ function buildUserInputContent(state, width) {
|
|
|
490
502
|
if (state.validationMessage) {
|
|
491
503
|
lines.push(plainLine(state.validationMessage, { color: 'yellow' }));
|
|
492
504
|
}
|
|
493
|
-
|
|
494
|
-
? 'Type a single-line note • ↑/↓ moves when empty • Enter saves • Esc closes
|
|
505
|
+
const footer = plainLine(state.noteOpen
|
|
506
|
+
? 'Type a single-line note • ↑/↓ moves when empty • Enter saves • Esc closes'
|
|
495
507
|
: question.multiSelect
|
|
496
|
-
? `↑/↓ move • 1–${otherNumber} choose • Enter toggles/edits • ←/→ questions •
|
|
497
|
-
: `↑/↓ move • 1–${otherNumber} choose • Enter selects/edits • ←/→ questions •
|
|
508
|
+
? `↑/↓ move • 1–${otherNumber} choose • Enter toggles/edits • ←/→ questions • Esc cancels`
|
|
509
|
+
: `↑/↓ move • 1–${otherNumber} choose • Enter selects/edits • ←/→ questions • Esc cancels`, { color: 'gray' });
|
|
498
510
|
return {
|
|
499
511
|
anchorRange,
|
|
512
|
+
footer: clipLineToWidth(footer, width),
|
|
500
513
|
lines: lines.map((row) => clipLineToWidth(row, width)),
|
|
501
514
|
};
|
|
502
515
|
}
|
|
503
516
|
function buildUserInputWindow(state, width, maxRows) {
|
|
504
517
|
const content = buildUserInputContent(state, width);
|
|
505
|
-
if (!Number.isFinite(maxRows)
|
|
506
|
-
return {
|
|
518
|
+
if (!Number.isFinite(maxRows)) {
|
|
519
|
+
return {
|
|
520
|
+
lines: [...content.lines, content.footer],
|
|
521
|
+
maxOffset: 0,
|
|
522
|
+
offset: 0,
|
|
523
|
+
};
|
|
507
524
|
}
|
|
508
525
|
const rowBudget = Math.max(1, Math.floor(maxRows));
|
|
509
|
-
|
|
526
|
+
if (rowBudget === 1) {
|
|
527
|
+
return { lines: [content.footer], maxOffset: 0, offset: 0 };
|
|
528
|
+
}
|
|
529
|
+
if (content.lines.length <= rowBudget - 1) {
|
|
530
|
+
return {
|
|
531
|
+
lines: [...content.lines, content.footer],
|
|
532
|
+
maxOffset: 0,
|
|
533
|
+
offset: 0,
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
const visibleRowBudget = Math.max(1, rowBudget - 2);
|
|
510
537
|
const maxOffset = Math.max(0, content.lines.length - visibleRowBudget);
|
|
511
538
|
let offset = Math.max(0, Math.min(state.contentScrollOffset, maxOffset));
|
|
512
539
|
const range = content.anchorRange;
|
|
@@ -527,13 +554,11 @@ function buildUserInputWindow(state, width, maxRows) {
|
|
|
527
554
|
offset = Math.max(0, Math.min(offset, maxOffset));
|
|
528
555
|
}
|
|
529
556
|
const visible = content.lines.slice(offset, offset + visibleRowBudget);
|
|
530
|
-
if (rowBudget === 1) {
|
|
531
|
-
return { lines: visible, maxOffset, offset };
|
|
532
|
-
}
|
|
533
557
|
return {
|
|
534
558
|
lines: [
|
|
535
559
|
...visible,
|
|
536
560
|
plainLine(fitText(`Rows ${offset + 1}–${offset + visible.length} of ${content.lines.length} · PgUp/PgDn scroll`, width), { color: 'gray' }),
|
|
561
|
+
content.footer,
|
|
537
562
|
],
|
|
538
563
|
maxOffset,
|
|
539
564
|
offset,
|
package/dist/src/update-check.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { stdin as input, stdout as output } from 'node:process';
|
|
4
|
+
import { emitKeypressEvents } from 'node:readline';
|
|
4
5
|
import readline from 'node:readline/promises';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
6
7
|
import chalk from './colors.js';
|
|
@@ -35,21 +36,55 @@ export function buildUpdateOffer({ currentVersion, latestVersion, moduleDir, })
|
|
|
35
36
|
display: [program, ...args].join(' '),
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
|
-
export
|
|
39
|
+
export const UPDATE_OPTIONS = ['update', 'skip'];
|
|
40
|
+
const UPDATE_KEY_HINT = '↑/↓ choose • Enter accepts • Ctrl+C quits';
|
|
41
|
+
function formatUpdateHeader(offer) {
|
|
39
42
|
return [
|
|
40
43
|
'',
|
|
41
44
|
` ${chalk.yellow('✨ Update available')} — ${chalk.dim(offer.currentVersion)} → ${chalk.bold(offer.latestVersion)}`,
|
|
42
45
|
'',
|
|
43
|
-
' TheGitAI
|
|
44
|
-
'
|
|
46
|
+
' TheGitAI works best with the latest version. TheGitAI may not work',
|
|
47
|
+
' optimally if not updated.',
|
|
45
48
|
'',
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
].join('\n');
|
|
50
|
+
}
|
|
51
|
+
export function formatUpdateOptions(offer, selectedIndex = 0) {
|
|
52
|
+
const rows = [
|
|
53
|
+
{ note: `(runs \`${offer.display}\`)`, text: 'Update now' },
|
|
54
|
+
{ note: '(Agent may not work optimally)', text: 'Skip' },
|
|
55
|
+
];
|
|
56
|
+
return [
|
|
57
|
+
...rows.map(({ note, text }, index) => index === selectedIndex
|
|
58
|
+
? ` ${chalk.cyan(`› ${index + 1}. ${text}`)} ${chalk.dim(note)}`
|
|
59
|
+
: ` ${chalk.dim(`${index + 1}.`)} ${text} ${chalk.dim(note)}`),
|
|
48
60
|
'',
|
|
49
|
-
` ${chalk.dim(
|
|
61
|
+
` ${chalk.dim(UPDATE_KEY_HINT)}`,
|
|
50
62
|
'',
|
|
51
63
|
].join('\n');
|
|
52
64
|
}
|
|
65
|
+
export function formatUpdateScreen(offer, selectedIndex = 0) {
|
|
66
|
+
return `${formatUpdateHeader(offer)}\n${formatUpdateOptions(offer, selectedIndex)}`;
|
|
67
|
+
}
|
|
68
|
+
export function readUpdateKey(key, selectedIndex) {
|
|
69
|
+
const name = key.name ?? '';
|
|
70
|
+
if (key.ctrl && (name === 'c' || name === 'd')) {
|
|
71
|
+
return { kind: 'answer', answer: 'cancel' };
|
|
72
|
+
}
|
|
73
|
+
if (name === 'return' || name === 'enter') {
|
|
74
|
+
return { kind: 'answer', answer: UPDATE_OPTIONS[selectedIndex] ?? 'update' };
|
|
75
|
+
}
|
|
76
|
+
if (name === 'up' || (name === 'tab' && key.sequence === '\u001b[Z')) {
|
|
77
|
+
return { kind: 'select', index: Math.max(selectedIndex - 1, 0) };
|
|
78
|
+
}
|
|
79
|
+
if (name === 'down' || name === 'tab') {
|
|
80
|
+
return { kind: 'select', index: Math.min(selectedIndex + 1, UPDATE_OPTIONS.length - 1) };
|
|
81
|
+
}
|
|
82
|
+
const digit = Number.parseInt(key.sequence ?? '', 10);
|
|
83
|
+
if (Number.isInteger(digit) && digit >= 1 && digit <= UPDATE_OPTIONS.length) {
|
|
84
|
+
return { kind: 'answer', answer: UPDATE_OPTIONS[digit - 1] };
|
|
85
|
+
}
|
|
86
|
+
return { kind: 'ignore' };
|
|
87
|
+
}
|
|
53
88
|
export function readUpdateChoice(answer) {
|
|
54
89
|
const value = answer.trim().toLowerCase();
|
|
55
90
|
if (value === '' || value === '1' || value === 'y' || value === 'yes') {
|
|
@@ -67,7 +102,56 @@ export async function resolveUpdateAnswer(question) {
|
|
|
67
102
|
throw error;
|
|
68
103
|
}
|
|
69
104
|
}
|
|
70
|
-
|
|
105
|
+
export function renderedRowCount(text, columns) {
|
|
106
|
+
const width = Math.max(1, columns);
|
|
107
|
+
const lines = text.split('\n');
|
|
108
|
+
if (lines[lines.length - 1] === '')
|
|
109
|
+
lines.pop();
|
|
110
|
+
return lines.reduce((rows, line) => {
|
|
111
|
+
const visible = [...line.replace(/\x1b\[[0-9;]*m/g, '')].length;
|
|
112
|
+
return rows + Math.max(1, Math.ceil(visible / width));
|
|
113
|
+
}, 0);
|
|
114
|
+
}
|
|
115
|
+
async function askUpdateChoiceWithKeys(offer) {
|
|
116
|
+
let selected = 0;
|
|
117
|
+
const show = (index) => {
|
|
118
|
+
const rows = renderedRowCount(`${formatUpdateOptions(offer, selected)}\n`, output.columns ?? 80);
|
|
119
|
+
selected = index;
|
|
120
|
+
output.write(`\u001b[${rows}A\u001b[0J${formatUpdateOptions(offer, selected)}\n`);
|
|
121
|
+
};
|
|
122
|
+
emitKeypressEvents(input);
|
|
123
|
+
const wasRaw = input.isRaw === true;
|
|
124
|
+
input.setRawMode(true);
|
|
125
|
+
input.resume();
|
|
126
|
+
try {
|
|
127
|
+
return await new Promise((resolve) => {
|
|
128
|
+
const onKeypress = (_text, key) => {
|
|
129
|
+
const action = readUpdateKey(key ?? {}, selected);
|
|
130
|
+
if (action.kind === 'ignore')
|
|
131
|
+
return;
|
|
132
|
+
if (action.kind === 'select') {
|
|
133
|
+
if (action.index !== selected)
|
|
134
|
+
show(action.index);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const chosen = UPDATE_OPTIONS.indexOf(action.answer);
|
|
138
|
+
if (chosen >= 0 && chosen !== selected)
|
|
139
|
+
show(chosen);
|
|
140
|
+
input.off('keypress', onKeypress);
|
|
141
|
+
resolve(action.answer);
|
|
142
|
+
};
|
|
143
|
+
input.on('keypress', onKeypress);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
if (!wasRaw)
|
|
148
|
+
input.setRawMode(false);
|
|
149
|
+
input.pause();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async function askUpdateChoice(offer) {
|
|
153
|
+
if (input.isTTY && output.isTTY)
|
|
154
|
+
return await askUpdateChoiceWithKeys(offer);
|
|
71
155
|
const rl = readline.createInterface({ input, output });
|
|
72
156
|
try {
|
|
73
157
|
return await resolveUpdateAnswer(() => rl.question(' ▸ '));
|
|
@@ -93,7 +177,7 @@ export async function offerUpdateAtStartup({ currentVersion, latestVersion, ask
|
|
|
93
177
|
if (!offer)
|
|
94
178
|
return 'none';
|
|
95
179
|
write(`${formatUpdateScreen(offer)}\n`);
|
|
96
|
-
const answer = await ask();
|
|
180
|
+
const answer = await ask(offer);
|
|
97
181
|
if (answer === 'cancel') {
|
|
98
182
|
write(`\n ${chalk.dim('Cancelled. Nothing was installed.')}\n\n`);
|
|
99
183
|
return 'cancelled';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thegitai/cli",
|
|
3
|
-
"version": "1.0.0-preview.
|
|
3
|
+
"version": "1.0.0-preview.46",
|
|
4
4
|
"description": "TheGitAI is an agentic AI coding tool for your terminal. It reads and searches your repository, writes and edits files, runs your tests, and verifies the change before handing it back.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentic-ai",
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"@lydell/node-pty-linux-x64": "1.1.0",
|
|
45
45
|
"@lydell/node-pty-win32-arm64": "1.1.0",
|
|
46
46
|
"@lydell/node-pty-win32-x64": "1.1.0",
|
|
47
|
-
"@thegitai/tui-darwin-arm64": "1.0.0-preview.
|
|
48
|
-
"@thegitai/tui-darwin-x64": "1.0.0-preview.
|
|
49
|
-
"@thegitai/tui-linux-arm64": "1.0.0-preview.
|
|
50
|
-
"@thegitai/tui-linux-x64": "1.0.0-preview.
|
|
51
|
-
"@thegitai/tui-win32-x64": "1.0.0-preview.
|
|
47
|
+
"@thegitai/tui-darwin-arm64": "1.0.0-preview.46",
|
|
48
|
+
"@thegitai/tui-darwin-x64": "1.0.0-preview.46",
|
|
49
|
+
"@thegitai/tui-linux-arm64": "1.0.0-preview.46",
|
|
50
|
+
"@thegitai/tui-linux-x64": "1.0.0-preview.46",
|
|
51
|
+
"@thegitai/tui-win32-x64": "1.0.0-preview.46",
|
|
52
52
|
"@vscode/ripgrep": "1.18.0"
|
|
53
53
|
},
|
|
54
54
|
"repository": {
|