@golba98/codexa 1.0.4 → 1.0.6
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 +6 -10
- package/package.json +1 -1
- package/src/app.tsx +188 -121
- package/src/commands/handler.ts +11 -20
- package/src/config/appVersion.ts +65 -0
- package/src/config/buildInfo.ts +2 -2
- package/src/config/settings.ts +5 -1
- package/src/config/updateCheckCache.ts +18 -8
- package/src/core/models/codexModelCapabilities.ts +57 -4
- package/src/core/models/codexModelsCacheSeed.ts +153 -0
- package/src/core/models/providerModelCache.ts +90 -0
- package/src/core/process/CommandRunner.ts +12 -1
- package/src/core/providerLauncher/registry.ts +37 -7
- package/src/core/providerLauncher/types.ts +2 -1
- package/src/core/providerRuntime/anthropic.ts +3 -5
- package/src/core/providerRuntime/antigravity.ts +257 -90
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -692
- package/src/core/providerRuntime/mistralVibe.ts +663 -0
- package/src/core/providerRuntime/models.ts +18 -4
- package/src/core/providerRuntime/reasoning.ts +9 -6
- package/src/core/providerRuntime/registry.ts +93 -44
- package/src/core/providerRuntime/types.ts +1 -0
- package/src/core/version/packageManager.ts +119 -0
- package/src/core/version/updateCheck.ts +15 -9
- package/src/index.tsx +23 -9
- package/src/ui/{ActivityBars.tsx → chrome/ActivityBars.tsx} +1 -1
- package/src/ui/{ActivityIndicator.tsx → chrome/ActivityIndicator.tsx} +3 -3
- package/src/ui/{AnimatedStatusText.tsx → chrome/AnimatedStatusText.tsx} +3 -3
- package/src/ui/{AppShell.tsx → chrome/AppShell.tsx} +11 -11
- package/src/ui/{BottomComposer.tsx → chrome/BottomComposer.tsx} +15 -15
- package/src/ui/{DashCard.tsx → chrome/DashCard.tsx} +2 -2
- package/src/ui/{RunFooter.tsx → chrome/RunFooter.tsx} +3 -3
- package/src/ui/{RuntimeStatusBar.tsx → chrome/RuntimeStatusBar.tsx} +3 -3
- package/src/ui/{Spinner.tsx → chrome/Spinner.tsx} +1 -1
- package/src/ui/{TopHeader.tsx → chrome/TopHeader.tsx} +15 -13
- package/src/ui/{UpdateAvailableCard.tsx → chrome/UpdateAvailableCard.tsx} +6 -5
- package/src/ui/{commandNormalize.ts → input/commandNormalize.ts} +1 -1
- package/src/ui/{focus.ts → input/focus.ts} +1 -1
- package/src/ui/{inputBuffer.ts → input/inputBuffer.ts} +3 -3
- package/src/ui/{slashCommands.ts → input/slashCommands.ts} +1 -0
- package/src/ui/{AttachmentImportPanel.tsx → panels/AttachmentImportPanel.tsx} +1 -1
- package/src/ui/{AuthPanel.tsx → panels/AuthPanel.tsx} +5 -5
- package/src/ui/{BackendPicker.tsx → panels/BackendPicker.tsx} +2 -2
- package/src/ui/{ModePicker.tsx → panels/ModePicker.tsx} +2 -2
- package/src/ui/{ModelPicker.tsx → panels/ModelPicker.tsx} +2 -2
- package/src/ui/{ModelPickerScreen.tsx → panels/ModelPickerScreen.tsx} +11 -9
- package/src/ui/{ModelReasoningPicker.tsx → panels/ModelReasoningPicker.tsx} +5 -5
- package/src/ui/{Panel.tsx → panels/Panel.tsx} +1 -1
- package/src/ui/{PermissionsPanel.tsx → panels/PermissionsPanel.tsx} +3 -3
- package/src/ui/{PlanActionPicker.tsx → panels/PlanActionPicker.tsx} +2 -2
- package/src/ui/{PlanReviewPanel.tsx → panels/PlanReviewPanel.tsx} +5 -5
- package/src/ui/{ProviderPicker.tsx → panels/ProviderPicker.tsx} +9 -7
- package/src/ui/{ReasoningPicker.tsx → panels/ReasoningPicker.tsx} +3 -3
- package/src/ui/{SelectionPanel.tsx → panels/SelectionPanel.tsx} +1 -1
- package/src/ui/{SettingsPanel.tsx → panels/SettingsPanel.tsx} +3 -3
- package/src/ui/{TextEntryPanel.tsx → panels/TextEntryPanel.tsx} +3 -3
- package/src/ui/{ThemePicker.tsx → panels/ThemePicker.tsx} +2 -2
- package/src/ui/{UpdatePromptPanel.tsx → panels/UpdatePromptPanel.tsx} +54 -44
- package/src/ui/{Markdown.tsx → render/Markdown.tsx} +2 -2
- package/src/ui/{diffRenderer.ts → render/diffRenderer.ts} +1 -1
- package/src/ui/{modeDisplay.ts → render/modeDisplay.ts} +2 -2
- package/src/ui/{outputPipeline.ts → render/outputPipeline.ts} +2 -2
- package/src/ui/{runtimeDisplay.ts → render/runtimeDisplay.ts} +9 -8
- package/src/ui/{ActionRequiredBlock.tsx → timeline/ActionRequiredBlock.tsx} +3 -3
- package/src/ui/{AgentBlock.tsx → timeline/AgentBlock.tsx} +10 -10
- package/src/ui/{StaticIntroItem.tsx → timeline/StaticIntroItem.tsx} +2 -2
- package/src/ui/{ThinkingBlock.tsx → timeline/ThinkingBlock.tsx} +4 -4
- package/src/ui/{Timeline.tsx → timeline/Timeline.tsx} +8 -8
- package/src/ui/{TranscriptShell.tsx → timeline/TranscriptShell.tsx} +6 -6
- package/src/ui/{TurnGroup.tsx → timeline/TurnGroup.tsx} +15 -15
- package/src/ui/{progressEntries.ts → timeline/progressEntries.ts} +3 -3
- package/src/ui/{runActivityView.ts → timeline/runActivityView.ts} +1 -1
- package/src/ui/{timelineMeasure.ts → timeline/timelineMeasure.ts} +14 -14
- /package/src/ui/{busyStatusAnimation.ts → chrome/busyStatusAnimation.ts} +0 -0
- /package/src/ui/{logoVariants.ts → render/logoVariants.ts} +0 -0
- /package/src/ui/{terminalAnswerFormat.ts → render/terminalAnswerFormat.ts} +0 -0
- /package/src/ui/{textLayout.ts → render/textLayout.ts} +0 -0
- /package/src/ui/{layoutListWindow.ts → timeline/layoutListWindow.ts} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { ReasoningEffortCapability } from "../models/codexModelCapabilities.js";
|
|
1
|
+
import type { ReasoningEffortCapability } from "../models/codexModelCapabilities.js";
|
|
2
|
+
import { formatReasoningLabel } from "../../config/settings.js";
|
|
2
3
|
|
|
3
4
|
export const CLAUDE_CODE_EFFORT_LEVELS: readonly ReasoningEffortCapability[] = [
|
|
4
5
|
{ id: "low", label: "Low", description: "Claude Code low effort." },
|
|
@@ -10,8 +11,10 @@ export const CLAUDE_CODE_EFFORT_LEVELS: readonly ReasoningEffortCapability[] = [
|
|
|
10
11
|
|
|
11
12
|
export const CLAUDE_CODE_EFFORT_IDS = new Set(CLAUDE_CODE_EFFORT_LEVELS.map((level) => level.id));
|
|
12
13
|
|
|
13
|
-
export function getClaudeCodeEffortLevels(ids: readonly string[]): readonly ReasoningEffortCapability[] {
|
|
14
|
-
return ids
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
export function getClaudeCodeEffortLevels(ids: readonly string[]): readonly ReasoningEffortCapability[] {
|
|
15
|
+
return ids.map((id) => CLAUDE_CODE_EFFORT_LEVELS.find((level) => level.id === id) ?? {
|
|
16
|
+
id,
|
|
17
|
+
label: formatReasoningLabel(id),
|
|
18
|
+
description: null,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { codexSubprocessProvider } from "../providers/codexSubprocess.js";
|
|
2
|
+
import { loadSeededOpenAiModels } from "../models/codexModelsCacheSeed.js";
|
|
3
|
+
import { loadCachedProviderModels, saveCachedProviderModels } from "../models/providerModelCache.js";
|
|
2
4
|
import type { BackendRunHandlers } from "../providers/types.js";
|
|
3
5
|
import type { ProviderId, ProviderActiveRoute, ProviderWorkspaceOverride } from "../providerLauncher/types.js";
|
|
4
|
-
import { anthropicRuntime } from "./anthropic.js";
|
|
5
|
-
import { geminiRuntime } from "./gemini.js";
|
|
6
|
-
import { localRuntime } from "./local.js";
|
|
7
|
-
import { antigravityRuntime, ANTIGRAVITY_DEFAULT_MODEL_ID, migrateAntigravityLegacyModelId } from "./antigravity.js";
|
|
6
|
+
import { anthropicRuntime } from "./anthropic.js";
|
|
7
|
+
import { geminiRuntime } from "./gemini.js";
|
|
8
|
+
import { localRuntime } from "./local.js";
|
|
9
|
+
import { antigravityRuntime, ANTIGRAVITY_DEFAULT_MODEL_ID, migrateAntigravityLegacyModelId } from "./antigravity.js";
|
|
10
|
+
import { mistralVibeRuntime } from "./mistralVibe.js";
|
|
8
11
|
import {
|
|
9
12
|
ANTHROPIC_FALLBACK_MODELS,
|
|
10
13
|
GEMINI_DEFAULT_MODEL_ID,
|
|
@@ -28,11 +31,14 @@ const openAiRuntime: ProviderRuntime = {
|
|
|
28
31
|
routeAvailable: true,
|
|
29
32
|
routeStatus: "Uses the configured Codex/OpenAI backend inside Codexa.",
|
|
30
33
|
launchAvailable: true,
|
|
34
|
+
// Seeded from local caches (codex's own models_cache.json or Codexa's
|
|
35
|
+
// last-good discovery); live app-server discovery refreshes them via
|
|
36
|
+
// getCodexModelCapabilities in the app run loop.
|
|
31
37
|
discoverModels: () => ({
|
|
32
38
|
status: "ready",
|
|
33
39
|
providerId: "openai",
|
|
34
40
|
backendKind: "codex-cli-auth",
|
|
35
|
-
models: [],
|
|
41
|
+
models: loadSeededOpenAiModels()?.models ?? [],
|
|
36
42
|
}),
|
|
37
43
|
run: (request: ProviderChatRequest, handlers: BackendRunHandlers) => {
|
|
38
44
|
handlers.onProgress?.({
|
|
@@ -72,11 +78,12 @@ function unavailableRuntime(providerId: ProviderId, label: string): ProviderRunt
|
|
|
72
78
|
|
|
73
79
|
const PROVIDER_RUNTIMES: Record<ProviderId, ProviderRuntime> = {
|
|
74
80
|
openai: openAiRuntime,
|
|
75
|
-
anthropic: anthropicRuntime,
|
|
76
|
-
google: geminiRuntime,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
anthropic: anthropicRuntime,
|
|
82
|
+
google: geminiRuntime,
|
|
83
|
+
mistral: mistralVibeRuntime,
|
|
84
|
+
local: localRuntime,
|
|
85
|
+
antigravity: antigravityRuntime,
|
|
86
|
+
};
|
|
80
87
|
|
|
81
88
|
export function getProviderRuntime(providerId: ProviderId): ProviderRuntime {
|
|
82
89
|
return PROVIDER_RUNTIMES[providerId];
|
|
@@ -97,17 +104,39 @@ export function getProviderRouteSetupMessage(providerId: ProviderId): string {
|
|
|
97
104
|
}
|
|
98
105
|
|
|
99
106
|
export function discoverProviderModels(providerId: ProviderId): ProviderModelDiscoveryResult {
|
|
100
|
-
|
|
107
|
+
const result = getProviderRuntime(providerId).discoverModels();
|
|
108
|
+
// When the live/synchronous probe only produced fallback entries (or none),
|
|
109
|
+
// prefer the last-good discovery persisted from a previous session.
|
|
110
|
+
const hasRuntimeModels = result.models.some((model) => model.source && model.source !== "fallback");
|
|
111
|
+
if (result.status === "ready" && !hasRuntimeModels) {
|
|
112
|
+
const cached = loadCachedProviderModels(providerId);
|
|
113
|
+
if (cached) {
|
|
114
|
+
return { ...result, models: cached.models };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Persist a ready discovery so the next session starts from last-good models.
|
|
121
|
+
export function persistProviderDiscovery(discovery: ProviderModelDiscoveryResult): void {
|
|
122
|
+
const runtimeModels = discovery.models.filter((model) => model.source && model.source !== "fallback");
|
|
123
|
+
if (discovery.status !== "ready" || runtimeModels.length === 0) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
saveCachedProviderModels(discovery.providerId, {
|
|
127
|
+
discoveredAt: Date.now(),
|
|
128
|
+
models: runtimeModels,
|
|
129
|
+
});
|
|
101
130
|
}
|
|
102
131
|
|
|
103
132
|
export async function validateProviderRouteActivation(options: {
|
|
104
133
|
route: ProviderRoute;
|
|
105
|
-
workspaceRoot: string;
|
|
106
|
-
geminiCommandPath?: string | null;
|
|
107
|
-
claudeCommandPath?: string | null;
|
|
108
|
-
antigravityCommandPath?: string | null;
|
|
109
|
-
localConfig?: ProviderWorkspaceOverride | null;
|
|
110
|
-
}): Promise<ProviderRouteValidationResult> {
|
|
134
|
+
workspaceRoot: string;
|
|
135
|
+
geminiCommandPath?: string | null;
|
|
136
|
+
claudeCommandPath?: string | null;
|
|
137
|
+
antigravityCommandPath?: string | null;
|
|
138
|
+
localConfig?: ProviderWorkspaceOverride | null;
|
|
139
|
+
}): Promise<ProviderRouteValidationResult> {
|
|
111
140
|
const runtime = getProviderRuntime(options.route.providerId);
|
|
112
141
|
if (!runtime.routeAvailable) {
|
|
113
142
|
return {
|
|
@@ -157,7 +186,7 @@ export function resolveActiveProviderRoute(options: {
|
|
|
157
186
|
currentReasoning: string;
|
|
158
187
|
}): ActiveProviderRoute {
|
|
159
188
|
const configuredRoute = options.workspaceConfigActiveRoute;
|
|
160
|
-
if (configuredRoute && configuredRoute.providerId !== "google" && isProviderRoutableInCodexa(configuredRoute.providerId)) {
|
|
189
|
+
if (configuredRoute && configuredRoute.providerId !== "google" && isProviderRoutableInCodexa(configuredRoute.providerId)) {
|
|
161
190
|
const route: ActiveProviderRoute = {
|
|
162
191
|
providerId: configuredRoute.providerId,
|
|
163
192
|
modelId: configuredRoute.modelId,
|
|
@@ -166,36 +195,52 @@ export function resolveActiveProviderRoute(options: {
|
|
|
166
195
|
...(configuredRoute.modelSelection ? { modelSelection: configuredRoute.modelSelection } : {}),
|
|
167
196
|
};
|
|
168
197
|
|
|
169
|
-
if (route.providerId === "google" && route.modelSelection) {
|
|
170
|
-
route.modelId = resolveGeminiModelId(route.modelSelection);
|
|
171
|
-
} else if (route.providerId === "google") {
|
|
172
|
-
route.modelId = normalizeGeminiModelId(route.modelId);
|
|
198
|
+
if (route.providerId === "google" && route.modelSelection) {
|
|
199
|
+
route.modelId = resolveGeminiModelId(route.modelSelection);
|
|
200
|
+
} else if (route.providerId === "google") {
|
|
201
|
+
route.modelId = normalizeGeminiModelId(route.modelId);
|
|
173
202
|
} else if (route.providerId === "anthropic") {
|
|
174
203
|
const discovery = discoverProviderModels("anthropic");
|
|
175
|
-
const stillAvailable = discovery.models.some((model) =>
|
|
176
|
-
model.modelId === route.modelId ||
|
|
177
|
-
model.id === route.modelId ||
|
|
178
|
-
model.canonicalId === route.modelId
|
|
179
|
-
);
|
|
204
|
+
const stillAvailable = discovery.models.some((model) =>
|
|
205
|
+
model.modelId === route.modelId ||
|
|
206
|
+
model.id === route.modelId ||
|
|
207
|
+
model.canonicalId === route.modelId
|
|
208
|
+
);
|
|
180
209
|
const hasNonFallbackModels = discovery.models.some((model) => model.source !== "fallback");
|
|
181
|
-
|
|
210
|
+
// Preserve explicit versioned Claude IDs even when the current discovery
|
|
211
|
+
// output does not advertise that historical model. Short aliases are the
|
|
212
|
+
// values that need migration to the first currently discovered model.
|
|
213
|
+
const isKnownShortAlias = ANTHROPIC_FALLBACK_MODELS.some((model) => model.modelId === route.modelId);
|
|
214
|
+
if (discovery.status === "ready" && hasNonFallbackModels && discovery.models.length > 0 && !stillAvailable && isKnownShortAlias) {
|
|
182
215
|
route.modelId = discovery.models[0]!.modelId;
|
|
183
216
|
}
|
|
184
|
-
} else if (route.providerId === "local") {
|
|
185
|
-
const discovery = discoverProviderModels("local");
|
|
186
|
-
const selectedModel = typeof discovery.diagnostics?.selectedModel === "string"
|
|
217
|
+
} else if (route.providerId === "local") {
|
|
218
|
+
const discovery = discoverProviderModels("local");
|
|
219
|
+
const selectedModel = typeof discovery.diagnostics?.selectedModel === "string"
|
|
187
220
|
? discovery.diagnostics.selectedModel.trim()
|
|
188
221
|
: "";
|
|
189
|
-
if (discovery.status === "ready" && selectedModel) {
|
|
190
|
-
route.modelId = selectedModel;
|
|
191
|
-
}
|
|
222
|
+
if (discovery.status === "ready" && selectedModel) {
|
|
223
|
+
route.modelId = selectedModel;
|
|
224
|
+
}
|
|
192
225
|
} else if (route.providerId === "antigravity") {
|
|
193
226
|
const migrated = migrateAntigravityLegacyModelId(route.modelId);
|
|
194
227
|
route.modelId = migrated.modelId;
|
|
195
228
|
if (!route.reasoning && migrated.reasoning) {
|
|
196
229
|
route.reasoning = migrated.reasoning;
|
|
197
230
|
}
|
|
198
|
-
|
|
231
|
+
const discovery = discoverProviderModels("antigravity");
|
|
232
|
+
if (discovery.status === "ready" && discovery.models.length > 0) {
|
|
233
|
+
let model = discovery.models.find((item) => item.modelId === route.modelId || item.id === route.modelId);
|
|
234
|
+
if (!model) {
|
|
235
|
+
model = discovery.models[0];
|
|
236
|
+
route.modelId = model.modelId;
|
|
237
|
+
}
|
|
238
|
+
const levels = model.supportedReasoningLevels;
|
|
239
|
+
if (levels?.length && (!route.reasoning || !levels.some((level) => level.id === route.reasoning))) {
|
|
240
|
+
route.reasoning = model.defaultReasoningLevel ?? levels[0]?.id;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
199
244
|
|
|
200
245
|
return route;
|
|
201
246
|
}
|
|
@@ -214,17 +259,21 @@ export function getDefaultRouteModel(providerId: ProviderId, currentOpenAiModel:
|
|
|
214
259
|
if (discovered.status === "ready" && discovered.models.length > 0) {
|
|
215
260
|
return discovered.models[0].modelId;
|
|
216
261
|
}
|
|
217
|
-
return ANTHROPIC_FALLBACK_MODELS[0]?.modelId ?? "sonnet";
|
|
262
|
+
return ANTHROPIC_FALLBACK_MODELS[0]?.modelId ?? "sonnet";
|
|
218
263
|
}
|
|
219
264
|
if (providerId === "google") {
|
|
220
265
|
return GEMINI_FALLBACK_MODELS[0]?.modelId ?? GEMINI_DEFAULT_MODEL_ID;
|
|
221
266
|
}
|
|
222
|
-
if (providerId === "local") {
|
|
223
|
-
const discovery = discoverProviderModels("local");
|
|
224
|
-
return discovery.models[0]?.modelId ?? "Local default";
|
|
225
|
-
}
|
|
267
|
+
if (providerId === "local") {
|
|
268
|
+
const discovery = discoverProviderModels("local");
|
|
269
|
+
return discovery.models[0]?.modelId ?? "Local default";
|
|
270
|
+
}
|
|
271
|
+
if (providerId === "mistral") {
|
|
272
|
+
const discovery = discoverProviderModels("mistral");
|
|
273
|
+
return discovery.models[0]?.modelId ?? "Vibe default";
|
|
274
|
+
}
|
|
226
275
|
if (providerId === "antigravity") {
|
|
227
|
-
return ANTIGRAVITY_DEFAULT_MODEL_ID;
|
|
228
|
-
}
|
|
229
|
-
return currentOpenAiModel;
|
|
230
|
-
}
|
|
276
|
+
return discoverProviderModels("antigravity").models[0]?.modelId ?? ANTIGRAVITY_DEFAULT_MODEL_ID;
|
|
277
|
+
}
|
|
278
|
+
return currentOpenAiModel;
|
|
279
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { ChildProcess } from "child_process";
|
|
2
|
+
import {
|
|
3
|
+
runCommand,
|
|
4
|
+
runShellCommand,
|
|
5
|
+
type CommandResult,
|
|
6
|
+
type CommandSpec,
|
|
7
|
+
type CommandStreamHandlers,
|
|
8
|
+
} from "../process/CommandRunner.js";
|
|
9
|
+
import { CODEXA_NPM_PACKAGE } from "./updateCheck.js";
|
|
10
|
+
|
|
11
|
+
export type GlobalPackageManager = "npm" | "pnpm" | "yarn" | "bun";
|
|
12
|
+
|
|
13
|
+
const UPDATE_TIMEOUT_MS = 300_000;
|
|
14
|
+
|
|
15
|
+
const PACKAGE_SPEC = `${CODEXA_NPM_PACKAGE}@latest`;
|
|
16
|
+
|
|
17
|
+
// Yarn Classic only — Yarn Berry (v2+) removed `yarn global`, but Berry installs
|
|
18
|
+
// don't produce the global launcher paths we detect, so Classic is the only case.
|
|
19
|
+
const UPDATE_ARGV: Record<GlobalPackageManager, readonly string[]> = {
|
|
20
|
+
npm: ["npm", "install", "-g", PACKAGE_SPEC],
|
|
21
|
+
pnpm: ["pnpm", "add", "-g", PACKAGE_SPEC],
|
|
22
|
+
yarn: ["yarn", "global", "add", PACKAGE_SPEC],
|
|
23
|
+
bun: ["bun", "add", "-g", PACKAGE_SPEC],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Infers which package manager owns the global Codexa install from the
|
|
28
|
+
* launcher script location (CODEXA_LAUNCHER_SCRIPT, set by bin/codexa.js).
|
|
29
|
+
*/
|
|
30
|
+
export function detectGlobalPackageManager(
|
|
31
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
32
|
+
launcherPathOverride?: string,
|
|
33
|
+
): GlobalPackageManager {
|
|
34
|
+
const launcherPath = launcherPathOverride ?? env.CODEXA_LAUNCHER_SCRIPT ?? process.argv[1] ?? "";
|
|
35
|
+
const normalized = launcherPath.toLowerCase().replace(/\\/g, "/");
|
|
36
|
+
if (!normalized) return "npm";
|
|
37
|
+
|
|
38
|
+
if (normalized.includes("pnpm")) return "pnpm";
|
|
39
|
+
if (normalized.includes("/.bun/") || normalized.includes("/bun/install/global/")) return "bun";
|
|
40
|
+
if (normalized.includes("/.yarn/") || normalized.includes("/yarn/") || normalized.includes(".config/yarn")) {
|
|
41
|
+
return "yarn";
|
|
42
|
+
}
|
|
43
|
+
return "npm";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getUpdateCommand(pm: GlobalPackageManager): {
|
|
47
|
+
displayCommand: string;
|
|
48
|
+
argv: readonly string[];
|
|
49
|
+
} {
|
|
50
|
+
const argv = UPDATE_ARGV[pm];
|
|
51
|
+
return { displayCommand: argv.join(" "), argv };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface RunUpdateCommandDeps {
|
|
55
|
+
platform?: NodeJS.Platform;
|
|
56
|
+
cwd?: string;
|
|
57
|
+
runCommandFn?: (
|
|
58
|
+
spec: CommandSpec,
|
|
59
|
+
handlers?: CommandStreamHandlers,
|
|
60
|
+
) => { child: ChildProcess; result: Promise<CommandResult>; cancel: () => void };
|
|
61
|
+
runShellCommandFn?: (
|
|
62
|
+
command: string,
|
|
63
|
+
options: Pick<CommandSpec, "cwd" | "env" | "timeoutMs">,
|
|
64
|
+
handlers?: CommandStreamHandlers,
|
|
65
|
+
) => { child: ChildProcess; result: Promise<CommandResult>; cancel: () => void };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Runs the global update command for the detected package manager.
|
|
70
|
+
* POSIX spawns the argv directly; Windows routes the constant, whitelisted
|
|
71
|
+
* command string through cmd.exe so .cmd shims (npm.cmd, pnpm.cmd) resolve —
|
|
72
|
+
* spawning them without a shell throws EINVAL on Node >= 18.20.
|
|
73
|
+
*/
|
|
74
|
+
export function runUpdateCommand(
|
|
75
|
+
pm: GlobalPackageManager,
|
|
76
|
+
handlers: CommandStreamHandlers = {},
|
|
77
|
+
deps: RunUpdateCommandDeps = {},
|
|
78
|
+
): { result: Promise<CommandResult>; cancel: () => void } {
|
|
79
|
+
const platform = deps.platform ?? process.platform;
|
|
80
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
81
|
+
const { displayCommand, argv } = getUpdateCommand(pm);
|
|
82
|
+
|
|
83
|
+
if (platform === "win32") {
|
|
84
|
+
const runShell = deps.runShellCommandFn ?? runShellCommand;
|
|
85
|
+
const { result, cancel } = runShell(displayCommand, { cwd, timeoutMs: UPDATE_TIMEOUT_MS }, handlers);
|
|
86
|
+
return { result, cancel };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const run = deps.runCommandFn ?? runCommand;
|
|
90
|
+
const { result, cancel } = run(
|
|
91
|
+
{ executable: argv[0]!, args: [...argv.slice(1)], cwd, timeoutMs: UPDATE_TIMEOUT_MS },
|
|
92
|
+
handlers,
|
|
93
|
+
);
|
|
94
|
+
return { result, cancel };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const PERMISSION_STDERR_RE = /EACCES|EPERM|permission denied/i;
|
|
98
|
+
|
|
99
|
+
/** npm reports EACCES via stderr with a nonzero exit, not as a spawn error. */
|
|
100
|
+
export function isPermissionError(result: CommandResult): boolean {
|
|
101
|
+
if (result.errorCode === "EACCES" || result.errorCode === "EPERM") return true;
|
|
102
|
+
return result.exitCode !== 0 && PERMISSION_STDERR_RE.test(result.stderr);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function formatPermissionGuidance(pm: GlobalPackageManager): string {
|
|
106
|
+
const { displayCommand } = getUpdateCommand(pm);
|
|
107
|
+
const lines = [
|
|
108
|
+
`${pm} could not write to its global package directory, so the update was not installed.`,
|
|
109
|
+
];
|
|
110
|
+
if (pm === "npm") {
|
|
111
|
+
lines.push(
|
|
112
|
+
"Check where global packages are installed with `npm config get prefix` and make sure that directory is writable by your user, or switch to a user-writable prefix (for example `npm config set prefix ~/.npm-global`, then add its bin directory to PATH).",
|
|
113
|
+
);
|
|
114
|
+
} else {
|
|
115
|
+
lines.push(`Make sure the ${pm} global package directory is writable by your user.`);
|
|
116
|
+
}
|
|
117
|
+
lines.push(`You can also run the command manually in a terminal with the right permissions: ${displayCommand}`);
|
|
118
|
+
return lines.join("\n");
|
|
119
|
+
}
|
|
@@ -154,7 +154,10 @@ export async function checkForUpdates(
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
export function formatUpdateInstructions(
|
|
157
|
+
export function formatUpdateInstructions(
|
|
158
|
+
result: UpdateCheckResult | null,
|
|
159
|
+
updateCommand: string = CODEXA_UPDATE_COMMAND,
|
|
160
|
+
): string {
|
|
158
161
|
const current = result?.currentVersion ?? APP_VERSION;
|
|
159
162
|
const latest = result?.latestVersion ?? "unknown";
|
|
160
163
|
|
|
@@ -166,21 +169,24 @@ export function formatUpdateInstructions(result: UpdateCheckResult | null): stri
|
|
|
166
169
|
].join("\n");
|
|
167
170
|
}
|
|
168
171
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
statusLine = "Status unknown — could not reach npm registry.";
|
|
172
|
+
if (result?.status === "up-to-date") {
|
|
173
|
+
return [
|
|
174
|
+
"Codexa is up to date.",
|
|
175
|
+
`Current installed version: ${current}`,
|
|
176
|
+
`npm latest version: ${latest}`,
|
|
177
|
+
].join("\n");
|
|
176
178
|
}
|
|
177
179
|
|
|
180
|
+
const statusLine = result?.status === "update-available" && result.latestVersion
|
|
181
|
+
? `Update available: Codexa ${formatVersionLabel(result.latestVersion)}`
|
|
182
|
+
: "Status unknown — could not reach npm registry.";
|
|
183
|
+
|
|
178
184
|
return [
|
|
179
185
|
`Current installed version: ${current}`,
|
|
180
186
|
`npm latest version: ${latest}`,
|
|
181
187
|
`Status: ${statusLine}`,
|
|
182
188
|
"",
|
|
183
|
-
`Run: ${
|
|
189
|
+
`Run: ${updateCommand}`,
|
|
184
190
|
].join("\n");
|
|
185
191
|
}
|
|
186
192
|
|
package/src/index.tsx
CHANGED
|
@@ -18,11 +18,24 @@ import {
|
|
|
18
18
|
import { resolveInkRenderInstance, type InkRenderInstance } from "./core/terminal/inkRenderReset.js";
|
|
19
19
|
import { resetFrameLockForResize, wrapStdoutWithFrameLock } from "./core/terminal/frameLock.js";
|
|
20
20
|
|
|
21
|
-
type RenderHandle = Pick<Instance, "clear" | "cleanup" | "waitUntilExit">;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
type RenderHandle = Pick<Instance, "clear" | "cleanup" | "waitUntilExit">;
|
|
22
|
+
|
|
23
|
+
export function resolveKittyKeyboardOptions(
|
|
24
|
+
env: Record<string, string | undefined>,
|
|
25
|
+
): RenderOptions["kittyKeyboard"] | undefined {
|
|
26
|
+
// Ink's auto mode probes with CSI ? u. Kitty's response can race Ink's
|
|
27
|
+
// regular input listeners and leak into both the first frame and composer.
|
|
28
|
+
// A direct Kitty session is already known to support the protocol, so enable
|
|
29
|
+
// it without probing. Avoid protocol negotiation in all other terminals.
|
|
30
|
+
if ((env.TERM ?? "").toLowerCase() !== "xterm-kitty") {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
mode: "enabled",
|
|
36
|
+
flags: ["disambiguateEscapeCodes"],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
26
39
|
|
|
27
40
|
export interface AppStdout {
|
|
28
41
|
isTTY: boolean;
|
|
@@ -222,10 +235,11 @@ export function startApp({
|
|
|
222
235
|
process.on("uncaughtException", handleFatal);
|
|
223
236
|
process.on("unhandledRejection", handleFatal);
|
|
224
237
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
238
|
+
const kittyKeyboard = resolveKittyKeyboardOptions(env);
|
|
239
|
+
renderHandle = renderApp(<App launchArgs={launchArgs} />, {
|
|
240
|
+
...(kittyKeyboard ? { kittyKeyboard } : {}),
|
|
241
|
+
stdout: wrappedStdout as any,
|
|
242
|
+
});
|
|
229
243
|
|
|
230
244
|
// Resolve the real Ink class instance to get access to lastOutput,
|
|
231
245
|
// onRender, calculateLayout, etc. Gracefully degrades to null in tests.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import { useTheme } from "
|
|
3
|
+
import { useTheme } from "../theme.js";
|
|
4
4
|
|
|
5
5
|
// ─── WaveBar ─────────────────────────────────────────────────────────────────
|
|
6
6
|
// Audio-visualizer style wave. Renders as a single <Text> node to minimise
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { Text } from "ink";
|
|
3
|
-
import type { UIState, ExternalCliStatus } from "
|
|
4
|
-
import { useTheme } from "
|
|
3
|
+
import type { UIState, ExternalCliStatus } from "../../session/types.js";
|
|
4
|
+
import { useTheme } from "../theme.js";
|
|
5
5
|
|
|
6
6
|
const SPINNER_FRAMES = ["?", "?", "?", "?", "?", "?", "?", "?", "?", "?"];
|
|
7
7
|
const STREAMING_FRAMES = ["?", "?", "?", "?"];
|
|
@@ -36,7 +36,7 @@ export function ActivityIndicator({ uiState, externalCliStatus = "idle" }: Activ
|
|
|
36
36
|
let bold = false;
|
|
37
37
|
|
|
38
38
|
if (isError) {
|
|
39
|
-
glyph = "�";
|
|
39
|
+
glyph = "�";
|
|
40
40
|
color = theme.error;
|
|
41
41
|
bold = true;
|
|
42
42
|
} else if (isAction) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { useEffect, useState } from "react";
|
|
2
2
|
import { Text } from "ink";
|
|
3
|
-
import * as renderDebug from "
|
|
4
|
-
import { useTheme } from "
|
|
5
|
-
import { sanitizeTerminalOutput } from "
|
|
3
|
+
import * as renderDebug from "../../core/perf/renderDebug.js";
|
|
4
|
+
import { useTheme } from "../theme.js";
|
|
5
|
+
import { sanitizeTerminalOutput } from "../../core/terminal/terminalSanitize.js";
|
|
6
6
|
import { BUSY_STATUS_FRAME_MS, BUSY_STATUS_FRAMES, getBusyStatusFrame } from "./busyStatusAnimation.js";
|
|
7
7
|
|
|
8
8
|
interface AnimatedStatusTextProps {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React, { memo, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { Box, Text, useStdin } from "ink";
|
|
3
|
-
import { useTheme } from "
|
|
4
|
-
import type { RuntimeSummary } from "
|
|
5
|
-
import type { CodexAuthState } from "
|
|
6
|
-
import { HEADER_CONFIG_DEFAULTS, type HeaderConfig } from "
|
|
7
|
-
import * as renderDebug from "
|
|
8
|
-
import type { Screen, TimelineEvent, UIState } from "
|
|
9
|
-
import { isBusy } from "
|
|
3
|
+
import { useTheme } from "../theme.js";
|
|
4
|
+
import type { RuntimeSummary } from "../../config/runtimeConfig.js";
|
|
5
|
+
import type { CodexAuthState } from "../../core/auth/codexAuth.js";
|
|
6
|
+
import { HEADER_CONFIG_DEFAULTS, type HeaderConfig } from "../../config/settings.js";
|
|
7
|
+
import * as renderDebug from "../../core/perf/renderDebug.js";
|
|
8
|
+
import type { Screen, TimelineEvent, UIState } from "../../session/types.js";
|
|
9
|
+
import { isBusy } from "../../session/types.js";
|
|
10
10
|
import {
|
|
11
11
|
ActivePanelLayoutContext,
|
|
12
12
|
type ActivePanelLayout,
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
MIN_TERMINAL_COLS,
|
|
25
25
|
MIN_TERMINAL_ROWS,
|
|
26
26
|
type TerminalViewport,
|
|
27
|
-
} from "
|
|
27
|
+
} from "../layout.js";
|
|
28
28
|
import {
|
|
29
29
|
buildActiveRenderItems,
|
|
30
30
|
buildStaticRenderItems,
|
|
@@ -33,9 +33,9 @@ import {
|
|
|
33
33
|
Timeline,
|
|
34
34
|
TimelineRowView,
|
|
35
35
|
type TimelineItem,
|
|
36
|
-
} from "
|
|
37
|
-
import { buildNativeTranscriptParts, type NativeTranscriptRowItem, type TimelineRow } from "
|
|
38
|
-
import type { TerminalSelectionProfile } from "
|
|
36
|
+
} from "../timeline/Timeline.js";
|
|
37
|
+
import { buildNativeTranscriptParts, type NativeTranscriptRowItem, type TimelineRow } from "../timeline/timelineMeasure.js";
|
|
38
|
+
import type { TerminalSelectionProfile } from "../../core/terminal/terminalSelection.js";
|
|
39
39
|
import { MemoizedTopHeader, measureTopHeaderRows, type UpdateAvailableInfo } from "./TopHeader.js";
|
|
40
40
|
|
|
41
41
|
const COMPACT_HEADER_TO_COMPOSER_GAP_ROWS = 1;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React, { memo, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { Box, Text, useFocus, useInput, useStdin } from "ink";
|
|
3
|
-
import { formatContextCompact } from "
|
|
4
|
-
import type { ModelSpec } from "
|
|
5
|
-
import type { ExternalCliStatus, UIState } from "
|
|
6
|
-
import { FOCUS_IDS } from "
|
|
3
|
+
import { formatContextCompact } from "../../core/providerRuntime/contextMetadata.js";
|
|
4
|
+
import type { ModelSpec } from "../../core/models/modelSpecs.js";
|
|
5
|
+
import type { ExternalCliStatus, UIState } from "../../session/types.js";
|
|
6
|
+
import { FOCUS_IDS } from "../input/focus.js";
|
|
7
7
|
import {
|
|
8
8
|
createInputViewport,
|
|
9
9
|
deleteInputBackward,
|
|
@@ -14,22 +14,22 @@ import {
|
|
|
14
14
|
moveCursorRight,
|
|
15
15
|
normalizeInputText,
|
|
16
16
|
normalizeCursorOffset,
|
|
17
|
-
} from "
|
|
18
|
-
import { getModeDisplaySpec } from "
|
|
17
|
+
} from "../input/inputBuffer.js";
|
|
18
|
+
import { getModeDisplaySpec } from "../render/modeDisplay.js";
|
|
19
19
|
import { ActivityIndicator } from "./ActivityIndicator.js";
|
|
20
20
|
import { measureRunFooterRows, MemoizedRunFooter } from "./RunFooter.js";
|
|
21
|
-
import { useTheme } from "
|
|
22
|
-
import { clampVisualText, getShellWidth, type Layout } from "
|
|
23
|
-
import { getTextWidth, splitTextAtColumn } from "
|
|
24
|
-
import { useThrottledValue } from "
|
|
25
|
-
import { sanitizeTerminalOutput } from "
|
|
26
|
-
import { getStdinDebugState, traceInputDebug } from "
|
|
27
|
-
import * as renderDebug from "
|
|
21
|
+
import { useTheme } from "../theme.js";
|
|
22
|
+
import { clampVisualText, getShellWidth, type Layout } from "../layout.js";
|
|
23
|
+
import { getTextWidth, splitTextAtColumn } from "../render/textLayout.js";
|
|
24
|
+
import { useThrottledValue } from "../useThrottledValue.js";
|
|
25
|
+
import { sanitizeTerminalOutput } from "../../core/terminal/terminalSanitize.js";
|
|
26
|
+
import { getStdinDebugState, traceInputDebug } from "../../core/debug/inputDebug.js";
|
|
27
|
+
import * as renderDebug from "../../core/perf/renderDebug.js";
|
|
28
28
|
import { AnimatedStatusText } from "./AnimatedStatusText.js";
|
|
29
29
|
import { isAnimatedBusyState } from "./busyStatusAnimation.js";
|
|
30
30
|
import { Spinner } from "./Spinner.js";
|
|
31
|
-
import type { TerminalSelectionProfile } from "
|
|
32
|
-
import { getSlashCommandSuggestions, type CommandSuggestion } from "
|
|
31
|
+
import type { TerminalSelectionProfile } from "../../core/terminal/terminalSelection.js";
|
|
32
|
+
import { getSlashCommandSuggestions, type CommandSuggestion } from "../input/slashCommands.js";
|
|
33
33
|
|
|
34
34
|
// ─── Types & constants ────────────────────────────────────────────────────────
|
|
35
35
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
3
|
import { Box, Text } from "ink";
|
|
4
|
-
import { useTheme } from "
|
|
5
|
-
import { getVisualWidth } from "
|
|
4
|
+
import { useTheme } from "../theme.js";
|
|
5
|
+
import { getVisualWidth } from "../layout.js";
|
|
6
6
|
|
|
7
7
|
interface DashCardProps {
|
|
8
8
|
cols: number;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { memo } from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import type { UIState } from "
|
|
4
|
-
import * as renderDebug from "
|
|
5
|
-
import { useTheme } from "
|
|
3
|
+
import type { UIState } from "../../session/types.js";
|
|
4
|
+
import * as renderDebug from "../../core/perf/renderDebug.js";
|
|
5
|
+
import { useTheme } from "../theme.js";
|
|
6
6
|
import { AnimatedStatusText } from "./AnimatedStatusText.js";
|
|
7
7
|
import { isAnimatedBusyState } from "./busyStatusAnimation.js";
|
|
8
8
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import { useTheme } from "
|
|
4
|
-
import type { Layout } from "
|
|
5
|
-
import * as renderDebug from "
|
|
3
|
+
import { useTheme } from "../theme.js";
|
|
4
|
+
import type { Layout } from "../layout.js";
|
|
5
|
+
import * as renderDebug from "../../core/perf/renderDebug.js";
|
|
6
6
|
|
|
7
7
|
export type RuntimeAvailability = "available" | "checking" | "reconnecting" | "unavailable" | "unknown";
|
|
8
8
|
|