@golba98/codexa 1.0.2 → 1.0.3
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 +391 -94
- package/package.json +12 -7
- package/src/app.tsx +149 -117
- package/src/commands/handler.ts +4 -4
- package/src/config/buildInfo.ts +2 -2
- package/src/config/persistence.ts +10 -0
- package/src/config/settings.ts +7 -15
- package/src/config/updateCheckCache.ts +19 -1
- package/src/core/channel.ts +23 -0
- package/src/core/executables/codexExecutable.ts +1 -0
- package/src/core/executables/executableResolver.ts +61 -43
- package/src/core/launchContext.ts +33 -33
- package/src/core/process/processValidation.ts +9 -5
- package/src/core/providerLauncher/launcher.ts +59 -42
- package/src/core/updateCheck.ts +111 -95
- package/src/session/types.ts +2 -1
- 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 +9 -24
- package/src/ui/AttachmentImportPanel.tsx +8 -8
- package/src/ui/AuthPanel.tsx +20 -20
- package/src/ui/BottomComposer.tsx +117 -78
- package/src/ui/DashCard.tsx +3 -3
- package/src/ui/Markdown.tsx +17 -17
- package/src/ui/ModelPickerScreen.tsx +10 -10
- package/src/ui/ModelReasoningPicker.tsx +14 -14
- package/src/ui/Panel.tsx +3 -3
- package/src/ui/PlanActionPicker.tsx +6 -6
- package/src/ui/PlanReviewPanel.tsx +8 -8
- package/src/ui/ProviderPicker.tsx +18 -18
- package/src/ui/RunFooter.tsx +3 -3
- package/src/ui/SelectionPanel.tsx +4 -4
- package/src/ui/SettingsPanel.tsx +8 -8
- package/src/ui/Spinner.tsx +1 -1
- package/src/ui/TextEntryPanel.tsx +10 -10
- package/src/ui/ThinkingBlock.tsx +8 -8
- package/src/ui/Timeline.tsx +21 -17
- package/src/ui/TopHeader.tsx +421 -293
- package/src/ui/TurnGroup.tsx +31 -31
- package/src/ui/UpdateAvailableCard.tsx +41 -0
- package/src/ui/UpdatePromptPanel.tsx +203 -0
- package/src/ui/focus.ts +3 -0
- package/src/ui/layout.ts +1 -1
- package/src/ui/logoVariants.ts +107 -0
- package/src/ui/modeDisplay.ts +12 -12
- package/src/ui/runtimeDisplay.ts +100 -0
- package/src/ui/theme.tsx +274 -395
- package/src/ui/timelineMeasure.ts +29 -19
- 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/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/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/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/updateCheck.ts
CHANGED
|
@@ -1,117 +1,96 @@
|
|
|
1
1
|
import { APP_VERSION } from "../config/settings.js";
|
|
2
|
+
import { isLocalDevChannel } from "./channel.js";
|
|
3
|
+
|
|
4
|
+
export const CODEXA_NPM_PACKAGE = "@golba98/codexa";
|
|
5
|
+
export const CODEXA_NPM_REGISTRY_URL = "https://registry.npmjs.org/@golba98%2Fcodexa";
|
|
6
|
+
export const CODEXA_UPDATE_COMMAND = `npm install -g ${CODEXA_NPM_PACKAGE}@latest`;
|
|
2
7
|
|
|
3
8
|
export type UpdateStatus = "up-to-date" | "update-available" | "unknown" | "error";
|
|
4
9
|
|
|
10
|
+
export interface NpmRegistryMetadata {
|
|
11
|
+
"dist-tags": { latest?: string };
|
|
12
|
+
}
|
|
13
|
+
|
|
5
14
|
export interface UpdateCheckResult {
|
|
6
15
|
status: UpdateStatus;
|
|
7
16
|
currentVersion: string;
|
|
8
17
|
latestVersion: string | null;
|
|
9
18
|
errorMessage?: string;
|
|
10
19
|
checkedAt: number;
|
|
20
|
+
source?: "npm" | "cache";
|
|
11
21
|
}
|
|
12
22
|
|
|
13
|
-
|
|
14
|
-
"dist-tags"?: {
|
|
15
|
-
latest?: unknown;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
23
|
+
const FETCH_TIMEOUT_MS = 5000;
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
now?: () => number;
|
|
25
|
+
/** Strip a leading "v" so "v1.0.2" and "1.0.2" are treated as equal. */
|
|
26
|
+
export function normalizeVersion(v: string): string {
|
|
27
|
+
return v.startsWith("v") ? v.slice(1) : v;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
export const CODEXA_NPM_REGISTRY_URL = "https://registry.npmjs.org/@golba98%2Fcodexa";
|
|
27
|
-
export const CODEXA_UPDATE_COMMAND = "npm install -g @golba98/codexa@latest";
|
|
30
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
major: number;
|
|
33
|
-
minor: number;
|
|
34
|
-
patch: number;
|
|
35
|
-
prerelease: string[];
|
|
32
|
+
/** Returns true for valid semver strings with or without a leading "v". */
|
|
33
|
+
export function isValidSemver(v: string): boolean {
|
|
34
|
+
return SEMVER_RE.test(normalizeVersion(v));
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return
|
|
43
|
-
major: Number(match[1]),
|
|
44
|
-
minor: Number(match[2]),
|
|
45
|
-
patch: Number(match[3]),
|
|
46
|
-
prerelease: match[4]?.split(".") ?? [],
|
|
47
|
-
};
|
|
37
|
+
export function shouldRunStartupUpdateCheck(
|
|
38
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
39
|
+
enabled = true,
|
|
40
|
+
): boolean {
|
|
41
|
+
return enabled && !isLocalDevChannel(env);
|
|
48
42
|
}
|
|
49
43
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const aNumeric = /^\d+$/.test(a);
|
|
64
|
-
const bNumeric = /^\d+$/.test(b);
|
|
65
|
-
if (aNumeric && bNumeric) return Number(a) > Number(b) ? 1 : -1;
|
|
66
|
-
if (aNumeric) return -1;
|
|
67
|
-
if (bNumeric) return 1;
|
|
68
|
-
return a > b ? 1 : -1;
|
|
69
|
-
}
|
|
44
|
+
// Compares two semver strings numerically. Returns negative if a < b, 0 if equal, positive if a > b.
|
|
45
|
+
// Pre-release versions (e.g. 1.0.2-beta.1) sort below their release counterpart (1.0.2 > 1.0.2-beta.1).
|
|
46
|
+
// Leading "v" is stripped before comparison.
|
|
47
|
+
export function compareSemver(a: string, b: string): number {
|
|
48
|
+
const parseParts = (v: string): { numeric: number[]; prerelease: string | null } => {
|
|
49
|
+
const norm = normalizeVersion(v);
|
|
50
|
+
const dashIdx = norm.indexOf("-");
|
|
51
|
+
const base = dashIdx === -1 ? norm : norm.slice(0, dashIdx);
|
|
52
|
+
const prerelease = dashIdx === -1 ? null : norm.slice(dashIdx + 1);
|
|
53
|
+
const numeric = base.split(".").map((p) => parseInt(p, 10) || 0);
|
|
54
|
+
return { numeric, prerelease };
|
|
55
|
+
};
|
|
70
56
|
|
|
71
|
-
|
|
72
|
-
|
|
57
|
+
const pa = parseParts(a);
|
|
58
|
+
const pb = parseParts(b);
|
|
59
|
+
const len = Math.max(pa.numeric.length, pb.numeric.length);
|
|
73
60
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (!left || !right) {
|
|
78
|
-
throw new Error(`Invalid semver version: ${!left ? leftVersion : rightVersion}`);
|
|
61
|
+
for (let i = 0; i < len; i++) {
|
|
62
|
+
const diff = (pa.numeric[i] ?? 0) - (pb.numeric[i] ?? 0);
|
|
63
|
+
if (diff !== 0) return diff;
|
|
79
64
|
}
|
|
80
65
|
|
|
81
|
-
|
|
82
|
-
|
|
66
|
+
// Same numeric version: no pre-release > has pre-release
|
|
67
|
+
if (pa.prerelease === null && pb.prerelease !== null) return 1;
|
|
68
|
+
if (pa.prerelease !== null && pb.prerelease === null) return -1;
|
|
69
|
+
if (pa.prerelease !== null && pb.prerelease !== null) {
|
|
70
|
+
return pa.prerelease < pb.prerelease ? -1 : pa.prerelease > pb.prerelease ? 1 : 0;
|
|
83
71
|
}
|
|
84
|
-
|
|
85
|
-
return comparePrerelease(left.prerelease, right.prerelease);
|
|
72
|
+
return 0;
|
|
86
73
|
}
|
|
87
74
|
|
|
88
|
-
export function isNewerVersion(
|
|
89
|
-
return compareSemver(
|
|
75
|
+
export function isNewerVersion(candidate: string, current: string): boolean {
|
|
76
|
+
return compareSemver(candidate, current) > 0;
|
|
90
77
|
}
|
|
91
78
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
throw new Error(`npm registry responded with HTTP ${res.status}`);
|
|
96
|
-
}
|
|
97
|
-
return await res.json() as NpmRegistryMetadata;
|
|
79
|
+
export interface UpdateCheckOverrides {
|
|
80
|
+
currentVersion?: string;
|
|
81
|
+
fetchNpmMetadataFn?: (url: string) => Promise<NpmRegistryMetadata>;
|
|
98
82
|
}
|
|
99
83
|
|
|
100
|
-
|
|
101
|
-
fetchMetadata: (url: string, init: RequestInit) => Promise<NpmRegistryMetadata> = fetchNpmMetadata,
|
|
102
|
-
): Promise<string> {
|
|
84
|
+
async function defaultFetchNpmMetadata(url: string): Promise<NpmRegistryMetadata> {
|
|
103
85
|
const controller = new AbortController();
|
|
104
|
-
const timer = setTimeout(() => controller.abort(),
|
|
86
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
105
87
|
try {
|
|
106
|
-
const
|
|
88
|
+
const res = await fetch(url, {
|
|
107
89
|
signal: controller.signal,
|
|
108
|
-
headers: { "User-Agent":
|
|
90
|
+
headers: { "User-Agent": `${CODEXA_NPM_PACKAGE}-update-checker/1.0` },
|
|
109
91
|
});
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
throw new Error("npm registry response did not include dist-tags.latest");
|
|
113
|
-
}
|
|
114
|
-
return latest.trim();
|
|
92
|
+
if (!res.ok) throw new Error(`npm registry returned HTTP ${res.status}`);
|
|
93
|
+
return await res.json() as NpmRegistryMetadata;
|
|
115
94
|
} finally {
|
|
116
95
|
clearTimeout(timer);
|
|
117
96
|
}
|
|
@@ -121,28 +100,51 @@ export async function checkForUpdates(
|
|
|
121
100
|
opts?: { enabled?: boolean },
|
|
122
101
|
overrides?: UpdateCheckOverrides,
|
|
123
102
|
): Promise<UpdateCheckResult> {
|
|
124
|
-
const
|
|
125
|
-
const currentVersion = overrides?.currentVersion ?? APP_VERSION;
|
|
103
|
+
const currentVersion = normalizeVersion(overrides?.currentVersion ?? APP_VERSION);
|
|
126
104
|
|
|
127
105
|
if (opts?.enabled === false) {
|
|
128
|
-
return { status: "unknown", currentVersion, latestVersion: null, checkedAt: now() };
|
|
106
|
+
return { status: "unknown", currentVersion, latestVersion: null, checkedAt: Date.now(), source: "npm" };
|
|
129
107
|
}
|
|
130
108
|
|
|
131
109
|
try {
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
110
|
+
const fetchFn = overrides?.fetchNpmMetadataFn ?? defaultFetchNpmMetadata;
|
|
111
|
+
const metadata = await fetchFn(CODEXA_NPM_REGISTRY_URL);
|
|
112
|
+
const rawLatest = metadata["dist-tags"]?.latest;
|
|
113
|
+
|
|
114
|
+
if (!rawLatest) {
|
|
115
|
+
return {
|
|
116
|
+
status: "error",
|
|
117
|
+
currentVersion,
|
|
118
|
+
latestVersion: null,
|
|
119
|
+
errorMessage: "npm registry response did not include dist-tags.latest",
|
|
120
|
+
checkedAt: Date.now(),
|
|
121
|
+
source: "npm",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const latestVersion = normalizeVersion(rawLatest);
|
|
126
|
+
|
|
127
|
+
if (!isValidSemver(latestVersion) || !isValidSemver(currentVersion)) {
|
|
128
|
+
return {
|
|
129
|
+
status: "unknown",
|
|
130
|
+
currentVersion,
|
|
131
|
+
latestVersion: rawLatest,
|
|
132
|
+
errorMessage: `Invalid semver — current: "${currentVersion}", latest: "${rawLatest}"`,
|
|
133
|
+
checkedAt: Date.now(),
|
|
134
|
+
source: "npm",
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const status = isNewerVersion(latestVersion, currentVersion) ? "update-available" : "up-to-date";
|
|
139
|
+
return { status, currentVersion, latestVersion, checkedAt: Date.now(), source: "npm" };
|
|
139
140
|
} catch (err) {
|
|
140
141
|
return {
|
|
141
142
|
status: "error",
|
|
142
143
|
currentVersion,
|
|
143
144
|
latestVersion: null,
|
|
144
145
|
errorMessage: err instanceof Error ? err.message : String(err),
|
|
145
|
-
checkedAt: now(),
|
|
146
|
+
checkedAt: Date.now(),
|
|
147
|
+
source: "npm",
|
|
146
148
|
};
|
|
147
149
|
}
|
|
148
150
|
}
|
|
@@ -151,22 +153,36 @@ export function formatUpdateInstructions(result: UpdateCheckResult | null): stri
|
|
|
151
153
|
const current = result?.currentVersion ?? APP_VERSION;
|
|
152
154
|
const latest = result?.latestVersion ?? "unknown";
|
|
153
155
|
|
|
156
|
+
if (result?.status === "error") {
|
|
157
|
+
return [
|
|
158
|
+
`Current installed version: ${current}`,
|
|
159
|
+
`npm latest version: ${latest}`,
|
|
160
|
+
`Error checking npm update status: ${result.errorMessage ?? "unknown error"}`,
|
|
161
|
+
].join("\n");
|
|
162
|
+
}
|
|
163
|
+
|
|
154
164
|
let statusLine: string;
|
|
155
165
|
if (result?.status === "update-available" && result.latestVersion) {
|
|
156
166
|
statusLine = `Update available: Codexa ${result.latestVersion} is available. You are using ${current}.`;
|
|
157
167
|
} else if (result?.status === "up-to-date") {
|
|
158
168
|
statusLine = "Already up to date.";
|
|
159
|
-
} else if (result?.status === "error") {
|
|
160
|
-
statusLine = `Error checking npm update status: ${result.errorMessage || "unknown error"}`;
|
|
161
169
|
} else {
|
|
162
|
-
statusLine = "Status unknown
|
|
170
|
+
statusLine = "Status unknown — could not reach npm registry.";
|
|
163
171
|
}
|
|
164
172
|
|
|
165
173
|
return [
|
|
166
174
|
`Current installed version: ${current}`,
|
|
167
|
-
`npm latest version:
|
|
168
|
-
`Status:
|
|
175
|
+
`npm latest version: ${latest}`,
|
|
176
|
+
`Status: ${statusLine}`,
|
|
169
177
|
"",
|
|
170
178
|
`Run: ${CODEXA_UPDATE_COMMAND}`,
|
|
171
179
|
].join("\n");
|
|
172
180
|
}
|
|
181
|
+
|
|
182
|
+
export function formatLocalDevUpdateStatus(): string {
|
|
183
|
+
return [
|
|
184
|
+
"Running local-dev Codexa.",
|
|
185
|
+
"Automatic published npm update prompts are disabled for this channel.",
|
|
186
|
+
"Run /update check to explicitly check the published npm package.",
|
|
187
|
+
].join("\n");
|
|
188
|
+
}
|
package/src/session/types.ts
CHANGED
|
@@ -23,7 +23,8 @@ export type Screen =
|
|
|
23
23
|
| "permissions-network-picker"
|
|
24
24
|
| "permissions-add-writable-root"
|
|
25
25
|
| "permissions-remove-writable-root"
|
|
26
|
-
| "import-confirmation"
|
|
26
|
+
| "import-confirmation"
|
|
27
|
+
| "update-prompt";
|
|
27
28
|
|
|
28
29
|
// ─── Provider readiness ───────────────────────────────────────────────────────
|
|
29
30
|
|
|
@@ -22,15 +22,15 @@ export function ActionRequiredBlock({ cols, turnIndex, question }: ActionRequire
|
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
|
-
<Box borderStyle="single" borderColor={theme.
|
|
25
|
+
<Box borderStyle="single" borderColor={theme.borderFocused} flexDirection="column" marginBottom={1} width="100%">
|
|
26
26
|
<Box width="100%" justifyContent="space-between" overflow="hidden" paddingX={1}>
|
|
27
|
-
<Text color={theme.
|
|
28
|
-
<Text color={theme.
|
|
27
|
+
<Text color={theme.text} bold>{`[${turnIndex}] ACTION REQUIRED`}</Text>
|
|
28
|
+
<Text color={theme.text} bold>{"⚡"}</Text>
|
|
29
29
|
</Box>
|
|
30
30
|
<Box flexDirection="column" paddingX={2} marginTop={1} marginBottom={1} width="100%">
|
|
31
|
-
<Text color={theme.
|
|
31
|
+
<Text color={theme.text} bold>{"Verification Question"}</Text>
|
|
32
32
|
{wrappedContent.map((row, index) => (
|
|
33
|
-
<Text key={`content-${index}`} color={theme.
|
|
33
|
+
<Text key={`content-${index}`} color={theme.text}>{row || " "}</Text>
|
|
34
34
|
))}
|
|
35
35
|
</Box>
|
|
36
36
|
</Box>
|
package/src/ui/ActivityBars.tsx
CHANGED
|
@@ -18,7 +18,7 @@ interface WaveBarProps {
|
|
|
18
18
|
|
|
19
19
|
export function WaveBar({ tick, color }: WaveBarProps) {
|
|
20
20
|
const theme = useTheme();
|
|
21
|
-
const activeColor = color ?? theme.
|
|
21
|
+
const activeColor = color ?? theme.accent;
|
|
22
22
|
|
|
23
23
|
let str = "";
|
|
24
24
|
for (let c = 0; c < WAVE_COLS; c++) {
|
|
@@ -48,8 +48,8 @@ interface PulseBarProps {
|
|
|
48
48
|
|
|
49
49
|
export function PulseBar({ tick, color, dimColor }: PulseBarProps) {
|
|
50
50
|
const theme = useTheme();
|
|
51
|
-
const activeColor = color ?? theme.
|
|
52
|
-
const inactiveColor = dimColor ?? theme.
|
|
51
|
+
const activeColor = color ?? theme.accent;
|
|
52
|
+
const inactiveColor = dimColor ?? theme.textDim;
|
|
53
53
|
|
|
54
54
|
const raw = Math.floor(tick / PULSE_SPEED) % PULSE_PERIOD;
|
|
55
55
|
const pos = raw < PULSE_MAX_POS ? raw : PULSE_PERIOD - raw;
|
|
@@ -32,26 +32,26 @@ export function ActivityIndicator({ uiState, externalCliStatus = "idle" }: Activ
|
|
|
32
32
|
}, [isAnimated]);
|
|
33
33
|
|
|
34
34
|
let glyph = "?";
|
|
35
|
-
let color = theme.
|
|
35
|
+
let color = theme.textDim;
|
|
36
36
|
let bold = false;
|
|
37
37
|
|
|
38
38
|
if (isError) {
|
|
39
39
|
glyph = "�";
|
|
40
|
-
color = theme.
|
|
40
|
+
color = theme.error;
|
|
41
41
|
bold = true;
|
|
42
42
|
} else if (isAction) {
|
|
43
43
|
glyph = "?";
|
|
44
|
-
color = theme.
|
|
44
|
+
color = theme.warning;
|
|
45
45
|
bold = true;
|
|
46
46
|
} else if (isStarting || isThinking) {
|
|
47
47
|
glyph = SPINNER_FRAMES[frameIndex % SPINNER_FRAMES.length]!;
|
|
48
|
-
color = theme.
|
|
48
|
+
color = theme.text;
|
|
49
49
|
} else if (isStreaming) {
|
|
50
50
|
glyph = STREAMING_FRAMES[frameIndex % STREAMING_FRAMES.length]!;
|
|
51
|
-
color = theme.
|
|
51
|
+
color = theme.success;
|
|
52
52
|
} else {
|
|
53
53
|
glyph = "?";
|
|
54
|
-
color = theme.
|
|
54
|
+
color = theme.textDim;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
return <Text color={color} bold={bold}>{glyph}</Text>;
|
package/src/ui/AgentBlock.tsx
CHANGED
|
@@ -49,7 +49,7 @@ const StreamingCursor = memo(function StreamingCursor() {
|
|
|
49
49
|
const theme = useTheme();
|
|
50
50
|
return (
|
|
51
51
|
<Box width="100%" paddingLeft={2}>
|
|
52
|
-
<Text color={theme.
|
|
52
|
+
<Text color={theme.accent}>{"▌"}</Text>
|
|
53
53
|
</Box>
|
|
54
54
|
);
|
|
55
55
|
});
|
|
@@ -93,14 +93,14 @@ export function AgentBlock({
|
|
|
93
93
|
: runStatus;
|
|
94
94
|
const heading = run?.runtime.model ? run.runtime.model.toUpperCase().replace(/-/g, " ") : "Codex";
|
|
95
95
|
|
|
96
|
-
const borderColor = dim ? theme.
|
|
96
|
+
const borderColor = dim ? theme.border : (runPhase === "streaming" ? theme.borderFocused : theme.border);
|
|
97
97
|
|
|
98
98
|
return (
|
|
99
99
|
<DashCard cols={cols} title={heading} rightBadge={rightBadge} borderColor={borderColor}>
|
|
100
100
|
{!streaming && failureMessage && (
|
|
101
101
|
<Box flexDirection="column" width="100%">
|
|
102
102
|
{wrapPlainText(failureMessage, contentWidth).map((row, index) => (
|
|
103
|
-
<Text key={index} color={theme.
|
|
103
|
+
<Text key={index} color={theme.error}>{index === 0 ? `✕ ${row || " "}` : row || " "}</Text>
|
|
104
104
|
))}
|
|
105
105
|
</Box>
|
|
106
106
|
)}
|
|
@@ -116,12 +116,12 @@ export function AgentBlock({
|
|
|
116
116
|
{!streaming && run && run.status !== "running" && (
|
|
117
117
|
<Box flexDirection="column" width="100%">
|
|
118
118
|
{run.status === "canceled" && cancelMessage ? (
|
|
119
|
-
<Text color={theme.
|
|
119
|
+
<Text color={theme.warning}>{cancelMessage}</Text>
|
|
120
120
|
) : run.status === "completed" && pipelineState.length === 0 ? (
|
|
121
|
-
<Text color={theme.
|
|
121
|
+
<Text color={theme.textDim}>{"(no output)"}</Text>
|
|
122
122
|
) : null}
|
|
123
123
|
{run.truncatedOutput && (
|
|
124
|
-
<Text color={theme.
|
|
124
|
+
<Text color={theme.textDim}>{RUN_OUTPUT_TRUNCATION_NOTICE}</Text>
|
|
125
125
|
)}
|
|
126
126
|
</Box>
|
|
127
127
|
)}
|
|
@@ -62,7 +62,7 @@ export function AnimatedStatusText({ baseText, isActive, isError = false, animat
|
|
|
62
62
|
const suffix = isActive ? animationFrame ?? localFrame : "";
|
|
63
63
|
|
|
64
64
|
return (
|
|
65
|
-
<Text color={isError ? theme.
|
|
65
|
+
<Text color={isError ? theme.error : theme.info} wrap="truncate">
|
|
66
66
|
{renderedText}{suffix}
|
|
67
67
|
</Text>
|
|
68
68
|
);
|
package/src/ui/AppShell.tsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React, { memo, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { Box, Text, useStdin } from "ink";
|
|
3
3
|
import { useTheme } from "./theme.js";
|
|
4
|
-
import type { UpdateCheckResult } from "../core/updateCheck.js";
|
|
5
4
|
import type { RuntimeSummary } from "../config/runtimeConfig.js";
|
|
6
5
|
import type { CodexAuthState } from "../core/auth/codexAuth.js";
|
|
7
6
|
import { HEADER_CONFIG_DEFAULTS, type HeaderConfig } from "../config/settings.js";
|
|
@@ -25,7 +24,7 @@ import {
|
|
|
25
24
|
} from "./Timeline.js";
|
|
26
25
|
import { buildNativeTranscriptParts, type NativeTranscriptRowItem, type TimelineRow } from "./timelineMeasure.js";
|
|
27
26
|
import type { TerminalSelectionProfile } from "../core/terminal/terminalSelection.js";
|
|
28
|
-
import { MemoizedTopHeader, measureTopHeaderRows } from "./TopHeader.js";
|
|
27
|
+
import { MemoizedTopHeader, measureTopHeaderRows, type UpdateAvailableInfo } from "./TopHeader.js";
|
|
29
28
|
|
|
30
29
|
const COMPACT_HEADER_TO_COMPOSER_GAP_ROWS = 1;
|
|
31
30
|
const MEDIUM_HEADER_TO_COMPOSER_GAP_ROWS = 1;
|
|
@@ -59,7 +58,7 @@ export interface AppShellProps {
|
|
|
59
58
|
selectionProfile?: TerminalSelectionProfile;
|
|
60
59
|
clearCount?: number;
|
|
61
60
|
headerConfig?: HeaderConfig;
|
|
62
|
-
|
|
61
|
+
updateAvailable?: UpdateAvailableInfo | null;
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
// ─── Helpers & subcomponents ─────────────────────────────────────────────────
|
|
@@ -164,7 +163,7 @@ function AppShellInner({
|
|
|
164
163
|
selectionProfile,
|
|
165
164
|
clearCount = 0,
|
|
166
165
|
headerConfig = HEADER_CONFIG_DEFAULTS,
|
|
167
|
-
|
|
166
|
+
updateAvailable = null,
|
|
168
167
|
}: AppShellProps) {
|
|
169
168
|
const theme = useTheme();
|
|
170
169
|
renderDebug.useRenderDebug("AppShell", {
|
|
@@ -193,7 +192,7 @@ function AppShellInner({
|
|
|
193
192
|
|
|
194
193
|
const shellWidth = getShellWidth(layout.cols);
|
|
195
194
|
const shellHeight = getShellHeight(layout.rows);
|
|
196
|
-
const headerRows = measureTopHeaderRows(layout, headerConfig);
|
|
195
|
+
const headerRows = measureTopHeaderRows(layout, headerConfig, !!updateAvailable);
|
|
197
196
|
const headerToContentGapRows = calculateHeaderToContentGapRows(layout);
|
|
198
197
|
const showComposer = screen === "main";
|
|
199
198
|
const showMainPanel = screen === "main" && mainPanel !== undefined && mainPanel !== null;
|
|
@@ -505,17 +504,6 @@ function AppShellInner({
|
|
|
505
504
|
? React.cloneElement(composer as React.ReactElement<{ selectionProfile?: TerminalSelectionProfile }>, { selectionProfile })
|
|
506
505
|
: composer;
|
|
507
506
|
|
|
508
|
-
const updateNotice = updateCheckResult?.status === "update-available" ? (
|
|
509
|
-
<Box paddingX={1} flexDirection="column">
|
|
510
|
-
<Text color={theme.WARNING}>
|
|
511
|
-
{`Update available: Codexa ${updateCheckResult.latestVersion ?? "unknown"} is available. You are using ${updateCheckResult.currentVersion}.`}
|
|
512
|
-
</Text>
|
|
513
|
-
<Text color={theme.WARNING}>
|
|
514
|
-
{"Run: npm install -g @golba98/codexa@latest"}
|
|
515
|
-
</Text>
|
|
516
|
-
</Box>
|
|
517
|
-
) : null;
|
|
518
|
-
|
|
519
507
|
if (showMainPanelFullOutput) {
|
|
520
508
|
return (
|
|
521
509
|
<Box flexDirection="column" width="100%">
|
|
@@ -526,10 +514,9 @@ function AppShellInner({
|
|
|
526
514
|
layout={layout}
|
|
527
515
|
runtimeSummary={runtimeSummary}
|
|
528
516
|
headerConfig={headerConfig}
|
|
517
|
+
updateAvailable={updateAvailable}
|
|
529
518
|
/>
|
|
530
519
|
|
|
531
|
-
{updateNotice}
|
|
532
|
-
|
|
533
520
|
{headerToContentGapRows > 0 && (
|
|
534
521
|
<Box height={headerToContentGapRows} />
|
|
535
522
|
)}
|
|
@@ -558,10 +545,9 @@ function AppShellInner({
|
|
|
558
545
|
layout={layout}
|
|
559
546
|
runtimeSummary={runtimeSummary}
|
|
560
547
|
headerConfig={headerConfig}
|
|
548
|
+
updateAvailable={updateAvailable}
|
|
561
549
|
/>
|
|
562
550
|
|
|
563
|
-
{updateNotice}
|
|
564
|
-
|
|
565
551
|
{headerToContentGapRows > 0 && (
|
|
566
552
|
<Box height={headerToContentGapRows} />
|
|
567
553
|
)}
|
|
@@ -614,10 +600,9 @@ function AppShellInner({
|
|
|
614
600
|
layout={layout}
|
|
615
601
|
runtimeSummary={runtimeSummary}
|
|
616
602
|
headerConfig={headerConfig}
|
|
603
|
+
updateAvailable={updateAvailable}
|
|
617
604
|
/>
|
|
618
605
|
|
|
619
|
-
{updateNotice}
|
|
620
|
-
|
|
621
606
|
{headerToContentGapRows > 0 && (
|
|
622
607
|
<Box height={headerToContentGapRows} />
|
|
623
608
|
)}
|
|
@@ -714,8 +699,8 @@ export const AppShell = memo(AppShellInner, (prev, next) => {
|
|
|
714
699
|
prev.verboseMode === next.verboseMode &&
|
|
715
700
|
prev.mouseCapture === next.mouseCapture &&
|
|
716
701
|
prev.onMouseActivity === next.onMouseActivity &&
|
|
717
|
-
prev.clearCount
|
|
718
|
-
prev.
|
|
702
|
+
prev.clearCount === next.clearCount &&
|
|
703
|
+
prev.updateAvailable === next.updateAvailable &&
|
|
719
704
|
panelPropsEqual
|
|
720
705
|
);
|
|
721
706
|
});
|
|
@@ -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.borderFocused}
|
|
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}>Enter copy and continue Esc cancel</Text>
|
|
94
94
|
</Box>
|
|
95
95
|
</Box>
|
|
96
96
|
</Box>
|