@golba98/codexa 1.0.1 → 1.0.2
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 +0 -0
- package/package.json +3 -2
- package/scripts/audit-codexa-capabilities.mjs +0 -0
- package/scripts/gen-build-info.mjs +33 -0
- package/scripts/smoke-terminal-bench.mjs +0 -0
- package/src/app.tsx +103 -2
- package/src/appRenderStability.test.ts +0 -0
- package/src/commands/handler.test.ts +12 -0
- package/src/commands/handler.ts +5 -0
- package/src/config/buildInfo.ts +3 -0
- package/src/config/launchArgs.test.ts +31 -0
- package/src/config/launchArgs.ts +10 -0
- package/src/config/layeredConfig.test.ts +0 -0
- package/src/config/layeredConfig.ts +0 -0
- package/src/config/persistence.test.ts +6 -2
- package/src/config/persistence.ts +41 -0
- package/src/config/runtimeConfig.test.ts +0 -0
- package/src/config/runtimeConfig.ts +4 -0
- package/src/config/settings.test.ts +0 -0
- package/src/config/settings.ts +7 -6
- package/src/config/toml-serialize.ts +0 -0
- package/src/config/trustStore.test.ts +0 -0
- package/src/config/trustStore.ts +0 -0
- package/src/config/updateCheckCache.ts +45 -0
- package/src/core/attachments.test.ts +0 -0
- package/src/core/attachments.ts +0 -0
- package/src/core/auth/codexAuth.test.ts +0 -0
- package/src/core/auth/codexAuth.ts +0 -0
- package/src/core/cleanupFastFail.test.ts +0 -0
- package/src/core/cleanupFastFail.ts +0 -0
- package/src/core/clipboard.ts +0 -0
- package/src/core/codex.ts +0 -0
- package/src/core/codexExecArgs.test.ts +0 -0
- package/src/core/codexExecArgs.ts +0 -0
- package/src/core/codexLaunch.test.ts +0 -0
- package/src/core/codexLaunch.ts +0 -0
- package/src/core/codexPrompt.test.ts +0 -0
- package/src/core/codexPrompt.ts +0 -0
- package/src/core/executables/claudeExecutable.ts +0 -0
- package/src/core/executables/codexExecutable.test.ts +0 -0
- package/src/core/executables/codexExecutable.ts +0 -0
- package/src/core/executables/executableResolver.test.ts +0 -0
- package/src/core/executables/executableResolver.ts +0 -0
- package/src/core/executables/geminiExecutable.test.ts +0 -0
- package/src/core/executables/geminiExecutable.ts +0 -0
- package/src/core/executables/pathSanityScan.test.ts +0 -0
- package/src/core/githubDiagnostics.test.ts +0 -0
- package/src/core/githubDiagnostics.ts +0 -0
- package/src/core/hollowResponseFormat.test.ts +0 -0
- package/src/core/hollowResponseFormat.ts +0 -0
- package/src/core/inputDebug.ts +0 -0
- package/src/core/launchContext.test.ts +0 -0
- package/src/core/launchContext.ts +0 -0
- package/src/core/models/codexCapabilities.test.ts +0 -0
- package/src/core/models/codexCapabilities.ts +0 -0
- package/src/core/models/codexModelCapabilities.test.ts +0 -0
- package/src/core/models/codexModelCapabilities.ts +0 -0
- package/src/core/models/modelSpecs.test.ts +0 -0
- package/src/core/models/modelSpecs.ts +0 -0
- package/src/core/perf/profiler.ts +0 -0
- package/src/core/perf/renderDebug.test.ts +0 -0
- package/src/core/perf/renderDebug.ts +0 -0
- package/src/core/planStorage.test.ts +0 -0
- package/src/core/planStorage.ts +0 -0
- package/src/core/process/CommandRunner.test.ts +0 -0
- package/src/core/process/CommandRunner.ts +0 -0
- package/src/core/process/processValidation.ts +0 -0
- package/src/core/projectInstructions.test.ts +0 -0
- package/src/core/projectInstructions.ts +0 -0
- package/src/core/providerLauncher/launcher.test.ts +0 -0
- package/src/core/providerLauncher/launcher.ts +0 -0
- package/src/core/providerLauncher/registry.test.ts +0 -0
- package/src/core/providerLauncher/registry.ts +0 -0
- package/src/core/providerLauncher/types.ts +0 -0
- package/src/core/providerLauncher/workspaceConfig.test.ts +0 -0
- package/src/core/providerLauncher/workspaceConfig.ts +0 -0
- package/src/core/providerRuntime/anthropic.test.ts +0 -0
- package/src/core/providerRuntime/anthropic.ts +0 -0
- package/src/core/providerRuntime/capabilityProfile.test.ts +0 -0
- package/src/core/providerRuntime/capabilityProfile.ts +0 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +0 -0
- package/src/core/providerRuntime/contextMetadata.test.ts +0 -0
- package/src/core/providerRuntime/contextMetadata.ts +0 -0
- package/src/core/providerRuntime/gemini.test.ts +0 -0
- package/src/core/providerRuntime/gemini.ts +0 -0
- package/src/core/providerRuntime/lmstudio.test.ts +0 -0
- package/src/core/providerRuntime/lmstudio.ts +0 -0
- package/src/core/providerRuntime/local.test.ts +0 -0
- package/src/core/providerRuntime/local.ts +0 -0
- package/src/core/providerRuntime/models.ts +0 -0
- package/src/core/providerRuntime/reasoning.ts +0 -0
- package/src/core/providerRuntime/registry.test.ts +0 -0
- package/src/core/providerRuntime/registry.ts +0 -0
- package/src/core/providerRuntime/types.ts +0 -0
- package/src/core/providers/codexJsonStream.test.ts +0 -0
- package/src/core/providers/codexJsonStream.ts +0 -0
- package/src/core/providers/codexSubprocess.test.ts +0 -0
- package/src/core/providers/codexSubprocess.ts +0 -0
- package/src/core/providers/codexTranscript.test.ts +0 -0
- package/src/core/providers/codexTranscript.ts +0 -0
- package/src/core/providers/openaiNative.ts +0 -0
- package/src/core/providers/registry.ts +0 -0
- package/src/core/providers/types.ts +0 -0
- package/src/core/terminal/startupClear.test.ts +55 -0
- package/src/core/terminal/startupClear.ts +20 -0
- package/src/core/terminal/terminalCapabilities.test.ts +0 -0
- package/src/core/terminal/terminalCapabilities.ts +0 -0
- package/src/core/terminal/terminalControl.test.ts +0 -0
- package/src/core/terminal/terminalControl.ts +0 -0
- package/src/core/terminal/terminalSanitize.test.ts +0 -0
- package/src/core/terminal/terminalSanitize.ts +0 -0
- package/src/core/terminal/terminalSelection.test.ts +0 -0
- package/src/core/terminal/terminalSelection.ts +0 -0
- package/src/core/terminal/terminalTitle.test.ts +0 -0
- package/src/core/terminal/terminalTitle.ts +0 -0
- package/src/core/updateCheck.test.ts +194 -0
- package/src/core/updateCheck.ts +172 -0
- package/src/core/workspaceActivity.test.ts +0 -0
- package/src/core/workspaceActivity.ts +0 -0
- package/src/core/workspaceGuard.test.ts +0 -0
- package/src/core/workspaceGuard.ts +0 -0
- package/src/core/workspaceRoot.test.ts +0 -0
- package/src/core/workspaceRoot.ts +0 -0
- package/src/exec.test.ts +0 -0
- package/src/exec.ts +0 -0
- package/src/headless/execArgs.test.ts +0 -0
- package/src/headless/execArgs.ts +1 -0
- package/src/headless/execRunner.test.ts +8 -6
- package/src/headless/execRunner.ts +0 -0
- package/src/index.test.tsx +5 -3
- package/src/index.tsx +13 -9
- package/src/session/appSession.test.ts +0 -0
- package/src/session/appSession.ts +0 -0
- package/src/session/chatLifecycle.test.ts +0 -0
- package/src/session/chatLifecycle.ts +0 -0
- package/src/session/liveRenderScheduler.test.ts +0 -0
- package/src/session/liveRenderScheduler.ts +0 -0
- package/src/session/planFlow.test.ts +0 -0
- package/src/session/planFlow.ts +0 -0
- package/src/session/planTranscript.test.ts +0 -0
- package/src/session/planTranscript.ts +0 -0
- package/src/session/promptRunSchedule.test.ts +0 -0
- package/src/session/promptRunSchedule.ts +0 -0
- package/src/session/types.ts +0 -0
- package/src/test/runtimeTestUtils.ts +0 -0
- package/src/types/react-dom.d.ts +0 -0
- package/src/ui/ActionRequiredBlock.tsx +0 -0
- package/src/ui/ActivityBars.tsx +0 -0
- package/src/ui/ActivityIndicator.test.tsx +0 -0
- package/src/ui/ActivityIndicator.tsx +0 -0
- package/src/ui/AgentBlock.test.ts +0 -0
- package/src/ui/AgentBlock.tsx +0 -0
- package/src/ui/AnimatedStatusText.test.ts +0 -0
- package/src/ui/AnimatedStatusText.tsx +0 -0
- package/src/ui/AppShell.test.tsx +60 -23
- package/src/ui/AppShell.tsx +28 -5
- package/src/ui/AttachmentImportPanel.test.tsx +0 -0
- package/src/ui/AttachmentImportPanel.tsx +0 -0
- package/src/ui/AuthPanel.tsx +0 -0
- package/src/ui/BackendPicker.tsx +0 -0
- package/src/ui/BottomComposer.test.ts +0 -0
- package/src/ui/BottomComposer.tsx +0 -0
- package/src/ui/CodexLogo.tsx +0 -0
- package/src/ui/DashCard.tsx +0 -0
- package/src/ui/Markdown.test.ts +0 -0
- package/src/ui/Markdown.tsx +0 -0
- package/src/ui/ModePicker.tsx +0 -0
- package/src/ui/ModelPicker.tsx +0 -0
- package/src/ui/ModelPickerProviderScope.test.tsx +0 -0
- package/src/ui/ModelPickerScreen.test.tsx +0 -0
- package/src/ui/ModelPickerScreen.tsx +0 -0
- package/src/ui/ModelPickerState.test.tsx +0 -0
- package/src/ui/ModelReasoningPicker.test.tsx +0 -0
- package/src/ui/ModelReasoningPicker.tsx +0 -0
- package/src/ui/Panel.tsx +0 -0
- package/src/ui/PermissionsPanel.tsx +0 -0
- package/src/ui/PlanActionPicker.tsx +0 -0
- package/src/ui/PlanReviewPanel.test.tsx +0 -0
- package/src/ui/PlanReviewPanel.tsx +0 -0
- package/src/ui/PromptCardBorder.test.tsx +0 -0
- package/src/ui/ProviderPicker.test.tsx +0 -0
- package/src/ui/ProviderPicker.tsx +0 -0
- package/src/ui/ProviderShortcut.test.tsx +0 -0
- package/src/ui/ReasoningPicker.tsx +0 -0
- package/src/ui/RunFooter.tsx +0 -0
- package/src/ui/SelectionPanel.tsx +0 -0
- package/src/ui/SettingsPanel.test.tsx +0 -0
- package/src/ui/SettingsPanel.tsx +0 -0
- package/src/ui/Spinner.tsx +0 -0
- package/src/ui/StaticIntroItem.tsx +0 -0
- package/src/ui/StaticTranscriptItem.tsx +0 -0
- package/src/ui/TextEntryPanel.tsx +0 -0
- package/src/ui/ThemePicker.tsx +0 -0
- package/src/ui/ThinkingBlock.tsx +0 -0
- package/src/ui/Timeline.test.ts +0 -0
- package/src/ui/Timeline.tsx +0 -0
- package/src/ui/TimelineNavigation.test.tsx +0 -0
- package/src/ui/TopHeader.test.tsx +53 -38
- package/src/ui/TopHeader.tsx +51 -15
- package/src/ui/TurnGroup.test.tsx +0 -0
- package/src/ui/TurnGroup.tsx +0 -0
- package/src/ui/busyStatusAnimation.test.ts +0 -0
- package/src/ui/busyStatusAnimation.ts +0 -0
- package/src/ui/commandNormalize.test.ts +0 -0
- package/src/ui/commandNormalize.ts +0 -0
- package/src/ui/diffRenderer.test.ts +0 -0
- package/src/ui/diffRenderer.ts +0 -0
- package/src/ui/focus.ts +0 -0
- package/src/ui/focusFlow.test.tsx +0 -0
- package/src/ui/inputBuffer.test.ts +0 -0
- package/src/ui/inputBuffer.ts +0 -0
- package/src/ui/layout.test.ts +3 -2
- package/src/ui/layout.ts +0 -0
- package/src/ui/modeDisplay.test.ts +0 -0
- package/src/ui/modeDisplay.ts +0 -0
- package/src/ui/outputPipeline.ts +0 -0
- package/src/ui/progressEntries.ts +0 -0
- package/src/ui/runActivityView.test.ts +0 -0
- package/src/ui/runActivityView.ts +0 -0
- package/src/ui/runLifecycleView.test.tsx +0 -0
- package/src/ui/slashCommands.ts +0 -0
- package/src/ui/statusRenderIsolation.test.tsx +0 -0
- package/src/ui/terminalAnswerFormat.test.ts +0 -0
- package/src/ui/terminalAnswerFormat.ts +0 -0
- package/src/ui/textLayout.test.ts +0 -0
- package/src/ui/textLayout.ts +0 -0
- package/src/ui/theme.tsx +0 -0
- package/src/ui/themeFlow.test.ts +0 -0
- package/src/ui/themeFlow.ts +0 -0
- package/src/ui/timelineMeasure.ts +0 -0
- package/src/ui/timelineMeasureCache.test.ts +0 -0
- package/src/ui/useThrottledValue.ts +0 -0
package/README.md
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@golba98/codexa",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Terminal UI for Codexa — TypeScript + Bun + Ink",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"test": "bun test",
|
|
14
14
|
"audit:codexa-gap": "node scripts/audit-codexa-capabilities.mjs",
|
|
15
15
|
"smoke:terminal-bench": "node scripts/smoke-terminal-bench.mjs",
|
|
16
|
-
"build": "
|
|
16
|
+
"gen-build-info": "node scripts/gen-build-info.mjs",
|
|
17
|
+
"build": "bun run gen-build-info && bun run typecheck",
|
|
17
18
|
"prepublishOnly": "npm run typecheck && npm test"
|
|
18
19
|
},
|
|
19
20
|
"dependencies": {
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Writes src/config/buildInfo.ts with package/build metadata.
|
|
3
|
+
// Used by `bun run build` so the embed is up-to-date at install time.
|
|
4
|
+
|
|
5
|
+
import { execSync } from "node:child_process";
|
|
6
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { resolve, dirname } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const outFile = resolve(__dirname, "..", "src", "config", "buildInfo.ts");
|
|
12
|
+
const packageFile = resolve(__dirname, "..", "package.json");
|
|
13
|
+
|
|
14
|
+
let commit = "unknown";
|
|
15
|
+
try {
|
|
16
|
+
commit = execSync("git rev-parse HEAD", { encoding: "utf-8", timeout: 5000 }).trim();
|
|
17
|
+
} catch {
|
|
18
|
+
// Not a git repo or git unavailable — keep default.
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let version = "0.0.0";
|
|
22
|
+
try {
|
|
23
|
+
const pkg = JSON.parse(readFileSync(packageFile, "utf-8"));
|
|
24
|
+
if (typeof pkg.version === "string" && pkg.version.trim()) {
|
|
25
|
+
version = pkg.version.trim();
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
// Keep default.
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const content = `// Generated by scripts/gen-build-info.mjs — do not edit manually.\nexport const BUILD_COMMIT = ${JSON.stringify(commit)} as const;\nexport const BUILD_VERSION = ${JSON.stringify(version)} as const;\n`;
|
|
32
|
+
writeFileSync(outFile, content, "utf-8");
|
|
33
|
+
console.log(`[gen-build-info] BUILD_COMMIT = ${commit.slice(0, 8)} BUILD_VERSION = ${version}`);
|
|
File without changes
|
package/src/app.tsx
CHANGED
|
@@ -228,9 +228,17 @@ import {
|
|
|
228
228
|
} from "./ui/themeFlow.js";
|
|
229
229
|
import { isBusy as isUiBusy } from "./session/types.js";
|
|
230
230
|
import { AppShell } from "./ui/AppShell.js";
|
|
231
|
+
import { checkForUpdates, formatUpdateInstructions, type UpdateCheckResult } from "./core/updateCheck.js";
|
|
232
|
+
import {
|
|
233
|
+
isCacheValid,
|
|
234
|
+
loadUpdateCheckCache,
|
|
235
|
+
saveUpdateCheckCache,
|
|
236
|
+
} from "./config/updateCheckCache.js";
|
|
237
|
+
import { DEFAULT_UPDATE_CHECK_SETTINGS } from "./config/persistence.js";
|
|
231
238
|
|
|
232
239
|
// ─── Module Constants & Helpers ────────────────────────────────────────────────
|
|
233
240
|
|
|
241
|
+
|
|
234
242
|
let nextEventId = 0;
|
|
235
243
|
let nextTurnId = 0;
|
|
236
244
|
// 50ms keeps assistant text live while avoiding frame-wide terminal repaint
|
|
@@ -397,6 +405,7 @@ export function App({ launchArgs }: AppProps) {
|
|
|
397
405
|
const [verboseMode, setVerboseMode] = useState(false);
|
|
398
406
|
const [planFlow, setPlanFlow] = useState<PlanFlowState>(createInitialPlanFlowState);
|
|
399
407
|
const [initialRevisionText, setInitialRevisionText] = useState("");
|
|
408
|
+
const [updateCheckResult, setUpdateCheckResult] = useState<UpdateCheckResult | null>(null);
|
|
400
409
|
// Mouse reporting defaults to the persisted terminalMouseMode setting.
|
|
401
410
|
// "wheel" (default) enables SGR mouse reporting so the timeline can receive
|
|
402
411
|
// wheel events; native drag-select then requires Shift in Windows Terminal.
|
|
@@ -1054,6 +1063,7 @@ export function App({ launchArgs }: AppProps) {
|
|
|
1054
1063
|
preference: authPreference,
|
|
1055
1064
|
},
|
|
1056
1065
|
header: headerConfig,
|
|
1066
|
+
updateCheck: initialSettings.current.updateCheck,
|
|
1057
1067
|
});
|
|
1058
1068
|
}, [authPreference, customTheme, showBusyLoader, terminalMouseMode, terminalTitleMode, themeSelection.committedTheme, workspaceDisplayMode]);
|
|
1059
1069
|
|
|
@@ -1350,6 +1360,46 @@ export function App({ launchArgs }: AppProps) {
|
|
|
1350
1360
|
appendSystemEvent("Launch mode", devLaunchNotice);
|
|
1351
1361
|
}, [appendSystemEvent, launchContext]);
|
|
1352
1362
|
|
|
1363
|
+
// Non-blocking background update check — runs once at startup.
|
|
1364
|
+
useEffect(() => {
|
|
1365
|
+
const ucSettings = initialSettings.current.updateCheck ?? DEFAULT_UPDATE_CHECK_SETTINGS;
|
|
1366
|
+
if (!ucSettings.enabled) return;
|
|
1367
|
+
|
|
1368
|
+
const timer = setTimeout(() => {
|
|
1369
|
+
void (async () => {
|
|
1370
|
+
try {
|
|
1371
|
+
const cache = loadUpdateCheckCache();
|
|
1372
|
+
if (cache && isCacheValid(cache, ucSettings.intervalHours)) {
|
|
1373
|
+
if (cache.updateAvailable) {
|
|
1374
|
+
setUpdateCheckResult({
|
|
1375
|
+
status: "update-available",
|
|
1376
|
+
currentVersion: cache.currentVersion,
|
|
1377
|
+
latestVersion: cache.latestVersion,
|
|
1378
|
+
checkedAt: cache.lastChecked,
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
const result = await checkForUpdates({ enabled: true });
|
|
1384
|
+
if (result.status !== "error") {
|
|
1385
|
+
setUpdateCheckResult(result);
|
|
1386
|
+
saveUpdateCheckCache({
|
|
1387
|
+
lastChecked: result.checkedAt,
|
|
1388
|
+
currentVersion: result.currentVersion,
|
|
1389
|
+
latestVersion: result.latestVersion,
|
|
1390
|
+
updateAvailable: result.status === "update-available",
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
} catch {
|
|
1394
|
+
// Never crash the TUI on a failed update check.
|
|
1395
|
+
}
|
|
1396
|
+
})();
|
|
1397
|
+
}, 2000);
|
|
1398
|
+
|
|
1399
|
+
return () => clearTimeout(timer);
|
|
1400
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1401
|
+
}, []);
|
|
1402
|
+
|
|
1353
1403
|
// Track clear epoch to suppress stale command result events
|
|
1354
1404
|
useEffect(() => {
|
|
1355
1405
|
clearEpochRef.current = sessionState.clearEpoch;
|
|
@@ -3999,6 +4049,30 @@ export function App({ launchArgs }: AppProps) {
|
|
|
3999
4049
|
appendSystemEvent("Command", commandResult.message);
|
|
4000
4050
|
}
|
|
4001
4051
|
return;
|
|
4052
|
+
case "update": {
|
|
4053
|
+
const arg = commandResult.value ?? "status";
|
|
4054
|
+
void (async () => {
|
|
4055
|
+
let freshResult = updateCheckResult;
|
|
4056
|
+
if (arg === "check" || freshResult === null) {
|
|
4057
|
+
try {
|
|
4058
|
+
freshResult = await checkForUpdates({ enabled: true });
|
|
4059
|
+
setUpdateCheckResult(freshResult);
|
|
4060
|
+
if (freshResult.status !== "error") {
|
|
4061
|
+
saveUpdateCheckCache({
|
|
4062
|
+
lastChecked: freshResult.checkedAt,
|
|
4063
|
+
currentVersion: freshResult.currentVersion,
|
|
4064
|
+
latestVersion: freshResult.latestVersion,
|
|
4065
|
+
updateAvailable: freshResult.status === "update-available",
|
|
4066
|
+
});
|
|
4067
|
+
}
|
|
4068
|
+
} catch {
|
|
4069
|
+
freshResult = null;
|
|
4070
|
+
}
|
|
4071
|
+
}
|
|
4072
|
+
appendSystemEvent("Update", formatUpdateInstructions(freshResult));
|
|
4073
|
+
})();
|
|
4074
|
+
return;
|
|
4075
|
+
}
|
|
4002
4076
|
case "help":
|
|
4003
4077
|
case "unknown":
|
|
4004
4078
|
if (commandResult.message) {
|
|
@@ -4154,6 +4228,32 @@ export function App({ launchArgs }: AppProps) {
|
|
|
4154
4228
|
const composerReasoningLevel = activeProviderRoute.providerId === "anthropic"
|
|
4155
4229
|
? ""
|
|
4156
4230
|
: activeProviderRoute.reasoning ?? reasoningLevel;
|
|
4231
|
+
const headerRuntimeSummary = useMemo(() => {
|
|
4232
|
+
const contextLabel = activeContextMetadata?.contextLength != null
|
|
4233
|
+
? `${formatContextCompact(estimateTokens(conversationChars))} / ${activeContextMetadata.confidence === "estimated" ? "~" : ""}${formatContextCompact(activeContextMetadata.contextLength)}`
|
|
4234
|
+
: "Unknown";
|
|
4235
|
+
|
|
4236
|
+
return {
|
|
4237
|
+
...runtimeSummary,
|
|
4238
|
+
providerLabel: activeRouteProvider?.displayName ?? runtimeSummary.providerLabel,
|
|
4239
|
+
modelLabel: modelDisplayName,
|
|
4240
|
+
contextLabel,
|
|
4241
|
+
};
|
|
4242
|
+
}, [
|
|
4243
|
+
activeContextMetadata?.confidence,
|
|
4244
|
+
activeContextMetadata?.contextLength,
|
|
4245
|
+
activeRouteProvider?.displayName,
|
|
4246
|
+
conversationChars,
|
|
4247
|
+
modelDisplayName,
|
|
4248
|
+
runtimeSummary,
|
|
4249
|
+
]);
|
|
4250
|
+
const effectiveHeaderConfig = useMemo<HeaderConfig>(() => ({
|
|
4251
|
+
...headerConfig,
|
|
4252
|
+
showProvider: true,
|
|
4253
|
+
showModel: false,
|
|
4254
|
+
showReasoning: false,
|
|
4255
|
+
showContext: false,
|
|
4256
|
+
}), [headerConfig]);
|
|
4157
4257
|
|
|
4158
4258
|
// Memoize the composer element so AppShell's memo check (prev.composer ===
|
|
4159
4259
|
// next.composer) passes during streaming. Without this, a new JSX element is
|
|
@@ -4285,7 +4385,7 @@ export function App({ launchArgs }: AppProps) {
|
|
|
4285
4385
|
authState={authStatus.state}
|
|
4286
4386
|
workspaceLabel={workspaceLabel}
|
|
4287
4387
|
workspaceRoot={workspaceRoot}
|
|
4288
|
-
runtimeSummary={
|
|
4388
|
+
runtimeSummary={headerRuntimeSummary}
|
|
4289
4389
|
staticEvents={staticEvents}
|
|
4290
4390
|
activeEvents={activeEvents}
|
|
4291
4391
|
uiState={uiState}
|
|
@@ -4294,7 +4394,8 @@ export function App({ launchArgs }: AppProps) {
|
|
|
4294
4394
|
onMouseActivity={resetMouseIdle}
|
|
4295
4395
|
selectionProfile={selectionProfile}
|
|
4296
4396
|
clearCount={sessionState.clearCount}
|
|
4297
|
-
headerConfig={
|
|
4397
|
+
headerConfig={effectiveHeaderConfig}
|
|
4398
|
+
updateCheckResult={updateCheckResult}
|
|
4298
4399
|
panel={
|
|
4299
4400
|
<>
|
|
4300
4401
|
{screen === "backend-picker" && (
|
|
File without changes
|
|
@@ -617,6 +617,7 @@ test("every command documented in help is recognized by the parser", () => {
|
|
|
617
617
|
["workspace relaunch .", "workspace_relaunch"],
|
|
618
618
|
["providers", "open_provider_picker"],
|
|
619
619
|
["route", "route_status"],
|
|
620
|
+
["update check", "update"],
|
|
620
621
|
] as const;
|
|
621
622
|
for (const [cmd, expectedAction] of multiWord) {
|
|
622
623
|
const result = runCommand(`/${cmd}`);
|
|
@@ -641,3 +642,14 @@ test("non-command text without / or ? prefix returns null", () => {
|
|
|
641
642
|
const result = runCommand("hello world");
|
|
642
643
|
assert.equal(result, null);
|
|
643
644
|
});
|
|
645
|
+
|
|
646
|
+
test("parses /update and /update check commands", () => {
|
|
647
|
+
const resultStatus = runCommand("/update");
|
|
648
|
+
assert.equal(resultStatus?.action, "update");
|
|
649
|
+
assert.equal(resultStatus?.value, "status");
|
|
650
|
+
|
|
651
|
+
const resultCheck = runCommand("/update check");
|
|
652
|
+
assert.equal(resultCheck?.action, "update");
|
|
653
|
+
assert.equal(resultCheck?.value, "check");
|
|
654
|
+
});
|
|
655
|
+
|
package/src/commands/handler.ts
CHANGED
|
@@ -97,6 +97,7 @@ export type CommandAction =
|
|
|
97
97
|
| "runtime_personality"
|
|
98
98
|
| "diagnose_github"
|
|
99
99
|
| "diagnose_providers"
|
|
100
|
+
| "update"
|
|
100
101
|
| "unknown";
|
|
101
102
|
|
|
102
103
|
export interface CommandResult {
|
|
@@ -395,6 +396,7 @@ function buildHelpMessage(context: CommandContext): string {
|
|
|
395
396
|
` Current reasoning: ${formatReasoningLabel(context.runtime.reasoningLevel)}`,
|
|
396
397
|
` Current plan mode: ${context.runtime.planMode ? "Enabled" : "Disabled"}`,
|
|
397
398
|
" /copy Copy last response to clipboard",
|
|
399
|
+
" /update [check] Show update status and instructions for updating Codexa",
|
|
398
400
|
" /help Show this help",
|
|
399
401
|
"",
|
|
400
402
|
"Install on Windows:",
|
|
@@ -854,6 +856,9 @@ export function handleCommand(text: string, context: CommandContext): CommandRes
|
|
|
854
856
|
case "help":
|
|
855
857
|
return { action: "help", message: buildHelpMessage(context) };
|
|
856
858
|
|
|
859
|
+
case "update":
|
|
860
|
+
return { action: "update", value: normalizedArg || "status" };
|
|
861
|
+
|
|
857
862
|
default:
|
|
858
863
|
return {
|
|
859
864
|
action: "unknown",
|
|
@@ -156,3 +156,34 @@ test("parses arguments after -- as an initial prompt", () => {
|
|
|
156
156
|
assert.equal(parsed.value.initialPrompt, "--help as text");
|
|
157
157
|
assert.deepEqual(parsed.value.passthroughArgs, []);
|
|
158
158
|
});
|
|
159
|
+
|
|
160
|
+
test("--no-clear sets noClear to true", () => {
|
|
161
|
+
const parsed = parseLaunchArgs(["--no-clear"]);
|
|
162
|
+
assert.equal(parsed.ok, true);
|
|
163
|
+
if (!parsed.ok) return;
|
|
164
|
+
assert.equal(parsed.value.noClear, true);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("noClear defaults to false when --no-clear is absent", () => {
|
|
168
|
+
const parsed = parseLaunchArgs(["--profile", "review"]);
|
|
169
|
+
assert.equal(parsed.ok, true);
|
|
170
|
+
if (!parsed.ok) return;
|
|
171
|
+
assert.equal(parsed.value.noClear, false);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("--no-clear does not consume the next argument as a value", () => {
|
|
175
|
+
const parsed = parseLaunchArgs(["--no-clear", "my prompt here"]);
|
|
176
|
+
assert.equal(parsed.ok, true);
|
|
177
|
+
if (!parsed.ok) return;
|
|
178
|
+
assert.equal(parsed.value.noClear, true);
|
|
179
|
+
assert.equal(parsed.value.initialPrompt, "my prompt here");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("--no-clear can be combined with other flags", () => {
|
|
183
|
+
const parsed = parseLaunchArgs(["--no-clear", "--profile", "fast", "do something"]);
|
|
184
|
+
assert.equal(parsed.ok, true);
|
|
185
|
+
if (!parsed.ok) return;
|
|
186
|
+
assert.equal(parsed.value.noClear, true);
|
|
187
|
+
assert.equal(parsed.value.profile, "fast");
|
|
188
|
+
assert.equal(parsed.value.initialPrompt, "do something");
|
|
189
|
+
});
|
package/src/config/launchArgs.ts
CHANGED
|
@@ -7,6 +7,7 @@ const FLAG_CONFIG = "--config";
|
|
|
7
7
|
const FLAG_CONFIG_SHORT = "-c";
|
|
8
8
|
const FLAG_MODEL = "--model";
|
|
9
9
|
const FLAG_MODEL_SHORT = "-m";
|
|
10
|
+
const FLAG_NO_CLEAR = "--no-clear";
|
|
10
11
|
|
|
11
12
|
export interface LaunchArgs {
|
|
12
13
|
help: boolean;
|
|
@@ -17,6 +18,8 @@ export interface LaunchArgs {
|
|
|
17
18
|
passthroughArgs: string[];
|
|
18
19
|
/** Explicitly set when --model / -m was passed on the command line. Null when no model flag was given. */
|
|
19
20
|
modelOverride: string | null;
|
|
21
|
+
/** True when --no-clear was passed. Suppresses the startup terminal clear. */
|
|
22
|
+
noClear: boolean;
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
export type LaunchArgsParseResult =
|
|
@@ -53,6 +56,7 @@ export function parseLaunchArgs(argv: readonly string[]): LaunchArgsParseResult
|
|
|
53
56
|
const promptArgs: string[] = [];
|
|
54
57
|
let help = false;
|
|
55
58
|
let version = false;
|
|
59
|
+
let noClear = false;
|
|
56
60
|
let profile: string | null = null;
|
|
57
61
|
let modelOverride: string | null = null;
|
|
58
62
|
|
|
@@ -77,6 +81,11 @@ export function parseLaunchArgs(argv: readonly string[]): LaunchArgsParseResult
|
|
|
77
81
|
continue;
|
|
78
82
|
}
|
|
79
83
|
|
|
84
|
+
if (arg === FLAG_NO_CLEAR) {
|
|
85
|
+
noClear = true;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
80
89
|
// --profile <value> and --profile=<value> both supported.
|
|
81
90
|
if (arg === FLAG_PROFILE) {
|
|
82
91
|
const value = normalizeProfileValue(argv[index + 1]);
|
|
@@ -181,6 +190,7 @@ export function parseLaunchArgs(argv: readonly string[]): LaunchArgsParseResult
|
|
|
181
190
|
configOverrides,
|
|
182
191
|
passthroughArgs,
|
|
183
192
|
modelOverride,
|
|
193
|
+
noClear,
|
|
184
194
|
},
|
|
185
195
|
};
|
|
186
196
|
}
|
|
File without changes
|
|
File without changes
|
|
@@ -39,12 +39,16 @@ test("keeps UI and auth settings separate from runtime persistence", () => {
|
|
|
39
39
|
header: {
|
|
40
40
|
showBrand: true,
|
|
41
41
|
showWorkspace: true,
|
|
42
|
-
showProvider:
|
|
43
|
-
showModel:
|
|
42
|
+
showProvider: true,
|
|
43
|
+
showModel: true,
|
|
44
44
|
showReasoning: false,
|
|
45
45
|
showContext: false,
|
|
46
46
|
showAuthStatus: false,
|
|
47
47
|
},
|
|
48
|
+
updateCheck: {
|
|
49
|
+
enabled: true,
|
|
50
|
+
intervalHours: 6,
|
|
51
|
+
},
|
|
48
52
|
};
|
|
49
53
|
|
|
50
54
|
const serialized = serializeSettings(initial);
|
|
@@ -47,10 +47,21 @@ export interface AuthSettings {
|
|
|
47
47
|
preference: AuthPreference;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
export interface UpdateCheckSettings {
|
|
51
|
+
enabled: boolean;
|
|
52
|
+
intervalHours: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const DEFAULT_UPDATE_CHECK_SETTINGS: UpdateCheckSettings = {
|
|
56
|
+
enabled: true,
|
|
57
|
+
intervalHours: 6,
|
|
58
|
+
};
|
|
59
|
+
|
|
50
60
|
export interface AppSettings {
|
|
51
61
|
ui: UiSettings;
|
|
52
62
|
auth: AuthSettings;
|
|
53
63
|
header: HeaderConfig;
|
|
64
|
+
updateCheck: UpdateCheckSettings;
|
|
54
65
|
}
|
|
55
66
|
|
|
56
67
|
// Pick the first string value found under the camelCase or snake_case key.
|
|
@@ -106,6 +117,17 @@ function normalizeHeaderConfig(input: Partial<HeaderConfig> | null | undefined):
|
|
|
106
117
|
};
|
|
107
118
|
}
|
|
108
119
|
|
|
120
|
+
function normalizeUpdateCheckSettings(
|
|
121
|
+
input: Partial<UpdateCheckSettings> | null | undefined,
|
|
122
|
+
): UpdateCheckSettings {
|
|
123
|
+
return {
|
|
124
|
+
enabled: typeof input?.enabled === "boolean" ? input.enabled : DEFAULT_UPDATE_CHECK_SETTINGS.enabled,
|
|
125
|
+
intervalHours: typeof input?.intervalHours === "number" && input.intervalHours > 0
|
|
126
|
+
? input.intervalHours
|
|
127
|
+
: DEFAULT_UPDATE_CHECK_SETTINGS.intervalHours,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
109
131
|
export function getDefaultSettings(): AppSettings {
|
|
110
132
|
return {
|
|
111
133
|
ui: normalizeUiSettings(null),
|
|
@@ -113,6 +135,7 @@ export function getDefaultSettings(): AppSettings {
|
|
|
113
135
|
preference: DEFAULT_AUTH_PREFERENCE,
|
|
114
136
|
},
|
|
115
137
|
header: normalizeHeaderConfig(null),
|
|
138
|
+
updateCheck: normalizeUpdateCheckSettings(null),
|
|
116
139
|
};
|
|
117
140
|
}
|
|
118
141
|
|
|
@@ -211,6 +234,12 @@ export function parseSettingsData(data: unknown): AppSettings {
|
|
|
211
234
|
? record.header as Record<string, unknown>
|
|
212
235
|
: {};
|
|
213
236
|
|
|
237
|
+
const updateCheckSource = typeof record.updateCheck === "object" && record.updateCheck !== null
|
|
238
|
+
? record.updateCheck as Record<string, unknown>
|
|
239
|
+
: typeof record.update_check === "object" && record.update_check !== null
|
|
240
|
+
? record.update_check as Record<string, unknown>
|
|
241
|
+
: {};
|
|
242
|
+
|
|
214
243
|
return {
|
|
215
244
|
ui: normalizeUiSettings({
|
|
216
245
|
layoutStyle: pickStr(uiSource, "layoutStyle", "layout_style") ?? defaults.ui.layoutStyle,
|
|
@@ -233,6 +262,14 @@ export function parseSettingsData(data: unknown): AppSettings {
|
|
|
233
262
|
showContext: pickBool(headerSource, "showContext", "show_context"),
|
|
234
263
|
showAuthStatus: pickBool(headerSource, "showAuthStatus", "show_auth_status"),
|
|
235
264
|
}),
|
|
265
|
+
updateCheck: normalizeUpdateCheckSettings({
|
|
266
|
+
enabled: pickBool(updateCheckSource, "enabled", "enabled"),
|
|
267
|
+
intervalHours: typeof updateCheckSource.intervalHours === "number"
|
|
268
|
+
? updateCheckSource.intervalHours
|
|
269
|
+
: typeof updateCheckSource.interval_hours === "number"
|
|
270
|
+
? updateCheckSource.interval_hours
|
|
271
|
+
: undefined,
|
|
272
|
+
}),
|
|
236
273
|
};
|
|
237
274
|
}
|
|
238
275
|
|
|
@@ -259,6 +296,10 @@ export function serializeSettings(settings: AppSettings): Record<string, unknown
|
|
|
259
296
|
show_context: settings.header.showContext,
|
|
260
297
|
show_auth_status: settings.header.showAuthStatus,
|
|
261
298
|
},
|
|
299
|
+
update_check: {
|
|
300
|
+
enabled: settings.updateCheck.enabled,
|
|
301
|
+
interval_hours: settings.updateCheck.intervalHours,
|
|
302
|
+
},
|
|
262
303
|
};
|
|
263
304
|
}
|
|
264
305
|
|
|
File without changes
|
|
@@ -110,8 +110,11 @@ export interface RuntimeStatusContext {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
export interface RuntimeSummary {
|
|
113
|
+
providerLabel: string;
|
|
113
114
|
model: AvailableModel;
|
|
115
|
+
modelLabel?: string;
|
|
114
116
|
reasoningLabel: string;
|
|
117
|
+
contextLabel?: string;
|
|
115
118
|
modeLabel: string;
|
|
116
119
|
sandboxLabel: string;
|
|
117
120
|
approvalLabel: string;
|
|
@@ -439,6 +442,7 @@ export function formatPersonalityLabel(value: RuntimePersonality): string {
|
|
|
439
442
|
|
|
440
443
|
export function buildRuntimeSummary(runtime: ResolvedRuntimeConfig): RuntimeSummary {
|
|
441
444
|
return {
|
|
445
|
+
providerLabel: formatBackendLabel(runtime.provider),
|
|
442
446
|
model: runtime.model,
|
|
443
447
|
reasoningLabel: formatReasoningLabel(runtime.reasoningLevel),
|
|
444
448
|
modeLabel: formatModeLabel(runtime.mode),
|
|
File without changes
|
package/src/config/settings.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { homedir } from "os";
|
|
2
|
-
import { basename, join, parse, win32 } from "path";
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import { basename, join, parse, win32 } from "path";
|
|
3
|
+
import { BUILD_VERSION } from "./buildInfo.js";
|
|
3
4
|
|
|
4
5
|
function isWindowsStylePath(p: string): boolean {
|
|
5
6
|
return /^[A-Za-z]:[\\/]/.test(p) || /^\\\\/.test(p);
|
|
@@ -9,8 +10,8 @@ function smartJoin(base: string, ...parts: string[]): string {
|
|
|
9
10
|
return isWindowsStylePath(base) ? win32.join(base, ...parts) : join(base, ...parts);
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
export const APP_NAME = "Codexa";
|
|
13
|
-
export const APP_VERSION =
|
|
13
|
+
export const APP_NAME = "Codexa";
|
|
14
|
+
export const APP_VERSION = BUILD_VERSION;
|
|
14
15
|
export const DEFAULT_BACKEND = "codex-subprocess";
|
|
15
16
|
export const DEFAULT_MODEL = "gpt-5.4";
|
|
16
17
|
export const DEFAULT_MODE = "full-auto";
|
|
@@ -377,8 +378,8 @@ export interface HeaderConfig {
|
|
|
377
378
|
export const HEADER_CONFIG_DEFAULTS: HeaderConfig = {
|
|
378
379
|
showBrand: true,
|
|
379
380
|
showWorkspace: true,
|
|
380
|
-
showProvider:
|
|
381
|
-
showModel:
|
|
381
|
+
showProvider: true,
|
|
382
|
+
showModel: true,
|
|
382
383
|
showReasoning: false,
|
|
383
384
|
showContext: false,
|
|
384
385
|
showAuthStatus: false,
|
|
File without changes
|
|
File without changes
|
package/src/config/trustStore.ts
CHANGED
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
|
+
|
|
5
|
+
export interface UpdateCheckCache {
|
|
6
|
+
lastChecked: number;
|
|
7
|
+
currentVersion: string;
|
|
8
|
+
latestVersion: string | null;
|
|
9
|
+
updateAvailable: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const CACHE_FILE = join(homedir(), ".codexa-update-check.json");
|
|
13
|
+
|
|
14
|
+
export function loadUpdateCheckCache(): UpdateCheckCache | null {
|
|
15
|
+
try {
|
|
16
|
+
const text = readFileSync(CACHE_FILE, "utf-8");
|
|
17
|
+
const data = JSON.parse(text) as Record<string, unknown>;
|
|
18
|
+
if (typeof data.lastChecked !== "number") return null;
|
|
19
|
+
if (typeof data.currentVersion !== "string") return null;
|
|
20
|
+
return {
|
|
21
|
+
lastChecked: data.lastChecked,
|
|
22
|
+
currentVersion: data.currentVersion,
|
|
23
|
+
latestVersion: typeof data.latestVersion === "string" ? data.latestVersion : null,
|
|
24
|
+
updateAvailable: data.updateAvailable === true,
|
|
25
|
+
};
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function saveUpdateCheckCache(cache: UpdateCheckCache): void {
|
|
32
|
+
try {
|
|
33
|
+
mkdirSync(dirname(CACHE_FILE), { recursive: true });
|
|
34
|
+
const tmp = `${CACHE_FILE}.tmp`;
|
|
35
|
+
writeFileSync(tmp, JSON.stringify(cache, null, 2), "utf-8");
|
|
36
|
+
renameSync(tmp, CACHE_FILE);
|
|
37
|
+
} catch {
|
|
38
|
+
// Best-effort — never crash on cache write failure.
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isCacheValid(cache: UpdateCheckCache, intervalHours: number): boolean {
|
|
43
|
+
const maxAgeMs = intervalHours * 60 * 60 * 1000;
|
|
44
|
+
return Date.now() - cache.lastChecked < maxAgeMs;
|
|
45
|
+
}
|
|
File without changes
|
package/src/core/attachments.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/core/clipboard.ts
CHANGED
|
File without changes
|
package/src/core/codex.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/core/codexLaunch.ts
CHANGED
|
File without changes
|
|
File without changes
|
package/src/core/codexPrompt.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/core/inputDebug.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|