@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
|
@@ -52,20 +52,20 @@ export function AttachmentImportPanel({
|
|
|
52
52
|
<Box flexDirection="column" width="100%" marginTop={1}>
|
|
53
53
|
<Box
|
|
54
54
|
borderStyle="round"
|
|
55
|
-
borderColor={theme.
|
|
55
|
+
borderColor={theme.border}
|
|
56
56
|
paddingX={2}
|
|
57
57
|
paddingY={1}
|
|
58
58
|
width="100%"
|
|
59
59
|
>
|
|
60
|
-
<Text color={theme.
|
|
61
|
-
<Text color={theme.
|
|
60
|
+
<Text color={theme.accent} bold>IMPORT FILE </Text>
|
|
61
|
+
<Text color={theme.textMuted}>
|
|
62
62
|
Copy {files.length} outside-workspace {fileLabel} into .codexa/attachments?
|
|
63
63
|
</Text>
|
|
64
64
|
</Box>
|
|
65
65
|
|
|
66
66
|
<Box
|
|
67
67
|
borderStyle="round"
|
|
68
|
-
borderColor={theme.
|
|
68
|
+
borderColor={theme.border}
|
|
69
69
|
paddingX={2}
|
|
70
70
|
paddingY={1}
|
|
71
71
|
marginTop={1}
|
|
@@ -74,8 +74,8 @@ export function AttachmentImportPanel({
|
|
|
74
74
|
>
|
|
75
75
|
{files.map((file, i) => (
|
|
76
76
|
<Box key={i} flexDirection="column" marginBottom={i < files.length - 1 ? 1 : 0}>
|
|
77
|
-
<Text color={theme.
|
|
78
|
-
<Text color={theme.
|
|
77
|
+
<Text color={theme.text}>{path.basename(file.srcPath)}</Text>
|
|
78
|
+
<Text color={theme.textDim}>
|
|
79
79
|
{"→ "}{relativeAttachmentsDir}/{file.destFilename}
|
|
80
80
|
</Text>
|
|
81
81
|
</Box>
|
|
@@ -83,14 +83,14 @@ export function AttachmentImportPanel({
|
|
|
83
83
|
|
|
84
84
|
{showVisionWarning && (
|
|
85
85
|
<Box marginTop={1}>
|
|
86
|
-
<Text color={theme.
|
|
86
|
+
<Text color={theme.warning}>
|
|
87
87
|
Note: active model may not support images.
|
|
88
88
|
</Text>
|
|
89
89
|
</Box>
|
|
90
90
|
)}
|
|
91
91
|
|
|
92
92
|
<Box marginTop={1}>
|
|
93
|
-
<Text color={theme.
|
|
93
|
+
<Text color={theme.textDim}>Esc to close · Enter to confirm</Text>
|
|
94
94
|
</Box>
|
|
95
95
|
</Box>
|
|
96
96
|
</Box>
|
package/src/ui/AuthPanel.tsx
CHANGED
|
@@ -49,10 +49,10 @@ export function AuthPanel({
|
|
|
49
49
|
const authStateLabel = getAuthStateLabel(authStatus.state);
|
|
50
50
|
const authStateColor =
|
|
51
51
|
authStatus.state === "authenticated"
|
|
52
|
-
? theme.
|
|
52
|
+
? theme.success
|
|
53
53
|
: authStatus.state === "unauthenticated"
|
|
54
|
-
? theme.
|
|
55
|
-
: theme.
|
|
54
|
+
? theme.error
|
|
55
|
+
: theme.warning;
|
|
56
56
|
const checkedAtLabel = authStatus.checkedAt > 0
|
|
57
57
|
? new Date(authStatus.checkedAt).toLocaleTimeString()
|
|
58
58
|
: "not checked yet";
|
|
@@ -60,52 +60,52 @@ export function AuthPanel({
|
|
|
60
60
|
return (
|
|
61
61
|
<Box
|
|
62
62
|
borderStyle="round"
|
|
63
|
-
borderColor={theme.
|
|
63
|
+
borderColor={theme.borderFocused}
|
|
64
64
|
flexDirection="column"
|
|
65
65
|
paddingX={2}
|
|
66
66
|
paddingY={1}
|
|
67
67
|
marginTop={1}
|
|
68
68
|
width="100%"
|
|
69
69
|
>
|
|
70
|
-
<Text color={theme.
|
|
70
|
+
<Text color={theme.accent} bold>
|
|
71
71
|
Auth and subscription guidance
|
|
72
72
|
</Text>
|
|
73
|
-
<Text color={theme.
|
|
74
|
-
<Text color={theme.
|
|
75
|
-
<Text color={theme.
|
|
73
|
+
<Text color={theme.textMuted}>Current backend: {provider.label}</Text>
|
|
74
|
+
<Text color={theme.textMuted}>Current preference: {formatAuthPreferenceLabel(authPreference)}</Text>
|
|
75
|
+
<Text color={theme.info}>Backend auth: {provider.authLabel}</Text>
|
|
76
76
|
<Text color={authStateColor}>Runtime auth state: {authStateLabel}</Text>
|
|
77
|
-
<Text color={theme.
|
|
78
|
-
<Text color={theme.
|
|
79
|
-
<Text color={theme.
|
|
77
|
+
<Text color={theme.textDim}>Last checked: {checkedAtLabel}</Text>
|
|
78
|
+
<Text color={theme.textDim}>Probe summary: {authStatus.rawSummary || "No probe output yet"}</Text>
|
|
79
|
+
<Text color={theme.text}>
|
|
80
80
|
This UI securely bridges to the Codexa neural network. It does not collect or store your ChatGPT credentials.
|
|
81
81
|
</Text>
|
|
82
|
-
<Text color={theme.
|
|
82
|
+
<Text color={theme.text}>{provider.statusMessage}</Text>
|
|
83
83
|
<Box flexDirection="column" marginTop={1}>
|
|
84
|
-
<Text color={theme.
|
|
85
|
-
<Text color={theme.
|
|
86
|
-
<Text color={theme.
|
|
87
|
-
<Text color={theme.
|
|
84
|
+
<Text color={theme.info}>Commands:</Text>
|
|
85
|
+
<Text color={theme.text}> /login guided ChatGPT sign-in steps</Text>
|
|
86
|
+
<Text color={theme.text}> /logout guided sign-out steps</Text>
|
|
87
|
+
<Text color={theme.text}> /auth status refresh Codexa authentication</Text>
|
|
88
88
|
</Box>
|
|
89
89
|
<Box flexDirection="column" marginTop={1}>
|
|
90
90
|
{AUTH_PREFERENCES.map((item, index) => (
|
|
91
|
-
<Text key={item.id} color={item.id === authPreference ? theme.
|
|
91
|
+
<Text key={item.id} color={item.id === authPreference ? theme.success : theme.text}>
|
|
92
92
|
{index + 1}. {item.label} {item.id === authPreference ? "✓" : ""}
|
|
93
93
|
</Text>
|
|
94
94
|
))}
|
|
95
95
|
</Box>
|
|
96
96
|
<Box marginTop={1}>
|
|
97
|
-
<Text color={theme.
|
|
97
|
+
<Text color={theme.textDim}>
|
|
98
98
|
Press 1-{AUTH_PREFERENCES.length} to change preference, R to refresh status, Esc to close.
|
|
99
99
|
</Text>
|
|
100
100
|
</Box>
|
|
101
101
|
{authStatusBusy && (
|
|
102
102
|
<Box marginTop={1}>
|
|
103
|
-
<Text color={theme.
|
|
103
|
+
<Text color={theme.warning}>Checking auth status...</Text>
|
|
104
104
|
</Box>
|
|
105
105
|
)}
|
|
106
106
|
{authStatus.recommendedAction && (
|
|
107
107
|
<Box marginTop={1}>
|
|
108
|
-
<Text color={theme.
|
|
108
|
+
<Text color={theme.textDim}>Next step: {authStatus.recommendedAction}</Text>
|
|
109
109
|
</Box>
|
|
110
110
|
)}
|
|
111
111
|
</Box>
|
|
@@ -23,7 +23,7 @@ import { clampVisualText, getShellWidth, type Layout } from "./layout.js";
|
|
|
23
23
|
import { getTextWidth, splitTextAtColumn } from "./textLayout.js";
|
|
24
24
|
import { useThrottledValue } from "./useThrottledValue.js";
|
|
25
25
|
import { sanitizeTerminalOutput } from "../core/terminal/terminalSanitize.js";
|
|
26
|
-
import { getStdinDebugState, traceInputDebug } from "../core/inputDebug.js";
|
|
26
|
+
import { getStdinDebugState, traceInputDebug } from "../core/debug/inputDebug.js";
|
|
27
27
|
import * as renderDebug from "../core/perf/renderDebug.js";
|
|
28
28
|
import { AnimatedStatusText } from "./AnimatedStatusText.js";
|
|
29
29
|
import { isAnimatedBusyState } from "./busyStatusAnimation.js";
|
|
@@ -100,9 +100,11 @@ interface BottomComposerProps {
|
|
|
100
100
|
uiState: UIState;
|
|
101
101
|
themeName?: string;
|
|
102
102
|
mode?: string;
|
|
103
|
-
model?: string;
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
model?: string;
|
|
104
|
+
footerModelDisplay?: string;
|
|
105
|
+
reasoningLevel?: string;
|
|
106
|
+
contextDisplay?: string;
|
|
107
|
+
planMode?: boolean;
|
|
106
108
|
showBusyLoader?: boolean;
|
|
107
109
|
tokensUsed?: number;
|
|
108
110
|
modelSpec?: ModelSpec;
|
|
@@ -136,11 +138,11 @@ export interface BottomComposerMeasureParams {
|
|
|
136
138
|
mode?: string;
|
|
137
139
|
model?: string;
|
|
138
140
|
reasoningLevel?: string;
|
|
139
|
-
tokensUsed?: number;
|
|
140
|
-
modelSpec?: ModelSpec;
|
|
141
|
-
value: string;
|
|
142
|
-
cursor: number;
|
|
143
|
-
}
|
|
141
|
+
tokensUsed?: number;
|
|
142
|
+
modelSpec?: ModelSpec;
|
|
143
|
+
value: string;
|
|
144
|
+
cursor: number;
|
|
145
|
+
}
|
|
144
146
|
|
|
145
147
|
export interface CommandSuggestionState {
|
|
146
148
|
showSuggestions: boolean;
|
|
@@ -174,9 +176,9 @@ export function shouldRenderBusyFooter(layout: Layout, uiState: UIState): boolea
|
|
|
174
176
|
return false;
|
|
175
177
|
}
|
|
176
178
|
|
|
177
|
-
export function getComposerToFooterGapRows(layout: Layout): number {
|
|
178
|
-
return
|
|
179
|
-
}
|
|
179
|
+
export function getComposerToFooterGapRows(layout: Layout): number {
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
180
182
|
|
|
181
183
|
export function getCommandSuggestionState({
|
|
182
184
|
value,
|
|
@@ -204,11 +206,11 @@ export function getCommandSuggestionState({
|
|
|
204
206
|
}
|
|
205
207
|
|
|
206
208
|
export function measureBottomComposerRows({
|
|
207
|
-
layout,
|
|
208
|
-
uiState,
|
|
209
|
-
value,
|
|
210
|
-
cursor,
|
|
211
|
-
}: BottomComposerMeasureParams): number {
|
|
209
|
+
layout,
|
|
210
|
+
uiState,
|
|
211
|
+
value,
|
|
212
|
+
cursor,
|
|
213
|
+
}: BottomComposerMeasureParams): number {
|
|
212
214
|
if (shouldRenderBusyFooter(layout, uiState)) {
|
|
213
215
|
return measureRunFooterRows();
|
|
214
216
|
}
|
|
@@ -234,24 +236,27 @@ export function measureBottomComposerRows({
|
|
|
234
236
|
inputLocked,
|
|
235
237
|
});
|
|
236
238
|
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
239
|
+
const bottomPadding = layout.mode === "compact" ? 0 : 1;
|
|
240
|
+
const footerGapRows = getComposerToFooterGapRows(layout);
|
|
241
|
+
const visibleStatusLine = getVisibleComposerStatusLine({
|
|
242
|
+
uiState,
|
|
243
|
+
value: normalizedValue,
|
|
244
|
+
allowCommands,
|
|
245
|
+
});
|
|
246
|
+
const transientStatusRows = visibleStatusLine.length > 0 ? 1 : 0;
|
|
247
|
+
|
|
248
|
+
const visiblePromptRows = inputLocked ? 1 : promptViewport.visibleRows.length;
|
|
249
|
+
|
|
245
250
|
return (
|
|
246
251
|
visiblePromptRows
|
|
247
|
-
+ 2
|
|
248
|
-
+ (commandSuggestionState.reserveSuggestionRow ? 1 : 0)
|
|
249
|
-
+ footerGapRows
|
|
250
|
-
+
|
|
251
|
-
+
|
|
252
|
-
+ bottomPadding
|
|
253
|
-
);
|
|
254
|
-
}
|
|
252
|
+
+ 2
|
|
253
|
+
+ (commandSuggestionState.reserveSuggestionRow ? 1 : 0)
|
|
254
|
+
+ footerGapRows
|
|
255
|
+
+ transientStatusRows
|
|
256
|
+
+ 1
|
|
257
|
+
+ bottomPadding
|
|
258
|
+
);
|
|
259
|
+
}
|
|
255
260
|
|
|
256
261
|
function getExternalCliLabel(providerId: string): string | null {
|
|
257
262
|
if (providerId === "google") return "Gemini CLI";
|
|
@@ -338,14 +343,54 @@ function getPlaceholder(persona: ComposerPersona): string {
|
|
|
338
343
|
|
|
339
344
|
// ─── Component ────────────────────────────────────────────────────────────────
|
|
340
345
|
|
|
346
|
+
function renderFooterRuntime(displayStr: string, theme: any) {
|
|
347
|
+
// e.g. "Claude Code CLI / Sonnet 4.6 (Low)"
|
|
348
|
+
const slashIndex = displayStr.indexOf("/");
|
|
349
|
+
if (slashIndex === -1) {
|
|
350
|
+
return <Text color={theme.model} wrap="truncate">{displayStr}</Text>;
|
|
351
|
+
}
|
|
352
|
+
const providerPart = displayStr.substring(0, slashIndex).trim();
|
|
353
|
+
let remaining = displayStr.substring(slashIndex + 1).trim();
|
|
354
|
+
|
|
355
|
+
const parenIndex = remaining.indexOf("(");
|
|
356
|
+
if (parenIndex === -1) {
|
|
357
|
+
return (
|
|
358
|
+
<Box flexDirection="row" overflow="hidden">
|
|
359
|
+
<Text color={theme.provider}>{providerPart}</Text>
|
|
360
|
+
<Text color={theme.textMuted}>{" / "}</Text>
|
|
361
|
+
<Text color={theme.model}>{remaining}</Text>
|
|
362
|
+
</Box>
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const modelPart = remaining.substring(0, parenIndex).trim();
|
|
367
|
+
let reasoningPart = remaining.substring(parenIndex + 1).trim();
|
|
368
|
+
if (reasoningPart.endsWith(")")) {
|
|
369
|
+
reasoningPart = reasoningPart.substring(0, reasoningPart.length - 1).trim();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return (
|
|
373
|
+
<Box flexDirection="row" overflow="hidden">
|
|
374
|
+
<Text color={theme.provider}>{providerPart}</Text>
|
|
375
|
+
<Text color={theme.textMuted}>{" / "}</Text>
|
|
376
|
+
<Text color={theme.model}>{modelPart}</Text>
|
|
377
|
+
<Text color={theme.textMuted}>{" ("}</Text>
|
|
378
|
+
<Text color={theme.accentMuted}>{reasoningPart}</Text>
|
|
379
|
+
<Text color={theme.textMuted}>{")"}</Text>
|
|
380
|
+
</Box>
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
341
384
|
export function BottomComposer({
|
|
342
385
|
layout,
|
|
343
386
|
uiState,
|
|
344
387
|
themeName = "purple",
|
|
345
388
|
mode = "",
|
|
346
|
-
model = "",
|
|
347
|
-
|
|
348
|
-
|
|
389
|
+
model = "",
|
|
390
|
+
footerModelDisplay,
|
|
391
|
+
reasoningLevel = "",
|
|
392
|
+
contextDisplay,
|
|
393
|
+
planMode = false,
|
|
349
394
|
showBusyLoader = true,
|
|
350
395
|
tokensUsed = 0,
|
|
351
396
|
modelSpec = FALLBACK_MODEL_SPEC,
|
|
@@ -522,9 +567,10 @@ export function BottomComposer({
|
|
|
522
567
|
.map((suggestion, index) => `${index === selectedIndex ? "›" : "·"} ${suggestion.cmd}`)
|
|
523
568
|
.join(" ");
|
|
524
569
|
|
|
525
|
-
const rawStatusLine = getVisibleComposerStatusLine({ uiState, value, allowCommands, activeProviderId, runElapsedSeconds, externalCliStatus });
|
|
526
|
-
const showStatusLine = rawStatusLine.length > 0;
|
|
527
|
-
const
|
|
570
|
+
const rawStatusLine = getVisibleComposerStatusLine({ uiState, value, allowCommands, activeProviderId, runElapsedSeconds, externalCliStatus });
|
|
571
|
+
const showStatusLine = rawStatusLine.length > 0;
|
|
572
|
+
const showTransientStatusRow = showStatusLine || inputLocked || !!selectionProfile;
|
|
573
|
+
const footerGapRows = getComposerToFooterGapRows(layout);
|
|
528
574
|
|
|
529
575
|
const promptViewport = useMemo(
|
|
530
576
|
() => createInputViewport({
|
|
@@ -807,16 +853,12 @@ export function BottomComposer({
|
|
|
807
853
|
}
|
|
808
854
|
}, { isActive: isFocused });
|
|
809
855
|
|
|
810
|
-
const modeDisplay = getModeDisplaySpec(mode, theme);
|
|
811
856
|
const tokenDisplay = getTokenBarDisplay(tokensUsed, modelSpec);
|
|
812
|
-
const tokenColor = tokenDisplay.percentage === null ? theme.DIM
|
|
813
|
-
: tokenDisplay.percentage >= 90 ? theme.ERROR
|
|
814
|
-
: tokenDisplay.percentage >= 70 ? theme.WARNING
|
|
815
|
-
: theme.SUCCESS;
|
|
816
857
|
const reasoningSuffix = reasoningLevel ? ` (${reasoningLevel})` : "";
|
|
858
|
+
const footerRuntimeDisplay = footerModelDisplay ?? `${model}${reasoningSuffix}`;
|
|
817
859
|
const isAnswerMode = persona === "answer";
|
|
818
860
|
const showBusyFooter = shouldRenderBusyFooter(layout, uiState);
|
|
819
|
-
const promptPrefixColor = inputLocked ? theme.
|
|
861
|
+
const promptPrefixColor = inputLocked ? theme.textDim : theme.text;
|
|
820
862
|
const lockedInputText = promptViewport.visibleRows[0]?.text ?? " ";
|
|
821
863
|
|
|
822
864
|
// The prompt line is shared between bordered and non-bordered layouts.
|
|
@@ -826,12 +868,12 @@ export function BottomComposer({
|
|
|
826
868
|
<Box flexDirection="column" flexGrow={1}>
|
|
827
869
|
{value.length === 0 && !inputLocked ? (
|
|
828
870
|
<Box width="100%" overflow="hidden">
|
|
829
|
-
<Text backgroundColor={cursorVisible ? theme.
|
|
830
|
-
<Text color={theme.
|
|
871
|
+
<Text backgroundColor={cursorVisible ? theme.text : undefined} color={cursorVisible ? theme.surface : undefined}>{" "}</Text>
|
|
872
|
+
<Text color={theme.textDim}>{placeholderText}</Text>
|
|
831
873
|
</Box>
|
|
832
874
|
) : inputLocked ? (
|
|
833
875
|
<Box key="busy-locked-input" width="100%" overflow="hidden">
|
|
834
|
-
<Text color={theme.
|
|
876
|
+
<Text color={theme.textDim}>{lockedInputText || " "}</Text>
|
|
835
877
|
</Box>
|
|
836
878
|
) : (
|
|
837
879
|
promptViewport.visibleRows.map((row, index) => {
|
|
@@ -845,14 +887,14 @@ export function BottomComposer({
|
|
|
845
887
|
<Box key={`${row.start}-${row.end}-${index}`} width="100%" overflow="hidden">
|
|
846
888
|
{isCursorRow && segments ? (
|
|
847
889
|
<>
|
|
848
|
-
<Text color={theme.
|
|
849
|
-
<Text backgroundColor={cursorVisible ? theme.
|
|
890
|
+
<Text color={theme.text}>{segments.before}</Text>
|
|
891
|
+
<Text backgroundColor={cursorVisible ? theme.text : undefined} color={cursorVisible ? theme.surface : undefined}>
|
|
850
892
|
{segments.current || " "}
|
|
851
893
|
</Text>
|
|
852
|
-
<Text color={theme.
|
|
894
|
+
<Text color={theme.text}>{segments.after}</Text>
|
|
853
895
|
</>
|
|
854
896
|
) : (
|
|
855
|
-
<Text color={theme.
|
|
897
|
+
<Text color={theme.text}>{row.text || " "}</Text>
|
|
856
898
|
)}
|
|
857
899
|
</Box>
|
|
858
900
|
);
|
|
@@ -866,17 +908,17 @@ export function BottomComposer({
|
|
|
866
908
|
return <MemoizedRunFooter uiState={uiState} showBusyLoader={showBusyLoader} onCancel={onCancel} onQuit={onQuit} />;
|
|
867
909
|
}
|
|
868
910
|
|
|
869
|
-
return (
|
|
870
|
-
<Box flexDirection="column" paddingBottom={layoutMode === "
|
|
871
|
-
{isAnswerMode ? (
|
|
872
|
-
// Answer mode: Highlighted prompt
|
|
873
|
-
<Box
|
|
911
|
+
return (
|
|
912
|
+
<Box flexDirection="column" paddingBottom={layoutMode === "compact" ? 0 : 1} width="100%">
|
|
913
|
+
{isAnswerMode ? (
|
|
914
|
+
// Answer mode: Highlighted prompt
|
|
915
|
+
<Box
|
|
874
916
|
flexDirection="column"
|
|
875
917
|
width="100%"
|
|
876
918
|
paddingX={1}
|
|
877
919
|
paddingY={0}
|
|
878
920
|
borderStyle="round"
|
|
879
|
-
borderColor={theme.
|
|
921
|
+
borderColor={theme.warning}
|
|
880
922
|
>
|
|
881
923
|
{promptLine}
|
|
882
924
|
</Box>
|
|
@@ -888,15 +930,15 @@ export function BottomComposer({
|
|
|
888
930
|
paddingX={1}
|
|
889
931
|
paddingY={0}
|
|
890
932
|
borderStyle="round"
|
|
891
|
-
borderColor={theme.
|
|
933
|
+
borderColor={theme.border}
|
|
892
934
|
>
|
|
893
935
|
{promptLine}
|
|
894
|
-
</Box>
|
|
895
|
-
)}
|
|
896
|
-
|
|
897
|
-
{commandSuggestionState.reserveSuggestionRow && (
|
|
898
|
-
<Box paddingLeft={1} marginTop={0} width="100%" overflow="hidden">
|
|
899
|
-
<Text color={theme.
|
|
936
|
+
</Box>
|
|
937
|
+
)}
|
|
938
|
+
|
|
939
|
+
{commandSuggestionState.reserveSuggestionRow && (
|
|
940
|
+
<Box paddingLeft={1} marginTop={0} width="100%" overflow="hidden">
|
|
941
|
+
<Text color={theme.textDim} wrap="truncate">{suggestionText || " "}</Text>
|
|
900
942
|
</Box>
|
|
901
943
|
)}
|
|
902
944
|
|
|
@@ -904,70 +946,66 @@ export function BottomComposer({
|
|
|
904
946
|
<Box height={footerGapRows} />
|
|
905
947
|
)}
|
|
906
948
|
|
|
907
|
-
{
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
{!!getExternalCliLabel(activeProviderId ?? "") && uiState.kind === "THINKING" && (
|
|
949
|
+
{showTransientStatusRow && (
|
|
950
|
+
<Box paddingX={1} marginTop={0} height={1} width="100%" justifyContent="space-between" overflow="hidden">
|
|
951
|
+
<>
|
|
952
|
+
<Box flexShrink={1} flexGrow={1} overflow="hidden" flexDirection="row">
|
|
953
|
+
{!!getExternalCliLabel(activeProviderId ?? "") && uiState.kind === "THINKING" && (
|
|
913
954
|
<>
|
|
914
|
-
<Spinner color={theme.
|
|
955
|
+
<Spinner color={theme.accent} />
|
|
915
956
|
<Text>{" "}</Text>
|
|
916
957
|
</>
|
|
917
958
|
)}
|
|
918
|
-
<AnimatedStatusText
|
|
919
|
-
baseText={rawStatusLine}
|
|
920
|
-
isActive={!getExternalCliLabel(activeProviderId ?? "") && inputLocked && showBusyLoader}
|
|
921
|
-
isError={persona === "error"}
|
|
922
|
-
/>
|
|
959
|
+
<AnimatedStatusText
|
|
960
|
+
baseText={rawStatusLine}
|
|
961
|
+
isActive={!getExternalCliLabel(activeProviderId ?? "") && inputLocked && showBusyLoader}
|
|
962
|
+
isError={persona === "error"}
|
|
963
|
+
/>
|
|
923
964
|
</Box>
|
|
924
965
|
{inputLocked && (
|
|
925
966
|
<Box flexShrink={0}>
|
|
926
|
-
<Text color={theme.
|
|
967
|
+
<Text color={theme.textDim}>Esc cancel Ctrl+C quit</Text>
|
|
927
968
|
</Box>
|
|
928
969
|
)}
|
|
929
970
|
{!inputLocked && selectionProfile && (
|
|
930
971
|
<Box flexShrink={0}>
|
|
931
|
-
<Text color={theme.
|
|
932
|
-
</Box>
|
|
933
|
-
)}
|
|
934
|
-
</>
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
{
|
|
939
|
-
<Box
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
{
|
|
947
|
-
</
|
|
948
|
-
|
|
949
|
-
<
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
)}
|
|
969
|
-
</Box>
|
|
970
|
-
);
|
|
972
|
+
<Text color={theme.textDim}>{selectionProfile.shortHint}</Text>
|
|
973
|
+
</Box>
|
|
974
|
+
)}
|
|
975
|
+
</>
|
|
976
|
+
</Box>
|
|
977
|
+
)}
|
|
978
|
+
|
|
979
|
+
<Box paddingLeft={1} paddingRight={1} marginTop={0} width="100%" justifyContent="space-between">
|
|
980
|
+
<Box flexGrow={1} flexShrink={1} overflow="hidden">
|
|
981
|
+
{renderFooterRuntime(footerRuntimeDisplay, theme)}
|
|
982
|
+
</Box>
|
|
983
|
+
<Box flexShrink={0}>
|
|
984
|
+
{contextDisplay ? (
|
|
985
|
+
<Box flexDirection="row">
|
|
986
|
+
<Text color={theme.textMuted}>Context: </Text>
|
|
987
|
+
<Text color={theme.context}>{contextDisplay}</Text>
|
|
988
|
+
</Box>
|
|
989
|
+
) : tokenDisplay.hasKnownLimit ? (
|
|
990
|
+
<Box flexDirection="row">
|
|
991
|
+
<Text color={theme.textMuted}>Context: </Text>
|
|
992
|
+
<Text color={theme.context}>{tokenDisplay.usedText}</Text>
|
|
993
|
+
<Text color={theme.textDim}>
|
|
994
|
+
{" / "}{tokenDisplay.limitText}
|
|
995
|
+
{tokenDisplay.percentage !== null ? ` · ${tokenDisplay.isEstimatedLimit ? "~" : ""}${tokenDisplay.percentage}%` : ""}
|
|
996
|
+
</Text>
|
|
997
|
+
</Box>
|
|
998
|
+
) : (
|
|
999
|
+
<Box flexDirection="row">
|
|
1000
|
+
<Text color={theme.textMuted}>Context: </Text>
|
|
1001
|
+
<Text color={theme.textDim}>Unknown</Text>
|
|
1002
|
+
</Box>
|
|
1003
|
+
)}
|
|
1004
|
+
</Box>
|
|
1005
|
+
</Box>
|
|
1006
|
+
|
|
1007
|
+
</Box>
|
|
1008
|
+
);
|
|
971
1009
|
}
|
|
972
1010
|
|
|
973
1011
|
// Helper to extract the relevant uiState kind for comparison
|
|
@@ -1003,7 +1041,9 @@ export const MemoizedBottomComposer = memo(BottomComposer, (prev, next) => {
|
|
|
1003
1041
|
// Re-render if display props change
|
|
1004
1042
|
if (prev.mode !== next.mode) return false;
|
|
1005
1043
|
if (prev.model !== next.model) return false;
|
|
1044
|
+
if (prev.footerModelDisplay !== next.footerModelDisplay) return false;
|
|
1006
1045
|
if (prev.reasoningLevel !== next.reasoningLevel) return false;
|
|
1046
|
+
if (prev.contextDisplay !== next.contextDisplay) return false;
|
|
1007
1047
|
if (prev.planMode !== next.planMode) return false;
|
|
1008
1048
|
if (prev.showBusyLoader !== next.showBusyLoader) return false;
|
|
1009
1049
|
if (prev.tokensUsed !== next.tokensUsed) return false;
|
package/src/ui/DashCard.tsx
CHANGED
|
@@ -24,9 +24,9 @@ export function DashCard({
|
|
|
24
24
|
children,
|
|
25
25
|
}: DashCardProps) {
|
|
26
26
|
const theme = useTheme();
|
|
27
|
-
const border = borderColor ?? theme.
|
|
28
|
-
const tColor = titleColor ?? theme.
|
|
29
|
-
const bColor = badgeColor ?? theme.
|
|
27
|
+
const border = borderColor ?? theme.border;
|
|
28
|
+
const tColor = titleColor ?? theme.textMuted;
|
|
29
|
+
const bColor = badgeColor ?? theme.textDim;
|
|
30
30
|
|
|
31
31
|
// Account for AppShell gutter (-1) and Timeline paddingX={1} (-2)
|
|
32
32
|
const w = cols - 3;
|