@golba98/codexa 1.0.1
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 +320 -0
- package/bin/codexa.js +445 -0
- package/package.json +45 -0
- package/scripts/audit-codexa-capabilities.mjs +466 -0
- package/scripts/smoke-terminal-bench.mjs +35 -0
- package/src/app.tsx +4561 -0
- package/src/appRenderStability.test.ts +131 -0
- package/src/commands/handler.test.ts +643 -0
- package/src/commands/handler.ts +875 -0
- package/src/config/launchArgs.test.ts +158 -0
- package/src/config/launchArgs.ts +186 -0
- package/src/config/layeredConfig.test.ts +143 -0
- package/src/config/layeredConfig.ts +836 -0
- package/src/config/persistence.test.ts +110 -0
- package/src/config/persistence.ts +311 -0
- package/src/config/runtimeConfig.test.ts +218 -0
- package/src/config/runtimeConfig.ts +554 -0
- package/src/config/settings.test.ts +155 -0
- package/src/config/settings.ts +401 -0
- package/src/config/toml-serialize.ts +98 -0
- package/src/config/trustStore.test.ts +29 -0
- package/src/config/trustStore.ts +68 -0
- package/src/core/attachments.test.ts +155 -0
- package/src/core/attachments.ts +71 -0
- package/src/core/auth/codexAuth.test.ts +68 -0
- package/src/core/auth/codexAuth.ts +359 -0
- package/src/core/cleanupFastFail.test.ts +76 -0
- package/src/core/cleanupFastFail.ts +67 -0
- package/src/core/clipboard.ts +24 -0
- package/src/core/codex.ts +124 -0
- package/src/core/codexExecArgs.test.ts +195 -0
- package/src/core/codexExecArgs.ts +152 -0
- package/src/core/codexLaunch.test.ts +205 -0
- package/src/core/codexLaunch.ts +162 -0
- package/src/core/codexPrompt.test.ts +252 -0
- package/src/core/codexPrompt.ts +428 -0
- package/src/core/executables/claudeExecutable.ts +63 -0
- package/src/core/executables/codexExecutable.test.ts +212 -0
- package/src/core/executables/codexExecutable.ts +159 -0
- package/src/core/executables/executableResolver.test.ts +129 -0
- package/src/core/executables/executableResolver.ts +138 -0
- package/src/core/executables/geminiExecutable.test.ts +116 -0
- package/src/core/executables/geminiExecutable.ts +78 -0
- package/src/core/executables/pathSanityScan.test.ts +47 -0
- package/src/core/githubDiagnostics.test.ts +92 -0
- package/src/core/githubDiagnostics.ts +222 -0
- package/src/core/hollowResponseFormat.test.ts +58 -0
- package/src/core/hollowResponseFormat.ts +39 -0
- package/src/core/inputDebug.ts +51 -0
- package/src/core/launchContext.test.ts +157 -0
- package/src/core/launchContext.ts +266 -0
- package/src/core/models/codexCapabilities.test.ts +45 -0
- package/src/core/models/codexCapabilities.ts +95 -0
- package/src/core/models/codexModelCapabilities.test.ts +246 -0
- package/src/core/models/codexModelCapabilities.ts +571 -0
- package/src/core/models/modelSpecs.test.ts +283 -0
- package/src/core/models/modelSpecs.ts +300 -0
- package/src/core/perf/profiler.ts +125 -0
- package/src/core/perf/renderDebug.test.ts +230 -0
- package/src/core/perf/renderDebug.ts +373 -0
- package/src/core/planStorage.test.ts +143 -0
- package/src/core/planStorage.ts +141 -0
- package/src/core/process/CommandRunner.test.ts +105 -0
- package/src/core/process/CommandRunner.ts +269 -0
- package/src/core/process/processValidation.ts +101 -0
- package/src/core/projectInstructions.test.ts +50 -0
- package/src/core/projectInstructions.ts +54 -0
- package/src/core/providerLauncher/launcher.test.ts +238 -0
- package/src/core/providerLauncher/launcher.ts +203 -0
- package/src/core/providerLauncher/registry.test.ts +324 -0
- package/src/core/providerLauncher/registry.ts +253 -0
- package/src/core/providerLauncher/types.ts +84 -0
- package/src/core/providerLauncher/workspaceConfig.test.ts +638 -0
- package/src/core/providerLauncher/workspaceConfig.ts +407 -0
- package/src/core/providerRuntime/anthropic.test.ts +1120 -0
- package/src/core/providerRuntime/anthropic.ts +576 -0
- package/src/core/providerRuntime/capabilityProfile.test.ts +311 -0
- package/src/core/providerRuntime/capabilityProfile.ts +288 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +446 -0
- package/src/core/providerRuntime/contextMetadata.test.ts +468 -0
- package/src/core/providerRuntime/contextMetadata.ts +409 -0
- package/src/core/providerRuntime/gemini.test.ts +437 -0
- package/src/core/providerRuntime/gemini.ts +784 -0
- package/src/core/providerRuntime/lmstudio.test.ts +168 -0
- package/src/core/providerRuntime/lmstudio.ts +118 -0
- package/src/core/providerRuntime/local.test.ts +787 -0
- package/src/core/providerRuntime/local.ts +754 -0
- package/src/core/providerRuntime/models.ts +150 -0
- package/src/core/providerRuntime/reasoning.ts +17 -0
- package/src/core/providerRuntime/registry.test.ts +233 -0
- package/src/core/providerRuntime/registry.ts +203 -0
- package/src/core/providerRuntime/types.ts +103 -0
- package/src/core/providers/codexJsonStream.test.ts +148 -0
- package/src/core/providers/codexJsonStream.ts +305 -0
- package/src/core/providers/codexSubprocess.test.ts +68 -0
- package/src/core/providers/codexSubprocess.ts +372 -0
- package/src/core/providers/codexTranscript.test.ts +284 -0
- package/src/core/providers/codexTranscript.ts +695 -0
- package/src/core/providers/openaiNative.ts +13 -0
- package/src/core/providers/registry.ts +21 -0
- package/src/core/providers/types.ts +59 -0
- package/src/core/terminal/terminalCapabilities.test.ts +93 -0
- package/src/core/terminal/terminalCapabilities.ts +100 -0
- package/src/core/terminal/terminalControl.test.ts +75 -0
- package/src/core/terminal/terminalControl.ts +147 -0
- package/src/core/terminal/terminalSanitize.test.ts +22 -0
- package/src/core/terminal/terminalSanitize.ts +147 -0
- package/src/core/terminal/terminalSelection.test.ts +42 -0
- package/src/core/terminal/terminalSelection.ts +66 -0
- package/src/core/terminal/terminalTitle.test.ts +328 -0
- package/src/core/terminal/terminalTitle.ts +483 -0
- package/src/core/workspaceActivity.test.ts +163 -0
- package/src/core/workspaceActivity.ts +380 -0
- package/src/core/workspaceGuard.test.ts +151 -0
- package/src/core/workspaceGuard.ts +288 -0
- package/src/core/workspaceRoot.test.ts +23 -0
- package/src/core/workspaceRoot.ts +47 -0
- package/src/exec.test.ts +13 -0
- package/src/exec.ts +72 -0
- package/src/headless/execArgs.test.ts +147 -0
- package/src/headless/execArgs.ts +294 -0
- package/src/headless/execRunner.test.ts +434 -0
- package/src/headless/execRunner.ts +304 -0
- package/src/index.test.tsx +618 -0
- package/src/index.tsx +296 -0
- package/src/session/appSession.test.ts +897 -0
- package/src/session/appSession.ts +761 -0
- package/src/session/chatLifecycle.test.ts +64 -0
- package/src/session/chatLifecycle.ts +951 -0
- package/src/session/liveRenderScheduler.test.ts +201 -0
- package/src/session/liveRenderScheduler.ts +214 -0
- package/src/session/planFlow.test.ts +103 -0
- package/src/session/planFlow.ts +149 -0
- package/src/session/planTranscript.test.ts +65 -0
- package/src/session/planTranscript.ts +15 -0
- package/src/session/promptRunSchedule.test.ts +36 -0
- package/src/session/promptRunSchedule.ts +26 -0
- package/src/session/types.ts +228 -0
- package/src/test/runtimeTestUtils.ts +14 -0
- package/src/types/react-dom.d.ts +3 -0
- package/src/ui/ActionRequiredBlock.tsx +38 -0
- package/src/ui/ActivityBars.tsx +68 -0
- package/src/ui/ActivityIndicator.test.tsx +58 -0
- package/src/ui/ActivityIndicator.tsx +58 -0
- package/src/ui/AgentBlock.test.ts +6 -0
- package/src/ui/AgentBlock.tsx +130 -0
- package/src/ui/AnimatedStatusText.test.ts +16 -0
- package/src/ui/AnimatedStatusText.tsx +69 -0
- package/src/ui/AppShell.test.tsx +1739 -0
- package/src/ui/AppShell.tsx +698 -0
- package/src/ui/AttachmentImportPanel.test.tsx +204 -0
- package/src/ui/AttachmentImportPanel.tsx +98 -0
- package/src/ui/AuthPanel.tsx +113 -0
- package/src/ui/BackendPicker.tsx +28 -0
- package/src/ui/BottomComposer.test.ts +674 -0
- package/src/ui/BottomComposer.tsx +1028 -0
- package/src/ui/CodexLogo.tsx +55 -0
- package/src/ui/DashCard.tsx +82 -0
- package/src/ui/Markdown.test.ts +157 -0
- package/src/ui/Markdown.tsx +310 -0
- package/src/ui/ModePicker.tsx +27 -0
- package/src/ui/ModelPicker.tsx +31 -0
- package/src/ui/ModelPickerProviderScope.test.tsx +411 -0
- package/src/ui/ModelPickerScreen.test.tsx +99 -0
- package/src/ui/ModelPickerScreen.tsx +416 -0
- package/src/ui/ModelPickerState.test.tsx +151 -0
- package/src/ui/ModelReasoningPicker.test.tsx +447 -0
- package/src/ui/ModelReasoningPicker.tsx +458 -0
- package/src/ui/Panel.tsx +51 -0
- package/src/ui/PermissionsPanel.tsx +78 -0
- package/src/ui/PlanActionPicker.tsx +119 -0
- package/src/ui/PlanReviewPanel.test.tsx +267 -0
- package/src/ui/PlanReviewPanel.tsx +212 -0
- package/src/ui/PromptCardBorder.test.tsx +161 -0
- package/src/ui/ProviderPicker.test.tsx +289 -0
- package/src/ui/ProviderPicker.tsx +321 -0
- package/src/ui/ProviderShortcut.test.tsx +143 -0
- package/src/ui/ReasoningPicker.tsx +46 -0
- package/src/ui/RunFooter.tsx +65 -0
- package/src/ui/SelectionPanel.tsx +67 -0
- package/src/ui/SettingsPanel.test.tsx +233 -0
- package/src/ui/SettingsPanel.tsx +156 -0
- package/src/ui/Spinner.tsx +25 -0
- package/src/ui/StaticIntroItem.tsx +54 -0
- package/src/ui/StaticTranscriptItem.tsx +56 -0
- package/src/ui/TextEntryPanel.tsx +139 -0
- package/src/ui/ThemePicker.tsx +31 -0
- package/src/ui/ThinkingBlock.tsx +100 -0
- package/src/ui/Timeline.test.ts +2067 -0
- package/src/ui/Timeline.tsx +1472 -0
- package/src/ui/TimelineNavigation.test.tsx +201 -0
- package/src/ui/TopHeader.test.tsx +239 -0
- package/src/ui/TopHeader.tsx +257 -0
- package/src/ui/TurnGroup.test.tsx +365 -0
- package/src/ui/TurnGroup.tsx +657 -0
- package/src/ui/busyStatusAnimation.test.ts +30 -0
- package/src/ui/busyStatusAnimation.ts +11 -0
- package/src/ui/commandNormalize.test.ts +142 -0
- package/src/ui/commandNormalize.ts +66 -0
- package/src/ui/diffRenderer.test.ts +102 -0
- package/src/ui/diffRenderer.ts +116 -0
- package/src/ui/focus.ts +61 -0
- package/src/ui/focusFlow.test.tsx +1098 -0
- package/src/ui/inputBuffer.test.ts +151 -0
- package/src/ui/inputBuffer.ts +203 -0
- package/src/ui/layout.test.ts +145 -0
- package/src/ui/layout.ts +287 -0
- package/src/ui/modeDisplay.test.ts +42 -0
- package/src/ui/modeDisplay.ts +52 -0
- package/src/ui/outputPipeline.ts +64 -0
- package/src/ui/progressEntries.ts +156 -0
- package/src/ui/runActivityView.test.ts +89 -0
- package/src/ui/runActivityView.ts +37 -0
- package/src/ui/runLifecycleView.test.tsx +237 -0
- package/src/ui/slashCommands.ts +41 -0
- package/src/ui/statusRenderIsolation.test.tsx +654 -0
- package/src/ui/terminalAnswerFormat.test.ts +19 -0
- package/src/ui/terminalAnswerFormat.ts +128 -0
- package/src/ui/textLayout.test.ts +18 -0
- package/src/ui/textLayout.ts +338 -0
- package/src/ui/theme.tsx +395 -0
- package/src/ui/themeFlow.test.ts +53 -0
- package/src/ui/themeFlow.ts +41 -0
- package/src/ui/timelineMeasure.ts +3088 -0
- package/src/ui/timelineMeasureCache.test.ts +986 -0
- package/src/ui/useThrottledValue.ts +31 -0
package/bin/codexa.js
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import { appendFileSync, mkdirSync, readFileSync } from "fs";
|
|
5
|
+
import { dirname, join } from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
|
|
8
|
+
process.title = "CODEXA";
|
|
9
|
+
|
|
10
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
11
|
+
const packageRoot = dirname(dirname(currentFile));
|
|
12
|
+
const forwardArgs = process.argv.slice(2);
|
|
13
|
+
const workspaceRoot = process.cwd();
|
|
14
|
+
const launcherStartTimeMs = Number(process.env.CODEXA_EXEC_TIMING_EPOCH_MS) || Date.now();
|
|
15
|
+
let launcherPreviousElapsedMs = 0;
|
|
16
|
+
const titleSequencePattern = /\x1b\](?:0|2);[^\x07]*(?:\x07|\x1b\\)/g;
|
|
17
|
+
const titleSequenceDetectPattern = /\x1b\]([02]);([\s\S]*?)(?:\x07|\x1b\\)/g;
|
|
18
|
+
const incompleteTitleSequencePattern = /\x1b\](?:0|2);[^\x07]*$/;
|
|
19
|
+
let intendedTerminalTitle = "Codexa";
|
|
20
|
+
|
|
21
|
+
function writeRenderDebugRecord(kind, fields) {
|
|
22
|
+
if (process.env.CODEXA_RENDER_DEBUG !== "1" && process.env.CODEXA_DEBUG_RENDER !== "1") {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const debugDir = join(workspaceRoot, ".codexa-debug");
|
|
28
|
+
mkdirSync(debugDir, { recursive: true });
|
|
29
|
+
appendFileSync(
|
|
30
|
+
join(debugDir, "render-debug.log"),
|
|
31
|
+
JSON.stringify({
|
|
32
|
+
ts: Date.now(),
|
|
33
|
+
pid: process.pid,
|
|
34
|
+
sessionId: `launcher-${Date.now()}-${process.pid}`,
|
|
35
|
+
kind,
|
|
36
|
+
...fields,
|
|
37
|
+
}) + "\n",
|
|
38
|
+
"utf8",
|
|
39
|
+
);
|
|
40
|
+
} catch {
|
|
41
|
+
// Debug logging must never disturb launcher startup.
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function debugLaunch(message, fields = {}) {
|
|
46
|
+
if (process.env.CODEXA_DEBUG_LAUNCH !== "1") {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const serializedFields = Object.entries(fields)
|
|
51
|
+
.map(([key, value]) => `${key}=${JSON.stringify(value)}`)
|
|
52
|
+
.join(" ");
|
|
53
|
+
process.stderr.write(`[codexa:launch] ${message}${serializedFields ? ` ${serializedFields}` : ""}\n`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function writeTerminalTitleDebugRecord(fields) {
|
|
57
|
+
if (process.env.CODEXA_DEBUG_TERMINAL_TITLE !== "1") {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const debugDir = join(workspaceRoot, ".codexa-debug");
|
|
63
|
+
mkdirSync(debugDir, { recursive: true });
|
|
64
|
+
appendFileSync(
|
|
65
|
+
process.env.CODEXA_TERMINAL_TITLE_DEBUG_FILE?.trim() || join(debugDir, "terminal-title-debug.log"),
|
|
66
|
+
JSON.stringify({
|
|
67
|
+
ts: Date.now(),
|
|
68
|
+
pid: process.pid,
|
|
69
|
+
lifecycleState: "launcher",
|
|
70
|
+
...fields,
|
|
71
|
+
}) + "\n",
|
|
72
|
+
"utf8",
|
|
73
|
+
);
|
|
74
|
+
} catch {
|
|
75
|
+
// Debug logging must never disturb launcher startup.
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function traceTitleSequences(text, fields) {
|
|
80
|
+
if (!text) return false;
|
|
81
|
+
let found = false;
|
|
82
|
+
titleSequenceDetectPattern.lastIndex = 0;
|
|
83
|
+
for (let match = titleSequenceDetectPattern.exec(text); match; match = titleSequenceDetectPattern.exec(text)) {
|
|
84
|
+
found = true;
|
|
85
|
+
const title = match[2] || "";
|
|
86
|
+
writeTerminalTitleDebugRecord({
|
|
87
|
+
event: "terminalTitleSequence",
|
|
88
|
+
osc: match[1] === "2" ? "OSC 2" : "OSC 0",
|
|
89
|
+
title,
|
|
90
|
+
containsWindowsSystem: title.toLowerCase().includes("c:\\windows\\system"),
|
|
91
|
+
bytes: Buffer.byteLength(match[0] || ""),
|
|
92
|
+
...fields,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return found;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function sanitizeTerminalTitle(title) {
|
|
99
|
+
return String(title ?? "")
|
|
100
|
+
.replace(/[\u0000-\u001f\u007f]/g, " ")
|
|
101
|
+
.replace(/\x1b/g, "")
|
|
102
|
+
.trim();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function normalizeTerminalTitle(title) {
|
|
106
|
+
const cleanTitle = sanitizeTerminalTitle(title);
|
|
107
|
+
if (!cleanTitle || /^[a-zA-Z]:[\\/]/.test(cleanTitle) || /^\\\\/.test(cleanTitle)) {
|
|
108
|
+
return "Codexa";
|
|
109
|
+
}
|
|
110
|
+
return cleanTitle;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function buildTitleSequence(title) {
|
|
114
|
+
const safeTitle = normalizeTerminalTitle(title);
|
|
115
|
+
return `\x1b]0;${safeTitle}\x07\x1b]2;${safeTitle}\x07`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function writeIntendedTitle(reason, force = true) {
|
|
119
|
+
const sequence = buildTitleSequence(intendedTerminalTitle);
|
|
120
|
+
writeRenderDebugRecord("stdout", {
|
|
121
|
+
event: "directWrite",
|
|
122
|
+
source: `bin/codexa.js:${reason}`,
|
|
123
|
+
bytes: Buffer.byteLength(sequence),
|
|
124
|
+
containsViewportClear: false,
|
|
125
|
+
containsScrollbackClear: false,
|
|
126
|
+
containsCursorHome: false,
|
|
127
|
+
containsTerminalReset: false,
|
|
128
|
+
containsTitleSequence: true,
|
|
129
|
+
});
|
|
130
|
+
process.stdout.write(sequence);
|
|
131
|
+
writeTerminalTitleDebugRecord({
|
|
132
|
+
event: "codexaTitleWrite",
|
|
133
|
+
source: `bin/codexa.js:${reason}`,
|
|
134
|
+
title: intendedTerminalTitle,
|
|
135
|
+
reason,
|
|
136
|
+
force,
|
|
137
|
+
bytes: Buffer.byteLength(sequence),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function startLauncherTitleGuard() {
|
|
142
|
+
const startedAt = Date.now();
|
|
143
|
+
const interval = setInterval(() => {
|
|
144
|
+
if (Date.now() - startedAt >= 2500) {
|
|
145
|
+
clearInterval(interval);
|
|
146
|
+
writeIntendedTitle("launcher-title-guard-end");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
writeIntendedTitle("launcher-title-guard");
|
|
150
|
+
}, 150);
|
|
151
|
+
interval.unref?.();
|
|
152
|
+
return () => clearInterval(interval);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function createTitleStripper(fields) {
|
|
156
|
+
let carryover = "";
|
|
157
|
+
return (chunk) => {
|
|
158
|
+
const input = carryover + Buffer.from(chunk).toString("utf8");
|
|
159
|
+
carryover = "";
|
|
160
|
+
const incomplete = incompleteTitleSequencePattern.exec(input);
|
|
161
|
+
const processable = incomplete?.index != null ? input.slice(0, incomplete.index) : input;
|
|
162
|
+
if (incomplete?.index != null) {
|
|
163
|
+
carryover = input.slice(incomplete.index);
|
|
164
|
+
}
|
|
165
|
+
return traceTitleSequences(processable, fields)
|
|
166
|
+
? processable.replace(titleSequencePattern, "")
|
|
167
|
+
: processable;
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function hasFlag(args, longFlag, shortFlag) {
|
|
172
|
+
for (const arg of args) {
|
|
173
|
+
if (arg === "--") return false;
|
|
174
|
+
if (arg === longFlag || arg === shortFlag) return true;
|
|
175
|
+
}
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function readPackageVersion() {
|
|
180
|
+
try {
|
|
181
|
+
const raw = readFileSync(join(packageRoot, "package.json"), "utf8");
|
|
182
|
+
const parsed = JSON.parse(raw);
|
|
183
|
+
return typeof parsed.version === "string" && parsed.version.trim()
|
|
184
|
+
? parsed.version.trim()
|
|
185
|
+
: null;
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function printHelp() {
|
|
192
|
+
const version = readPackageVersion();
|
|
193
|
+
const versionLine = version ? `codexa ${version}` : "codexa";
|
|
194
|
+
console.log(`${versionLine}
|
|
195
|
+
|
|
196
|
+
Usage:
|
|
197
|
+
codexa
|
|
198
|
+
codexa "explain this repo"
|
|
199
|
+
codexa exec "print the current directory"
|
|
200
|
+
codexa --headless-benchmark "print the current directory"
|
|
201
|
+
codexa [options] [prompt]
|
|
202
|
+
|
|
203
|
+
Options:
|
|
204
|
+
-h, --help Show this help text and exit.
|
|
205
|
+
-v, --version Show the installed Codexa version and exit.
|
|
206
|
+
--headless-benchmark
|
|
207
|
+
Run Codexa through the headless benchmark path.
|
|
208
|
+
--profile <name> Load a Codex profile from config.
|
|
209
|
+
-m, --model <name> Select the Codex model for this launch.
|
|
210
|
+
--reasoning <effort>
|
|
211
|
+
Reasoning effort for codexa exec: none, minimal, low, medium, high, xhigh.
|
|
212
|
+
-c, --config <key=val> Override a runtime config value.
|
|
213
|
+
|
|
214
|
+
Inside Codexa:
|
|
215
|
+
/help Show interactive commands.
|
|
216
|
+
/model Open model selection.
|
|
217
|
+
/mode Open mode selection.
|
|
218
|
+
/exit Exit the interactive UI.
|
|
219
|
+
`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const isHeadlessExec = forwardArgs[0] === "exec";
|
|
223
|
+
const isHeadlessBenchmark = forwardArgs[0] === "--headless-benchmark";
|
|
224
|
+
const isHeadlessMode = isHeadlessExec || isHeadlessBenchmark;
|
|
225
|
+
const execTimingEnabled = isHeadlessMode
|
|
226
|
+
&& (
|
|
227
|
+
process.env.CODEXA_EXEC_TIMING === "1"
|
|
228
|
+
|| forwardArgs.includes("--timing")
|
|
229
|
+
|| forwardArgs.includes("--benchmark-diagnostics")
|
|
230
|
+
);
|
|
231
|
+
const parentStdinIsTTY = Boolean(process.stdin.isTTY);
|
|
232
|
+
const parentStdoutIsTTY = Boolean(process.stdout.isTTY);
|
|
233
|
+
const parentStderrIsTTY = Boolean(process.stderr.isTTY);
|
|
234
|
+
const parentHasTTY = parentStdinIsTTY && parentStdoutIsTTY;
|
|
235
|
+
|
|
236
|
+
function markExecTiming(phase, fields = {}) {
|
|
237
|
+
if (!execTimingEnabled) return;
|
|
238
|
+
const elapsedMs = Date.now() - launcherStartTimeMs;
|
|
239
|
+
const deltaMs = elapsedMs - launcherPreviousElapsedMs;
|
|
240
|
+
launcherPreviousElapsedMs = elapsedMs;
|
|
241
|
+
const formattedFields = Object.entries(fields)
|
|
242
|
+
.map(([key, value]) => {
|
|
243
|
+
const serialized = Array.isArray(value) || typeof value === "string"
|
|
244
|
+
? JSON.stringify(value)
|
|
245
|
+
: String(value);
|
|
246
|
+
return `${key}=${serialized}`;
|
|
247
|
+
})
|
|
248
|
+
.join(" ");
|
|
249
|
+
process.stderr.write(`[codexa exec timing] phase=${phase} elapsed_ms=${elapsedMs} delta_ms=${deltaMs}${formattedFields ? ` ${formattedFields}` : ""}\n`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
markExecTiming("launcher_start", { pid: process.pid });
|
|
253
|
+
|
|
254
|
+
let stopLauncherTitleGuard = null;
|
|
255
|
+
if (!isHeadlessMode && parentHasTTY) {
|
|
256
|
+
intendedTerminalTitle = normalizeTerminalTitle(process.env.CODEXA_INITIAL_TERMINAL_TITLE || "Codexa");
|
|
257
|
+
writeIntendedTitle("launcher-startup-title");
|
|
258
|
+
stopLauncherTitleGuard = startLauncherTitleGuard();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (!isHeadlessMode && hasFlag(forwardArgs, "--help", "-h")) {
|
|
262
|
+
printHelp();
|
|
263
|
+
process.exit(0);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (!isHeadlessMode && hasFlag(forwardArgs, "--version", "-v")) {
|
|
267
|
+
console.log(readPackageVersion() ?? "unknown");
|
|
268
|
+
process.exit(0);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Filters out terminal mouse reporting escape sequences from stdin data.
|
|
273
|
+
* Prevents SGR mouse clicks and scroll events from leaking into the TUI app.
|
|
274
|
+
*/
|
|
275
|
+
function createMouseFilter() {
|
|
276
|
+
let buffer = "";
|
|
277
|
+
let timer = null;
|
|
278
|
+
|
|
279
|
+
return (data) => {
|
|
280
|
+
buffer += data;
|
|
281
|
+
|
|
282
|
+
// Clear existing timer
|
|
283
|
+
if (timer) clearTimeout(timer);
|
|
284
|
+
|
|
285
|
+
// Check for complete SGR mouse sequences: ESC [ < button ; x ; y M/m
|
|
286
|
+
// Also handle scroll events: button 64/96 (scroll up), 65/97 (scroll down)
|
|
287
|
+
const sgrMouseRegex = /\x1b\[<[0-9]+;[0-9]+;[0-9]+[Mm]/g;
|
|
288
|
+
let hasFullSequence = sgrMouseRegex.test(buffer);
|
|
289
|
+
|
|
290
|
+
// If we have a complete sequence, filter and emit immediately
|
|
291
|
+
if (hasFullSequence) {
|
|
292
|
+
buffer = buffer.replace(sgrMouseRegex, "");
|
|
293
|
+
return buffer;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// If we have partial SGR start, wait for completion or timeout
|
|
297
|
+
if (/\x1b\[<[0-9]*;?[0-9]*;?[0-9]*$/.test(buffer)) {
|
|
298
|
+
timer = setTimeout(() => {
|
|
299
|
+
// Timeout: assume incomplete mouse sequence, just emit filtered buffer
|
|
300
|
+
timer = null;
|
|
301
|
+
}, 50);
|
|
302
|
+
return null; // Wait for more data
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Otherwise, emit immediately (normal input)
|
|
306
|
+
return buffer;
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const bunExecutable = process.env.CODEXA_BUN_EXECUTABLE?.trim()
|
|
311
|
+
|| (process.platform === "win32" ? "bun.exe" : "bun");
|
|
312
|
+
|
|
313
|
+
const appEntry = join(packageRoot, "src", "index.tsx");
|
|
314
|
+
const execEntry = join(packageRoot, "src", "exec.ts");
|
|
315
|
+
const bunEntry = isHeadlessMode ? execEntry : appEntry;
|
|
316
|
+
const bunForwardArgs = isHeadlessMode ? forwardArgs.slice(1) : forwardArgs;
|
|
317
|
+
|
|
318
|
+
// Detect if parent process has a real TTY
|
|
319
|
+
const childStdio = isHeadlessMode
|
|
320
|
+
? ["ignore", "inherit", "inherit"]
|
|
321
|
+
: parentHasTTY
|
|
322
|
+
? ["inherit", "inherit", "inherit"]
|
|
323
|
+
: ["pipe", "pipe", "pipe"];
|
|
324
|
+
|
|
325
|
+
debugLaunch("resolved launch mode", {
|
|
326
|
+
mode: isHeadlessMode ? "headless" : "interactive-ui",
|
|
327
|
+
stdinIsTTY: parentStdinIsTTY,
|
|
328
|
+
stdoutIsTTY: parentStdoutIsTTY,
|
|
329
|
+
stderrIsTTY: parentStderrIsTTY,
|
|
330
|
+
TERM: process.env.TERM,
|
|
331
|
+
WT_SESSION: process.env.WT_SESSION,
|
|
332
|
+
TERM_PROGRAM: process.env.TERM_PROGRAM,
|
|
333
|
+
argv: process.argv,
|
|
334
|
+
childStdio,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
markExecTiming("bun_spawn_start", { executable: bunExecutable });
|
|
338
|
+
if (!isHeadlessMode && parentHasTTY) {
|
|
339
|
+
writeIntendedTitle("before-bun-spawn");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const child = spawn(
|
|
343
|
+
bunExecutable,
|
|
344
|
+
["run", "--silent", bunEntry, ...(bunForwardArgs.length > 0 ? ["--", ...bunForwardArgs] : [])],
|
|
345
|
+
{
|
|
346
|
+
cwd: workspaceRoot,
|
|
347
|
+
stdio: childStdio,
|
|
348
|
+
env: {
|
|
349
|
+
...process.env,
|
|
350
|
+
CODEX_WORKSPACE_ROOT: workspaceRoot,
|
|
351
|
+
CODEXA_LAUNCH_KIND: "installed-bin",
|
|
352
|
+
CODEXA_PACKAGE_ROOT: packageRoot,
|
|
353
|
+
CODEXA_LAUNCHER_SCRIPT: currentFile,
|
|
354
|
+
CODEXA_RELAUNCH_EXECUTABLE: process.execPath,
|
|
355
|
+
CODEXA_RELAUNCH_ARGS: JSON.stringify([currentFile, ...forwardArgs]),
|
|
356
|
+
CODEXA_PARENT_HAS_TTY: parentHasTTY ? "1" : "0",
|
|
357
|
+
CODEXA_HEADLESS_BENCHMARK: isHeadlessBenchmark ? "1" : "0",
|
|
358
|
+
CODEXA_EXEC_TIMING_EPOCH_MS: String(launcherStartTimeMs),
|
|
359
|
+
CODEXA_INITIAL_TERMINAL_TITLE: intendedTerminalTitle,
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
child.once("spawn", () => {
|
|
365
|
+
if (!isHeadlessMode && parentHasTTY) {
|
|
366
|
+
writeIntendedTitle("after-bun-spawn");
|
|
367
|
+
stopLauncherTitleGuard?.();
|
|
368
|
+
stopLauncherTitleGuard = null;
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
if (!isHeadlessMode && !parentHasTTY) {
|
|
373
|
+
const mouseFilter = createMouseFilter();
|
|
374
|
+
|
|
375
|
+
process.stdin.on("data", (data) => {
|
|
376
|
+
const filtered = mouseFilter(data);
|
|
377
|
+
if (filtered) {
|
|
378
|
+
child.stdin.write(filtered);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
process.stdin.on("end", () => {
|
|
383
|
+
child.stdin.end();
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
process.stdin.on("error", (error) => {
|
|
387
|
+
console.error(`stdin error: ${error.message}`);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
child.stdin.on("error", (error) => {
|
|
391
|
+
// Ignore broken pipe errors
|
|
392
|
+
if (error.code !== "EPIPE") {
|
|
393
|
+
console.error(`child stdin error: ${error.message}`);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (!isHeadlessMode) {
|
|
399
|
+
const stripStdoutTitle = createTitleStripper({
|
|
400
|
+
source: "bin/codexa.js:bun.stdout",
|
|
401
|
+
stream: "stdout",
|
|
402
|
+
origin: "child",
|
|
403
|
+
action: "stripped",
|
|
404
|
+
});
|
|
405
|
+
const stripStderrTitle = createTitleStripper({
|
|
406
|
+
source: "bin/codexa.js:bun.stderr",
|
|
407
|
+
stream: "stderr",
|
|
408
|
+
origin: "child",
|
|
409
|
+
action: "stripped",
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
child.stdout?.on("data", (chunk) => {
|
|
413
|
+
const safeChunk = stripStdoutTitle(chunk);
|
|
414
|
+
if (safeChunk) process.stdout.write(safeChunk);
|
|
415
|
+
});
|
|
416
|
+
child.stderr?.on("data", (chunk) => {
|
|
417
|
+
const safeChunk = stripStderrTitle(chunk);
|
|
418
|
+
if (safeChunk) process.stderr.write(safeChunk);
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
child.on("error", (error) => {
|
|
423
|
+
if (!isHeadlessMode && parentHasTTY) {
|
|
424
|
+
writeIntendedTitle("bun-spawn-error");
|
|
425
|
+
}
|
|
426
|
+
markExecTiming("bun_spawn_error", { message: error.message });
|
|
427
|
+
console.error(`Failed to launch Bun: ${error.message}`);
|
|
428
|
+
console.error("Bun is required to launch codexa. Install Bun, then run this command again.");
|
|
429
|
+
process.exit(1);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
child.on("close", (code, signal) => {
|
|
433
|
+
if (!isHeadlessMode && parentHasTTY) {
|
|
434
|
+
writeIntendedTitle("bun-close");
|
|
435
|
+
stopLauncherTitleGuard?.();
|
|
436
|
+
stopLauncherTitleGuard = null;
|
|
437
|
+
}
|
|
438
|
+
markExecTiming("launcher_exit", { exit_code: code ?? 0, signal: signal ?? null });
|
|
439
|
+
if (signal) {
|
|
440
|
+
process.kill(process.pid, signal);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
process.exit(code ?? 0);
|
|
445
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@golba98/codexa",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Terminal UI for Codexa — TypeScript + Bun + Ink",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"codexa": "./bin/codexa.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "bun run --silent src/index.tsx",
|
|
11
|
+
"dev": "bun --watch --silent src/index.tsx",
|
|
12
|
+
"typecheck": "tsc --noEmit",
|
|
13
|
+
"test": "bun test",
|
|
14
|
+
"audit:codexa-gap": "node scripts/audit-codexa-capabilities.mjs",
|
|
15
|
+
"smoke:terminal-bench": "node scripts/smoke-terminal-bench.mjs",
|
|
16
|
+
"build": "bun run typecheck",
|
|
17
|
+
"prepublishOnly": "npm run typecheck && npm test"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"ink": "^7.0.0",
|
|
21
|
+
"ink-select-input": "^6.2.0",
|
|
22
|
+
"react": "^19.2.5",
|
|
23
|
+
"react-dom": "^19.2.5"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^25.5.2",
|
|
27
|
+
"@types/react": "^19.2.14",
|
|
28
|
+
"typescript": "^6.0.2"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/golba98/Codexa.git"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/golba98/Codexa/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/golba98/Codexa#readme",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"files": [
|
|
40
|
+
"bin/",
|
|
41
|
+
"src/",
|
|
42
|
+
"scripts/",
|
|
43
|
+
"config.toml"
|
|
44
|
+
]
|
|
45
|
+
}
|