@golba98/codexa 1.0.2 → 1.0.4
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 +396 -100
- package/bin/codexa.js +62 -144
- package/package.json +14 -8
- package/src/app.tsx +596 -306
- package/src/commands/handler.ts +6 -6
- package/src/config/buildInfo.ts +2 -2
- package/src/config/layeredConfig.ts +1 -1
- package/src/config/persistence.ts +10 -0
- package/src/config/runtimeConfig.ts +1 -1
- package/src/config/settings.ts +8 -16
- package/src/config/trustStore.ts +1 -1
- package/src/config/updateCheckCache.ts +19 -1
- package/src/core/README.md +52 -0
- package/src/core/agent/loop.ts +282 -0
- package/src/core/agent/protocol.ts +211 -0
- package/src/core/agent/tools.ts +414 -0
- package/src/core/{codexExecArgs.ts → codex/codexExecArgs.ts} +2 -2
- package/src/core/{codexLaunch.ts → codex/codexLaunch.ts} +3 -3
- package/src/core/{codexPrompt.ts → codex/codexPrompt.ts} +3 -3
- package/src/core/debug/modelStateDebug.ts +34 -0
- package/src/core/executables/antigravityExecutable.ts +48 -0
- package/src/core/executables/codexExecutable.ts +1 -0
- package/src/core/executables/executableResolver.ts +65 -43
- package/src/core/perf/renderDebug.ts +10 -6
- package/src/core/process/processValidation.ts +9 -5
- package/src/core/providerLauncher/launcher.ts +59 -42
- package/src/core/providerLauncher/registry.ts +30 -14
- package/src/core/providerLauncher/types.ts +11 -9
- package/src/core/providerLauncher/workspaceConfig.ts +41 -26
- package/src/core/providerRuntime/anthropic.ts +7 -1
- package/src/core/providerRuntime/antigravity.ts +305 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +268 -22
- package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
- package/src/core/providerRuntime/contextMetadata.ts +12 -24
- package/src/core/providerRuntime/local.ts +129 -51
- package/src/core/providerRuntime/models.ts +22 -11
- package/src/core/providerRuntime/registry.ts +58 -31
- package/src/core/providerRuntime/types.ts +19 -14
- package/src/core/providers/codexSubprocess.ts +2 -2
- package/src/core/providers/types.ts +1 -1
- package/src/core/{attachments.ts → shared/attachments.ts} +27 -4
- package/src/core/{cleanupFastFail.ts → shared/cleanupFastFail.ts} +1 -1
- package/src/core/{hollowResponseFormat.ts → shared/hollowResponseFormat.ts} +1 -1
- package/src/core/terminal/clearFrameBoundary.ts +814 -0
- package/src/core/terminal/frameLock.ts +109 -0
- package/src/core/terminal/inkRenderReset.ts +123 -0
- package/src/core/terminal/terminalControl.ts +22 -0
- package/src/core/terminal/terminalTitle.ts +16 -102
- package/src/core/version/channel.ts +23 -0
- package/src/core/version/updateCheck.ts +193 -0
- package/src/core/{launchContext.ts → workspace/launchContext.ts} +32 -39
- package/src/core/{planStorage.ts → workspace/planStorage.ts} +2 -2
- package/src/core/{workspaceGuard.ts → workspace/workspaceGuard.ts} +97 -13
- package/src/headless/execRunner.ts +2 -2
- package/src/index.tsx +43 -89
- package/src/session/appSession.ts +10 -7
- package/src/session/chatLifecycle.ts +1 -1
- package/src/session/liveRenderScheduler.ts +1 -1
- package/src/session/types.ts +3 -2
- package/src/ui/ActionRequiredBlock.tsx +5 -5
- package/src/ui/ActivityBars.tsx +3 -3
- package/src/ui/ActivityIndicator.tsx +6 -6
- package/src/ui/AgentBlock.tsx +6 -6
- package/src/ui/AnimatedStatusText.tsx +1 -1
- package/src/ui/AppShell.tsx +670 -719
- package/src/ui/AttachmentImportPanel.tsx +8 -8
- package/src/ui/AuthPanel.tsx +20 -20
- package/src/ui/BottomComposer.tsx +158 -118
- package/src/ui/DashCard.tsx +3 -3
- package/src/ui/Markdown.tsx +17 -17
- package/src/ui/ModelPickerScreen.tsx +222 -42
- package/src/ui/ModelReasoningPicker.tsx +15 -15
- package/src/ui/Panel.tsx +3 -3
- package/src/ui/PlanActionPicker.tsx +6 -6
- package/src/ui/PlanReviewPanel.tsx +9 -9
- package/src/ui/ProviderPicker.tsx +735 -321
- package/src/ui/RunFooter.tsx +3 -3
- package/src/ui/RuntimeStatusBar.tsx +108 -0
- package/src/ui/SelectionPanel.tsx +8 -4
- package/src/ui/SettingsPanel.tsx +9 -9
- package/src/ui/Spinner.tsx +1 -1
- package/src/ui/TextEntryPanel.tsx +11 -11
- package/src/ui/ThinkingBlock.tsx +8 -8
- package/src/ui/Timeline.tsx +1625 -1472
- package/src/ui/TopHeader.tsx +437 -293
- package/src/ui/TranscriptShell.tsx +322 -0
- package/src/ui/TurnGroup.tsx +33 -33
- package/src/ui/UpdateAvailableCard.tsx +41 -0
- package/src/ui/UpdatePromptPanel.tsx +197 -0
- package/src/ui/focus.ts +3 -0
- package/src/ui/layout.ts +299 -25
- package/src/ui/layoutListWindow.ts +145 -0
- package/src/ui/logoVariants.ts +103 -0
- package/src/ui/modeDisplay.ts +12 -12
- package/src/ui/runtimeDisplay.ts +112 -0
- package/src/ui/textLayout.ts +15 -4
- package/src/ui/theme.tsx +274 -395
- package/src/ui/timelineMeasure.ts +218 -136
- package/scripts/audit-codexa-capabilities.mjs +0 -466
- package/scripts/gen-build-info.mjs +0 -33
- package/scripts/smoke-terminal-bench.mjs +0 -35
- package/src/appRenderStability.test.ts +0 -131
- package/src/commands/handler.test.ts +0 -655
- package/src/config/launchArgs.test.ts +0 -189
- package/src/config/layeredConfig.test.ts +0 -143
- package/src/config/persistence.test.ts +0 -114
- package/src/config/runtimeConfig.test.ts +0 -218
- package/src/config/settings.test.ts +0 -155
- package/src/config/trustStore.test.ts +0 -29
- package/src/core/attachments.test.ts +0 -155
- package/src/core/auth/codexAuth.test.ts +0 -68
- package/src/core/cleanupFastFail.test.ts +0 -76
- package/src/core/codex.ts +0 -124
- package/src/core/codexExecArgs.test.ts +0 -195
- package/src/core/codexLaunch.test.ts +0 -205
- package/src/core/codexPrompt.test.ts +0 -252
- package/src/core/executables/codexExecutable.test.ts +0 -212
- package/src/core/executables/executableResolver.test.ts +0 -129
- package/src/core/executables/geminiExecutable.test.ts +0 -116
- package/src/core/executables/pathSanityScan.test.ts +0 -47
- package/src/core/githubDiagnostics.test.ts +0 -92
- package/src/core/hollowResponseFormat.test.ts +0 -58
- package/src/core/launchContext.test.ts +0 -157
- package/src/core/models/codexCapabilities.test.ts +0 -45
- package/src/core/models/codexModelCapabilities.test.ts +0 -246
- package/src/core/models/modelSpecs.test.ts +0 -283
- package/src/core/perf/renderDebug.test.ts +0 -230
- package/src/core/planStorage.test.ts +0 -143
- package/src/core/process/CommandRunner.test.ts +0 -105
- package/src/core/projectInstructions.test.ts +0 -50
- package/src/core/providerLauncher/launcher.test.ts +0 -238
- package/src/core/providerLauncher/registry.test.ts +0 -324
- package/src/core/providerLauncher/workspaceConfig.test.ts +0 -638
- package/src/core/providerRuntime/anthropic.test.ts +0 -1120
- package/src/core/providerRuntime/capabilityProfile.test.ts +0 -311
- package/src/core/providerRuntime/contextMetadata.test.ts +0 -468
- package/src/core/providerRuntime/gemini.test.ts +0 -437
- package/src/core/providerRuntime/lmstudio.test.ts +0 -168
- package/src/core/providerRuntime/local.test.ts +0 -787
- package/src/core/providerRuntime/registry.test.ts +0 -233
- package/src/core/providers/codexJsonStream.test.ts +0 -148
- package/src/core/providers/codexSubprocess.test.ts +0 -68
- package/src/core/providers/codexTranscript.test.ts +0 -284
- package/src/core/terminal/startupClear.test.ts +0 -55
- package/src/core/terminal/terminalCapabilities.test.ts +0 -93
- package/src/core/terminal/terminalControl.test.ts +0 -75
- package/src/core/terminal/terminalSanitize.test.ts +0 -22
- package/src/core/terminal/terminalSelection.test.ts +0 -42
- package/src/core/terminal/terminalTitle.test.ts +0 -328
- package/src/core/updateCheck.test.ts +0 -194
- package/src/core/updateCheck.ts +0 -172
- package/src/core/workspaceActivity.test.ts +0 -163
- package/src/core/workspaceGuard.test.ts +0 -151
- package/src/core/workspaceRoot.test.ts +0 -23
- package/src/exec.test.ts +0 -13
- package/src/headless/execArgs.test.ts +0 -147
- package/src/headless/execRunner.test.ts +0 -436
- package/src/index.test.tsx +0 -620
- package/src/session/appSession.test.ts +0 -897
- package/src/session/chatLifecycle.test.ts +0 -64
- package/src/session/liveRenderScheduler.test.ts +0 -201
- package/src/session/planFlow.test.ts +0 -103
- package/src/session/planTranscript.test.ts +0 -65
- package/src/session/promptRunSchedule.test.ts +0 -36
- package/src/ui/ActivityIndicator.test.tsx +0 -58
- package/src/ui/AgentBlock.test.ts +0 -6
- package/src/ui/AnimatedStatusText.test.ts +0 -16
- package/src/ui/AppShell.test.tsx +0 -1776
- package/src/ui/AttachmentImportPanel.test.tsx +0 -204
- package/src/ui/BottomComposer.test.ts +0 -674
- package/src/ui/CodexLogo.tsx +0 -55
- package/src/ui/Markdown.test.ts +0 -157
- package/src/ui/ModelPickerProviderScope.test.tsx +0 -411
- package/src/ui/ModelPickerScreen.test.tsx +0 -99
- package/src/ui/ModelPickerState.test.tsx +0 -151
- package/src/ui/ModelReasoningPicker.test.tsx +0 -447
- package/src/ui/PlanReviewPanel.test.tsx +0 -267
- package/src/ui/PromptCardBorder.test.tsx +0 -161
- package/src/ui/ProviderPicker.test.tsx +0 -289
- package/src/ui/ProviderShortcut.test.tsx +0 -143
- package/src/ui/SettingsPanel.test.tsx +0 -233
- package/src/ui/StaticTranscriptItem.tsx +0 -56
- package/src/ui/Timeline.test.ts +0 -2067
- package/src/ui/TimelineNavigation.test.tsx +0 -201
- package/src/ui/TopHeader.test.tsx +0 -254
- package/src/ui/TurnGroup.test.tsx +0 -365
- package/src/ui/busyStatusAnimation.test.ts +0 -30
- package/src/ui/commandNormalize.test.ts +0 -142
- package/src/ui/diffRenderer.test.ts +0 -102
- package/src/ui/focusFlow.test.tsx +0 -1098
- package/src/ui/inputBuffer.test.ts +0 -151
- package/src/ui/layout.test.ts +0 -146
- package/src/ui/modeDisplay.test.ts +0 -42
- package/src/ui/runActivityView.test.ts +0 -89
- package/src/ui/runLifecycleView.test.tsx +0 -237
- package/src/ui/statusRenderIsolation.test.tsx +0 -654
- package/src/ui/terminalAnswerFormat.test.ts +0 -19
- package/src/ui/textLayout.test.ts +0 -18
- package/src/ui/themeFlow.test.ts +0 -53
- package/src/ui/timelineMeasureCache.test.ts +0 -986
- /package/src/core/{inputDebug.ts → debug/inputDebug.ts} +0 -0
- /package/src/core/{clipboard.ts → shared/clipboard.ts} +0 -0
- /package/src/core/{githubDiagnostics.ts → shared/githubDiagnostics.ts} +0 -0
- /package/src/core/{projectInstructions.ts → workspace/projectInstructions.ts} +0 -0
- /package/src/core/{workspaceActivity.ts → workspace/workspaceActivity.ts} +0 -0
- /package/src/core/{workspaceRoot.ts → workspace/workspaceRoot.ts} +0 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utility for windowing large lists in limited terminal rows.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface ListWindowOptions {
|
|
6
|
+
itemCount: number;
|
|
7
|
+
selectedIndex: number;
|
|
8
|
+
availableRows: number;
|
|
9
|
+
/** Fixed rows consumed by headers, borders, titles, etc. */
|
|
10
|
+
chromeRows: number;
|
|
11
|
+
/** Whether to show "↑ N more" and "↓ N more" indicators. */
|
|
12
|
+
showIndicators?: boolean;
|
|
13
|
+
/** Whether to show "Showing X-Y of N" range line. */
|
|
14
|
+
showRangeLine?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ListWindowResult {
|
|
18
|
+
start: number;
|
|
19
|
+
end: number;
|
|
20
|
+
visibleCount: number;
|
|
21
|
+
showAbove: boolean;
|
|
22
|
+
showBelow: boolean;
|
|
23
|
+
showRange: boolean;
|
|
24
|
+
hiddenAbove: number;
|
|
25
|
+
hiddenBelow: number;
|
|
26
|
+
/** Total rows consumed including chrome and indicators. */
|
|
27
|
+
totalRows: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Calculates a contiguous window of items that fits within available rows,
|
|
32
|
+
* ensuring the selected item is always visible.
|
|
33
|
+
*/
|
|
34
|
+
export function calculateListWindow({
|
|
35
|
+
itemCount,
|
|
36
|
+
selectedIndex,
|
|
37
|
+
availableRows,
|
|
38
|
+
chromeRows,
|
|
39
|
+
showIndicators = true,
|
|
40
|
+
showRangeLine = false,
|
|
41
|
+
}: ListWindowOptions): ListWindowResult {
|
|
42
|
+
if (itemCount === 0) {
|
|
43
|
+
return {
|
|
44
|
+
start: 0,
|
|
45
|
+
end: 0,
|
|
46
|
+
visibleCount: 0,
|
|
47
|
+
showAbove: false,
|
|
48
|
+
showBelow: false,
|
|
49
|
+
showRange: false,
|
|
50
|
+
hiddenAbove: 0,
|
|
51
|
+
hiddenBelow: 0,
|
|
52
|
+
totalRows: chromeRows,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const rangeReserved = showRangeLine ? 1 : 0;
|
|
57
|
+
|
|
58
|
+
// If everything fits including the range line, just show everything.
|
|
59
|
+
if (itemCount + chromeRows + rangeReserved <= availableRows) {
|
|
60
|
+
return {
|
|
61
|
+
start: 0,
|
|
62
|
+
end: itemCount,
|
|
63
|
+
visibleCount: itemCount,
|
|
64
|
+
showAbove: false,
|
|
65
|
+
showBelow: false,
|
|
66
|
+
showRange: showRangeLine,
|
|
67
|
+
hiddenAbove: 0,
|
|
68
|
+
hiddenBelow: 0,
|
|
69
|
+
totalRows: itemCount + chromeRows + rangeReserved,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Otherwise we must window.
|
|
74
|
+
// We reserve space for indicators if requested.
|
|
75
|
+
// We MUST fit within availableRows.
|
|
76
|
+
const indicatorReserved = showIndicators ? 2 : 0;
|
|
77
|
+
const baseReserved = chromeRows + indicatorReserved + rangeReserved;
|
|
78
|
+
|
|
79
|
+
let itemRows = Math.max(1, availableRows - baseReserved);
|
|
80
|
+
let start = 0;
|
|
81
|
+
let end = 0;
|
|
82
|
+
|
|
83
|
+
// Iteratively refine to account for dynamic indicators.
|
|
84
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
85
|
+
const visibleCount = Math.max(1, Math.min(itemCount, itemRows));
|
|
86
|
+
|
|
87
|
+
// Center selected item if possible
|
|
88
|
+
start = Math.max(0, selectedIndex - Math.floor(visibleCount / 2));
|
|
89
|
+
if (start + visibleCount > itemCount) {
|
|
90
|
+
start = Math.max(0, itemCount - visibleCount);
|
|
91
|
+
}
|
|
92
|
+
end = Math.min(itemCount, start + visibleCount);
|
|
93
|
+
|
|
94
|
+
const actualAbove = start > 0;
|
|
95
|
+
const actualBelow = end < itemCount;
|
|
96
|
+
const dynamicReserved = chromeRows
|
|
97
|
+
+ (showRangeLine ? 1 : 0)
|
|
98
|
+
+ (showIndicators && actualAbove ? 1 : 0)
|
|
99
|
+
+ (showIndicators && actualBelow ? 1 : 0);
|
|
100
|
+
|
|
101
|
+
const nextItemRows = Math.max(1, availableRows - dynamicReserved);
|
|
102
|
+
if (nextItemRows === itemRows) break;
|
|
103
|
+
itemRows = nextItemRows;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Final visibility check for selectedIndex
|
|
107
|
+
if (selectedIndex < start) {
|
|
108
|
+
start = selectedIndex;
|
|
109
|
+
end = Math.min(itemCount, start + itemRows);
|
|
110
|
+
} else if (selectedIndex >= end) {
|
|
111
|
+
end = selectedIndex + 1;
|
|
112
|
+
start = Math.max(0, end - itemRows);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Final clamping to ensure we don't exceed availableRows if indicators are shown
|
|
116
|
+
let finalAbove = showIndicators && start > 0;
|
|
117
|
+
let finalBelow = showIndicators && end < itemCount;
|
|
118
|
+
|
|
119
|
+
while (end - start + chromeRows + (showRangeLine ? 1 : 0) + (finalAbove ? 1 : 0) + (finalBelow ? 1 : 0) > availableRows && end - start > 1) {
|
|
120
|
+
// Shrink window from the side further from selectedIndex
|
|
121
|
+
if (selectedIndex - start > end - 1 - selectedIndex) {
|
|
122
|
+
start++;
|
|
123
|
+
} else {
|
|
124
|
+
end--;
|
|
125
|
+
}
|
|
126
|
+
finalAbove = showIndicators && start > 0;
|
|
127
|
+
finalBelow = showIndicators && end < itemCount;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
start,
|
|
132
|
+
end,
|
|
133
|
+
visibleCount: end - start,
|
|
134
|
+
showAbove: finalAbove,
|
|
135
|
+
showBelow: finalBelow,
|
|
136
|
+
showRange: showRangeLine,
|
|
137
|
+
hiddenAbove: start,
|
|
138
|
+
hiddenBelow: itemCount - end,
|
|
139
|
+
totalRows: (end - start)
|
|
140
|
+
+ chromeRows
|
|
141
|
+
+ (showRangeLine ? 1 : 0)
|
|
142
|
+
+ (finalAbove ? 1 : 0)
|
|
143
|
+
+ (finalBelow ? 1 : 0),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { getTextWidth } from "./textLayout.js";
|
|
2
|
+
|
|
3
|
+
// ─── Logo constants ───────────────────────────────────────────────────────────
|
|
4
|
+
// Each variant is an array of exact terminal rows.
|
|
5
|
+
//
|
|
6
|
+
// IMPORTANT: Never apply `bold` when rendering these rows. Bold rendering of
|
|
7
|
+
// Unicode full-block (█) and box-drawing (╔═╗╝) characters causes per-glyph
|
|
8
|
+
// width/stroke differences in most terminal fonts (Ptyxis, GNOME Terminal,
|
|
9
|
+
// VS Code), producing visible gaps between characters that should be flush.
|
|
10
|
+
// The companion `wrap="truncate"` rule keeps each row on exactly one terminal
|
|
11
|
+
// line regardless of the surrounding Ink flex layout.
|
|
12
|
+
|
|
13
|
+
// Canonical Codexa brand wordmark — the ██ block art is the authoritative
|
|
14
|
+
// large logo for wide/max layouts.
|
|
15
|
+
export const CODEXA_WORDMARK = [
|
|
16
|
+
" ██████╗ ██████╗ ██████╗ ███████╗██╗ ██╗ █████╗ ",
|
|
17
|
+
"██╔════╝██╔═══██╗██╔══██╗██╔════╝╚██╗██╔╝██╔══██╗",
|
|
18
|
+
"██║ ██║ ██║██║ ██║█████╗ ╚███╔╝ ███████║",
|
|
19
|
+
"██║ ██║ ██║██║ ██║██╔══╝ ██╔██╗ ██╔══██║",
|
|
20
|
+
"╚██████╗╚██████╔╝██████╔╝███████╗██╔╝ ██╗██║ ██║",
|
|
21
|
+
" ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝",
|
|
22
|
+
].join("\n");
|
|
23
|
+
|
|
24
|
+
/** 6-row ANSI Shadow block-char logo. Requires cols ≥ LOGO_LARGE_MIN_COLS. */
|
|
25
|
+
export const LOGO_LARGE: readonly string[] = CODEXA_WORDMARK.split("\n");
|
|
26
|
+
|
|
27
|
+
/** 4-row pure-ASCII art logo. Requires cols ≥ LOGO_MEDIUM_MIN_COLS. */
|
|
28
|
+
export const LOGO_MEDIUM: readonly string[] = [
|
|
29
|
+
" ____ ___ ____ _____ _ __ _ ",
|
|
30
|
+
" / ___| / _ \\| _ \\| ____| |/ / / \\ ",
|
|
31
|
+
"| | | | | | | | | _| | ' / / _ \\ ",
|
|
32
|
+
"|_| \\___/|_| |_|_____|_|\\_\\ /_/ \\_\\ ",
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/** 1-row compact logo. Requires cols ≥ LOGO_COMPACT_MIN_COLS. */
|
|
36
|
+
export const LOGO_COMPACT: readonly string[] = [
|
|
37
|
+
"✦ CODEXA",
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
// ─── Breakpoints ──────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
// Aligned with MEDIUM_HEADER_MIN_COLUMNS so any side-by-side-capable terminal
|
|
43
|
+
// shows the canonical block wordmark instead of the thin ASCII fallback.
|
|
44
|
+
export const LOGO_LARGE_MIN_COLS = 72;
|
|
45
|
+
export const LOGO_MEDIUM_MIN_COLS = 72;
|
|
46
|
+
export const LOGO_COMPACT_MIN_COLS = 48;
|
|
47
|
+
|
|
48
|
+
// Minimum terminal rows each variant needs to render without crowding out the
|
|
49
|
+
// metadata + composer. A wide-but-short terminal (e.g. VS Code's bottom panel)
|
|
50
|
+
// must step DOWN to a smaller logo instead of dropping straight to text-only.
|
|
51
|
+
export const LOGO_LARGE_MIN_ROWS = 35;
|
|
52
|
+
export const LOGO_MEDIUM_MIN_ROWS = 16;
|
|
53
|
+
export const LOGO_COMPACT_MIN_ROWS = 12;
|
|
54
|
+
|
|
55
|
+
const LOGO_VARIANTS: readonly { logo: readonly string[]; minCols: number; minRows: number }[] = [
|
|
56
|
+
{ logo: LOGO_LARGE, minCols: LOGO_LARGE_MIN_COLS, minRows: LOGO_LARGE_MIN_ROWS },
|
|
57
|
+
{ logo: LOGO_MEDIUM, minCols: LOGO_MEDIUM_MIN_COLS, minRows: LOGO_MEDIUM_MIN_ROWS },
|
|
58
|
+
{ logo: LOGO_COMPACT, minCols: LOGO_COMPACT_MIN_COLS, minRows: LOGO_COMPACT_MIN_ROWS },
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
// ─── Selection ────────────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Returns the best logo variant for the given terminal column count.
|
|
65
|
+
*
|
|
66
|
+
* Env overrides:
|
|
67
|
+
* CODEXA_NO_ASCII_LOGO=1 → always text-only (empty array)
|
|
68
|
+
* CODEXA_COMPACT_LOGO=1 → always compact single-line logo
|
|
69
|
+
*/
|
|
70
|
+
export function selectLogoVariant(cols: number): readonly string[] {
|
|
71
|
+
if (process.env["CODEXA_NO_ASCII_LOGO"] === "1") return [];
|
|
72
|
+
if (process.env["CODEXA_COMPACT_LOGO"] === "1") return LOGO_COMPACT;
|
|
73
|
+
if (cols >= LOGO_LARGE_MIN_COLS) return LOGO_LARGE;
|
|
74
|
+
if (cols >= LOGO_COMPACT_MIN_COLS) return LOGO_COMPACT;
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Returns the largest logo variant that fits BOTH the available columns and
|
|
80
|
+
* rows. Unlike {@link selectLogoVariant} (columns-only), this degrades a
|
|
81
|
+
* too-tall logo to a shorter one before falling back to text-only — so a
|
|
82
|
+
* wide-but-short terminal keeps a logo instead of collapsing to a flat line.
|
|
83
|
+
* Returns an empty array only when even the 1-row compact logo cannot fit.
|
|
84
|
+
*
|
|
85
|
+
* Honours the same env overrides as {@link selectLogoVariant}.
|
|
86
|
+
*/
|
|
87
|
+
export function selectLogoVariantForViewport(cols: number, rows: number): readonly string[] {
|
|
88
|
+
if (process.env["CODEXA_NO_ASCII_LOGO"] === "1") return [];
|
|
89
|
+
if (process.env["CODEXA_COMPACT_LOGO"] === "1") {
|
|
90
|
+
return rows >= LOGO_COMPACT_MIN_ROWS ? LOGO_COMPACT : [];
|
|
91
|
+
}
|
|
92
|
+
for (const variant of LOGO_VARIANTS) {
|
|
93
|
+
if (cols >= variant.minCols && rows >= variant.minRows) {
|
|
94
|
+
return variant.logo;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Returns the visual column width of the widest row in a logo variant. */
|
|
101
|
+
export function getLogoWidth(logo: readonly string[]): number {
|
|
102
|
+
return logo.reduce((max, line) => Math.max(max, getTextWidth(line)), 0);
|
|
103
|
+
}
|
package/src/ui/modeDisplay.ts
CHANGED
|
@@ -18,10 +18,10 @@ export function getModeDisplaySpec(mode: string, theme: Theme): ModeDisplaySpec
|
|
|
18
18
|
return {
|
|
19
19
|
label: formatModeLabel(mode),
|
|
20
20
|
ringGlyph: "◉",
|
|
21
|
-
ringColor: theme.
|
|
22
|
-
ringFill: theme.
|
|
23
|
-
iconColor: theme.
|
|
24
|
-
labelColor: theme.
|
|
21
|
+
ringColor: theme.warning,
|
|
22
|
+
ringFill: theme.border,
|
|
23
|
+
iconColor: theme.warning,
|
|
24
|
+
labelColor: theme.text,
|
|
25
25
|
labelBold: true,
|
|
26
26
|
ringBold: true,
|
|
27
27
|
};
|
|
@@ -29,10 +29,10 @@ export function getModeDisplaySpec(mode: string, theme: Theme): ModeDisplaySpec
|
|
|
29
29
|
return {
|
|
30
30
|
label: formatModeLabel(mode),
|
|
31
31
|
ringGlyph: "◎",
|
|
32
|
-
ringColor: theme.
|
|
33
|
-
ringFill: theme.
|
|
34
|
-
iconColor: theme.
|
|
35
|
-
labelColor: theme.
|
|
32
|
+
ringColor: theme.borderFocused,
|
|
33
|
+
ringFill: theme.surfaceMuted,
|
|
34
|
+
iconColor: theme.prompt,
|
|
35
|
+
labelColor: theme.text,
|
|
36
36
|
labelBold: true,
|
|
37
37
|
ringBold: false,
|
|
38
38
|
};
|
|
@@ -41,10 +41,10 @@ export function getModeDisplaySpec(mode: string, theme: Theme): ModeDisplaySpec
|
|
|
41
41
|
return {
|
|
42
42
|
label: formatModeLabel(mode),
|
|
43
43
|
ringGlyph: "○",
|
|
44
|
-
ringColor: theme.
|
|
45
|
-
ringFill: theme.
|
|
46
|
-
iconColor: theme.
|
|
47
|
-
labelColor: theme.
|
|
44
|
+
ringColor: theme.success,
|
|
45
|
+
ringFill: theme.surfaceMuted,
|
|
46
|
+
iconColor: theme.success,
|
|
47
|
+
labelColor: theme.textMuted,
|
|
48
48
|
labelBold: false,
|
|
49
49
|
ringBold: false,
|
|
50
50
|
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { formatModeLabel, formatReasoningLabel } from "../config/settings.js";
|
|
2
|
+
import type { RuntimeSummary } from "../config/runtimeConfig.js";
|
|
3
|
+
import type { CodexModelCapability } from "../core/models/codexModelCapabilities.js";
|
|
4
|
+
import type { ModelSpec } from "../core/models/modelSpecs.js";
|
|
5
|
+
import type { ModelContextMetadata } from "../core/providerRuntime/contextMetadata.js";
|
|
6
|
+
import { contextMetadataToModelSpec, formatContextCompact } from "../core/providerRuntime/contextMetadata.js";
|
|
7
|
+
import type { ActiveProviderRoute } from "../core/providerRuntime/types.js";
|
|
8
|
+
import { getAntigravityModelLabel } from "../core/providerRuntime/antigravity.js";
|
|
9
|
+
|
|
10
|
+
export interface ActiveRuntimeDisplayInput {
|
|
11
|
+
route: ActiveProviderRoute;
|
|
12
|
+
reasoningLevel: string;
|
|
13
|
+
mode: string;
|
|
14
|
+
tokensUsed: number;
|
|
15
|
+
modelCapability?: CodexModelCapability | null;
|
|
16
|
+
contextMetadata?: ModelContextMetadata | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ActiveRuntimeDisplay {
|
|
20
|
+
providerLabel: string;
|
|
21
|
+
modelDisplay: string;
|
|
22
|
+
footerModelDisplay: string;
|
|
23
|
+
contextDisplay: string;
|
|
24
|
+
modeLabel: string;
|
|
25
|
+
modelSpec: ModelSpec;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const PROVIDER_DISPLAY: Record<string, string> = {
|
|
29
|
+
openai: "OpenAI Codex CLI",
|
|
30
|
+
anthropic: "Claude Code CLI",
|
|
31
|
+
google: "Gemini CLI",
|
|
32
|
+
local: "Local",
|
|
33
|
+
antigravity: "Antigravity CLI",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function formatContextLimit(value: number): string {
|
|
37
|
+
if (value >= 1_000_000) {
|
|
38
|
+
const millions = value / 1_000_000;
|
|
39
|
+
return Number.isInteger(millions) || Math.abs(millions - 1.048576) < 0.000001
|
|
40
|
+
? `${Math.round(millions)}M`
|
|
41
|
+
: `${millions.toFixed(1)}M`;
|
|
42
|
+
}
|
|
43
|
+
return formatContextCompact(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function formatUsedTokens(value: number): string {
|
|
47
|
+
return formatContextCompact(value).replace(/k$/, "K");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isContextForRoute(metadata: ModelContextMetadata | null | undefined, route: ActiveProviderRoute): metadata is ModelContextMetadata {
|
|
51
|
+
return metadata?.providerId === route.providerId && metadata.modelId === route.modelId;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getModelLabel(route: ActiveProviderRoute, capability?: CodexModelCapability | null): string {
|
|
55
|
+
if (route.providerId === "anthropic") {
|
|
56
|
+
return capability?.label ?? route.modelId;
|
|
57
|
+
}
|
|
58
|
+
if (route.providerId === "antigravity") {
|
|
59
|
+
return getAntigravityModelLabel(route.modelId);
|
|
60
|
+
}
|
|
61
|
+
return route.modelId;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function buildActiveRuntimeDisplay({
|
|
65
|
+
route,
|
|
66
|
+
reasoningLevel,
|
|
67
|
+
mode,
|
|
68
|
+
tokensUsed,
|
|
69
|
+
modelCapability = null,
|
|
70
|
+
contextMetadata = null,
|
|
71
|
+
}: ActiveRuntimeDisplayInput): ActiveRuntimeDisplay {
|
|
72
|
+
const providerLabel = PROVIDER_DISPLAY[route.providerId] ?? route.providerId;
|
|
73
|
+
const rawReasoning = route.providerId === "antigravity"
|
|
74
|
+
? route.reasoning
|
|
75
|
+
: route.reasoning ?? reasoningLevel;
|
|
76
|
+
const reasoning = rawReasoning ? formatReasoningLabel(rawReasoning) : null;
|
|
77
|
+
const modelLabel = getModelLabel(route, modelCapability);
|
|
78
|
+
const validContextMetadata = isContextForRoute(contextMetadata, route) ? contextMetadata : null;
|
|
79
|
+
const contextDisplay = validContextMetadata?.contextLength != null
|
|
80
|
+
? `${formatUsedTokens(tokensUsed)} / ${validContextMetadata.confidence === "estimated" ? "~" : ""}${formatContextLimit(validContextMetadata.contextLength)}`
|
|
81
|
+
: "Unknown";
|
|
82
|
+
const modelSpec = contextMetadataToModelSpec(validContextMetadata ?? {
|
|
83
|
+
providerId: route.providerId,
|
|
84
|
+
modelId: route.modelId,
|
|
85
|
+
contextLength: null,
|
|
86
|
+
source: "unknown",
|
|
87
|
+
confidence: "unknown",
|
|
88
|
+
error: "Context length unavailable for this model.",
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
providerLabel,
|
|
93
|
+
modelDisplay: reasoning
|
|
94
|
+
? `${providerLabel} / ${modelLabel} / reasoning: ${reasoning}`
|
|
95
|
+
: `${providerLabel} / ${modelLabel}`,
|
|
96
|
+
footerModelDisplay: reasoning
|
|
97
|
+
? `${providerLabel} / ${modelLabel} (${reasoning})`
|
|
98
|
+
: `${providerLabel} / ${modelLabel}`,
|
|
99
|
+
contextDisplay,
|
|
100
|
+
modeLabel: formatModeLabel(mode),
|
|
101
|
+
modelSpec,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function runtimeDisplayToSummary(display: ActiveRuntimeDisplay, base: RuntimeSummary): RuntimeSummary {
|
|
106
|
+
return {
|
|
107
|
+
...base,
|
|
108
|
+
providerLabel: display.providerLabel,
|
|
109
|
+
modelLabel: display.modelDisplay,
|
|
110
|
+
contextLabel: display.contextDisplay,
|
|
111
|
+
};
|
|
112
|
+
}
|
package/src/ui/textLayout.ts
CHANGED
|
@@ -198,9 +198,14 @@ export function splitTextAtColumn(text: string, column: number): { before: strin
|
|
|
198
198
|
|
|
199
199
|
// ─── Text wrapping ────────────────────────────────────────────────────────────
|
|
200
200
|
|
|
201
|
-
export function wrapTextRows(
|
|
201
|
+
export function wrapTextRows(
|
|
202
|
+
text: string,
|
|
203
|
+
maxWidth: number,
|
|
204
|
+
firstLineWidth: number = maxWidth,
|
|
205
|
+
): WrappedTextRow[] {
|
|
202
206
|
const normalized = normalizeLineBreaks(text);
|
|
203
207
|
const safeWidth = Math.max(1, maxWidth);
|
|
208
|
+
const safeFirstWidth = Math.max(1, firstLineWidth);
|
|
204
209
|
const rows: WrappedTextRow[] = [];
|
|
205
210
|
let rowStart = 0;
|
|
206
211
|
let rowText = "";
|
|
@@ -220,7 +225,9 @@ export function wrapTextRows(text: string, maxWidth: number): WrappedTextRow[] {
|
|
|
220
225
|
continue;
|
|
221
226
|
}
|
|
222
227
|
|
|
223
|
-
|
|
228
|
+
// Only the first emitted row honors firstLineWidth; later rows use maxWidth.
|
|
229
|
+
const limit = rows.length === 0 ? safeFirstWidth : safeWidth;
|
|
230
|
+
if (rowText.length > 0 && rowWidth + unit.width > limit) {
|
|
224
231
|
rows.push({
|
|
225
232
|
text: rowText,
|
|
226
233
|
start: rowStart,
|
|
@@ -252,8 +259,12 @@ export function wrapTextRows(text: string, maxWidth: number): WrappedTextRow[] {
|
|
|
252
259
|
}];
|
|
253
260
|
}
|
|
254
261
|
|
|
255
|
-
export function wrapPlainText(
|
|
256
|
-
|
|
262
|
+
export function wrapPlainText(
|
|
263
|
+
text: string,
|
|
264
|
+
maxWidth: number,
|
|
265
|
+
firstLineWidth: number = maxWidth,
|
|
266
|
+
): string[] {
|
|
267
|
+
return wrapTextRows(text, maxWidth, firstLineWidth).map((row) => row.text);
|
|
257
268
|
}
|
|
258
269
|
|
|
259
270
|
export function wrapCommandText(text: string, maxWidth: number): string[] {
|