@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
package/src/ui/TopHeader.tsx
CHANGED
|
@@ -1,293 +1,437 @@
|
|
|
1
|
-
import React, { memo } from "react";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
layout
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
1
|
+
import React, { memo } from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { HEADER_CONFIG_DEFAULTS, type HeaderConfig } from "../config/settings.js";
|
|
4
|
+
import { formatCodexaBrandLabel } from "../core/version/channel.js";
|
|
5
|
+
import { formatVersionLabel } from "../core/version/updateCheck.js";
|
|
6
|
+
import type { RuntimeSummary } from "../config/runtimeConfig.js";
|
|
7
|
+
import type { CodexAuthState } from "../core/auth/codexAuth.js";
|
|
8
|
+
import { getAuthStateLabel } from "../core/auth/codexAuth.js";
|
|
9
|
+
import * as renderDebug from "../core/perf/renderDebug.js";
|
|
10
|
+
import { useTheme } from "./theme.js";
|
|
11
|
+
import { clampVisualText, isDecorativeLayoutMode, type Layout, useAppLayoutBudget } from "./layout.js";
|
|
12
|
+
import { getTextWidth } from "./textLayout.js";
|
|
13
|
+
import { UPDATE_CARD_ROWS, UpdateAvailableCard } from "./UpdateAvailableCard.js";
|
|
14
|
+
import {
|
|
15
|
+
LOGO_LARGE,
|
|
16
|
+
LOGO_COMPACT,
|
|
17
|
+
LOGO_COMPACT_MIN_COLS,
|
|
18
|
+
LOGO_LARGE_MIN_COLS,
|
|
19
|
+
LOGO_LARGE_MIN_ROWS,
|
|
20
|
+
getLogoWidth,
|
|
21
|
+
} from "./logoVariants.js";
|
|
22
|
+
|
|
23
|
+
// Re-exported for backward compatibility with existing tests.
|
|
24
|
+
export const HEADER_WORDMARK_LINES = LOGO_LARGE;
|
|
25
|
+
|
|
26
|
+
const HEADER_PADDING_COLUMNS = 2;
|
|
27
|
+
const SHELL_GUTTER_COLUMNS = 1;
|
|
28
|
+
// Require 130+ cols for wide side-by-side so the UpdateAvailableCard has room.
|
|
29
|
+
const WIDE_HEADER_MIN_COLUMNS = 130;
|
|
30
|
+
// Require 72+ cols for medium side-by-side canonical-logo layout.
|
|
31
|
+
const MEDIUM_HEADER_MIN_COLUMNS = LOGO_LARGE_MIN_COLS;
|
|
32
|
+
const MIN_SIDE_BY_SIDE_METADATA_WIDTH = 18;
|
|
33
|
+
const STACKED_METADATA_GAP_ROWS = 1;
|
|
34
|
+
// Gap row between the UpdateAvailableCard and the metadata lines.
|
|
35
|
+
const UPDATE_CARD_GAP_ROWS = 1;
|
|
36
|
+
// Recommended terminal size shown in the compact-mode hint when the viewport is
|
|
37
|
+
// too small to render any logo art.
|
|
38
|
+
const RECOMMENDED_FULL_HEADER_HINT = `Resize to ≥${LOGO_LARGE_MIN_COLS}×${LOGO_LARGE_MIN_ROWS} for the full Codexa header`;
|
|
39
|
+
|
|
40
|
+
export type HeaderHeroMode = "wide" | "medium" | "narrow" | "compact";
|
|
41
|
+
|
|
42
|
+
export interface HeaderHeroLayout {
|
|
43
|
+
mode: HeaderHeroMode;
|
|
44
|
+
topMarginRows: number;
|
|
45
|
+
bottomMarginRows: number;
|
|
46
|
+
metadataGapColumns: number;
|
|
47
|
+
metadataGapRows: number;
|
|
48
|
+
logoRows: number;
|
|
49
|
+
metadataRows: number;
|
|
50
|
+
// Extra rows used by the compact-mode recommended-size hint (0 in non-compact
|
|
51
|
+
// modes). Threaded through so measureTopHeaderRows matches what renders.
|
|
52
|
+
compactHintRows: number;
|
|
53
|
+
totalRows: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type HeaderMetadataLine = { key: string; text: string; color: string; bold: boolean };
|
|
57
|
+
|
|
58
|
+
export interface UpdateAvailableInfo {
|
|
59
|
+
latestVersion: string;
|
|
60
|
+
currentVersion: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface TopHeaderProps {
|
|
64
|
+
authState: CodexAuthState;
|
|
65
|
+
workspaceLabel: string;
|
|
66
|
+
layout: Layout;
|
|
67
|
+
runtimeSummary?: RuntimeSummary | null;
|
|
68
|
+
headerConfig?: HeaderConfig;
|
|
69
|
+
updateAvailable?: UpdateAvailableInfo | null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getHeaderContentWidth(cols: number): number {
|
|
73
|
+
return Math.max(1, cols - SHELL_GUTTER_COLUMNS - HEADER_PADDING_COLUMNS);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getMetadataRowCount(headerConfig: HeaderConfig): number {
|
|
77
|
+
return [
|
|
78
|
+
headerConfig.showBrand,
|
|
79
|
+
headerConfig.showAuthStatus,
|
|
80
|
+
headerConfig.showWorkspace,
|
|
81
|
+
headerConfig.showProvider,
|
|
82
|
+
].filter(Boolean).length;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getHeaderVerticalMargins(layout: Layout): { topMarginRows: number; bottomMarginRows: number } {
|
|
86
|
+
if (!isDecorativeLayoutMode(layout.mode)) {
|
|
87
|
+
return {
|
|
88
|
+
topMarginRows: 0,
|
|
89
|
+
bottomMarginRows: 0,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (layout.rows <= 24) {
|
|
94
|
+
return { topMarginRows: 0, bottomMarginRows: 0 };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
topMarginRows: 1,
|
|
99
|
+
bottomMarginRows: 0,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function selectHeaderLogo(layout: Layout): readonly string[] {
|
|
104
|
+
if (process.env["CODEXA_NO_ASCII_LOGO"] === "1") return [];
|
|
105
|
+
const showNormalLogo =
|
|
106
|
+
process.env["CODEXA_NO_ASCII_LOGO"] !== "1" && (
|
|
107
|
+
layout.mode === "regular" ||
|
|
108
|
+
layout.mode === "expanded" ||
|
|
109
|
+
(layout.mode === "compact" && layout.cols >= 72)
|
|
110
|
+
);
|
|
111
|
+
if (!showNormalLogo) return [];
|
|
112
|
+
if (process.env["CODEXA_COMPACT_LOGO"] === "1") return LOGO_COMPACT;
|
|
113
|
+
return LOGO_LARGE;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function getHeaderHeroLayout(
|
|
117
|
+
layout: Layout,
|
|
118
|
+
headerConfig: HeaderConfig = HEADER_CONFIG_DEFAULTS,
|
|
119
|
+
hasUpdate = false,
|
|
120
|
+
): HeaderHeroLayout {
|
|
121
|
+
const { topMarginRows, bottomMarginRows } = getHeaderVerticalMargins(layout);
|
|
122
|
+
const metadataRows = getMetadataRowCount(headerConfig);
|
|
123
|
+
const contentWidth = getHeaderContentWidth(layout.cols);
|
|
124
|
+
|
|
125
|
+
const showNormalLogo =
|
|
126
|
+
process.env["CODEXA_NO_ASCII_LOGO"] !== "1" && (
|
|
127
|
+
layout.mode === "regular" ||
|
|
128
|
+
layout.mode === "expanded" ||
|
|
129
|
+
(layout.mode === "compact" && layout.cols >= 72)
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const placeMetadataBesideLogo = showNormalLogo && layout.cols >= 95;
|
|
133
|
+
|
|
134
|
+
let mode: HeaderHeroMode;
|
|
135
|
+
if (!showNormalLogo) {
|
|
136
|
+
mode = "compact";
|
|
137
|
+
} else if (placeMetadataBesideLogo) {
|
|
138
|
+
mode = layout.cols >= 130 ? "wide" : "medium";
|
|
139
|
+
} else {
|
|
140
|
+
mode = "narrow";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const logo = selectHeaderLogo(layout);
|
|
144
|
+
const logoWidth = logo.length > 0 ? getLogoWidth(logo) : 0;
|
|
145
|
+
|
|
146
|
+
if (mode === "compact") {
|
|
147
|
+
// Compact / micro text-only header.
|
|
148
|
+
return {
|
|
149
|
+
mode: "compact",
|
|
150
|
+
topMarginRows,
|
|
151
|
+
bottomMarginRows,
|
|
152
|
+
metadataGapColumns: 0,
|
|
153
|
+
metadataGapRows: 0,
|
|
154
|
+
logoRows: 1,
|
|
155
|
+
metadataRows: 0,
|
|
156
|
+
compactHintRows: 0,
|
|
157
|
+
totalRows: topMarginRows + 1 + bottomMarginRows,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const logoRowCount = logo.length;
|
|
162
|
+
const metadataGapColumns = layout.cols >= WIDE_HEADER_MIN_COLUMNS ? 4 : 2;
|
|
163
|
+
const metadataGapRows = mode === "narrow" && metadataRows > 0 ? STACKED_METADATA_GAP_ROWS : 0;
|
|
164
|
+
|
|
165
|
+
const isSideBySide = mode === "wide" || mode === "medium";
|
|
166
|
+
|
|
167
|
+
// In side-by-side mode the right column grows to: update card + gap + metadata.
|
|
168
|
+
// In narrow/stacked mode, the update card is a single extra line below metadata.
|
|
169
|
+
const rightColRows = isSideBySide
|
|
170
|
+
? (hasUpdate ? UPDATE_CARD_ROWS + UPDATE_CARD_GAP_ROWS : 0) + metadataRows
|
|
171
|
+
: 0;
|
|
172
|
+
|
|
173
|
+
const contentRows = isSideBySide
|
|
174
|
+
? Math.max(logoRowCount, rightColRows)
|
|
175
|
+
: logoRowCount + metadataGapRows + metadataRows + (hasUpdate ? STACKED_METADATA_GAP_ROWS + 1 : 0);
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
mode,
|
|
179
|
+
topMarginRows,
|
|
180
|
+
bottomMarginRows,
|
|
181
|
+
metadataGapColumns,
|
|
182
|
+
metadataGapRows,
|
|
183
|
+
logoRows: logoRowCount,
|
|
184
|
+
metadataRows,
|
|
185
|
+
compactHintRows: 0,
|
|
186
|
+
totalRows: topMarginRows + contentRows + bottomMarginRows,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function measureTopHeaderRows(
|
|
191
|
+
layout: Layout,
|
|
192
|
+
headerConfig: HeaderConfig = HEADER_CONFIG_DEFAULTS,
|
|
193
|
+
hasUpdate = false,
|
|
194
|
+
): number {
|
|
195
|
+
return getHeaderHeroLayout(layout, headerConfig, hasUpdate).totalRows;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function takeVisualSuffix(text: string, maxWidth: number): string {
|
|
199
|
+
if (maxWidth <= 0) return "";
|
|
200
|
+
let output = "";
|
|
201
|
+
|
|
202
|
+
for (const char of Array.from(text).reverse()) {
|
|
203
|
+
if (getTextWidth(char + output) > maxWidth) break;
|
|
204
|
+
output = char + output;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return output;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function shortenHeaderWorkspaceLabel(workspaceLabel: string, maxWidth: number): string {
|
|
211
|
+
const trimmed = workspaceLabel.trim();
|
|
212
|
+
if (!trimmed || maxWidth <= 0) return "";
|
|
213
|
+
if (getTextWidth(trimmed) <= maxWidth) return trimmed;
|
|
214
|
+
if (maxWidth <= 1) return "…";
|
|
215
|
+
|
|
216
|
+
const normalized = trimmed.replace(/[\\/]+$/, "");
|
|
217
|
+
const separatorMatch = normalized.match(/[\\/]/g);
|
|
218
|
+
const separator = normalized.includes("\\") && (!separatorMatch || normalized.lastIndexOf("\\") >= normalized.lastIndexOf("/"))
|
|
219
|
+
? "\\"
|
|
220
|
+
: "/";
|
|
221
|
+
const lastSlash = Math.max(normalized.lastIndexOf("\\"), normalized.lastIndexOf("/"));
|
|
222
|
+
const leaf = lastSlash >= 0 ? normalized.slice(lastSlash + 1) : normalized;
|
|
223
|
+
const prefix = lastSlash >= 0 ? `…${separator}` : "…";
|
|
224
|
+
const leafWidth = Math.max(1, maxWidth - getTextWidth(prefix));
|
|
225
|
+
|
|
226
|
+
return prefix + takeVisualSuffix(leaf, leafWidth);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function clampMetadataText(text: string, maxWidth: number): string {
|
|
230
|
+
return clampVisualText(text, Math.max(1, maxWidth));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function TopHeader({
|
|
234
|
+
authState,
|
|
235
|
+
workspaceLabel,
|
|
236
|
+
layout,
|
|
237
|
+
runtimeSummary = null,
|
|
238
|
+
headerConfig = HEADER_CONFIG_DEFAULTS,
|
|
239
|
+
updateAvailable = null,
|
|
240
|
+
}: TopHeaderProps) {
|
|
241
|
+
renderDebug.useRenderDebug("Header", {
|
|
242
|
+
authState,
|
|
243
|
+
workspaceLabel,
|
|
244
|
+
cols: layout.cols,
|
|
245
|
+
rows: layout.rows,
|
|
246
|
+
mode: layout.mode,
|
|
247
|
+
});
|
|
248
|
+
renderDebug.useLifecycleDebug("Header", {
|
|
249
|
+
authState,
|
|
250
|
+
cols: layout.cols,
|
|
251
|
+
rows: layout.rows,
|
|
252
|
+
mode: layout.mode,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const theme = useTheme();
|
|
256
|
+
|
|
257
|
+
const authLabelRaw = getAuthStateLabel(authState);
|
|
258
|
+
const authLabel = authLabelRaw.length > 0
|
|
259
|
+
? authLabelRaw[0]!.toUpperCase() + authLabelRaw.slice(1)
|
|
260
|
+
: authLabelRaw;
|
|
261
|
+
|
|
262
|
+
const heroLayout = getHeaderHeroLayout(layout, headerConfig, !!updateAvailable);
|
|
263
|
+
const selectedLogo = selectHeaderLogo(layout);
|
|
264
|
+
const selectedLogoWidth = selectedLogo.length > 0 ? getLogoWidth(selectedLogo) : 0;
|
|
265
|
+
|
|
266
|
+
const contentWidth = getHeaderContentWidth(layout.cols);
|
|
267
|
+
const sideBySideMetadataWidth = Math.max(1, contentWidth - selectedLogoWidth - heroLayout.metadataGapColumns);
|
|
268
|
+
const metadataWidth = heroLayout.mode === "wide" || heroLayout.mode === "medium"
|
|
269
|
+
? sideBySideMetadataWidth
|
|
270
|
+
: contentWidth;
|
|
271
|
+
const workspaceValueWidth = Math.max(1, metadataWidth - getTextWidth("Workspace: "));
|
|
272
|
+
const wsDisplay = shortenHeaderWorkspaceLabel(workspaceLabel, workspaceValueWidth);
|
|
273
|
+
const brandLabel = formatCodexaBrandLabel();
|
|
274
|
+
const metadataLinesRaw = [
|
|
275
|
+
headerConfig.showBrand ? { key: "brand", text: brandLabel, color: theme.text, bold: true } : null,
|
|
276
|
+
headerConfig.showAuthStatus ? { key: "auth", text: `Auth: ${authLabel}`, color: theme.text, bold: false } : null,
|
|
277
|
+
headerConfig.showWorkspace ? { key: "workspace", text: `Workspace: ${wsDisplay}`, color: theme.textMuted, bold: false } : null,
|
|
278
|
+
headerConfig.showProvider && runtimeSummary?.providerLabel
|
|
279
|
+
? { key: "provider", text: `Provider: ${runtimeSummary.providerLabel}`, color: theme.text, bold: false }
|
|
280
|
+
: null,
|
|
281
|
+
].filter((line): line is HeaderMetadataLine => Boolean(line));
|
|
282
|
+
const metadataLines = metadataLinesRaw.map((line) => ({
|
|
283
|
+
...line,
|
|
284
|
+
text: clampMetadataText(line.text, metadataWidth),
|
|
285
|
+
}));
|
|
286
|
+
|
|
287
|
+
// Add a 1-row gap between the version line and workspace line in wide mode
|
|
288
|
+
// so the two pieces of metadata have breathing room beside the logo.
|
|
289
|
+
const hasMetadataGap = heroLayout.mode === "wide"
|
|
290
|
+
&& metadataLines.length <= 3
|
|
291
|
+
&& metadataLines.some((l) => l.key === "brand")
|
|
292
|
+
&& metadataLines.some((l) => l.key === "workspace");
|
|
293
|
+
const metadataColumn = (
|
|
294
|
+
<Box flexDirection="column" flexGrow={1} flexShrink={1} width={metadataWidth}>
|
|
295
|
+
{metadataLines.map((line) =>
|
|
296
|
+
hasMetadataGap && line.key === "workspace" ? (
|
|
297
|
+
<Box key={line.key} marginTop={1}>
|
|
298
|
+
<Text color={line.color} bold={line.bold} wrap="truncate">{line.text}</Text>
|
|
299
|
+
</Box>
|
|
300
|
+
) : (
|
|
301
|
+
<Text key={line.key} color={line.color} bold={line.bold} wrap="truncate">{line.text}</Text>
|
|
302
|
+
)
|
|
303
|
+
)}
|
|
304
|
+
</Box>
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
// Canonical CODEXA wordmark — uses per-line LOGO palette defined in each theme.
|
|
308
|
+
// No `bold`: bold on Unicode block/box-drawing characters causes per-glyph
|
|
309
|
+
// spacing artifacts in common terminal fonts (Ptyxis, GNOME Terminal).
|
|
310
|
+
// wrap="truncate" keeps each row on exactly one terminal line.
|
|
311
|
+
const logoColumn = (
|
|
312
|
+
<Box flexDirection="column" flexShrink={0}>
|
|
313
|
+
{selectedLogo.map((line, i) => {
|
|
314
|
+
let lineColor = theme.logoPrimary;
|
|
315
|
+
if (selectedLogo.length === 6) {
|
|
316
|
+
if (i === 2 || i === 3) lineColor = theme.logoSecondary;
|
|
317
|
+
else if (i === 4 || i === 5) lineColor = theme.logoShadow;
|
|
318
|
+
}
|
|
319
|
+
return (
|
|
320
|
+
<Text key={i} color={lineColor} wrap="truncate">{line}</Text>
|
|
321
|
+
);
|
|
322
|
+
})}
|
|
323
|
+
</Box>
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
if (heroLayout.mode !== "compact") {
|
|
327
|
+
const isSideBySide = heroLayout.mode === "wide" || heroLayout.mode === "medium";
|
|
328
|
+
const metadataTopOffset = 0;
|
|
329
|
+
|
|
330
|
+
return (
|
|
331
|
+
<Box flexDirection="column" paddingX={1} width="100%">
|
|
332
|
+
{heroLayout.topMarginRows > 0 && (
|
|
333
|
+
<Box height={heroLayout.topMarginRows} />
|
|
334
|
+
)}
|
|
335
|
+
|
|
336
|
+
{isSideBySide ? (
|
|
337
|
+
<Box flexDirection="row" width="100%" alignItems="flex-start">
|
|
338
|
+
{logoColumn}
|
|
339
|
+
<Box width={heroLayout.metadataGapColumns} flexShrink={0} />
|
|
340
|
+
<Box flexDirection="column" flexGrow={1} paddingTop={metadataTopOffset}>
|
|
341
|
+
{updateAvailable && (
|
|
342
|
+
<>
|
|
343
|
+
<UpdateAvailableCard
|
|
344
|
+
latestVersion={updateAvailable.latestVersion}
|
|
345
|
+
currentVersion={updateAvailable.currentVersion}
|
|
346
|
+
width={metadataWidth}
|
|
347
|
+
/>
|
|
348
|
+
<Box height={UPDATE_CARD_GAP_ROWS} />
|
|
349
|
+
</>
|
|
350
|
+
)}
|
|
351
|
+
{metadataColumn}
|
|
352
|
+
</Box>
|
|
353
|
+
</Box>
|
|
354
|
+
) : (
|
|
355
|
+
<Box flexDirection="column" width="100%">
|
|
356
|
+
{logoColumn}
|
|
357
|
+
{heroLayout.metadataGapRows > 0 && (
|
|
358
|
+
<Box height={heroLayout.metadataGapRows} />
|
|
359
|
+
)}
|
|
360
|
+
{metadataColumn}
|
|
361
|
+
{updateAvailable && (
|
|
362
|
+
<Text color={theme.warning} wrap="truncate">{`Update available: Codexa ${formatVersionLabel(updateAvailable.latestVersion)} — Run: npm install -g @golba98/codexa@latest`}</Text>
|
|
363
|
+
)}
|
|
364
|
+
</Box>
|
|
365
|
+
)}
|
|
366
|
+
|
|
367
|
+
{heroLayout.bottomMarginRows > 0 && (
|
|
368
|
+
<Box height={heroLayout.bottomMarginRows} />
|
|
369
|
+
)}
|
|
370
|
+
</Box>
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Compact / micro / activity-collapsed: single-line header.
|
|
375
|
+
const compactMetadataWidth = contentWidth;
|
|
376
|
+
const compactWorkspaceValueWidth = Math.max(1, compactMetadataWidth - getTextWidth("Workspace: "));
|
|
377
|
+
const compactWorkspaceDisplay = shortenHeaderWorkspaceLabel(workspaceLabel, compactWorkspaceValueWidth);
|
|
378
|
+
// A leading ✦ accent makes the single-line header read as a deliberate
|
|
379
|
+
// compact Codexa header rather than a broken fallback.
|
|
380
|
+
const compactParts: React.ReactNode[] = [
|
|
381
|
+
<Text key="accent" color={theme.accent} bold>{"✦ "}</Text>,
|
|
382
|
+
];
|
|
383
|
+
if (headerConfig.showBrand) {
|
|
384
|
+
compactParts.push(
|
|
385
|
+
<Text key="brand" color={theme.text} bold>{brandLabel}</Text>,
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
if (headerConfig.showAuthStatus) {
|
|
389
|
+
if (compactParts.length > 0) compactParts.push(<Text key="sep-auth" color={theme.textDim}>{" · "}</Text>);
|
|
390
|
+
compactParts.push(<Text key="auth" color={theme.text}>{authLabel}</Text>);
|
|
391
|
+
}
|
|
392
|
+
if (headerConfig.showWorkspace) {
|
|
393
|
+
if (compactParts.length > 0) compactParts.push(<Text key="sep-ws" color={theme.textDim}>{" · "}</Text>);
|
|
394
|
+
compactParts.push(<Text key="ws" color={theme.textMuted} wrap="truncate">{`Workspace: ${compactWorkspaceDisplay}`}</Text>);
|
|
395
|
+
}
|
|
396
|
+
if (headerConfig.showProvider && runtimeSummary?.providerLabel) {
|
|
397
|
+
if (compactParts.length > 0) compactParts.push(<Text key="sep-provider" color={theme.textDim}>{" · "}</Text>);
|
|
398
|
+
compactParts.push(<Text key="provider" color={theme.text} wrap="truncate">{`Provider: ${runtimeSummary.providerLabel}`}</Text>);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return (
|
|
402
|
+
<Box flexDirection="column" paddingX={1} width="100%">
|
|
403
|
+
{heroLayout.topMarginRows > 0 && (
|
|
404
|
+
<Box height={heroLayout.topMarginRows} />
|
|
405
|
+
)}
|
|
406
|
+
<Box flexDirection="row" width="100%">
|
|
407
|
+
{compactParts}
|
|
408
|
+
</Box>
|
|
409
|
+
{heroLayout.compactHintRows > 0 && (
|
|
410
|
+
<Text color={theme.textDim} wrap="truncate">{clampMetadataText(RECOMMENDED_FULL_HEADER_HINT, compactMetadataWidth)}</Text>
|
|
411
|
+
)}
|
|
412
|
+
{heroLayout.bottomMarginRows > 0 && (
|
|
413
|
+
<Box height={heroLayout.bottomMarginRows} />
|
|
414
|
+
)}
|
|
415
|
+
</Box>
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Memoize to prevent re-renders during streaming when props haven't changed
|
|
420
|
+
export const MemoizedTopHeader = memo(TopHeader, (prev, next) => {
|
|
421
|
+
return (
|
|
422
|
+
prev.authState === next.authState &&
|
|
423
|
+
prev.workspaceLabel === next.workspaceLabel &&
|
|
424
|
+
prev.layout.cols === next.layout.cols &&
|
|
425
|
+
prev.layout.rows === next.layout.rows &&
|
|
426
|
+
prev.layout.mode === next.layout.mode &&
|
|
427
|
+
prev.runtimeSummary === next.runtimeSummary &&
|
|
428
|
+
prev.headerConfig === next.headerConfig &&
|
|
429
|
+
prev.updateAvailable === next.updateAvailable
|
|
430
|
+
);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// Minimum terminal cols to render any logo art (the compact 1-row variant).
|
|
434
|
+
export const MIN_LOGO_TERMINAL_WIDTH = LOGO_COMPACT_MIN_COLS;
|
|
435
|
+
|
|
436
|
+
// Re-export for consumers that reference these constants directly.
|
|
437
|
+
export { LOGO_LARGE_MIN_COLS, LOGO_MEDIUM_MIN_COLS, LOGO_COMPACT_MIN_COLS } from "./logoVariants.js";
|