@golba98/codexa 1.0.6 → 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 +116 -7
- package/bin/codexa.js +15 -4
- package/package.json +1 -1
- package/src/app.tsx +5 -7
- package/src/config/buildInfo.ts +2 -2
- package/src/config/runtimeConfig.ts +1 -1
- package/src/core/debug/modelStateDebug.ts +3 -2
- 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/shared/attachments.ts +10 -10
- package/src/core/terminal/frameLock.ts +2 -1
- package/src/core/terminal/terminalTitle.ts +3 -2
- package/src/core/workspace/appData.ts +45 -0
- package/src/core/workspace/planStorage.ts +7 -13
- package/src/ui/panels/AttachmentImportPanel.tsx +4 -3
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
|
|
@@ -605,8 +708,14 @@ If you expect a notice but don't see one:
|
|
|
605
708
|
|
|
606
709
|
See [CHANGELOG.md](CHANGELOG.md) for the full release history.
|
|
607
710
|
|
|
608
|
-
**Current release: v1.0.
|
|
711
|
+
**Current release: v1.0.7**
|
|
609
712
|
|
|
610
|
-
v1.0.
|
|
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.
|
|
611
714
|
|
|
612
|
-
|
|
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,
|
|
@@ -3830,13 +3831,12 @@ export function App({ launchArgs }: AppProps) {
|
|
|
3830
3831
|
|
|
3831
3832
|
const handleImportConfirm = useCallback(async () => {
|
|
3832
3833
|
if (!pendingImport) return;
|
|
3833
|
-
const replacements: Array<{ rawPath: string;
|
|
3834
|
+
const replacements: Array<{ rawPath: string; replacementPath: string }> = [];
|
|
3834
3835
|
for (const file of pendingImport.files) {
|
|
3835
3836
|
try {
|
|
3836
3837
|
const destPath = await importExternalFile(file.srcPath, pendingImport.attachmentsDir);
|
|
3837
3838
|
if (destPath) {
|
|
3838
|
-
|
|
3839
|
-
replacements.push({ rawPath: file.rawPath, workspaceRelativePath: relPath });
|
|
3839
|
+
replacements.push({ rawPath: file.rawPath, replacementPath: destPath });
|
|
3840
3840
|
}
|
|
3841
3841
|
} catch (err: any) {
|
|
3842
3842
|
appendErrorEvent("Import failed", `Could not import ${path.basename(file.srcPath)}: ${err.message}`);
|
|
@@ -4470,9 +4470,7 @@ export function App({ launchArgs }: AppProps) {
|
|
|
4470
4470
|
|
|
4471
4471
|
if (outsideViolations.length > 0) {
|
|
4472
4472
|
if (runtimeConfig.policy.allowExternalFileImport) {
|
|
4473
|
-
const attachmentsDir =
|
|
4474
|
-
? runtimeConfig.policy.attachmentDir
|
|
4475
|
-
: path.join(workspaceRoot, runtimeConfig.policy.attachmentDir);
|
|
4473
|
+
const attachmentsDir = resolveCodexaAttachmentDir(workspaceRoot, runtimeConfig.policy.attachmentDir);
|
|
4476
4474
|
const importFiles: PendingImportFile[] = outsideViolations.map((v) => ({
|
|
4477
4475
|
srcPath: v.normalizedPath,
|
|
4478
4476
|
rawPath: v.rawPath,
|
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 = {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { appendFileSync, mkdirSync } from "fs";
|
|
2
|
-
import { dirname
|
|
2
|
+
import { dirname } from "path";
|
|
3
|
+
import { resolveCodexaDebugLogPath } from "../workspace/appData.js";
|
|
3
4
|
|
|
4
5
|
type ModelStateDebugDetails = Record<string, unknown>;
|
|
5
6
|
|
|
@@ -12,7 +13,7 @@ export function isModelStateDebugEnabled(): boolean {
|
|
|
12
13
|
export function getModelStateDebugLogPath(): string {
|
|
13
14
|
return process.env.CODEXA_RENDER_DEBUG_FILE?.trim()
|
|
14
15
|
|| process.env.CODEXA_DEBUG_MODEL_STATE_LOG?.trim()
|
|
15
|
-
||
|
|
16
|
+
|| resolveCodexaDebugLogPath();
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export function traceModelStateDebug(event: string, details: ModelStateDebugDetails = {}): void {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { appendFileSync, mkdirSync } from "fs";
|
|
2
|
-
import { dirname
|
|
3
|
-
import { useEffect, useRef } from "react";
|
|
2
|
+
import { dirname } from "path";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import { resolveCodexaDebugLogPath } from "../workspace/appData.js";
|
|
4
5
|
|
|
5
6
|
type DebugEnv = Record<string, string | undefined>;
|
|
6
7
|
|
|
@@ -11,7 +12,7 @@ let renderTraceEnabled = false;
|
|
|
11
12
|
let lifecycleEnabled = false;
|
|
12
13
|
let flickerEnabled = false;
|
|
13
14
|
let plainActionsEnabled = false;
|
|
14
|
-
let logPath =
|
|
15
|
+
let logPath = resolveCodexaDebugLogPath();
|
|
15
16
|
let sessionId = `${Date.now()}-${process.pid}`;
|
|
16
17
|
const counters = new Map<string, number>();
|
|
17
18
|
|
|
@@ -29,7 +30,7 @@ function configureFromEnv(env: DebugEnv = process.env): void {
|
|
|
29
30
|
lifecycleEnabled = env["CODEXA_DEBUG_LIFECYCLE"] === "1";
|
|
30
31
|
flickerEnabled = env["CODEXA_DEBUG_FLICKER"] === "1";
|
|
31
32
|
plainActionsEnabled = env["CODEXA_DEBUG_PLAIN_ACTIONS"] === "1";
|
|
32
|
-
logPath = env["CODEXA_RENDER_DEBUG_FILE"]?.trim() ||
|
|
33
|
+
logPath = env["CODEXA_RENDER_DEBUG_FILE"]?.trim() || resolveCodexaDebugLogPath(env);
|
|
33
34
|
sessionId = `${Date.now()}-${process.pid}`;
|
|
34
35
|
configured = true;
|
|
35
36
|
}
|
|
@@ -39,7 +39,7 @@ export function buildProviderLaunchSpec(provider: ProviderConfig, cwd: string):
|
|
|
39
39
|
if (!provider.enabled) {
|
|
40
40
|
return {
|
|
41
41
|
status: "disabled",
|
|
42
|
-
message: `${provider.displayName} is disabled. Configure a command in
|
|
42
|
+
message: `${provider.displayName} is disabled. Configure a command in Codexa's provider settings before launching it.`,
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
2
2
|
import { dirname, join } from "path";
|
|
3
|
-
import { DEFAULT_MODEL } from "../../config/settings.js";
|
|
4
|
-
import {
|
|
3
|
+
import { DEFAULT_MODEL } from "../../config/settings.js";
|
|
4
|
+
import { resolveCodexaWorkspaceDataDir } from "../workspace/appData.js";
|
|
5
|
+
import { normalizeWorkspaceRoot } from "../workspace/workspaceRoot.js";
|
|
5
6
|
import { isKnownProviderId } from "./registry.js";
|
|
6
7
|
import { getDefaultRouteModel, getProviderRuntime, isProviderRouteConfigured, isProviderRoutableInCodexa } from "../providerRuntime/registry.js";
|
|
7
8
|
import { normalizeGeminiModelId } from "../providerRuntime/models.js";
|
|
@@ -17,8 +18,12 @@ const DEPRECATED_ANTIGRAVITY_PROVIDER_ID = "antigravity";
|
|
|
17
18
|
const DEPRECATED_ANTIGRAVITY_BACKENDS = new Set(["antigravity-cli-auth", "agy"]);
|
|
18
19
|
const DEPRECATED_GOOGLE_PROVIDER_ID = "google";
|
|
19
20
|
|
|
20
|
-
export function getProviderWorkspaceConfigFile(workspaceRoot: string): string {
|
|
21
|
-
return join(normalizeWorkspaceRoot(workspaceRoot), "
|
|
21
|
+
export function getProviderWorkspaceConfigFile(workspaceRoot: string): string {
|
|
22
|
+
return join(resolveCodexaWorkspaceDataDir(normalizeWorkspaceRoot(workspaceRoot)), "providers.json");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getLegacyProviderWorkspaceConfigFile(workspaceRoot: string): string {
|
|
26
|
+
return join(normalizeWorkspaceRoot(workspaceRoot), ".codexa", "providers.json");
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -345,15 +350,24 @@ export function serializeProviderWorkspaceConfig(config: ProviderWorkspaceConfig
|
|
|
345
350
|
};
|
|
346
351
|
}
|
|
347
352
|
|
|
348
|
-
export function loadProviderWorkspaceConfig(workspaceRoot: string): ProviderWorkspaceConfig {
|
|
349
|
-
const filePath = getProviderWorkspaceConfigFile(workspaceRoot);
|
|
350
|
-
if (
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
353
|
+
export function loadProviderWorkspaceConfig(workspaceRoot: string): ProviderWorkspaceConfig {
|
|
354
|
+
const filePath = getProviderWorkspaceConfigFile(workspaceRoot);
|
|
355
|
+
if (existsSync(filePath)) {
|
|
356
|
+
try {
|
|
357
|
+
return parseProviderWorkspaceConfig(JSON.parse(readFileSync(filePath, "utf-8")));
|
|
358
|
+
} catch {
|
|
359
|
+
return {};
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const legacyFilePath = getLegacyProviderWorkspaceConfigFile(workspaceRoot);
|
|
364
|
+
if (!existsSync(legacyFilePath)) return {};
|
|
365
|
+
try {
|
|
366
|
+
return parseProviderWorkspaceConfig(JSON.parse(readFileSync(legacyFilePath, "utf-8")));
|
|
367
|
+
} catch {
|
|
368
|
+
return {};
|
|
369
|
+
}
|
|
370
|
+
}
|
|
357
371
|
|
|
358
372
|
export function saveProviderWorkspaceConfig(workspaceRoot: string, config: ProviderWorkspaceConfig): void {
|
|
359
373
|
const filePath = getProviderWorkspaceConfigFile(workspaceRoot);
|
|
@@ -74,16 +74,16 @@ export async function importExternalFile(
|
|
|
74
74
|
return destPath;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
export function rewritePromptWithImportedPaths(
|
|
78
|
-
prompt: string,
|
|
79
|
-
replacements: Array<{ rawPath: string;
|
|
80
|
-
): string {
|
|
81
|
-
let result = prompt;
|
|
82
|
-
for (const { rawPath,
|
|
83
|
-
const needsQuotes = /\s/.test(
|
|
84
|
-
const quotedReplacement = needsQuotes
|
|
85
|
-
? `"${
|
|
86
|
-
:
|
|
77
|
+
export function rewritePromptWithImportedPaths(
|
|
78
|
+
prompt: string,
|
|
79
|
+
replacements: Array<{ rawPath: string; replacementPath: string }>,
|
|
80
|
+
): string {
|
|
81
|
+
let result = prompt;
|
|
82
|
+
for (const { rawPath, replacementPath } of replacements) {
|
|
83
|
+
const needsQuotes = /\s/.test(replacementPath);
|
|
84
|
+
const quotedReplacement = needsQuotes
|
|
85
|
+
? `"${replacementPath}"`
|
|
86
|
+
: replacementPath;
|
|
87
87
|
// Replace quoted forms first so the unquoted pass doesn't double-replace
|
|
88
88
|
result = result.split(`"${rawPath}"`).join(quotedReplacement);
|
|
89
89
|
result = result.split(`'${rawPath}'`).join(quotedReplacement);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { resolveCodexaDebugLogPath } from "../workspace/appData.js";
|
|
3
4
|
import { isTerminalResizing } from "./terminalControl.js";
|
|
4
5
|
|
|
5
6
|
export interface FrameLockOptions {
|
|
@@ -28,7 +29,7 @@ export function wrapStdoutWithFrameLock({
|
|
|
28
29
|
if (env.CODEXA_RENDER_DEBUG === "1") {
|
|
29
30
|
try {
|
|
30
31
|
const logPath = env.CODEXA_RENDER_DEBUG_FILE?.trim()
|
|
31
|
-
||
|
|
32
|
+
|| resolveCodexaDebugLogPath(env);
|
|
32
33
|
const logDir = path.dirname(logPath);
|
|
33
34
|
if (!fs.existsSync(logDir)) {
|
|
34
35
|
fs.mkdirSync(logDir, { recursive: true });
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { APP_NAME, type TerminalTitleMode, formatTerminalTitlePath } from "../../config/settings.js";
|
|
2
2
|
import { appendFileSync, mkdirSync } from "fs";
|
|
3
|
-
import { dirname
|
|
3
|
+
import { dirname } from "path";
|
|
4
4
|
import * as renderDebug from "../perf/renderDebug.js";
|
|
5
|
+
import { resolveCodexaDebugLogPath } from "../workspace/appData.js";
|
|
5
6
|
|
|
6
7
|
export const DEFAULT_TERMINAL_TITLE = APP_NAME;
|
|
7
8
|
|
|
@@ -52,7 +53,7 @@ function findIncompleteOscTitleStart(text: string): number {
|
|
|
52
53
|
|
|
53
54
|
const TERMINAL_TITLE_DEBUG_LOG_PATH = process.env["CODEXA_TERMINAL_TITLE_DEBUG_FILE"]?.trim()
|
|
54
55
|
|| process.env["CODEXA_RENDER_DEBUG_FILE"]?.trim()
|
|
55
|
-
||
|
|
56
|
+
|| resolveCodexaDebugLogPath();
|
|
56
57
|
|
|
57
58
|
let terminalTitleLifecycleState = "unknown";
|
|
58
59
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { isAbsolute, join, normalize } from "node:path";
|
|
4
|
+
|
|
5
|
+
type Platform = "win32" | "darwin" | "linux" | string;
|
|
6
|
+
type Environment = Record<string, string | undefined>;
|
|
7
|
+
|
|
8
|
+
export function resolveCodexaDataDir(
|
|
9
|
+
platformOverride?: Platform,
|
|
10
|
+
env: Environment = process.env,
|
|
11
|
+
home = homedir(),
|
|
12
|
+
): string {
|
|
13
|
+
const configuredDir = env["CODEXA_DATA_DIR"]?.trim();
|
|
14
|
+
if (configuredDir) return configuredDir;
|
|
15
|
+
|
|
16
|
+
const platform = platformOverride ?? process.platform;
|
|
17
|
+
if (platform === "win32") {
|
|
18
|
+
return join(env["LOCALAPPDATA"]?.trim() || env["APPDATA"]?.trim() || join(home, "AppData", "Local"), "Codexa");
|
|
19
|
+
}
|
|
20
|
+
if (platform === "darwin") {
|
|
21
|
+
return join(home, "Library", "Application Support", "Codexa");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return join(env["XDG_DATA_HOME"]?.trim() || join(home, ".local", "share"), "codexa");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function workspaceStorageKey(workspaceRoot: string): string {
|
|
28
|
+
return createHash("sha256").update(workspaceRoot).digest("hex").slice(0, 16);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function resolveCodexaWorkspaceDataDir(workspaceRoot: string): string {
|
|
32
|
+
return join(resolveCodexaDataDir(), "workspaces", workspaceStorageKey(workspaceRoot));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function resolveCodexaAttachmentDir(workspaceRoot: string, configuredDir: string): string {
|
|
36
|
+
if (isAbsolute(configuredDir)) return configuredDir;
|
|
37
|
+
|
|
38
|
+
const normalized = configuredDir.trim().replace(/\\/g, "/").replace(/^\.codexa\/?/, "") || "attachments";
|
|
39
|
+
const safeRelativeDir = normalize(normalized).replace(/^(\.\.([/\\]|$))+/, "") || "attachments";
|
|
40
|
+
return join(resolveCodexaWorkspaceDataDir(workspaceRoot), safeRelativeDir);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function resolveCodexaDebugLogPath(env: Environment = process.env): string {
|
|
44
|
+
return join(resolveCodexaDataDir(undefined, env), "debug", "render-status.log");
|
|
45
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createHash } from "crypto";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
-
import { homedir } from "os";
|
|
4
|
-
import { join } from "path";
|
|
5
|
-
import { isNoiseLine } from "../providers/codexTranscript.js";
|
|
6
|
-
import { sanitizeTerminalOutput } from "../terminal/terminalSanitize.js";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { isNoiseLine } from "../providers/codexTranscript.js";
|
|
6
|
+
import { sanitizeTerminalOutput } from "../terminal/terminalSanitize.js";
|
|
7
|
+
import { resolveCodexaDataDir } from "./appData.js";
|
|
7
8
|
|
|
8
9
|
type Platform = "win32" | "darwin" | "linux" | string;
|
|
9
10
|
|
|
@@ -91,15 +92,8 @@ export function resolvePlanDir(platformOverride?: Platform): string {
|
|
|
91
92
|
return join(homedir(), "AppData", "Local", "Codexa", "plans");
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Linux and other Unix-like
|
|
99
|
-
const xdgDataHome = process.env["XDG_DATA_HOME"];
|
|
100
|
-
if (xdgDataHome) return join(xdgDataHome, "codexa", "plans");
|
|
101
|
-
return join(homedir(), ".local", "share", "codexa", "plans");
|
|
102
|
-
}
|
|
95
|
+
return join(resolveCodexaDataDir(platform), "plans");
|
|
96
|
+
}
|
|
103
97
|
|
|
104
98
|
// SHA-256 of the workspace path ensures filename uniqueness across projects with the same name.
|
|
105
99
|
function workspaceHash(workspaceRoot: string): string {
|
|
@@ -43,7 +43,8 @@ export function AttachmentImportPanel({
|
|
|
43
43
|
}
|
|
44
44
|
}, { isActive: isFocused });
|
|
45
45
|
|
|
46
|
-
const relativeAttachmentsDir = path.relative(workspaceRoot, attachmentsDir).replace(/\\/g, "/");
|
|
46
|
+
const relativeAttachmentsDir = path.relative(workspaceRoot, attachmentsDir).replace(/\\/g, "/");
|
|
47
|
+
const displayedAttachmentsDir = relativeAttachmentsDir.startsWith("..") ? attachmentsDir : relativeAttachmentsDir;
|
|
47
48
|
const hasImages = files.some((f) => f.isImage);
|
|
48
49
|
const showVisionWarning = hasImages && modelSupportsVision === false;
|
|
49
50
|
const fileLabel = files.length === 1 ? "file" : "files";
|
|
@@ -59,7 +60,7 @@ export function AttachmentImportPanel({
|
|
|
59
60
|
>
|
|
60
61
|
<Text color={theme.accent} bold>IMPORT FILE </Text>
|
|
61
62
|
<Text color={theme.textMuted}>
|
|
62
|
-
Copy {files.length} outside-workspace {fileLabel} into
|
|
63
|
+
Copy {files.length} outside-workspace {fileLabel} into {displayedAttachmentsDir}?
|
|
63
64
|
</Text>
|
|
64
65
|
</Box>
|
|
65
66
|
|
|
@@ -76,7 +77,7 @@ export function AttachmentImportPanel({
|
|
|
76
77
|
<Box key={i} flexDirection="column" marginBottom={i < files.length - 1 ? 1 : 0}>
|
|
77
78
|
<Text color={theme.text}>{path.basename(file.srcPath)}</Text>
|
|
78
79
|
<Text color={theme.textDim}>
|
|
79
|
-
{"→ "}{
|
|
80
|
+
{"→ "}{displayedAttachmentsDir}/{file.destFilename}
|
|
80
81
|
</Text>
|
|
81
82
|
</Box>
|
|
82
83
|
))}
|