@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.
Files changed (232) hide show
  1. package/README.md +0 -0
  2. package/package.json +3 -2
  3. package/scripts/audit-codexa-capabilities.mjs +0 -0
  4. package/scripts/gen-build-info.mjs +33 -0
  5. package/scripts/smoke-terminal-bench.mjs +0 -0
  6. package/src/app.tsx +103 -2
  7. package/src/appRenderStability.test.ts +0 -0
  8. package/src/commands/handler.test.ts +12 -0
  9. package/src/commands/handler.ts +5 -0
  10. package/src/config/buildInfo.ts +3 -0
  11. package/src/config/launchArgs.test.ts +31 -0
  12. package/src/config/launchArgs.ts +10 -0
  13. package/src/config/layeredConfig.test.ts +0 -0
  14. package/src/config/layeredConfig.ts +0 -0
  15. package/src/config/persistence.test.ts +6 -2
  16. package/src/config/persistence.ts +41 -0
  17. package/src/config/runtimeConfig.test.ts +0 -0
  18. package/src/config/runtimeConfig.ts +4 -0
  19. package/src/config/settings.test.ts +0 -0
  20. package/src/config/settings.ts +7 -6
  21. package/src/config/toml-serialize.ts +0 -0
  22. package/src/config/trustStore.test.ts +0 -0
  23. package/src/config/trustStore.ts +0 -0
  24. package/src/config/updateCheckCache.ts +45 -0
  25. package/src/core/attachments.test.ts +0 -0
  26. package/src/core/attachments.ts +0 -0
  27. package/src/core/auth/codexAuth.test.ts +0 -0
  28. package/src/core/auth/codexAuth.ts +0 -0
  29. package/src/core/cleanupFastFail.test.ts +0 -0
  30. package/src/core/cleanupFastFail.ts +0 -0
  31. package/src/core/clipboard.ts +0 -0
  32. package/src/core/codex.ts +0 -0
  33. package/src/core/codexExecArgs.test.ts +0 -0
  34. package/src/core/codexExecArgs.ts +0 -0
  35. package/src/core/codexLaunch.test.ts +0 -0
  36. package/src/core/codexLaunch.ts +0 -0
  37. package/src/core/codexPrompt.test.ts +0 -0
  38. package/src/core/codexPrompt.ts +0 -0
  39. package/src/core/executables/claudeExecutable.ts +0 -0
  40. package/src/core/executables/codexExecutable.test.ts +0 -0
  41. package/src/core/executables/codexExecutable.ts +0 -0
  42. package/src/core/executables/executableResolver.test.ts +0 -0
  43. package/src/core/executables/executableResolver.ts +0 -0
  44. package/src/core/executables/geminiExecutable.test.ts +0 -0
  45. package/src/core/executables/geminiExecutable.ts +0 -0
  46. package/src/core/executables/pathSanityScan.test.ts +0 -0
  47. package/src/core/githubDiagnostics.test.ts +0 -0
  48. package/src/core/githubDiagnostics.ts +0 -0
  49. package/src/core/hollowResponseFormat.test.ts +0 -0
  50. package/src/core/hollowResponseFormat.ts +0 -0
  51. package/src/core/inputDebug.ts +0 -0
  52. package/src/core/launchContext.test.ts +0 -0
  53. package/src/core/launchContext.ts +0 -0
  54. package/src/core/models/codexCapabilities.test.ts +0 -0
  55. package/src/core/models/codexCapabilities.ts +0 -0
  56. package/src/core/models/codexModelCapabilities.test.ts +0 -0
  57. package/src/core/models/codexModelCapabilities.ts +0 -0
  58. package/src/core/models/modelSpecs.test.ts +0 -0
  59. package/src/core/models/modelSpecs.ts +0 -0
  60. package/src/core/perf/profiler.ts +0 -0
  61. package/src/core/perf/renderDebug.test.ts +0 -0
  62. package/src/core/perf/renderDebug.ts +0 -0
  63. package/src/core/planStorage.test.ts +0 -0
  64. package/src/core/planStorage.ts +0 -0
  65. package/src/core/process/CommandRunner.test.ts +0 -0
  66. package/src/core/process/CommandRunner.ts +0 -0
  67. package/src/core/process/processValidation.ts +0 -0
  68. package/src/core/projectInstructions.test.ts +0 -0
  69. package/src/core/projectInstructions.ts +0 -0
  70. package/src/core/providerLauncher/launcher.test.ts +0 -0
  71. package/src/core/providerLauncher/launcher.ts +0 -0
  72. package/src/core/providerLauncher/registry.test.ts +0 -0
  73. package/src/core/providerLauncher/registry.ts +0 -0
  74. package/src/core/providerLauncher/types.ts +0 -0
  75. package/src/core/providerLauncher/workspaceConfig.test.ts +0 -0
  76. package/src/core/providerLauncher/workspaceConfig.ts +0 -0
  77. package/src/core/providerRuntime/anthropic.test.ts +0 -0
  78. package/src/core/providerRuntime/anthropic.ts +0 -0
  79. package/src/core/providerRuntime/capabilityProfile.test.ts +0 -0
  80. package/src/core/providerRuntime/capabilityProfile.ts +0 -0
  81. package/src/core/providerRuntime/claudeCodeDiscovery.ts +0 -0
  82. package/src/core/providerRuntime/contextMetadata.test.ts +0 -0
  83. package/src/core/providerRuntime/contextMetadata.ts +0 -0
  84. package/src/core/providerRuntime/gemini.test.ts +0 -0
  85. package/src/core/providerRuntime/gemini.ts +0 -0
  86. package/src/core/providerRuntime/lmstudio.test.ts +0 -0
  87. package/src/core/providerRuntime/lmstudio.ts +0 -0
  88. package/src/core/providerRuntime/local.test.ts +0 -0
  89. package/src/core/providerRuntime/local.ts +0 -0
  90. package/src/core/providerRuntime/models.ts +0 -0
  91. package/src/core/providerRuntime/reasoning.ts +0 -0
  92. package/src/core/providerRuntime/registry.test.ts +0 -0
  93. package/src/core/providerRuntime/registry.ts +0 -0
  94. package/src/core/providerRuntime/types.ts +0 -0
  95. package/src/core/providers/codexJsonStream.test.ts +0 -0
  96. package/src/core/providers/codexJsonStream.ts +0 -0
  97. package/src/core/providers/codexSubprocess.test.ts +0 -0
  98. package/src/core/providers/codexSubprocess.ts +0 -0
  99. package/src/core/providers/codexTranscript.test.ts +0 -0
  100. package/src/core/providers/codexTranscript.ts +0 -0
  101. package/src/core/providers/openaiNative.ts +0 -0
  102. package/src/core/providers/registry.ts +0 -0
  103. package/src/core/providers/types.ts +0 -0
  104. package/src/core/terminal/startupClear.test.ts +55 -0
  105. package/src/core/terminal/startupClear.ts +20 -0
  106. package/src/core/terminal/terminalCapabilities.test.ts +0 -0
  107. package/src/core/terminal/terminalCapabilities.ts +0 -0
  108. package/src/core/terminal/terminalControl.test.ts +0 -0
  109. package/src/core/terminal/terminalControl.ts +0 -0
  110. package/src/core/terminal/terminalSanitize.test.ts +0 -0
  111. package/src/core/terminal/terminalSanitize.ts +0 -0
  112. package/src/core/terminal/terminalSelection.test.ts +0 -0
  113. package/src/core/terminal/terminalSelection.ts +0 -0
  114. package/src/core/terminal/terminalTitle.test.ts +0 -0
  115. package/src/core/terminal/terminalTitle.ts +0 -0
  116. package/src/core/updateCheck.test.ts +194 -0
  117. package/src/core/updateCheck.ts +172 -0
  118. package/src/core/workspaceActivity.test.ts +0 -0
  119. package/src/core/workspaceActivity.ts +0 -0
  120. package/src/core/workspaceGuard.test.ts +0 -0
  121. package/src/core/workspaceGuard.ts +0 -0
  122. package/src/core/workspaceRoot.test.ts +0 -0
  123. package/src/core/workspaceRoot.ts +0 -0
  124. package/src/exec.test.ts +0 -0
  125. package/src/exec.ts +0 -0
  126. package/src/headless/execArgs.test.ts +0 -0
  127. package/src/headless/execArgs.ts +1 -0
  128. package/src/headless/execRunner.test.ts +8 -6
  129. package/src/headless/execRunner.ts +0 -0
  130. package/src/index.test.tsx +5 -3
  131. package/src/index.tsx +13 -9
  132. package/src/session/appSession.test.ts +0 -0
  133. package/src/session/appSession.ts +0 -0
  134. package/src/session/chatLifecycle.test.ts +0 -0
  135. package/src/session/chatLifecycle.ts +0 -0
  136. package/src/session/liveRenderScheduler.test.ts +0 -0
  137. package/src/session/liveRenderScheduler.ts +0 -0
  138. package/src/session/planFlow.test.ts +0 -0
  139. package/src/session/planFlow.ts +0 -0
  140. package/src/session/planTranscript.test.ts +0 -0
  141. package/src/session/planTranscript.ts +0 -0
  142. package/src/session/promptRunSchedule.test.ts +0 -0
  143. package/src/session/promptRunSchedule.ts +0 -0
  144. package/src/session/types.ts +0 -0
  145. package/src/test/runtimeTestUtils.ts +0 -0
  146. package/src/types/react-dom.d.ts +0 -0
  147. package/src/ui/ActionRequiredBlock.tsx +0 -0
  148. package/src/ui/ActivityBars.tsx +0 -0
  149. package/src/ui/ActivityIndicator.test.tsx +0 -0
  150. package/src/ui/ActivityIndicator.tsx +0 -0
  151. package/src/ui/AgentBlock.test.ts +0 -0
  152. package/src/ui/AgentBlock.tsx +0 -0
  153. package/src/ui/AnimatedStatusText.test.ts +0 -0
  154. package/src/ui/AnimatedStatusText.tsx +0 -0
  155. package/src/ui/AppShell.test.tsx +60 -23
  156. package/src/ui/AppShell.tsx +28 -5
  157. package/src/ui/AttachmentImportPanel.test.tsx +0 -0
  158. package/src/ui/AttachmentImportPanel.tsx +0 -0
  159. package/src/ui/AuthPanel.tsx +0 -0
  160. package/src/ui/BackendPicker.tsx +0 -0
  161. package/src/ui/BottomComposer.test.ts +0 -0
  162. package/src/ui/BottomComposer.tsx +0 -0
  163. package/src/ui/CodexLogo.tsx +0 -0
  164. package/src/ui/DashCard.tsx +0 -0
  165. package/src/ui/Markdown.test.ts +0 -0
  166. package/src/ui/Markdown.tsx +0 -0
  167. package/src/ui/ModePicker.tsx +0 -0
  168. package/src/ui/ModelPicker.tsx +0 -0
  169. package/src/ui/ModelPickerProviderScope.test.tsx +0 -0
  170. package/src/ui/ModelPickerScreen.test.tsx +0 -0
  171. package/src/ui/ModelPickerScreen.tsx +0 -0
  172. package/src/ui/ModelPickerState.test.tsx +0 -0
  173. package/src/ui/ModelReasoningPicker.test.tsx +0 -0
  174. package/src/ui/ModelReasoningPicker.tsx +0 -0
  175. package/src/ui/Panel.tsx +0 -0
  176. package/src/ui/PermissionsPanel.tsx +0 -0
  177. package/src/ui/PlanActionPicker.tsx +0 -0
  178. package/src/ui/PlanReviewPanel.test.tsx +0 -0
  179. package/src/ui/PlanReviewPanel.tsx +0 -0
  180. package/src/ui/PromptCardBorder.test.tsx +0 -0
  181. package/src/ui/ProviderPicker.test.tsx +0 -0
  182. package/src/ui/ProviderPicker.tsx +0 -0
  183. package/src/ui/ProviderShortcut.test.tsx +0 -0
  184. package/src/ui/ReasoningPicker.tsx +0 -0
  185. package/src/ui/RunFooter.tsx +0 -0
  186. package/src/ui/SelectionPanel.tsx +0 -0
  187. package/src/ui/SettingsPanel.test.tsx +0 -0
  188. package/src/ui/SettingsPanel.tsx +0 -0
  189. package/src/ui/Spinner.tsx +0 -0
  190. package/src/ui/StaticIntroItem.tsx +0 -0
  191. package/src/ui/StaticTranscriptItem.tsx +0 -0
  192. package/src/ui/TextEntryPanel.tsx +0 -0
  193. package/src/ui/ThemePicker.tsx +0 -0
  194. package/src/ui/ThinkingBlock.tsx +0 -0
  195. package/src/ui/Timeline.test.ts +0 -0
  196. package/src/ui/Timeline.tsx +0 -0
  197. package/src/ui/TimelineNavigation.test.tsx +0 -0
  198. package/src/ui/TopHeader.test.tsx +53 -38
  199. package/src/ui/TopHeader.tsx +51 -15
  200. package/src/ui/TurnGroup.test.tsx +0 -0
  201. package/src/ui/TurnGroup.tsx +0 -0
  202. package/src/ui/busyStatusAnimation.test.ts +0 -0
  203. package/src/ui/busyStatusAnimation.ts +0 -0
  204. package/src/ui/commandNormalize.test.ts +0 -0
  205. package/src/ui/commandNormalize.ts +0 -0
  206. package/src/ui/diffRenderer.test.ts +0 -0
  207. package/src/ui/diffRenderer.ts +0 -0
  208. package/src/ui/focus.ts +0 -0
  209. package/src/ui/focusFlow.test.tsx +0 -0
  210. package/src/ui/inputBuffer.test.ts +0 -0
  211. package/src/ui/inputBuffer.ts +0 -0
  212. package/src/ui/layout.test.ts +3 -2
  213. package/src/ui/layout.ts +0 -0
  214. package/src/ui/modeDisplay.test.ts +0 -0
  215. package/src/ui/modeDisplay.ts +0 -0
  216. package/src/ui/outputPipeline.ts +0 -0
  217. package/src/ui/progressEntries.ts +0 -0
  218. package/src/ui/runActivityView.test.ts +0 -0
  219. package/src/ui/runActivityView.ts +0 -0
  220. package/src/ui/runLifecycleView.test.tsx +0 -0
  221. package/src/ui/slashCommands.ts +0 -0
  222. package/src/ui/statusRenderIsolation.test.tsx +0 -0
  223. package/src/ui/terminalAnswerFormat.test.ts +0 -0
  224. package/src/ui/terminalAnswerFormat.ts +0 -0
  225. package/src/ui/textLayout.test.ts +0 -0
  226. package/src/ui/textLayout.ts +0 -0
  227. package/src/ui/theme.tsx +0 -0
  228. package/src/ui/themeFlow.test.ts +0 -0
  229. package/src/ui/themeFlow.ts +0 -0
  230. package/src/ui/timelineMeasure.ts +0 -0
  231. package/src/ui/timelineMeasureCache.test.ts +0 -0
  232. 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.1",
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": "bun run typecheck",
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={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={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
+
@@ -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",
@@ -0,0 +1,3 @@
1
+ // Generated by scripts/gen-build-info.mjs — do not edit manually.
2
+ export const BUILD_COMMIT = "6ef3a8a85efef3a525472973403a628a84804c4d" as const;
3
+ export const BUILD_VERSION = "1.0.2" as const;
@@ -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
+ });
@@ -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: false,
43
- showModel: false,
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
@@ -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 = "1.0.1";
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: false,
381
- showModel: false,
381
+ showProvider: true,
382
+ showModel: true,
382
383
  showReasoning: false,
383
384
  showContext: false,
384
385
  showAuthStatus: false,
File without changes
File without changes
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/src/core/codex.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
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