@golba98/codexa 1.0.5 → 1.0.7
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 +119 -14
- package/bin/codexa.js +15 -4
- package/package.json +1 -1
- package/src/app.tsx +65 -81
- package/src/commands/handler.ts +13 -11
- package/src/config/appVersion.ts +65 -0
- package/src/config/buildInfo.ts +2 -2
- package/src/config/runtimeConfig.ts +1 -1
- package/src/config/settings.ts +5 -1
- package/src/config/updateCheckCache.ts +18 -8
- package/src/core/debug/modelStateDebug.ts +3 -2
- package/src/core/models/providerModelCache.ts +9 -25
- package/src/core/perf/renderDebug.ts +5 -4
- package/src/core/providerLauncher/launcher.ts +1 -1
- package/src/core/providerLauncher/workspaceConfig.ts +27 -13
- package/src/core/providerRuntime/antigravity.ts +12 -1
- package/src/core/providerRuntime/registry.ts +10 -6
- package/src/core/shared/attachments.ts +10 -10
- package/src/core/terminal/frameLock.ts +2 -1
- package/src/core/terminal/terminalTitle.ts +3 -2
- package/src/core/version/packageManager.ts +119 -0
- package/src/core/version/updateCheck.ts +15 -9
- package/src/core/workspace/appData.ts +45 -0
- package/src/core/workspace/planStorage.ts +7 -13
- package/src/test/runtimeTestUtils.ts +0 -90
- package/src/ui/chrome/TopHeader.tsx +4 -2
- package/src/ui/chrome/UpdateAvailableCard.tsx +3 -2
- package/src/ui/input/slashCommands.ts +1 -0
- package/src/ui/panels/AttachmentImportPanel.tsx +4 -3
- package/src/ui/panels/UpdatePromptPanel.tsx +53 -43
package/README.md
CHANGED
|
@@ -26,8 +26,10 @@ codexa --version
|
|
|
26
26
|
| Local dev / running from source | [Bun](https://bun.sh) |
|
|
27
27
|
| Codex/OpenAI route | Codex CLI installed and authenticated |
|
|
28
28
|
| Claude/Anthropic route | Claude Code CLI installed and authenticated |
|
|
29
|
-
|
|
|
29
|
+
| Mistral Vibe route | Mistral Vibe CLI (`vibe`) installed and authenticated |
|
|
30
|
+
| Antigravity route | Antigravity CLI (`agy`) installed and authenticated |
|
|
30
31
|
| Local route | LM Studio (or any OpenAI-compatible server) |
|
|
32
|
+
| Gemini/Google route | *(Legacy)* Gemini CLI installed and authenticated (falls back to OpenAI route) |
|
|
31
33
|
|
|
32
34
|
## Installing Codexa
|
|
33
35
|
|
|
@@ -73,6 +75,8 @@ Codexa and provider CLIs are independent tools with separate version numbers. Up
|
|
|
73
75
|
|------|---------|---------------|--------|
|
|
74
76
|
| Codex CLI | OpenAI coding agent | `codex --version` | `codex update` or reinstall |
|
|
75
77
|
| Claude Code CLI | Anthropic coding agent | `claude --version` | `npm install -g @anthropic-ai/claude-code@latest` |
|
|
78
|
+
| Mistral Vibe CLI | Mistral coding agent | `vibe --version` | `vibe --setup` or reinstall |
|
|
79
|
+
| Antigravity CLI | Antigravity coding agent | `agy --version` | Reinstall via package manager |
|
|
76
80
|
| Codexa | TUI wrapper / workspace experience | `codexa --version` | `npm install -g @golba98/codexa@latest` |
|
|
77
81
|
|
|
78
82
|
## Provider CLI Setup
|
|
@@ -146,9 +150,12 @@ claude
|
|
|
146
150
|
npm install -g @anthropic-ai/claude-code@latest
|
|
147
151
|
```
|
|
148
152
|
|
|
149
|
-
### Installing Gemini CLI
|
|
153
|
+
### Installing Gemini CLI (Legacy / Deprecated)
|
|
150
154
|
|
|
151
|
-
|
|
155
|
+
> [!NOTE]
|
|
156
|
+
> Google/Gemini is no longer supported as an active provider route inside Codexa and falls back automatically to the OpenAI/Codex route.
|
|
157
|
+
|
|
158
|
+
If you are using legacy features that route to the `gemini` executable, install the Gemini CLI separately and authenticate per Google's instructions.
|
|
152
159
|
|
|
153
160
|
If the Gemini executable is not on PATH or has a non-standard name, specify it explicitly:
|
|
154
161
|
|
|
@@ -160,6 +167,30 @@ export GEMINI_EXECUTABLE="/path/to/gemini"
|
|
|
160
167
|
gemini_command_path = "/path/to/gemini"
|
|
161
168
|
```
|
|
162
169
|
|
|
170
|
+
### Installing Mistral Vibe CLI
|
|
171
|
+
|
|
172
|
+
Codexa supports routing through the Mistral Vibe CLI (`vibe`).
|
|
173
|
+
|
|
174
|
+
1. **Install Vibe CLI** — Install the Mistral Vibe CLI tool.
|
|
175
|
+
2. **Authenticate Vibe** — Run `vibe` or `vibe --setup` in a terminal and sign in when prompted.
|
|
176
|
+
3. **Environment & Config (Optional)**:
|
|
177
|
+
- Codexa automatically detects the active model from your environment (`VIBE_ACTIVE_MODEL`) or configuration files (`.vibe/config.toml` in your project/workspace, or `~/.vibe/config.toml`).
|
|
178
|
+
- You can customize the active session location using the `VIBE_HOME` environment variable.
|
|
179
|
+
|
|
180
|
+
### Installing Antigravity CLI
|
|
181
|
+
|
|
182
|
+
Codexa supports routing through the Antigravity CLI (`agy`).
|
|
183
|
+
|
|
184
|
+
1. **Install Antigravity CLI** — Install the Antigravity CLI tool (`agy`).
|
|
185
|
+
2. **Authenticate Antigravity** — Authenticate the CLI according to your provider setup.
|
|
186
|
+
3. **Environment & Config (Optional)**:
|
|
187
|
+
- Specifying the `agy` command path:
|
|
188
|
+
```toml
|
|
189
|
+
# in .codex/config.toml
|
|
190
|
+
antigravity_command_path = "/path/to/agy"
|
|
191
|
+
```
|
|
192
|
+
- Alternatively, set the `AGY_EXECUTABLE` environment variable.
|
|
193
|
+
|
|
163
194
|
### Using Local Models (LM Studio)
|
|
164
195
|
|
|
165
196
|
Codexa can route the Local provider through any OpenAI-compatible server.
|
|
@@ -178,7 +209,15 @@ export CODEXA_LOCAL_API_KEY="lm-studio"
|
|
|
178
209
|
export CODEXA_LOCAL_MODEL="google/gemma-4-26b-a4b"
|
|
179
210
|
```
|
|
180
211
|
|
|
181
|
-
|
|
212
|
+
Provider configuration is stored in Codexa user data and remains isolated per workspace:
|
|
213
|
+
|
|
214
|
+
- Windows: `%LOCALAPPDATA%\\Codexa\\workspaces\\<workspace-hash>\\providers.json`
|
|
215
|
+
- macOS: `~/Library/Application Support/Codexa/workspaces/<workspace-hash>/providers.json`
|
|
216
|
+
- Linux: `$XDG_DATA_HOME/codexa/workspaces/<workspace-hash>/providers.json` (or `~/.local/share/codexa/...`)
|
|
217
|
+
|
|
218
|
+
Existing `.codexa/providers.json` files are read as a legacy fallback and are never deleted. New saves go to user data, so opening or configuring Codexa no longer adds `.codexa` files to projects.
|
|
219
|
+
|
|
220
|
+
The provider config format is:
|
|
182
221
|
|
|
183
222
|
```json
|
|
184
223
|
{
|
|
@@ -305,6 +344,7 @@ Type `/` in the composer to access in-app commands. Key commands:
|
|
|
305
344
|
| `/verbose` | Toggle verbose output |
|
|
306
345
|
| `/copy` | Copy last response to clipboard |
|
|
307
346
|
| `/diagnose github` | Run GitHub connectivity diagnostics |
|
|
347
|
+
| `/diagnose providers` | Collect diagnostics for all configured providers |
|
|
308
348
|
| `!<command>` | Run a shell command inline |
|
|
309
349
|
|
|
310
350
|
## Layered Config
|
|
@@ -425,6 +465,12 @@ UI-only preferences are stored in `~/.codexa-settings.json` (separate from runti
|
|
|
425
465
|
|
|
426
466
|
> **Gotcha:** When testing local changes, confirm you are running the local build and not the global one. Use `Get-Command codexa` (Windows) or `which -a codexa` (Linux/macOS) to check.
|
|
427
467
|
|
|
468
|
+
## Maintainer Documentation
|
|
469
|
+
|
|
470
|
+
- [Architecture](docs/ARCHITECTURE.md) explains Codexa's entry points, subsystem boundaries, prompt and rendering flows, provider layers, state model, persistence, and maintenance invariants with diagrams.
|
|
471
|
+
- [Source Guide](docs/SOURCE_GUIDE.md) catalogs the purpose of every file under `src/` and records the maintenance rules for each source area.
|
|
472
|
+
- [Developer Scripts](scripts/README.md) lists every package command and explains launcher, shim, audit, build-metadata, smoke, and test behavior.
|
|
473
|
+
|
|
428
474
|
## Local Development
|
|
429
475
|
|
|
430
476
|
**Requirement:** [Bun](https://bun.sh) installed.
|
|
@@ -486,6 +532,63 @@ This replaces the global `codexa` with a symlink to the repo. Undo with `npm unl
|
|
|
486
532
|
npm install -g .
|
|
487
533
|
```
|
|
488
534
|
|
|
535
|
+
## Maintainer release and NPM publishing
|
|
536
|
+
|
|
537
|
+
The package name is `@golba98/codexa`. Publish from the repository root, and
|
|
538
|
+
increment `version` in `package.json` before each release. NPM versions are
|
|
539
|
+
immutable, so never reuse a version that has already been published.
|
|
540
|
+
|
|
541
|
+
Check the current local and registry versions:
|
|
542
|
+
|
|
543
|
+
```bash
|
|
544
|
+
npm pkg get name version
|
|
545
|
+
npm whoami
|
|
546
|
+
npm view @golba98/codexa version
|
|
547
|
+
npm view @golba98/codexa dist-tags --json
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
Run the release checks:
|
|
551
|
+
|
|
552
|
+
```bash
|
|
553
|
+
bun install
|
|
554
|
+
bun run typecheck
|
|
555
|
+
bun test
|
|
556
|
+
git diff --check
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
Inspect exactly what will be included in the package:
|
|
560
|
+
|
|
561
|
+
```bash
|
|
562
|
+
npm pack --dry-run
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
Publish the next version publicly:
|
|
566
|
+
|
|
567
|
+
```bash
|
|
568
|
+
npm publish --access public
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
`npm publish` automatically runs `prepublishOnly`, which regenerates build
|
|
572
|
+
metadata, typechecks, and runs the full test suite. Verify the registry and
|
|
573
|
+
the installed command after publishing:
|
|
574
|
+
|
|
575
|
+
```bash
|
|
576
|
+
npm view @golba98/codexa version
|
|
577
|
+
npm view @golba98/codexa dist-tags --json
|
|
578
|
+
npm install -g @golba98/codexa@latest
|
|
579
|
+
codexa --version
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
If the release is tracked in Git, commit the version and generated build-info
|
|
583
|
+
change, then tag and push it:
|
|
584
|
+
|
|
585
|
+
```bash
|
|
586
|
+
git add package.json src/config/buildInfo.ts README.md scripts/README.md
|
|
587
|
+
git commit -m "release: publish Codexa vX.Y.Z"
|
|
588
|
+
git tag vX.Y.Z
|
|
589
|
+
git push origin main --follow-tags
|
|
590
|
+
```
|
|
591
|
+
|
|
489
592
|
## Testing
|
|
490
593
|
|
|
491
594
|
```bash
|
|
@@ -577,7 +680,7 @@ npm install -g @golba98/codexa@latest
|
|
|
577
680
|
|
|
578
681
|
### Update notice does not appear
|
|
579
682
|
|
|
580
|
-
Codexa checks for updates on
|
|
683
|
+
Codexa checks npm for updates on every interactive startup. The local cache at `~/.codexa-update-check.json` is only used as a best-effort fallback if npm is temporarily unavailable. The update notice appears when the npm registry `latest` version is newer than the running version.
|
|
581
684
|
|
|
582
685
|
Published npm versions are immutable. Versions before the fixed update checker may not show update notices even when a newer package exists. If in doubt, update directly:
|
|
583
686
|
|
|
@@ -588,9 +691,8 @@ npm install -g @golba98/codexa@latest
|
|
|
588
691
|
If you expect a notice but don't see one:
|
|
589
692
|
|
|
590
693
|
1. Check the current registry state: `npm view @golba98/codexa dist-tags --json`
|
|
591
|
-
2.
|
|
592
|
-
3.
|
|
593
|
-
4. Update checks are disabled for local dev builds (`codexa-dev` / `cxd`)
|
|
694
|
+
2. Force an explicit check in-app: `/update check`
|
|
695
|
+
3. Update checks are disabled for local dev builds (`codexa-dev` / `cxd`)
|
|
594
696
|
|
|
595
697
|
| Symptom | Fix |
|
|
596
698
|
|---------|-----|
|
|
@@ -606,11 +708,14 @@ If you expect a notice but don't see one:
|
|
|
606
708
|
|
|
607
709
|
See [CHANGELOG.md](CHANGELOG.md) for the full release history.
|
|
608
710
|
|
|
609
|
-
**Current release: v1.0.
|
|
711
|
+
**Current release: v1.0.7**
|
|
610
712
|
|
|
611
|
-
v1.0.
|
|
612
|
-
- Older installed versions can detect when npm `latest` is newer
|
|
613
|
-
- `/update check` bypasses stale cache and reports a useful status
|
|
614
|
-
- Failed registry lookups do not get cached as “up to date”
|
|
713
|
+
v1.0.7 keeps workspaces clean: provider choices, imported attachments, and default diagnostic logs now live in Codexa user data instead of a project-local `.codexa` directory.
|
|
615
714
|
|
|
616
|
-
|
|
715
|
+
Other recent releases introduce major additions, including:
|
|
716
|
+
- **Mistral Vibe CLI Routing**: Connect through the `vibe` CLI tool (`vibe --setup`).
|
|
717
|
+
- **Antigravity CLI Routing**: Connect through the `agy` CLI tool (`agy`).
|
|
718
|
+
- **Dynamic Model Discovery & Caching**: Models are discovered dynamically and cached locally at `~/.codexa-model-cache.json` for instant launch, with instant provider sync for the model picker.
|
|
719
|
+
- **Native Terminal Scrollback**: History follows native scrollback with composer anchored at the bottom.
|
|
720
|
+
- **Kitty Keyboard Protocol Fix**: Corrected probe output leak.
|
|
721
|
+
- **Google/Gemini Route Deprecation**: In-Codexa routing for Google/Gemini now falls back to OpenAI/Codex.
|
package/bin/codexa.js
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawn } from "child_process";
|
|
4
|
-
import { appendFileSync, mkdirSync, readFileSync } from "fs";
|
|
4
|
+
import { appendFileSync, mkdirSync, readFileSync } from "fs";
|
|
5
|
+
import { homedir } from "os";
|
|
5
6
|
import { dirname, join } from "path";
|
|
6
7
|
import { fileURLToPath } from "url";
|
|
7
8
|
|
|
8
|
-
process.title = "CODEXA";
|
|
9
|
+
process.title = "CODEXA";
|
|
10
|
+
|
|
11
|
+
function resolveLauncherDebugDir() {
|
|
12
|
+
const dataDir = process.env.CODEXA_DATA_DIR?.trim()
|
|
13
|
+
|| (process.platform === "win32"
|
|
14
|
+
? join(process.env.LOCALAPPDATA?.trim() || process.env.APPDATA?.trim() || join(homedir(), "AppData", "Local"), "Codexa")
|
|
15
|
+
: process.platform === "darwin"
|
|
16
|
+
? join(homedir(), "Library", "Application Support", "Codexa")
|
|
17
|
+
: join(process.env.XDG_DATA_HOME?.trim() || join(homedir(), ".local", "share"), "codexa"));
|
|
18
|
+
return join(dataDir, "debug");
|
|
19
|
+
}
|
|
9
20
|
|
|
10
21
|
const currentFile = fileURLToPath(import.meta.url);
|
|
11
22
|
const packageRoot = dirname(dirname(currentFile));
|
|
@@ -21,7 +32,7 @@ function writeRenderDebugRecord(kind, fields) {
|
|
|
21
32
|
}
|
|
22
33
|
|
|
23
34
|
try {
|
|
24
|
-
const debugDir =
|
|
35
|
+
const debugDir = resolveLauncherDebugDir();
|
|
25
36
|
mkdirSync(debugDir, { recursive: true });
|
|
26
37
|
appendFileSync(
|
|
27
38
|
process.env.CODEXA_RENDER_DEBUG_FILE?.trim() || join(debugDir, "render-status.log"),
|
|
@@ -56,7 +67,7 @@ function writeTerminalTitleDebugRecord(fields) {
|
|
|
56
67
|
}
|
|
57
68
|
|
|
58
69
|
try {
|
|
59
|
-
const debugDir =
|
|
70
|
+
const debugDir = resolveLauncherDebugDir();
|
|
60
71
|
mkdirSync(debugDir, { recursive: true });
|
|
61
72
|
appendFileSync(
|
|
62
73
|
process.env.CODEXA_TERMINAL_TITLE_DEBUG_FILE?.trim()
|
package/package.json
CHANGED
package/src/app.tsx
CHANGED
|
@@ -154,7 +154,8 @@ import {
|
|
|
154
154
|
type ModelContextMetadata,
|
|
155
155
|
} from "./core/providerRuntime/contextMetadata.js";
|
|
156
156
|
import { captureWorkspaceSnapshot, createWorkspaceActivityTracker, diffWorkspaceSnapshots } from "./core/workspace/workspaceActivity.js";
|
|
157
|
-
import { resolveWorkspaceRoot } from "./core/workspace/workspaceRoot.js";
|
|
157
|
+
import { resolveWorkspaceRoot } from "./core/workspace/workspaceRoot.js";
|
|
158
|
+
import { resolveCodexaAttachmentDir } from "./core/workspace/appData.js";
|
|
158
159
|
import {
|
|
159
160
|
importExternalFile,
|
|
160
161
|
isImageFile,
|
|
@@ -270,9 +271,10 @@ import { AppShell } from "./ui/chrome/AppShell.js";
|
|
|
270
271
|
import { TranscriptShell } from "./ui/timeline/TranscriptShell.js";
|
|
271
272
|
import type { RuntimeAvailability } from "./ui/chrome/RuntimeStatusBar.js";
|
|
272
273
|
import { checkForUpdates, formatLocalDevUpdateStatus, formatUpdateInstructions, shouldRunStartupUpdateCheck, type UpdateCheckResult } from "./core/version/updateCheck.js";
|
|
274
|
+
import { detectGlobalPackageManager, getUpdateCommand } from "./core/version/packageManager.js";
|
|
273
275
|
import { isLocalDevChannel } from "./core/version/channel.js";
|
|
274
276
|
import {
|
|
275
|
-
|
|
277
|
+
isCacheForRunningVersion,
|
|
276
278
|
loadUpdateCheckCache,
|
|
277
279
|
saveUpdateCheckCache,
|
|
278
280
|
} from "./config/updateCheckCache.js";
|
|
@@ -394,9 +396,6 @@ export function App({ launchArgs }: AppProps) {
|
|
|
394
396
|
? projectInstructionsLoad.instructions
|
|
395
397
|
: null;
|
|
396
398
|
const initialSettings = useRef(loadSettings());
|
|
397
|
-
const skippedUpdateVersionRef = useRef<string | null>(
|
|
398
|
-
initialSettings.current.updateCheck.skippedUpdateVersion ?? null,
|
|
399
|
-
);
|
|
400
399
|
const initialProviderWorkspaceConfig = useRef<ProviderWorkspaceConfig>(loadProviderWorkspaceConfig(workspaceRoot));
|
|
401
400
|
const initialLayeredConfig = useRef<LayeredConfigResult | null>(null);
|
|
402
401
|
if (initialLayeredConfig.current === null) {
|
|
@@ -550,6 +549,9 @@ export function App({ launchArgs }: AppProps) {
|
|
|
550
549
|
const [planFlow, setPlanFlow] = useState<PlanFlowState>(createInitialPlanFlowState);
|
|
551
550
|
const [initialRevisionText, setInitialRevisionText] = useState("");
|
|
552
551
|
const [updateCheckResult, setUpdateCheckResult] = useState<UpdateCheckResult | null>(null);
|
|
552
|
+
const [startupUpdateDismissed, setStartupUpdateDismissed] = useState(false);
|
|
553
|
+
// Launcher path is fixed for the process lifetime, so detect once.
|
|
554
|
+
const globalPackageManager = useMemo(() => detectGlobalPackageManager(), []);
|
|
553
555
|
// Transcript mode leaves mouse reporting off so wheel/trackpad input scrolls
|
|
554
556
|
// the terminal emulator's native scrollback instead of an in-app viewport.
|
|
555
557
|
const mouseCapture = (mouseOverride ?? (terminalMouseMode === "wheel")) && !isMouseIdle;
|
|
@@ -1625,54 +1627,53 @@ export function App({ launchArgs }: AppProps) {
|
|
|
1625
1627
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1626
1628
|
}, [workspaceRoot]);
|
|
1627
1629
|
|
|
1628
|
-
// Non-blocking background update check —
|
|
1630
|
+
// Non-blocking background update check — fetches npm on every interactive startup.
|
|
1629
1631
|
useEffect(() => {
|
|
1630
1632
|
const ucSettings = initialSettings.current.updateCheck ?? DEFAULT_UPDATE_CHECK_SETTINGS;
|
|
1631
1633
|
if (!shouldRunStartupUpdateCheck(process.env, ucSettings.enabled)) return;
|
|
1632
1634
|
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
}
|
|
1664
|
-
}
|
|
1665
|
-
}
|
|
1666
|
-
} catch {
|
|
1667
|
-
// Never crash the TUI on a failed update check.
|
|
1668
|
-
}
|
|
1669
|
-
})();
|
|
1670
|
-
}, 2000);
|
|
1671
|
-
|
|
1672
|
-
return () => clearTimeout(timer);
|
|
1635
|
+
void (async () => {
|
|
1636
|
+
const cache = loadUpdateCheckCache();
|
|
1637
|
+
try {
|
|
1638
|
+
const result = await checkForUpdates({ enabled: true });
|
|
1639
|
+
if (result.status === "error") {
|
|
1640
|
+
// A previously confirmed update is still useful when npm is briefly
|
|
1641
|
+
// unreachable, but it must never suppress the next fresh startup check.
|
|
1642
|
+
if (cache?.updateAvailable && cache.latestVersion && isCacheForRunningVersion(cache, APP_VERSION)) {
|
|
1643
|
+
setUpdateCheckResult({
|
|
1644
|
+
status: "update-available",
|
|
1645
|
+
currentVersion: cache.currentVersion,
|
|
1646
|
+
latestVersion: cache.latestVersion,
|
|
1647
|
+
checkedAt: cache.lastChecked,
|
|
1648
|
+
source: "cache",
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
setUpdateCheckResult(result);
|
|
1655
|
+
saveUpdateCheckCache({
|
|
1656
|
+
lastChecked: result.checkedAt,
|
|
1657
|
+
currentVersion: result.currentVersion,
|
|
1658
|
+
latestVersion: result.latestVersion,
|
|
1659
|
+
updateAvailable: result.status === "update-available",
|
|
1660
|
+
});
|
|
1661
|
+
} catch {
|
|
1662
|
+
// Never crash the TUI on a failed update check.
|
|
1663
|
+
}
|
|
1664
|
+
})();
|
|
1673
1665
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1674
1666
|
}, []);
|
|
1675
1667
|
|
|
1668
|
+
// A startup update prompt must not interrupt an active run or another panel.
|
|
1669
|
+
// Keep the result until the user returns to the idle main screen instead.
|
|
1670
|
+
useEffect(() => {
|
|
1671
|
+
if (startupUpdateDismissed || busy || screen !== "main") return;
|
|
1672
|
+
if (updateCheckResult?.status === "update-available" && updateCheckResult.latestVersion) {
|
|
1673
|
+
setScreen("update-prompt");
|
|
1674
|
+
}
|
|
1675
|
+
}, [busy, screen, startupUpdateDismissed, updateCheckResult]);
|
|
1676
|
+
|
|
1676
1677
|
// Track clear epoch to suppress stale command result events
|
|
1677
1678
|
useEffect(() => {
|
|
1678
1679
|
clearEpochRef.current = sessionState.clearEpoch;
|
|
@@ -2160,32 +2161,10 @@ export function App({ launchArgs }: AppProps) {
|
|
|
2160
2161
|
}, [applyWorkspaceDisplayMode, showBusyLoader, terminalMouseMode, terminalTitleMode, workspaceDisplayMode]);
|
|
2161
2162
|
|
|
2162
2163
|
const handleSkipUpdateForSession = useCallback(() => {
|
|
2164
|
+
setStartupUpdateDismissed(true);
|
|
2163
2165
|
setScreen("main");
|
|
2164
2166
|
}, []);
|
|
2165
2167
|
|
|
2166
|
-
const handleSkipUpdateVersion = useCallback((version: string) => {
|
|
2167
|
-
initialSettings.current.updateCheck = {
|
|
2168
|
-
...initialSettings.current.updateCheck,
|
|
2169
|
-
skippedUpdateVersion: version,
|
|
2170
|
-
};
|
|
2171
|
-
saveSettings({
|
|
2172
|
-
ui: {
|
|
2173
|
-
layoutStyle: initialSettings.current.ui.layoutStyle,
|
|
2174
|
-
theme: themeSelection.committedTheme,
|
|
2175
|
-
workspaceDisplayMode,
|
|
2176
|
-
terminalTitleMode,
|
|
2177
|
-
showBusyLoader,
|
|
2178
|
-
terminalMouseMode,
|
|
2179
|
-
customTheme,
|
|
2180
|
-
},
|
|
2181
|
-
auth: { preference: authPreference },
|
|
2182
|
-
header: headerConfig,
|
|
2183
|
-
updateCheck: initialSettings.current.updateCheck,
|
|
2184
|
-
});
|
|
2185
|
-
skippedUpdateVersionRef.current = version;
|
|
2186
|
-
setScreen("main");
|
|
2187
|
-
}, [authPreference, customTheme, headerConfig, showBusyLoader, terminalMouseMode, terminalTitleMode, themeSelection.committedTheme, workspaceDisplayMode]);
|
|
2188
|
-
|
|
2189
2168
|
const setApprovalPolicyWithNotice = useCallback((nextValue: RuntimeApprovalPolicy) => {
|
|
2190
2169
|
const gate = guardConfigMutation("mode", busy);
|
|
2191
2170
|
if (!gate.allowed) {
|
|
@@ -3852,13 +3831,12 @@ export function App({ launchArgs }: AppProps) {
|
|
|
3852
3831
|
|
|
3853
3832
|
const handleImportConfirm = useCallback(async () => {
|
|
3854
3833
|
if (!pendingImport) return;
|
|
3855
|
-
const replacements: Array<{ rawPath: string;
|
|
3834
|
+
const replacements: Array<{ rawPath: string; replacementPath: string }> = [];
|
|
3856
3835
|
for (const file of pendingImport.files) {
|
|
3857
3836
|
try {
|
|
3858
3837
|
const destPath = await importExternalFile(file.srcPath, pendingImport.attachmentsDir);
|
|
3859
3838
|
if (destPath) {
|
|
3860
|
-
|
|
3861
|
-
replacements.push({ rawPath: file.rawPath, workspaceRelativePath: relPath });
|
|
3839
|
+
replacements.push({ rawPath: file.rawPath, replacementPath: destPath });
|
|
3862
3840
|
}
|
|
3863
3841
|
} catch (err: any) {
|
|
3864
3842
|
appendErrorEvent("Import failed", `Could not import ${path.basename(file.srcPath)}: ${err.message}`);
|
|
@@ -4435,7 +4413,10 @@ export function App({ launchArgs }: AppProps) {
|
|
|
4435
4413
|
if (freshResult?.status === "update-available" && freshResult.latestVersion) {
|
|
4436
4414
|
setScreen("update-prompt");
|
|
4437
4415
|
} else {
|
|
4438
|
-
appendSystemEvent(
|
|
4416
|
+
appendSystemEvent(
|
|
4417
|
+
"Update",
|
|
4418
|
+
formatUpdateInstructions(freshResult, getUpdateCommand(globalPackageManager).displayCommand),
|
|
4419
|
+
);
|
|
4439
4420
|
}
|
|
4440
4421
|
})();
|
|
4441
4422
|
return;
|
|
@@ -4489,9 +4470,7 @@ export function App({ launchArgs }: AppProps) {
|
|
|
4489
4470
|
|
|
4490
4471
|
if (outsideViolations.length > 0) {
|
|
4491
4472
|
if (runtimeConfig.policy.allowExternalFileImport) {
|
|
4492
|
-
const attachmentsDir =
|
|
4493
|
-
? runtimeConfig.policy.attachmentDir
|
|
4494
|
-
: path.join(workspaceRoot, runtimeConfig.policy.attachmentDir);
|
|
4473
|
+
const attachmentsDir = resolveCodexaAttachmentDir(workspaceRoot, runtimeConfig.policy.attachmentDir);
|
|
4495
4474
|
const importFiles: PendingImportFile[] = outsideViolations.map((v) => ({
|
|
4496
4475
|
srcPath: v.normalizedPath,
|
|
4497
4476
|
rawPath: v.rawPath,
|
|
@@ -4527,6 +4506,7 @@ export function App({ launchArgs }: AppProps) {
|
|
|
4527
4506
|
dispatchSession,
|
|
4528
4507
|
findUserPromptForTurn,
|
|
4529
4508
|
focusManager,
|
|
4509
|
+
globalPackageManager,
|
|
4530
4510
|
handleCopy,
|
|
4531
4511
|
handleClear,
|
|
4532
4512
|
handleQuit,
|
|
@@ -4751,8 +4731,12 @@ export function App({ launchArgs }: AppProps) {
|
|
|
4751
4731
|
clearCount={sessionState.clearCount}
|
|
4752
4732
|
headerConfig={effectiveHeaderConfig}
|
|
4753
4733
|
updateAvailable={
|
|
4754
|
-
updateCheckResult?.status === "update-available" && updateCheckResult.latestVersion
|
|
4755
|
-
? {
|
|
4734
|
+
screen !== "update-prompt" && updateCheckResult?.status === "update-available" && updateCheckResult.latestVersion
|
|
4735
|
+
? {
|
|
4736
|
+
latestVersion: updateCheckResult.latestVersion,
|
|
4737
|
+
currentVersion: updateCheckResult.currentVersion,
|
|
4738
|
+
updateCommand: getUpdateCommand(globalPackageManager).displayCommand,
|
|
4739
|
+
}
|
|
4756
4740
|
: null
|
|
4757
4741
|
}
|
|
4758
4742
|
panel={
|
|
@@ -5010,9 +4994,9 @@ export function App({ launchArgs }: AppProps) {
|
|
|
5010
4994
|
focusId={FOCUS_IDS.updatePrompt}
|
|
5011
4995
|
currentVersion={updateCheckResult.currentVersion}
|
|
5012
4996
|
latestVersion={updateCheckResult.latestVersion}
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
/>
|
|
4997
|
+
packageManager={globalPackageManager}
|
|
4998
|
+
onSkip={handleSkipUpdateForSession}
|
|
4999
|
+
/>
|
|
5016
5000
|
)}
|
|
5017
5001
|
</>
|
|
5018
5002
|
}
|
package/src/commands/handler.ts
CHANGED
|
@@ -391,12 +391,12 @@ function buildHelpMessage(context: CommandContext): string {
|
|
|
391
391
|
` Current reasoning: ${formatReasoningLabel(context.runtime.reasoningLevel)}`,
|
|
392
392
|
` Current plan mode: ${context.runtime.planMode ? "Enabled" : "Disabled"}`,
|
|
393
393
|
" /copy Copy last response to clipboard",
|
|
394
|
-
" /update [
|
|
394
|
+
" /update [status] Check for updates and install the latest Codexa (status: cached result only)",
|
|
395
395
|
" /help Show this help",
|
|
396
|
-
"",
|
|
397
|
-
"Local development:",
|
|
398
|
-
" npm run install:dev-bin Install the codexa-dev command",
|
|
399
|
-
" codexa-dev Run this repo without replacing codexa",
|
|
396
|
+
"",
|
|
397
|
+
"Local development:",
|
|
398
|
+
" npm run install:dev-bin Install the codexa-dev command",
|
|
399
|
+
" codexa-dev Run this repo without replacing codexa",
|
|
400
400
|
"",
|
|
401
401
|
"Shortcuts:",
|
|
402
402
|
" Ctrl+B Open backend picker",
|
|
@@ -515,11 +515,11 @@ export function handleCommand(text: string, context: CommandContext): CommandRes
|
|
|
515
515
|
message: `Unknown reasoning level for ${context.runtime.model}: ${arg}. Valid: ${valid}`,
|
|
516
516
|
};
|
|
517
517
|
}
|
|
518
|
-
return {
|
|
519
|
-
action: "reasoning",
|
|
520
|
-
value: normalized,
|
|
521
|
-
message: `Reasoning level switched to ${formatReasoningLabel(normalized)} (runtime metadata unavailable; unverified until runtime)`,
|
|
522
|
-
};
|
|
518
|
+
return {
|
|
519
|
+
action: "reasoning",
|
|
520
|
+
value: normalized,
|
|
521
|
+
message: `Reasoning level switched to ${formatReasoningLabel(normalized)} (runtime metadata unavailable; unverified until runtime)`,
|
|
522
|
+
};
|
|
523
523
|
}
|
|
524
524
|
|
|
525
525
|
case "plan": {
|
|
@@ -846,7 +846,9 @@ export function handleCommand(text: string, context: CommandContext): CommandRes
|
|
|
846
846
|
return { action: "help", message: buildHelpMessage(context) };
|
|
847
847
|
|
|
848
848
|
case "update":
|
|
849
|
-
|
|
849
|
+
// Bare /update forces a fresh registry check; /update status is the
|
|
850
|
+
// explicit no-network path that reuses the cached/in-memory result.
|
|
851
|
+
return { action: "update", value: normalizedArg || "check" };
|
|
850
852
|
|
|
851
853
|
default:
|
|
852
854
|
return {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { dirname, join } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import { APP_VERSION as BUILD_INFO_VERSION } from "./buildInfo.js";
|
|
5
|
+
|
|
6
|
+
// Leaf module: must not import settings.ts or anything under src/core/version
|
|
7
|
+
// (settings.ts re-exports APP_VERSION from here, so that would be a cycle).
|
|
8
|
+
|
|
9
|
+
const CODEXA_PACKAGE_NAME = "@golba98/codexa";
|
|
10
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
|
|
11
|
+
|
|
12
|
+
function readPackageVersion(packageJsonPath: string, requireName?: string): string | null {
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8")) as {
|
|
15
|
+
name?: unknown;
|
|
16
|
+
version?: unknown;
|
|
17
|
+
};
|
|
18
|
+
if (requireName !== undefined && parsed.name !== requireName) return null;
|
|
19
|
+
if (typeof parsed.version !== "string") return null;
|
|
20
|
+
const version = parsed.version.trim();
|
|
21
|
+
return SEMVER_RE.test(version) ? version : null;
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Resolves the version of the running Codexa install:
|
|
29
|
+
* 1. `CODEXA_PACKAGE_ROOT` (set by bin/codexa.js) → that package.json's version
|
|
30
|
+
* 2. Walk up from this module for a package.json named "@golba98/codexa" (local dev)
|
|
31
|
+
* 3. The committed buildInfo.ts APP_VERSION as the last resort
|
|
32
|
+
*
|
|
33
|
+
* `startDir` overrides the walk-up starting directory (test seam).
|
|
34
|
+
*/
|
|
35
|
+
export function resolveAppVersion(
|
|
36
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
37
|
+
startDir?: string,
|
|
38
|
+
): string {
|
|
39
|
+
const packageRoot = env.CODEXA_PACKAGE_ROOT?.trim();
|
|
40
|
+
if (packageRoot) {
|
|
41
|
+
const version = readPackageVersion(join(packageRoot, "package.json"));
|
|
42
|
+
if (version) return version;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let dir = startDir ?? dirname(fileURLToPath(import.meta.url));
|
|
46
|
+
for (;;) {
|
|
47
|
+
const version = readPackageVersion(join(dir, "package.json"), CODEXA_PACKAGE_NAME);
|
|
48
|
+
if (version) return version;
|
|
49
|
+
const parent = dirname(dir);
|
|
50
|
+
if (parent === dir) break;
|
|
51
|
+
dir = parent;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return BUILD_INFO_VERSION;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let cachedVersion: string | null = null;
|
|
58
|
+
|
|
59
|
+
/** Cached form of resolveAppVersion() — the installed version cannot change mid-process. */
|
|
60
|
+
export function getAppVersion(): string {
|
|
61
|
+
if (cachedVersion === null) {
|
|
62
|
+
cachedVersion = resolveAppVersion();
|
|
63
|
+
}
|
|
64
|
+
return cachedVersion;
|
|
65
|
+
}
|
package/src/config/buildInfo.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Generated by scripts/gen-build-info.mjs — do not edit manually.
|
|
2
|
-
export const BUILD_COMMIT = "
|
|
3
|
-
export const APP_VERSION = "1.0.
|
|
2
|
+
export const BUILD_COMMIT = "c04f84f315a4b2c4b8bea27d41ac370a27467572" as const;
|
|
3
|
+
export const APP_VERSION = "1.0.7" as const;
|
|
@@ -130,7 +130,7 @@ export const DEFAULT_RUNTIME_POLICY: RuntimePolicyConfig = {
|
|
|
130
130
|
serviceTier: "flex",
|
|
131
131
|
personality: "none",
|
|
132
132
|
allowExternalFileImport: true,
|
|
133
|
-
attachmentDir: "
|
|
133
|
+
attachmentDir: "attachments",
|
|
134
134
|
};
|
|
135
135
|
|
|
136
136
|
export const DEFAULT_RUNTIME_CONFIG: RuntimeConfig = {
|
package/src/config/settings.ts
CHANGED
|
@@ -9,7 +9,11 @@ function smartJoin(base: string, ...parts: string[]): string {
|
|
|
9
9
|
return isWindowsStylePath(base) ? win32.join(base, ...parts) : join(base, ...parts);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
import { getAppVersion } from "./appVersion.js";
|
|
13
|
+
|
|
14
|
+
// Authoritative runtime version: resolved from the installed package.json at
|
|
15
|
+
// startup (buildInfo.ts is only the committed fallback and can drift).
|
|
16
|
+
export const APP_VERSION: string = getAppVersion();
|
|
13
17
|
export const APP_NAME = "Codexa";
|
|
14
18
|
export const DEFAULT_BACKEND = "codex-subprocess";
|
|
15
19
|
export const DEFAULT_MODEL = "gpt-5.4";
|