@thegitai/cli 1.0.0-preview.8 → 1.0.0-preview.9
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/dist/bin/ai.js +3 -1
- package/dist/src/api/browser-login.js +70 -1
- package/dist/src/api/chat.js +10 -0
- package/dist/src/session-store.js +52 -20
- package/dist/src/ui/prompt-history-store.js +1 -1
- package/dist/src/ui/repl.js +14 -2
- package/dist/src/ui/tui/build-frame.js +51 -7
- package/dist/src/utils.js +9 -0
- package/package.json +5 -5
package/dist/bin/ai.js
CHANGED
|
@@ -8,7 +8,7 @@ import { authenticationErrorMessage, isAuthenticationError, isTransientNetworkEr
|
|
|
8
8
|
import { formatCliHelpText } from '../src/help-text.js';
|
|
9
9
|
import { createIndex } from '../src/project-index.js';
|
|
10
10
|
import { createSession } from '../src/session.js';
|
|
11
|
-
import { applySessionSnapshot, listSessionMetadata, loadSessionSnapshot, saveSessionState, } from '../src/session-store.js';
|
|
11
|
+
import { applySessionSnapshot, listSessionMetadata, loadSessionSnapshot, saveSessionState, sessionHasUserMessage, } from '../src/session-store.js';
|
|
12
12
|
import { runClientInteractive } from '../src/ui/repl.js';
|
|
13
13
|
import { appendPromptToFile } from '../src/ui/prompt-history-store.js';
|
|
14
14
|
import { formatSessionExitNotice } from '../src/session-exit.js';
|
|
@@ -116,6 +116,8 @@ function modelLabel(serverModels, modelId) {
|
|
|
116
116
|
`Model ${modelId}`);
|
|
117
117
|
}
|
|
118
118
|
async function saveSessionBoth({ session, serverSessionClient, }) {
|
|
119
|
+
if (!sessionHasUserMessage(session))
|
|
120
|
+
return;
|
|
119
121
|
saveSessionState(session);
|
|
120
122
|
try {
|
|
121
123
|
await serverSessionClient.save(session);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
1
2
|
import crypto from 'node:crypto';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
2
4
|
import http from 'node:http';
|
|
3
5
|
import os from 'node:os';
|
|
4
6
|
import { openUrl } from '../core/open-url.js';
|
|
@@ -21,6 +23,73 @@ function defaultDeviceName() {
|
|
|
21
23
|
return os.hostname();
|
|
22
24
|
}
|
|
23
25
|
}
|
|
26
|
+
function readLinuxOsPrettyName() {
|
|
27
|
+
try {
|
|
28
|
+
const content = readFileSync('/etc/os-release', 'utf8');
|
|
29
|
+
return content.match(/^PRETTY_NAME="?([^"\n]*)"?$/m)?.[1]?.trim() ?? '';
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return '';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function macArchLabel() {
|
|
36
|
+
try {
|
|
37
|
+
if (process.arch === 'arm64')
|
|
38
|
+
return 'Apple Silicon';
|
|
39
|
+
if (os.cpus().some((cpu) => cpu.model.includes('Apple'))) {
|
|
40
|
+
return 'Apple Silicon';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
}
|
|
45
|
+
return 'Intel';
|
|
46
|
+
}
|
|
47
|
+
export function describeOperatingSystem() {
|
|
48
|
+
try {
|
|
49
|
+
if (process.platform === 'linux') {
|
|
50
|
+
const base = readLinuxOsPrettyName() || `Linux ${os.release()}`;
|
|
51
|
+
return process.arch === 'arm64' ? `${base}, ARM64` : base;
|
|
52
|
+
}
|
|
53
|
+
if (process.platform === 'darwin') {
|
|
54
|
+
let version = '';
|
|
55
|
+
try {
|
|
56
|
+
version = execSync('sw_vers -productVersion', {
|
|
57
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
58
|
+
})
|
|
59
|
+
.toString()
|
|
60
|
+
.trim();
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
}
|
|
64
|
+
return `${version ? `macOS ${version}` : 'macOS'}, ${macArchLabel()}`;
|
|
65
|
+
}
|
|
66
|
+
if (process.platform === 'win32') {
|
|
67
|
+
let label = '';
|
|
68
|
+
try {
|
|
69
|
+
label = os.version();
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
}
|
|
73
|
+
const build = Number(os.release().split('.')[2] ?? '0');
|
|
74
|
+
if (build >= 22000)
|
|
75
|
+
label = label.replace(/Windows 10/i, 'Windows 11');
|
|
76
|
+
const base = label || `Windows ${os.release()}`;
|
|
77
|
+
return process.arch === 'arm64' ? `${base}, ARM64` : base;
|
|
78
|
+
}
|
|
79
|
+
return `${process.platform} ${os.release()}`;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return process.platform;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export function withOperatingSystemInfo(name) {
|
|
86
|
+
const trimmed = name.trim();
|
|
87
|
+
const osLabel = describeOperatingSystem();
|
|
88
|
+
const combined = osLabel && !trimmed.includes(osLabel)
|
|
89
|
+
? `${trimmed} (${osLabel})`
|
|
90
|
+
: trimmed;
|
|
91
|
+
return combined.slice(0, 180);
|
|
92
|
+
}
|
|
24
93
|
export function generatePkce() {
|
|
25
94
|
const verifier = crypto.randomBytes(32).toString('base64url');
|
|
26
95
|
const challenge = crypto
|
|
@@ -77,7 +146,7 @@ export async function loginViaBrowser(options) {
|
|
|
77
146
|
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
78
147
|
const openBrowser = options.openBrowser ?? openUrl;
|
|
79
148
|
const onUrl = options.onUrl ?? (() => { });
|
|
80
|
-
const deviceName = options.deviceName ?? defaultDeviceName();
|
|
149
|
+
const deviceName = withOperatingSystemInfo(options.deviceName ?? defaultDeviceName());
|
|
81
150
|
const { verifier, challenge } = generatePkce();
|
|
82
151
|
if (options.noBrowser) {
|
|
83
152
|
const authUrl = buildAuthUrl(websiteUrl, {
|
package/dist/src/api/chat.js
CHANGED
|
@@ -146,6 +146,9 @@ function publicStatusMessage(data) {
|
|
|
146
146
|
: 'tool';
|
|
147
147
|
if (event.phase === 'thinking')
|
|
148
148
|
return 'Thinking...';
|
|
149
|
+
if (event.phase === 'analyzing_image') {
|
|
150
|
+
return (event.imageCount ?? 1) > 1 ? 'Analyzing images...' : 'Analyzing image...';
|
|
151
|
+
}
|
|
149
152
|
if (event.phase === 'running_tool')
|
|
150
153
|
return `Running ${toolName}...`;
|
|
151
154
|
if (event.phase === 'waiting_for_tool')
|
|
@@ -303,6 +306,13 @@ async function consumeTurnStream({ response, config, projectIndex, session, inpu
|
|
|
303
306
|
}
|
|
304
307
|
async function handleEvent(event) {
|
|
305
308
|
if (event.event === 'status') {
|
|
309
|
+
const data = event.data;
|
|
310
|
+
if (data?.phase === 'analyzing_image') {
|
|
311
|
+
session.onImageAnalysis?.(Math.max(1, Number(data.imageCount ?? 1) || 1));
|
|
312
|
+
}
|
|
313
|
+
else if (data?.phase) {
|
|
314
|
+
session.onImageAnalysis?.(0);
|
|
315
|
+
}
|
|
306
316
|
const message = publicStatusMessage(event.data);
|
|
307
317
|
if (message)
|
|
308
318
|
session.onStatus(message);
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
1
2
|
import { createHash } from 'node:crypto';
|
|
2
3
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { getClientStateDir } from './client-state.js';
|
|
5
6
|
import { normalizeAssistantEditJournal } from './edit-journal.js';
|
|
6
7
|
import { cloneSessionSafetyState, createSessionSafetyState, mergeLocalSessionSafetyState, normalizeSessionSafetyState, } from './session-safety.js';
|
|
7
|
-
import {
|
|
8
|
+
import { singleLinePreview } from './utils.js';
|
|
8
9
|
const SESSION_STORE_VERSION = 1;
|
|
9
|
-
const MAX_RECENT_SESSIONS =
|
|
10
|
+
export const MAX_RECENT_SESSIONS = 10;
|
|
10
11
|
function cloneJson(value) {
|
|
11
12
|
return JSON.parse(JSON.stringify(value ?? null));
|
|
12
13
|
}
|
|
@@ -77,6 +78,12 @@ function listSessionFiles(rootDir, env = process.env) {
|
|
|
77
78
|
.filter((name) => name.endsWith('.json'))
|
|
78
79
|
.map((name) => path.join(dir, name));
|
|
79
80
|
}
|
|
81
|
+
function normalizeBranch(value) {
|
|
82
|
+
const text = String(value ?? '')
|
|
83
|
+
.replace(/[\r\n\t]/g, ' ')
|
|
84
|
+
.trim();
|
|
85
|
+
return text ? text.slice(0, 120) : null;
|
|
86
|
+
}
|
|
80
87
|
function normalizeHistory(value) {
|
|
81
88
|
if (!Array.isArray(value))
|
|
82
89
|
return [];
|
|
@@ -107,6 +114,7 @@ function normalizeSnapshot(raw, rootDir) {
|
|
|
107
114
|
createdAt: normalizeIsoDate(raw.createdAt),
|
|
108
115
|
updatedAt: normalizeIsoDate(raw.updatedAt),
|
|
109
116
|
modelId,
|
|
117
|
+
branch: normalizeBranch(raw.branch),
|
|
110
118
|
history: cloneJson(normalizeHistory(raw.history)),
|
|
111
119
|
clientState: sanitizeClientState(raw.clientState),
|
|
112
120
|
serverState: sanitizeOpaqueState(raw.serverState),
|
|
@@ -154,6 +162,21 @@ export function pruneSavedSessions(rootDir, env = process.env) {
|
|
|
154
162
|
}
|
|
155
163
|
}
|
|
156
164
|
}
|
|
165
|
+
export function readGitBranch(rootDir) {
|
|
166
|
+
try {
|
|
167
|
+
const result = spawnSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
168
|
+
cwd: rootDir,
|
|
169
|
+
encoding: 'utf8',
|
|
170
|
+
timeout: 1500,
|
|
171
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
172
|
+
});
|
|
173
|
+
const branch = result.status === 0 ? String(result.stdout ?? '').trim() : '';
|
|
174
|
+
return branch && branch !== 'HEAD' ? branch : null;
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
157
180
|
export function snapshotFromSession(session) {
|
|
158
181
|
const now = new Date().toISOString();
|
|
159
182
|
const createdAt = normalizeIsoDate(session.sessionCreatedAt ?? now);
|
|
@@ -168,6 +191,7 @@ export function snapshotFromSession(session) {
|
|
|
168
191
|
createdAt,
|
|
169
192
|
updatedAt: now,
|
|
170
193
|
modelId: session.modelId,
|
|
194
|
+
branch: readGitBranch(session.rootDir),
|
|
171
195
|
history: cloneJson(session.history),
|
|
172
196
|
clientState: {
|
|
173
197
|
editCounter: session.clientState.editCounter,
|
|
@@ -210,6 +234,9 @@ export function applySessionSnapshot(session, snapshot, options = {}) {
|
|
|
210
234
|
safety: mergeLocalSessionSafetyState(session.clientState.safety, snapshot.clientState.safety),
|
|
211
235
|
};
|
|
212
236
|
}
|
|
237
|
+
export function sessionHasUserMessage(session) {
|
|
238
|
+
return session.history.some((entry) => Boolean(userPromptText(entry)));
|
|
239
|
+
}
|
|
213
240
|
function extractMarkedSection(text, marker) {
|
|
214
241
|
const index = text.indexOf(marker);
|
|
215
242
|
if (index === -1)
|
|
@@ -218,23 +245,27 @@ function extractMarkedSection(text, marker) {
|
|
|
218
245
|
const end = after.indexOf('\n\n');
|
|
219
246
|
return (end === -1 ? after : after.slice(0, end)).trim();
|
|
220
247
|
}
|
|
221
|
-
function
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
248
|
+
export function userPromptText(entry) {
|
|
249
|
+
if (!entry || entry.role !== 'user' || entry.kind !== 'turnStart')
|
|
250
|
+
return '';
|
|
251
|
+
const text = (entry.parts ?? [])
|
|
252
|
+
.map((part) => (typeof part?.text === 'string' ? part.text : ''))
|
|
253
|
+
.filter(Boolean)
|
|
254
|
+
.join('\n')
|
|
255
|
+
.trim();
|
|
256
|
+
if (!text)
|
|
257
|
+
return '';
|
|
258
|
+
const request = extractMarkedSection(text, 'Current user request:') ||
|
|
259
|
+
extractMarkedSection(text, 'User request:');
|
|
260
|
+
const message = extractMarkedSection(text, 'Current user message:') ||
|
|
261
|
+
extractMarkedSection(text, 'User message:');
|
|
262
|
+
return (request || message || text).trim();
|
|
263
|
+
}
|
|
264
|
+
function extractFirstUserPrompt(history) {
|
|
265
|
+
for (const entry of history) {
|
|
266
|
+
const text = userPromptText(entry);
|
|
267
|
+
if (text)
|
|
268
|
+
return singleLinePreview(text, 120);
|
|
238
269
|
}
|
|
239
270
|
return '';
|
|
240
271
|
}
|
|
@@ -247,8 +278,9 @@ function metadataFromSnapshot(snapshot) {
|
|
|
247
278
|
updatedAt: snapshot.updatedAt,
|
|
248
279
|
modelId: snapshot.modelId,
|
|
249
280
|
messageCount: snapshot.history.length,
|
|
250
|
-
lastUserMessage:
|
|
281
|
+
lastUserMessage: extractFirstUserPrompt(snapshot.history),
|
|
251
282
|
summaryPreview: '',
|
|
283
|
+
branch: snapshot.branch ?? null,
|
|
252
284
|
};
|
|
253
285
|
}
|
|
254
286
|
export function listSessionMetadata(rootDir, env = process.env) {
|
|
@@ -3,7 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { getClientStateDir } from '../client-state.js';
|
|
4
4
|
const HISTORY_FILE = 'prompt-history.json';
|
|
5
5
|
const LEGACY_HISTORY_FILE = 'prompt-history.txt';
|
|
6
|
-
export const MAX_PROMPT_HISTORY_ENTRIES =
|
|
6
|
+
export const MAX_PROMPT_HISTORY_ENTRIES = 20;
|
|
7
7
|
const ENTRY_SEPARATOR = '\x1e';
|
|
8
8
|
function getHistoryFilePath(env = process.env) {
|
|
9
9
|
return path.join(getClientStateDir(env), HISTORY_FILE);
|
package/dist/src/ui/repl.js
CHANGED
|
@@ -16,7 +16,7 @@ import { authenticationErrorMessage, isAuthenticationError, } from '../api/http.
|
|
|
16
16
|
import { setCommandOutputHook, withTuiMode } from '../runtime-mode.js';
|
|
17
17
|
import { collectBackgroundJobUiKillMutations, collectBackgroundJobUiOutputMutations, } from '../tool-executor.js';
|
|
18
18
|
import { startNewConversation, } from '../session.js';
|
|
19
|
-
import { applySessionSnapshot, listSessionMetadata, loadSessionSnapshot, saveSessionState, } from '../session-store.js';
|
|
19
|
+
import { applySessionSnapshot, listSessionMetadata, loadSessionSnapshot, saveSessionState, sessionHasUserMessage, } from '../session-store.js';
|
|
20
20
|
import { truncate } from '../utils.js';
|
|
21
21
|
import { writeClipboardText } from '../core/clipboard.js';
|
|
22
22
|
import { openUrl } from '../core/open-url.js';
|
|
@@ -963,6 +963,7 @@ function createInitialShellState(session, serverModels, debugUi) {
|
|
|
963
963
|
activeTurnInput: '',
|
|
964
964
|
activeTurnInputPreformatted: false,
|
|
965
965
|
agentMode: session.agentMode,
|
|
966
|
+
analyzingImages: 0,
|
|
966
967
|
approvalCursor: getDefaultApprovalCursor(),
|
|
967
968
|
approvalPrompt: null,
|
|
968
969
|
autoYes: session.autoYes,
|
|
@@ -1282,6 +1283,8 @@ function appendCommandLog(state, text) {
|
|
|
1282
1283
|
};
|
|
1283
1284
|
}
|
|
1284
1285
|
async function saveSessionBoth({ serverSessionClient, session, }) {
|
|
1286
|
+
if (!sessionHasUserMessage(session))
|
|
1287
|
+
return;
|
|
1285
1288
|
saveSessionState(session);
|
|
1286
1289
|
await serverSessionClient.save(session);
|
|
1287
1290
|
}
|
|
@@ -1661,7 +1664,9 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
1661
1664
|
if (!isAuthenticationError(error))
|
|
1662
1665
|
return false;
|
|
1663
1666
|
clearCliAuthConfig(session.env);
|
|
1664
|
-
|
|
1667
|
+
if (sessionHasUserMessage(session)) {
|
|
1668
|
+
saveSessionState(session);
|
|
1669
|
+
}
|
|
1665
1670
|
fatalError = new Error(authenticationErrorMessage(error));
|
|
1666
1671
|
requestExit();
|
|
1667
1672
|
return true;
|
|
@@ -2327,6 +2332,7 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2327
2332
|
...current,
|
|
2328
2333
|
activeTurnInput: input,
|
|
2329
2334
|
activeTurnInputPreformatted: preformatted,
|
|
2335
|
+
analyzingImages: 0,
|
|
2330
2336
|
busy: true,
|
|
2331
2337
|
busySince: turnStartedAt,
|
|
2332
2338
|
clockNow: turnStartedAt,
|
|
@@ -2450,6 +2456,12 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2450
2456
|
}
|
|
2451
2457
|
}
|
|
2452
2458
|
};
|
|
2459
|
+
session.onImageAnalysis = (activeImageCount) => {
|
|
2460
|
+
store.update((current) => ({
|
|
2461
|
+
...current,
|
|
2462
|
+
analyzingImages: Math.max(0, activeImageCount),
|
|
2463
|
+
}));
|
|
2464
|
+
};
|
|
2453
2465
|
session.onStatus = (message) => {
|
|
2454
2466
|
const panel = thinkingPanelFromStatus(message);
|
|
2455
2467
|
store.update((current) => ({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { agentModeLabel } from '../../agent-mode.js';
|
|
2
|
-
import { truncate } from '../../utils.js';
|
|
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, plainLine, sliceToWidth, span, wrapText, } from './text.js';
|
|
@@ -212,6 +212,9 @@ export function getSlashCommandSuggestions(input) {
|
|
|
212
212
|
function formatModelLabel(modelId, serverModels) {
|
|
213
213
|
return serverModels.find((model) => model.id === modelId)?.label ?? 'Unknown model';
|
|
214
214
|
}
|
|
215
|
+
function pickerModelLabel(modelId, serverModels) {
|
|
216
|
+
return formatModelLabel(modelId, serverModels).replace(/\s*\([^)]*\)\s*$/, '');
|
|
217
|
+
}
|
|
215
218
|
function filterResumeSessions(sessions, filter, serverModels) {
|
|
216
219
|
const q = filter.trim().toLowerCase();
|
|
217
220
|
if (!q)
|
|
@@ -549,7 +552,12 @@ function buildLiveLines(state, width, spinnerFrame, elapsedSeconds) {
|
|
|
549
552
|
}
|
|
550
553
|
lines.push(plainLine(''));
|
|
551
554
|
}
|
|
552
|
-
|
|
555
|
+
const busyLabel = state.analyzingImages > 0
|
|
556
|
+
? state.analyzingImages > 1
|
|
557
|
+
? 'Analyzing images'
|
|
558
|
+
: 'Analyzing image'
|
|
559
|
+
: 'Working';
|
|
560
|
+
lines.push(plainLine(`${WORKING_CLOCK_ICON} ${busyLabel} · ${elapsedSeconds < 60 ? `${elapsedSeconds}s` : `${Math.floor(elapsedSeconds / 60)}m ${String(elapsedSeconds % 60).padStart(2, '0')}s`}`, { color: 'yellow' }));
|
|
553
561
|
const todos = state.todos ?? [];
|
|
554
562
|
if (todos.length > 0) {
|
|
555
563
|
lines.push(plainLine(''));
|
|
@@ -865,18 +873,54 @@ function buildOverlayLines(state, width, height, nowMs) {
|
|
|
865
873
|
}
|
|
866
874
|
if (state.resumePickerOpen) {
|
|
867
875
|
const filtered = filterResumeSessions(state.resumePickerSessions, state.resumePickerFilter, state.serverModels);
|
|
876
|
+
const pickerWidth = Math.max(20, width - 2);
|
|
877
|
+
const divider = () => plainLine('─'.repeat(pickerWidth), { color: 'gray', dim: true });
|
|
868
878
|
lines.push(plainLine('Resume a previous session', { color: 'cyan', bold: true }));
|
|
869
879
|
lines.push(line(span('Search: ', { color: 'gray' }), span(state.resumePickerFilter, {}), span('█', { color: 'gray' })));
|
|
870
|
-
|
|
880
|
+
lines.push(divider());
|
|
881
|
+
const maxCards = Math.max(2, Math.min(filtered.length, Math.floor((height - 10) / 3)));
|
|
882
|
+
let start = 0;
|
|
883
|
+
if (filtered.length > maxCards) {
|
|
884
|
+
start = Math.min(Math.max(0, state.resumePickerIndex - Math.floor(maxCards / 2)), filtered.length - maxCards);
|
|
885
|
+
}
|
|
886
|
+
const visible = filtered.slice(start, start + maxCards);
|
|
887
|
+
if (start > 0) {
|
|
888
|
+
lines.push(plainLine(` … ${start} newer`, { color: 'gray', dim: true }));
|
|
889
|
+
}
|
|
890
|
+
for (const [offset, session] of visible.entries()) {
|
|
891
|
+
const index = start + offset;
|
|
871
892
|
const selected = index === state.resumePickerIndex;
|
|
872
|
-
const
|
|
873
|
-
|
|
874
|
-
|
|
893
|
+
const prompt = singleLinePreview(session.lastUserMessage, pickerWidth) ||
|
|
894
|
+
session.name ||
|
|
895
|
+
`Session ${session.id}`;
|
|
896
|
+
lines.push(line(span(selected ? '› ' : ' ', { color: 'cyan' }), span(fitLine(prompt, Math.max(10, pickerWidth - 2)), selected
|
|
897
|
+
? { color: 'cyan', bold: true }
|
|
898
|
+
: session.lastUserMessage
|
|
899
|
+
? {}
|
|
900
|
+
: { color: 'gray', dim: true })));
|
|
901
|
+
const meta = [
|
|
902
|
+
formatRelativeTime(session.updatedAt),
|
|
903
|
+
pickerModelLabel(session.modelId, state.serverModels),
|
|
904
|
+
session.branch ?? null,
|
|
905
|
+
]
|
|
906
|
+
.filter(Boolean)
|
|
907
|
+
.join(' · ');
|
|
908
|
+
lines.push(line(span(' '), span(fitLine(meta, Math.max(10, pickerWidth - 4)), {
|
|
909
|
+
color: selected ? 'cyan' : 'gray',
|
|
910
|
+
dim: !selected,
|
|
911
|
+
})));
|
|
912
|
+
if (offset < visible.length - 1)
|
|
913
|
+
lines.push(plainLine(''));
|
|
914
|
+
}
|
|
915
|
+
const remaining = filtered.length - (start + visible.length);
|
|
916
|
+
if (remaining > 0) {
|
|
917
|
+
lines.push(plainLine(` … ${remaining} older`, { color: 'gray', dim: true }));
|
|
875
918
|
}
|
|
876
919
|
if (filtered.length === 0) {
|
|
877
920
|
lines.push(plainLine('No sessions match.', { color: 'gray' }));
|
|
878
921
|
}
|
|
879
|
-
lines.push(
|
|
922
|
+
lines.push(divider());
|
|
923
|
+
lines.push(plainLine('↑/↓ move · enter resume · esc start new · ctrl+c quit', {
|
|
880
924
|
color: 'gray',
|
|
881
925
|
}));
|
|
882
926
|
}
|
package/dist/src/utils.js
CHANGED
|
@@ -17,6 +17,15 @@ export function truncate(text, maxChars = 4000) {
|
|
|
17
17
|
return text;
|
|
18
18
|
return `${text.slice(0, maxChars)}\n... (truncated)`;
|
|
19
19
|
}
|
|
20
|
+
export function singleLinePreview(text, maxChars) {
|
|
21
|
+
const collapsed = String(text ?? '')
|
|
22
|
+
.replace(/\n\.\.\. \(truncated\)\s*$/, '…')
|
|
23
|
+
.replace(/\s+/g, ' ')
|
|
24
|
+
.trim();
|
|
25
|
+
if (collapsed.length <= maxChars)
|
|
26
|
+
return collapsed;
|
|
27
|
+
return `${collapsed.slice(0, Math.max(0, maxChars - 1)).trimEnd()}…`;
|
|
28
|
+
}
|
|
20
29
|
export function clampInteger(value, fallback, max) {
|
|
21
30
|
const numeric = Number(value);
|
|
22
31
|
if (!Number.isFinite(numeric) || numeric <= 0) {
|
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.9",
|
|
4
4
|
"description": "TheGitAI is an AI coding agent for your terminal. It indexes your repository, writes and edits files, runs commands, and builds features with you.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
"@lydell/node-pty-linux-x64": "1.1.0",
|
|
38
38
|
"@lydell/node-pty-win32-arm64": "1.1.0",
|
|
39
39
|
"@lydell/node-pty-win32-x64": "1.1.0",
|
|
40
|
-
"@thegitai/tui-darwin-arm64": "1.0.0-preview.
|
|
41
|
-
"@thegitai/tui-darwin-x64": "1.0.0-preview.
|
|
42
|
-
"@thegitai/tui-linux-x64": "1.0.0-preview.
|
|
43
|
-
"@thegitai/tui-win32-x64": "1.0.0-preview.
|
|
40
|
+
"@thegitai/tui-darwin-arm64": "1.0.0-preview.9",
|
|
41
|
+
"@thegitai/tui-darwin-x64": "1.0.0-preview.9",
|
|
42
|
+
"@thegitai/tui-linux-x64": "1.0.0-preview.9",
|
|
43
|
+
"@thegitai/tui-win32-x64": "1.0.0-preview.9",
|
|
44
44
|
"@vscode/ripgrep": "1.18.0"
|
|
45
45
|
},
|
|
46
46
|
"publishConfig": {
|