@thegitai/cli 1.0.0-preview.41 → 1.0.0-preview.42
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 +5 -4
- package/dist/src/agent-mode.js +24 -1
- package/dist/src/api/http.js +27 -3
- package/dist/src/help-text.js +12 -7
- package/dist/src/quota-notice.js +35 -0
- package/dist/src/ui/repl.js +29 -6
- package/dist/src/ui/tui/build-frame.js +146 -39
- package/dist/src/ui/tui/shell-input.js +18 -1
- package/dist/src/ui/tui/shortcuts.js +60 -0
- package/dist/src/usage.js +5 -2
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -53,8 +53,8 @@ is required. Tabs used by the agent join a TheGitAI tab group in an existing
|
|
|
53
53
|
window. The conversation stays in your terminal.
|
|
54
54
|
|
|
55
55
|
Default mode asks before using a website and can remember that site for the
|
|
56
|
-
session.
|
|
57
|
-
clicking, typing, or navigating. Use Stop/Resume in the extension popup to
|
|
56
|
+
session. Bypass all permissions allows browser actions; Plan mode reads pages
|
|
57
|
+
without clicking, typing, or navigating. Use Stop/Resume in the extension popup to
|
|
58
58
|
pause browser control, or Disable to disconnect it. Chrome's protected pages,
|
|
59
59
|
including browser settings and extension management, cannot be controlled.
|
|
60
60
|
|
|
@@ -76,8 +76,9 @@ Multi-select questions toggle choices. The last question has an explicit
|
|
|
76
76
|
the form; **Ctrl+C** cancels the whole turn.
|
|
77
77
|
|
|
78
78
|
Answers return to the same turn, and completed question/answer records remain
|
|
79
|
-
readable when the session is resumed. Default,
|
|
80
|
-
support the form;
|
|
79
|
+
readable when the session is resumed. Default, Bypass all permissions, and Plan
|
|
80
|
+
modes all support the form; Bypass all permissions does not choose answers for
|
|
81
|
+
you.
|
|
81
82
|
|
|
82
83
|
## Visible to-do list
|
|
83
84
|
|
package/dist/src/agent-mode.js
CHANGED
|
@@ -145,8 +145,31 @@ export function buildPlanModeToolBlockedResult(toolName, action = 'Plan mode is
|
|
|
145
145
|
}
|
|
146
146
|
export function agentModeLabel(mode) {
|
|
147
147
|
if (mode === 'auto-accept')
|
|
148
|
-
return '
|
|
148
|
+
return 'Bypass all permissions';
|
|
149
149
|
if (mode === 'plan')
|
|
150
150
|
return 'Plan · read-only';
|
|
151
|
+
return 'Default · asks to edit & run unless permitted';
|
|
152
|
+
}
|
|
153
|
+
export function agentModeLabelTiers(mode) {
|
|
154
|
+
if (mode === 'default') {
|
|
155
|
+
return [agentModeLabel(mode), 'Default · asks to edit & run'];
|
|
156
|
+
}
|
|
157
|
+
return [agentModeLabel(mode)];
|
|
158
|
+
}
|
|
159
|
+
export function agentModeShortLabel(mode) {
|
|
160
|
+
if (mode === 'auto-accept')
|
|
161
|
+
return 'Bypass';
|
|
162
|
+
if (mode === 'plan')
|
|
163
|
+
return 'Plan';
|
|
151
164
|
return 'Default';
|
|
152
165
|
}
|
|
166
|
+
export function agentModeColor(mode) {
|
|
167
|
+
if (mode === 'auto-accept')
|
|
168
|
+
return 'red';
|
|
169
|
+
if (mode === 'plan')
|
|
170
|
+
return 'yellow';
|
|
171
|
+
return 'cyan';
|
|
172
|
+
}
|
|
173
|
+
export function agentModeCycleSequence() {
|
|
174
|
+
return AGENT_MODES.map(agentModeShortLabel).join(' → ');
|
|
175
|
+
}
|
package/dist/src/api/http.js
CHANGED
|
@@ -7,12 +7,14 @@ export class ServerApiError extends Error {
|
|
|
7
7
|
status;
|
|
8
8
|
traceId;
|
|
9
9
|
code;
|
|
10
|
-
|
|
10
|
+
details;
|
|
11
|
+
constructor(message, status, traceId, code = '', details = {}) {
|
|
11
12
|
super(traceId ? `${message}\nTrace ID: ${traceId}` : message);
|
|
12
13
|
this.name = 'ServerApiError';
|
|
13
14
|
this.status = status;
|
|
14
15
|
this.traceId = traceId;
|
|
15
16
|
this.code = code;
|
|
17
|
+
this.details = details;
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
export function createTraceId() {
|
|
@@ -187,6 +189,8 @@ export function gatewayFailureCategory(error) {
|
|
|
187
189
|
return 'connection_error';
|
|
188
190
|
if (!(error instanceof ServerApiError))
|
|
189
191
|
return null;
|
|
192
|
+
if (isQuotaExceededError(error))
|
|
193
|
+
return 'quota_exceeded';
|
|
190
194
|
return error.status in GATEWAY_STATUS_MESSAGES ? 'gateway_error' : null;
|
|
191
195
|
}
|
|
192
196
|
export async function readJsonResponse(response) {
|
|
@@ -208,6 +212,26 @@ export function failureMessage(data, status) {
|
|
|
208
212
|
export function failureCode(data) {
|
|
209
213
|
return typeof data?.error?.code === 'string' ? data.error.code : '';
|
|
210
214
|
}
|
|
215
|
+
export function failureDetails(data) {
|
|
216
|
+
const error = data?.error;
|
|
217
|
+
if (!error || typeof error !== 'object')
|
|
218
|
+
return {};
|
|
219
|
+
return {
|
|
220
|
+
...(typeof error.window === 'string' ? { window: error.window } : {}),
|
|
221
|
+
...(typeof error.resetsAt === 'string' ? { resetsAt: error.resetsAt } : {}),
|
|
222
|
+
...(typeof error.percentLeft === 'number'
|
|
223
|
+
? { percentLeft: error.percentLeft }
|
|
224
|
+
: {}),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
export const QUOTA_EXCEEDED_CODE = 'QUOTA_EXCEEDED';
|
|
228
|
+
export function isQuotaExceededError(error) {
|
|
229
|
+
if (!(error instanceof ServerApiError))
|
|
230
|
+
return false;
|
|
231
|
+
if (error.code === QUOTA_EXCEEDED_CODE)
|
|
232
|
+
return true;
|
|
233
|
+
return error.status === 429 && !error.code;
|
|
234
|
+
}
|
|
211
235
|
export function isAuthenticationError(error) {
|
|
212
236
|
return error instanceof ServerApiError && error.status === 401;
|
|
213
237
|
}
|
|
@@ -218,7 +242,7 @@ export function authenticationErrorMessage(error) {
|
|
|
218
242
|
}
|
|
219
243
|
export async function readErrorResponse(response, traceId = response.headers.get(TRACE_ID_HEADER) ?? '') {
|
|
220
244
|
const data = await readJsonResponse(response);
|
|
221
|
-
return new ServerApiError(failureMessage(data, response.status), response.status, traceId, failureCode(data));
|
|
245
|
+
return new ServerApiError(failureMessage(data, response.status), response.status, traceId, failureCode(data), failureDetails(data));
|
|
222
246
|
}
|
|
223
247
|
export async function authorizedJson({ config, path, method = 'GET', body = null, headers = {}, fetchImpl = globalThis.fetch, timeoutMs = REQUEST_TIMEOUT_MS, }) {
|
|
224
248
|
const trace = createTraceContext();
|
|
@@ -235,7 +259,7 @@ export async function authorizedJson({ config, path, method = 'GET', body = null
|
|
|
235
259
|
});
|
|
236
260
|
const data = await readJsonResponse(response);
|
|
237
261
|
if (!response.ok) {
|
|
238
|
-
throw new ServerApiError(failureMessage(data, response.status), response.status, trace.traceId, failureCode(data));
|
|
262
|
+
throw new ServerApiError(failureMessage(data, response.status), response.status, trace.traceId, failureCode(data), failureDetails(data));
|
|
239
263
|
}
|
|
240
264
|
return data;
|
|
241
265
|
}
|
package/dist/src/help-text.js
CHANGED
|
@@ -45,24 +45,29 @@ const HELP_MARKDOWN = [
|
|
|
45
45
|
'',
|
|
46
46
|
'## Options',
|
|
47
47
|
'',
|
|
48
|
-
'- `-y, --yes` — start in
|
|
48
|
+
'- `-y, --yes` — start in Bypass all permissions mode',
|
|
49
49
|
'- `-h, --help` — show this help',
|
|
50
50
|
'- `-v, --version` — print the version and exit',
|
|
51
51
|
'',
|
|
52
52
|
'## Modes',
|
|
53
53
|
'',
|
|
54
|
-
'- Default
|
|
55
|
-
'
|
|
56
|
-
'
|
|
57
|
-
'
|
|
54
|
+
'- Default · asks to edit & run unless permitted — asks before creating,',
|
|
55
|
+
' editing or deleting files, before running commands, and before touching a',
|
|
56
|
+
' website. Each answer can be remembered for the rest of the session, and',
|
|
57
|
+
' once it is, Default stops asking for that action; see Safety & approvals.',
|
|
58
|
+
'- Bypass all permissions — approves every command, file edit, deletion and',
|
|
59
|
+
' browser action for the rest of the session, without asking. Shown in red',
|
|
60
|
+
' in the footer. Blocked commands stay blocked.',
|
|
58
61
|
'- Plan — read-only; the agent can inspect, ask typed questions, and plan,',
|
|
59
62
|
' with file-reading shell commands only. It will not edit files or run tests,',
|
|
60
63
|
' builds, diagnostics, package managers, project code, or network probes.',
|
|
61
|
-
'- In the TUI, Shift+Tab cycles
|
|
64
|
+
'- 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.',
|
|
62
65
|
'',
|
|
63
66
|
'## Keys & clipboard',
|
|
64
67
|
'',
|
|
65
|
-
'-
|
|
68
|
+
'- **?** on an empty composer opens the keyboard-and-mouse panel.',
|
|
69
|
+
'- **Enter** sends • **Shift+Tab** cycles Default → Bypass → Plan (only',
|
|
70
|
+
' between turns) • **Esc** cancels the turn •',
|
|
66
71
|
' **Ctrl+C** clears the composer or the queued message, and quits once there',
|
|
67
72
|
' is nothing left to clear. These are the same on macOS, Linux, and Windows.',
|
|
68
73
|
'- **While the agent is working**, Enter queues your message and locks the',
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { isQuotaExceededError, ServerApiError } from './api/http.js';
|
|
2
|
+
import { USAGE_WINDOWS, formatUtcMinute, usageWindowLabel } from './usage.js';
|
|
3
|
+
export function buildQuotaNotice(error) {
|
|
4
|
+
if (!(error instanceof ServerApiError) || !isQuotaExceededError(error)) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
const label = usageWindowLabel(String(error.details.window ?? ''));
|
|
8
|
+
const inSentence = label.toLowerCase();
|
|
9
|
+
const resetsAt = error.details.resetsAt ?? null;
|
|
10
|
+
const lines = [];
|
|
11
|
+
if (label) {
|
|
12
|
+
const reset = resetsAt ? formatUtcMinute(resetsAt) : '';
|
|
13
|
+
lines.push(reset && reset !== 'unknown'
|
|
14
|
+
? `You've used your ${inSentence} limit. It resets at ${reset}.`
|
|
15
|
+
: `You've used your ${inSentence} limit.`);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
lines.push(firstMessageLine(error));
|
|
19
|
+
}
|
|
20
|
+
lines.push(label
|
|
21
|
+
? `Run /usage to see your ${otherWindowNames(label)} windows.`
|
|
22
|
+
: 'Run /usage to see your remaining usage.');
|
|
23
|
+
return { title: 'Usage limit reached', body: lines.join('\n') };
|
|
24
|
+
}
|
|
25
|
+
function otherWindowNames(usedLabel) {
|
|
26
|
+
const names = USAGE_WINDOWS.map(([name]) => name)
|
|
27
|
+
.filter((name) => name !== usedLabel)
|
|
28
|
+
.map((name) => name.toLowerCase());
|
|
29
|
+
return names.length > 1
|
|
30
|
+
? `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`
|
|
31
|
+
: (names[0] ?? '');
|
|
32
|
+
}
|
|
33
|
+
function firstMessageLine(error) {
|
|
34
|
+
return String(error.message ?? '').split('\n')[0]?.trim() || 'Usage limit reached.';
|
|
35
|
+
}
|
package/dist/src/ui/repl.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRatatuiBridge } from './tui/bridge.js';
|
|
2
2
|
import { bucketActionLabel, } from '../permissions.js';
|
|
3
|
-
import { approvalScrollLimit, buildTuiFrame, formatJobElapsed, formatTodoProgress, pickThinkingFallbackPhrase, renderTranscriptEntryLines, THINKING_FALLBACK_PHRASES, userInputViewportForFrame, } from './tui/build-frame.js';
|
|
3
|
+
import { AGENT_MODE_LOCKED_STATUS, approvalScrollLimit, buildTuiFrame, formatJobElapsed, formatTodoProgress, pickThinkingFallbackPhrase, renderTranscriptEntryLines, THINKING_FALLBACK_PHRASES, userInputViewportForFrame, } from './tui/build-frame.js';
|
|
4
4
|
import { createTerminalTitleController } from './tui/terminal-title.js';
|
|
5
5
|
import { captureTerminalWrites, releaseTerminalWrites, } from './tui/terminal-writes.js';
|
|
6
6
|
export { getSlashCommandSuggestions } from './tui/build-frame.js';
|
|
@@ -16,6 +16,7 @@ import { cancelActiveCommand } from '../executor.js';
|
|
|
16
16
|
import { isTurnFailureMarker } from '../turn-failure-marker.js';
|
|
17
17
|
import { clearCliAuthConfig, logoutFromServer, } from '../api/auth.js';
|
|
18
18
|
import { authenticationErrorMessage, isAuthenticationError, } from '../api/http.js';
|
|
19
|
+
import { buildQuotaNotice } from '../quota-notice.js';
|
|
19
20
|
import { createUserInputPromptState, formatUserInputTranscript, } from './tui/user-input.js';
|
|
20
21
|
import { setCommandOutputHook, withTuiMode } from '../runtime-mode.js';
|
|
21
22
|
import { collectBackgroundJobUiKillMutations, collectBackgroundJobUiOutputMutations, } from '../tool-executor.js';
|
|
@@ -76,7 +77,6 @@ const TRANSCRIPT_DIFF_PREVIEW_LINES = 24;
|
|
|
76
77
|
const WORKING_TOOL_PREVIEW_ITEMS = 4;
|
|
77
78
|
const THINKING_NOTE_PREVIEW_ROWS = 3;
|
|
78
79
|
const WORKING_TOOL_PREVIEW_ROWS = 3;
|
|
79
|
-
const AGENT_MODE_LABEL_WIDTH = 16;
|
|
80
80
|
export const REPORT_ISSUE_URL = 'https://thegit.ai/report';
|
|
81
81
|
export function buildReportTranscriptEntries(openedUrl) {
|
|
82
82
|
return openedUrl
|
|
@@ -1105,6 +1105,8 @@ function createInitialShellState(session, serverModels, debugUi) {
|
|
|
1105
1105
|
jobsPickerOpen: false,
|
|
1106
1106
|
modelPickerIndex: getDefaultModelPickerIndex(session.modelId, serverModels.models),
|
|
1107
1107
|
modelPickerOpen: false,
|
|
1108
|
+
shortcutsOpen: false,
|
|
1109
|
+
transientStatus: null,
|
|
1108
1110
|
projectRoot: session.rootDir,
|
|
1109
1111
|
sessionId: session.sessionId,
|
|
1110
1112
|
showSessionId: debugUi.showSessionId,
|
|
@@ -1532,15 +1534,17 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
1532
1534
|
void remountTui();
|
|
1533
1535
|
}, 0);
|
|
1534
1536
|
};
|
|
1535
|
-
const showTransientStatus = (
|
|
1537
|
+
const showTransientStatus = (transientStatus) => {
|
|
1536
1538
|
if (transientStatusTimer) {
|
|
1537
1539
|
clearTimeout(transientStatusTimer);
|
|
1538
1540
|
transientStatusTimer = null;
|
|
1539
1541
|
}
|
|
1540
|
-
store.update((current) => ({ ...current,
|
|
1542
|
+
store.update((current) => ({ ...current, transientStatus }));
|
|
1541
1543
|
transientStatusTimer = setTimeout(() => {
|
|
1542
1544
|
transientStatusTimer = null;
|
|
1543
|
-
store.update((current) => current.
|
|
1545
|
+
store.update((current) => current.transientStatus === transientStatus
|
|
1546
|
+
? { ...current, transientStatus: null }
|
|
1547
|
+
: current);
|
|
1544
1548
|
}, 2500);
|
|
1545
1549
|
};
|
|
1546
1550
|
const terminalTitle = createTerminalTitleController({
|
|
@@ -1806,6 +1810,9 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
1806
1810
|
setAgentMode(nextAgentMode(session.agentMode));
|
|
1807
1811
|
scheduleLiveFrameRemount();
|
|
1808
1812
|
};
|
|
1813
|
+
const reportAgentModeLocked = () => {
|
|
1814
|
+
showTransientStatus(AGENT_MODE_LOCKED_STATUS);
|
|
1815
|
+
};
|
|
1809
1816
|
const clearLiveFrameRemountTimer = () => {
|
|
1810
1817
|
if (!liveFrameRemountTimer)
|
|
1811
1818
|
return;
|
|
@@ -2899,6 +2906,7 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2899
2906
|
return;
|
|
2900
2907
|
const failedTurnAttachments = activeTurnImageAttachments;
|
|
2901
2908
|
activeTurnImageAttachments = [];
|
|
2909
|
+
const quotaNotice = cancelled ? null : buildQuotaNotice(error);
|
|
2902
2910
|
store.update((current) => ({
|
|
2903
2911
|
...current,
|
|
2904
2912
|
activeTurnInput: '',
|
|
@@ -2908,7 +2916,11 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2908
2916
|
busySince: null,
|
|
2909
2917
|
commandLog: [],
|
|
2910
2918
|
imageAttachments: failedTurnAttachments,
|
|
2911
|
-
status: cancelled
|
|
2919
|
+
status: cancelled
|
|
2920
|
+
? 'Ready'
|
|
2921
|
+
: quotaNotice
|
|
2922
|
+
? quotaNotice.title
|
|
2923
|
+
: 'Turn failed',
|
|
2912
2924
|
thinkingTitle: '',
|
|
2913
2925
|
thinkingNotes: [],
|
|
2914
2926
|
tokenUsage: formatClientTokenUsage(Date.now() - turnStartedAt, latestUsageSummary),
|
|
@@ -2927,6 +2939,16 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2927
2939
|
if (!(await saveActiveSession()))
|
|
2928
2940
|
return;
|
|
2929
2941
|
}
|
|
2942
|
+
else if (quotaNotice) {
|
|
2943
|
+
appendStaticEntries([
|
|
2944
|
+
...turnEntries,
|
|
2945
|
+
{
|
|
2946
|
+
body: quotaNotice.body,
|
|
2947
|
+
kind: 'system',
|
|
2948
|
+
title: quotaNotice.title,
|
|
2949
|
+
},
|
|
2950
|
+
]);
|
|
2951
|
+
}
|
|
2930
2952
|
else {
|
|
2931
2953
|
appendStaticEntries(turnEntries);
|
|
2932
2954
|
appendError(error.message);
|
|
@@ -3090,6 +3112,7 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
3090
3112
|
},
|
|
3091
3113
|
onCancelTurn: cancelActiveTurn,
|
|
3092
3114
|
onCycleAgentMode: cycleAgentMode,
|
|
3115
|
+
onAgentModeChangeBlocked: reportAgentModeLocked,
|
|
3093
3116
|
onCtrlC: handleCtrlC,
|
|
3094
3117
|
onLinkCopy: handleLinkCopy,
|
|
3095
3118
|
onLinkOpen: handleLinkOpen,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
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
4
|
import { renderFormattedBodyLines, renderPreformattedBodyLines, } from './markdown-render.js';
|
|
5
5
|
import { displayWidth, line, padToWidth, plainLine, sliceToWidth, span, wrapText, } from './text.js';
|
|
6
6
|
import { isDarkTerminalBackground, mutedColor, mutedStyle } from './terminal-theme.js';
|
|
7
|
+
import { buildShortcutGroups, shortcutsPanelAvailable, } from './shortcuts.js';
|
|
7
8
|
import { buildUserInputOverlayLines, } from './user-input.js';
|
|
8
9
|
import { composerPromptLabel, frameContentWidth, splitComposerInput, } from './composer-layout.js';
|
|
9
10
|
const WORKING_CLOCK_ICON = '◷';
|
|
@@ -28,7 +29,10 @@ const APPROVAL_SCROLLBAR_THUMB = '█';
|
|
|
28
29
|
const APPROVAL_SCROLLBAR_TRACK = '░';
|
|
29
30
|
const THINKING_NOTE_PREVIEW_ROWS = 3;
|
|
30
31
|
const COMPOSER_INPUT_MAX_ROWS = 6;
|
|
31
|
-
const
|
|
32
|
+
const FOOTER_HINT_MIN_GAP = 2;
|
|
33
|
+
const FOOTER_HELP_HINT = '? for shortcuts';
|
|
34
|
+
const FOOTER_CANCEL_HINT = 'esc cancels turn';
|
|
35
|
+
const FOOTER_CYCLE_HINT = ' (shift+tab to cycle)';
|
|
32
36
|
const OVERLAY_PANEL_MAX_WIDTH = 86;
|
|
33
37
|
const OVERLAY_PANEL_MARGIN_LINES = 2;
|
|
34
38
|
const OVERLAY_PANEL_MARGIN_MIN_ROWS = 30;
|
|
@@ -434,10 +438,14 @@ function tokenUsageLines(usage) {
|
|
|
434
438
|
}
|
|
435
439
|
return [line(...spans)];
|
|
436
440
|
}
|
|
441
|
+
export const AGENT_MODE_LOCKED_STATUS = 'Mode locked while the agent is working';
|
|
437
442
|
function footerTransientStatus(status) {
|
|
438
443
|
const text = String(status ?? '').trim();
|
|
439
444
|
if (!text)
|
|
440
445
|
return null;
|
|
446
|
+
if (text === AGENT_MODE_LOCKED_STATUS) {
|
|
447
|
+
return { color: 'yellow', text, icon: '!' };
|
|
448
|
+
}
|
|
441
449
|
if (text === 'Copied selection' || text === 'Copied link') {
|
|
442
450
|
return { color: 'green', text, icon: '✓' };
|
|
443
451
|
}
|
|
@@ -478,9 +486,39 @@ function todoIndicatorSpans(state) {
|
|
|
478
486
|
}),
|
|
479
487
|
];
|
|
480
488
|
}
|
|
481
|
-
function
|
|
489
|
+
function composerFooterRightHints(state) {
|
|
490
|
+
const cancelHint = state.busy && !state.queuedMessage && !state.userInputPrompt
|
|
491
|
+
? FOOTER_CANCEL_HINT
|
|
492
|
+
: '';
|
|
493
|
+
const helpHint = shortcutsPanelAvailable(state) ? FOOTER_HELP_HINT : '';
|
|
494
|
+
const tiers = [];
|
|
495
|
+
if (cancelHint && helpHint)
|
|
496
|
+
tiers.push(`${cancelHint} · ${helpHint}`);
|
|
497
|
+
if (cancelHint)
|
|
498
|
+
tiers.push(cancelHint);
|
|
499
|
+
if (helpHint)
|
|
500
|
+
tiers.push(helpHint);
|
|
501
|
+
tiers.push('');
|
|
502
|
+
return tiers;
|
|
503
|
+
}
|
|
504
|
+
export function fitComposerFooterRow({ modeLabels, modeLabelFallback, cycleHint, rightHints, leadWidth, tailWidth, contentWidth, }) {
|
|
505
|
+
const candidates = rightHints.flatMap((rightHint) => (cycleHint ? [cycleHint, ''] : ['']).flatMap((cycle) => modeLabels.map((modeLabel) => ({ modeLabel, cycleHint: cycle, rightHint }))));
|
|
506
|
+
const hasTail = (rightHint) => Boolean(rightHint) || tailWidth > 0;
|
|
507
|
+
const rowWidth = (choice, gap) => leadWidth +
|
|
508
|
+
displayWidth(choice.modeLabel) +
|
|
509
|
+
displayWidth(choice.cycleHint) +
|
|
510
|
+
(hasTail(choice.rightHint) ? gap : 0) +
|
|
511
|
+
displayWidth(choice.rightHint) +
|
|
512
|
+
tailWidth;
|
|
513
|
+
const chosen = candidates.find((choice) => rowWidth(choice, FOOTER_HINT_MIN_GAP) <= contentWidth) ?? { modeLabel: modeLabelFallback, cycleHint: '', rightHint: '' };
|
|
514
|
+
const gap = hasTail(chosen.rightHint)
|
|
515
|
+
? Math.max(FOOTER_HINT_MIN_GAP, contentWidth - rowWidth(chosen, 0))
|
|
516
|
+
: 0;
|
|
517
|
+
return { ...chosen, gap };
|
|
518
|
+
}
|
|
519
|
+
function composerFooterLines(state, contentWidth) {
|
|
482
520
|
const visibleSessionId = formatPromptSessionIdLabel(state.showSessionId, state.sessionId);
|
|
483
|
-
const transientStatus = footerTransientStatus(state.
|
|
521
|
+
const transientStatus = footerTransientStatus(state.transientStatus ?? null);
|
|
484
522
|
const lines = [
|
|
485
523
|
line(span('TheGitAI', { color: 'cyan', bold: true }), span(' • ', { color: 'gray', dim: true }), span(formatPromptDirectoryLabel(state.projectRoot), { color: 'cyan' }), ...(transientStatus
|
|
486
524
|
? [
|
|
@@ -498,11 +536,6 @@ function composerFooterLines(state) {
|
|
|
498
536
|
? [span(` ${visibleSessionId}`, { color: 'gray', dim: true })]
|
|
499
537
|
: []), ...backgroundJobIndicatorSpans(state), ...todoIndicatorSpans(state)),
|
|
500
538
|
plainLine(''),
|
|
501
|
-
plainLine(formatModelLabel(state.currentModelId, state.serverModels), {
|
|
502
|
-
color: 'cyan',
|
|
503
|
-
dim: true,
|
|
504
|
-
}),
|
|
505
|
-
plainLine(''),
|
|
506
539
|
];
|
|
507
540
|
if (state.exitConfirmUntil != null) {
|
|
508
541
|
lines.push(plainLine(state.status || 'Press Ctrl+C again to quit.', {
|
|
@@ -511,34 +544,36 @@ function composerFooterLines(state) {
|
|
|
511
544
|
}));
|
|
512
545
|
return lines;
|
|
513
546
|
}
|
|
514
|
-
const busyHelperText = state.queuedMessage
|
|
515
|
-
? 'Enter sends it to the agent • ↑ edit • Esc / Ctrl+C discard'
|
|
516
|
-
: state.input
|
|
517
|
-
? 'Enter queues • Esc cancels turn • Ctrl+C clears draft'
|
|
518
|
-
: 'Enter queues • Esc / Ctrl+C cancel turn';
|
|
519
|
-
const helperText = state.userInputPrompt
|
|
520
|
-
? 'Answering agent questions • Ctrl+C cancels turn'
|
|
521
|
-
: state.busy
|
|
522
|
-
? busyHelperText
|
|
523
|
-
: process.platform === 'win32'
|
|
524
|
-
? 'Enter sends • Shift+Tab mode • Alt+V image • Esc cancel turn • Ctrl+C clears / quits'
|
|
525
|
-
: process.platform === 'darwin'
|
|
526
|
-
? 'Enter sends • Shift+Tab mode • Ctrl+V image • Esc cancel turn • Ctrl+C clears / quits'
|
|
527
|
-
: 'Enter sends • Shift+Tab mode • Ctrl+V image • Esc cancel turn • Ctrl+C clears / quits';
|
|
528
|
-
const agentLabel = agentModeLabel(state.agentMode).padEnd(AGENT_MODE_LABEL_WIDTH);
|
|
529
547
|
const tokenUsageText = state.tokenUsage || formatClientTokenUsage(null);
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
];
|
|
539
|
-
lines.push(line(...footerSpans));
|
|
548
|
+
lines.push(composerFooterModeLine(state, contentWidth, formatModelLabel(state.currentModelId, state.serverModels), tokenUsageText
|
|
549
|
+
? displayWidth(tokenUsageText) + FOOTER_USAGE_SEPARATOR.length
|
|
550
|
+
: 0, tokenUsageText
|
|
551
|
+
? [
|
|
552
|
+
span(FOOTER_USAGE_SEPARATOR, mutedStyle()),
|
|
553
|
+
span(tokenUsageText, { color: 'cyan' }),
|
|
554
|
+
]
|
|
555
|
+
: []));
|
|
540
556
|
return lines;
|
|
541
557
|
}
|
|
558
|
+
function composerFooterModeLine(state, contentWidth, modelLabel, usageWidth, usageSpans) {
|
|
559
|
+
const lead = modelLabel ? `${modelLabel}${FOOTER_MODEL_SEPARATOR}` : '';
|
|
560
|
+
const row = fitComposerFooterRow({
|
|
561
|
+
modeLabels: agentModeLabelTiers(state.agentMode),
|
|
562
|
+
modeLabelFallback: agentModeShortLabel(state.agentMode),
|
|
563
|
+
cycleHint: state.busy ? '' : FOOTER_CYCLE_HINT,
|
|
564
|
+
rightHints: composerFooterRightHints(state),
|
|
565
|
+
leadWidth: displayWidth(lead),
|
|
566
|
+
tailWidth: usageWidth,
|
|
567
|
+
contentWidth,
|
|
568
|
+
});
|
|
569
|
+
const footerMuted = mutedStyle();
|
|
570
|
+
return line(...(lead ? [span(lead, { color: 'cyan', dim: true })] : []), span(row.modeLabel, {
|
|
571
|
+
color: agentModeColor(state.agentMode),
|
|
572
|
+
bold: state.agentMode === 'auto-accept',
|
|
573
|
+
}), ...(row.cycleHint ? [span(row.cycleHint, footerMuted)] : []), ...(row.gap ? [span(' '.repeat(row.gap), footerMuted)] : []), ...(row.rightHint ? [span(row.rightHint, footerMuted)] : []), ...usageSpans);
|
|
574
|
+
}
|
|
575
|
+
const FOOTER_USAGE_SEPARATOR = ' ';
|
|
576
|
+
const FOOTER_MODEL_SEPARATOR = ' ';
|
|
542
577
|
export function formatJobElapsed(elapsedMs) {
|
|
543
578
|
const totalSeconds = Math.max(0, Math.floor(elapsedMs / 1000));
|
|
544
579
|
if (totalSeconds < 60)
|
|
@@ -895,6 +930,71 @@ function buildModelPickerPanel(options, selectedIndex, width, availableHeight) {
|
|
|
895
930
|
body.push(modelPickerDivider(panelWidth), modelPickerPanelSideLine(plainLine(fitLine(hint, innerWidth), { color: 'gray' }), innerWidth), plainLine(`╰${'─'.repeat(panelWidth - 2)}╯`, { color: MODEL_PICKER_BORDER_COLOR }));
|
|
896
931
|
return [...margin, ...body, ...margin];
|
|
897
932
|
}
|
|
933
|
+
const SHORTCUTS_PANEL_MAX_WIDTH = 96;
|
|
934
|
+
const SHORTCUTS_KEY_COLUMN_MIN = 10;
|
|
935
|
+
function shortcutsTopBorder(panelWidth) {
|
|
936
|
+
const fullTitle = ' TheGitAI - Keyboard & mouse ';
|
|
937
|
+
const compactTitle = ' Shortcuts ';
|
|
938
|
+
const title = panelWidth >= fullTitle.length + 4 ? fullTitle : compactTitle;
|
|
939
|
+
const available = Math.max(0, panelWidth - 2 - [...title].length);
|
|
940
|
+
const left = Math.floor(available / 2);
|
|
941
|
+
const right = available - left;
|
|
942
|
+
return line(span(`╭${'─'.repeat(left)}`, { color: MODEL_PICKER_BORDER_COLOR }), span(title, { color: MODEL_PICKER_ACCENT_COLOR, bold: true }), span(`${'─'.repeat(right)}╮`, { color: MODEL_PICKER_BORDER_COLOR }));
|
|
943
|
+
}
|
|
944
|
+
export function buildShortcutsPanel(groups, width, availableHeight) {
|
|
945
|
+
const panelWidth = Math.max(28, Math.min(width, SHORTCUTS_PANEL_MAX_WIDTH));
|
|
946
|
+
const innerWidth = Math.max(1, panelWidth - 4);
|
|
947
|
+
const keyWidth = Math.max(SHORTCUTS_KEY_COLUMN_MIN, ...groups.flatMap((group) => group.rows.map((row) => displayWidth(row.keys))));
|
|
948
|
+
const side = (row) => overlayPanelLine(row, innerWidth, MODEL_PICKER_BORDER_COLOR);
|
|
949
|
+
const groupLines = (group, spaced) => [
|
|
950
|
+
...(spaced ? [side(plainLine(''))] : []),
|
|
951
|
+
side(plainLine(fitLine(group.title, innerWidth), {
|
|
952
|
+
color: MODEL_PICKER_ACCENT_COLOR,
|
|
953
|
+
bold: true,
|
|
954
|
+
})),
|
|
955
|
+
...group.rows.map((row) => side(line(span(` ${row.keys}${' '.repeat(Math.max(0, keyWidth - displayWidth(row.keys)))} `, { bold: true }), span(fitLine(row.description, Math.max(1, innerWidth - keyWidth - 4)), { color: 'gray' })))),
|
|
956
|
+
];
|
|
957
|
+
const chromeRows = 4;
|
|
958
|
+
const budget = Math.max(1, availableHeight - chromeRows);
|
|
959
|
+
const spacedBody = groups.flatMap((group, index) => groupLines(group, index > 0));
|
|
960
|
+
const plainBody = groups.flatMap((group) => groupLines(group, false));
|
|
961
|
+
let body = spacedBody;
|
|
962
|
+
let hidden = 0;
|
|
963
|
+
if (spacedBody.length > budget) {
|
|
964
|
+
body = plainBody;
|
|
965
|
+
if (plainBody.length > budget) {
|
|
966
|
+
body = [];
|
|
967
|
+
hidden = groups.reduce((total, group) => total + group.rows.length, 0);
|
|
968
|
+
for (const group of groups) {
|
|
969
|
+
const whole = groupLines(group, false);
|
|
970
|
+
if (body.length + whole.length <= budget) {
|
|
971
|
+
body = [...body, ...whole];
|
|
972
|
+
hidden -= group.rows.length;
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
975
|
+
const room = budget - body.length - 1;
|
|
976
|
+
if (room >= 1) {
|
|
977
|
+
const kept = group.rows.slice(0, room);
|
|
978
|
+
body = [...body, ...groupLines({ ...group, rows: kept }, false)];
|
|
979
|
+
hidden -= kept.length;
|
|
980
|
+
}
|
|
981
|
+
break;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
const hint = hidden > 0 ? `esc close • ${hidden} more in /help` : 'esc close';
|
|
986
|
+
return [
|
|
987
|
+
shortcutsTopBorder(panelWidth),
|
|
988
|
+
...body,
|
|
989
|
+
plainLine(`├${'─'.repeat(panelWidth - 2)}┤`, {
|
|
990
|
+
color: MODEL_PICKER_BORDER_COLOR,
|
|
991
|
+
}),
|
|
992
|
+
side(plainLine(fitLine(hint, innerWidth), { color: 'gray' })),
|
|
993
|
+
plainLine(`╰${'─'.repeat(panelWidth - 2)}╯`, {
|
|
994
|
+
color: MODEL_PICKER_BORDER_COLOR,
|
|
995
|
+
}),
|
|
996
|
+
];
|
|
997
|
+
}
|
|
898
998
|
function commandPaletteTopBorder(panelWidth) {
|
|
899
999
|
const prefix = '╭─ Commands ';
|
|
900
1000
|
const suffix = '╮';
|
|
@@ -1134,6 +1234,9 @@ function buildOverlayLines(state, width, height, nowMs) {
|
|
|
1134
1234
|
}));
|
|
1135
1235
|
return buildOverlayPanel(lines, width, OVERLAY_BORDER_COLOR, height);
|
|
1136
1236
|
}
|
|
1237
|
+
if (state.shortcutsOpen) {
|
|
1238
|
+
lines.push(...buildShortcutsPanel(buildShortcutGroups(), width, height));
|
|
1239
|
+
}
|
|
1137
1240
|
if (state.modelPickerOpen) {
|
|
1138
1241
|
const options = buildModelPickerOptions(state.currentModelId, state.serverModels);
|
|
1139
1242
|
lines.push(...buildModelPickerPanel(options, state.modelPickerIndex, width, height));
|
|
@@ -1212,7 +1315,7 @@ function countSectionLines(sections) {
|
|
|
1212
1315
|
export function userInputViewportForFrame(state, cols, rows, spinnerFrame, elapsedSeconds, nowMs = 0) {
|
|
1213
1316
|
const contentWidth = frameContentWidth(cols);
|
|
1214
1317
|
const liveRows = buildLiveLines(state, contentWidth, spinnerFrame, elapsedSeconds, nowMs).length;
|
|
1215
|
-
const overlayHeight = Math.max(0, rows - liveRows - composerFooterLines(state).length);
|
|
1318
|
+
const overlayHeight = Math.max(0, rows - liveRows - composerFooterLines(state, contentWidth).length);
|
|
1216
1319
|
return {
|
|
1217
1320
|
width: approvalPanelInnerWidth(contentWidth),
|
|
1218
1321
|
maxRows: overlayPanelContentBudget(overlayHeight),
|
|
@@ -1248,7 +1351,11 @@ export function buildTuiFrame(state, cols, rows, spinnerFrame, elapsedSeconds, n
|
|
|
1248
1351
|
sections.push({ kind: 'live', lines: liveLines });
|
|
1249
1352
|
}
|
|
1250
1353
|
const overlayActive = Boolean(state.approvalPrompt || state.sudoPrompt || state.userInputPrompt);
|
|
1251
|
-
if (!state.resumePickerOpen &&
|
|
1354
|
+
if (!state.resumePickerOpen &&
|
|
1355
|
+
!state.modelPickerOpen &&
|
|
1356
|
+
!state.jobsPickerOpen &&
|
|
1357
|
+
!state.shortcutsOpen &&
|
|
1358
|
+
!overlayActive) {
|
|
1252
1359
|
const composerLines = [];
|
|
1253
1360
|
if (state.busy && state.status === 'Starting a new conversation...') {
|
|
1254
1361
|
composerLines.push(line(span('Starting a new conversation…', { color: 'gray', dim: true })));
|
|
@@ -1268,18 +1375,18 @@ export function buildTuiFrame(state, cols, rows, spinnerFrame, elapsedSeconds, n
|
|
|
1268
1375
|
}
|
|
1269
1376
|
sections.push({
|
|
1270
1377
|
kind: 'composer',
|
|
1271
|
-
lines: [...composerLines, plainLine(''), ...composerFooterLines(state)],
|
|
1378
|
+
lines: [...composerLines, plainLine(''), ...composerFooterLines(state, contentWidth)],
|
|
1272
1379
|
});
|
|
1273
1380
|
}
|
|
1274
1381
|
if (state.userInputPrompt) {
|
|
1275
1382
|
sections.push({
|
|
1276
1383
|
kind: 'busyFooter',
|
|
1277
|
-
lines: composerFooterLines(state),
|
|
1384
|
+
lines: composerFooterLines(state, contentWidth),
|
|
1278
1385
|
});
|
|
1279
1386
|
}
|
|
1280
1387
|
const overlayHeight = state.userInputPrompt
|
|
1281
1388
|
? Math.max(0, rows - countSectionLines(sections))
|
|
1282
|
-
: state.jobsPickerOpen
|
|
1389
|
+
: state.jobsPickerOpen || state.shortcutsOpen
|
|
1283
1390
|
? Math.max(Math.min(rows, JOBS_PICKER_MIN_ROWS), rows - countSectionLines(sections))
|
|
1284
1391
|
: rows;
|
|
1285
1392
|
const overlayLines = buildOverlayLines(state, contentWidth, overlayHeight, nowMs);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { shortcutsPanelAvailable } from './shortcuts.js';
|
|
1
2
|
import { moveComposerCursorVertically } from './composer-layout.js';
|
|
2
3
|
import { readClipboardImage, readClipboardText } from '../../core/clipboard.js';
|
|
3
4
|
import { MAX_IMAGES_PER_MESSAGE, MAX_TOTAL_IMAGE_BYTES_PER_MESSAGE, approximateBase64DecodedBytes, totalAttachmentBytes, } from '../../core/image-limits.js';
|
|
@@ -105,7 +106,7 @@ function pasteTextFromClipboard(store, handlers) {
|
|
|
105
106
|
if (!text) {
|
|
106
107
|
store.update((state) => ({
|
|
107
108
|
...state,
|
|
108
|
-
|
|
109
|
+
transientStatus: 'Clipboard has no text to paste',
|
|
109
110
|
}));
|
|
110
111
|
return;
|
|
111
112
|
}
|
|
@@ -415,12 +416,24 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
415
416
|
}
|
|
416
417
|
return;
|
|
417
418
|
}
|
|
419
|
+
if (state.shortcutsOpen) {
|
|
420
|
+
if (key.escape || key.returnKey || key.input === '?' || key.input === 'q') {
|
|
421
|
+
handlers.onLiveFrameShapeChange();
|
|
422
|
+
store.update((current) => ({ ...current, shortcutsOpen: false }));
|
|
423
|
+
}
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
418
426
|
if (key.ctrl && key.input === 'b' && !key.meta && !key.shift) {
|
|
419
427
|
if ((state.backgroundJobs ?? []).length > 0) {
|
|
420
428
|
handlers.onOpenJobsPicker();
|
|
421
429
|
}
|
|
422
430
|
return;
|
|
423
431
|
}
|
|
432
|
+
if (key.input === '?' && !key.ctrl && !key.meta && shortcutsPanelAvailable(state)) {
|
|
433
|
+
handlers.onLiveFrameShapeChange();
|
|
434
|
+
store.update((current) => ({ ...current, shortcutsOpen: true }));
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
424
437
|
if (key.escape) {
|
|
425
438
|
if (state.busy) {
|
|
426
439
|
if (state.queuedMessage) {
|
|
@@ -449,6 +462,10 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
449
462
|
return;
|
|
450
463
|
}
|
|
451
464
|
if (key.tab && key.shift) {
|
|
465
|
+
if (state.busy) {
|
|
466
|
+
handlers.onAgentModeChangeBlocked?.();
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
452
469
|
handlers.onCycleAgentMode();
|
|
453
470
|
return;
|
|
454
471
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { agentModeCycleSequence } from '../../agent-mode.js';
|
|
2
|
+
export function shortcutsPanelAvailable(state) {
|
|
3
|
+
return (!state.exiting &&
|
|
4
|
+
!state.approvalPrompt &&
|
|
5
|
+
!state.sudoPrompt &&
|
|
6
|
+
!state.userInputPrompt &&
|
|
7
|
+
!state.queuedMessage &&
|
|
8
|
+
!state.input &&
|
|
9
|
+
(state.cursor ?? 0) === 0 &&
|
|
10
|
+
(state.pastedChunks?.length ?? 0) === 0 &&
|
|
11
|
+
state.promptHistoryCursor == null);
|
|
12
|
+
}
|
|
13
|
+
export function buildShortcutGroups(platform = process.platform) {
|
|
14
|
+
const imagePaste = platform === 'win32' ? 'Alt+V' : 'Ctrl+V';
|
|
15
|
+
const textPaste = platform === 'darwin' ? 'Cmd+V' : 'Ctrl+Shift+V';
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
title: 'Composer',
|
|
19
|
+
rows: [
|
|
20
|
+
{ keys: 'Enter', description: 'Send your message' },
|
|
21
|
+
{
|
|
22
|
+
keys: 'Shift+Tab',
|
|
23
|
+
description: `Cycle ${agentModeCycleSequence()} (between turns)`,
|
|
24
|
+
},
|
|
25
|
+
{ keys: '/', description: 'Slash commands — /help lists them all' },
|
|
26
|
+
{ keys: '↑ / ↓', description: 'Earlier prompts' },
|
|
27
|
+
{ keys: 'Esc', description: 'Clear the composer' },
|
|
28
|
+
{ keys: 'Ctrl+C', description: 'Clear the composer, or quit when empty' },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
title: 'While the agent is working',
|
|
33
|
+
rows: [
|
|
34
|
+
{ keys: 'Enter', description: 'Queue a message; Enter again sends it into the turn' },
|
|
35
|
+
{ keys: '↑', description: 'Bring a queued message back to edit it' },
|
|
36
|
+
{ keys: 'Esc', description: 'Discard the queued message, or cancel the turn' },
|
|
37
|
+
{ keys: 'Ctrl+C', description: 'Cancel the turn' },
|
|
38
|
+
{ keys: 'Ctrl+B', description: 'Background jobs' },
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
title: 'Clipboard & images',
|
|
43
|
+
rows: [
|
|
44
|
+
{ keys: imagePaste, description: 'Paste a screenshot into the message' },
|
|
45
|
+
{ keys: textPaste, description: 'Paste text (your terminal’s own shortcut)' },
|
|
46
|
+
{ keys: 'Right-click', description: 'Paste, or copy the link under the cursor' },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
title: 'Transcript',
|
|
51
|
+
rows: [
|
|
52
|
+
{ keys: 'PageUp / PageDown', description: 'Scroll — the mouse wheel works too' },
|
|
53
|
+
{ keys: 'Drag', description: 'Select and copy' },
|
|
54
|
+
{ keys: 'Double-click', description: 'Copy a word' },
|
|
55
|
+
{ keys: 'Triple-click', description: 'Copy a whole row' },
|
|
56
|
+
{ keys: 'Left-click', description: 'Open a link' },
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
}
|
package/dist/src/usage.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
const USAGE_WINDOWS = [
|
|
1
|
+
export const USAGE_WINDOWS = [
|
|
2
2
|
['5-hour', '5-hourly'],
|
|
3
3
|
['Weekly', 'weekly'],
|
|
4
4
|
['Monthly', 'monthly'],
|
|
5
5
|
];
|
|
6
|
-
function
|
|
6
|
+
export function usageWindowLabel(key) {
|
|
7
|
+
return USAGE_WINDOWS.find(([, id]) => id === key)?.[0] ?? '';
|
|
8
|
+
}
|
|
9
|
+
export function formatUtcMinute(value) {
|
|
7
10
|
if (!value)
|
|
8
11
|
return 'no active usage';
|
|
9
12
|
const date = new Date(value);
|
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.42",
|
|
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.42",
|
|
48
|
+
"@thegitai/tui-darwin-x64": "1.0.0-preview.42",
|
|
49
|
+
"@thegitai/tui-linux-arm64": "1.0.0-preview.42",
|
|
50
|
+
"@thegitai/tui-linux-x64": "1.0.0-preview.42",
|
|
51
|
+
"@thegitai/tui-win32-x64": "1.0.0-preview.42",
|
|
52
52
|
"@vscode/ripgrep": "1.18.0"
|
|
53
53
|
},
|
|
54
54
|
"repository": {
|