@golba98/codexa 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/README.md +391 -94
  2. package/package.json +14 -8
  3. package/src/app.tsx +196 -63
  4. package/src/commands/handler.ts +9 -4
  5. package/src/config/buildInfo.ts +3 -0
  6. package/src/config/launchArgs.ts +10 -0
  7. package/src/config/layeredConfig.ts +0 -0
  8. package/src/config/persistence.ts +51 -0
  9. package/src/config/runtimeConfig.ts +4 -0
  10. package/src/config/settings.ts +6 -13
  11. package/src/config/toml-serialize.ts +0 -0
  12. package/src/config/trustStore.ts +0 -0
  13. package/src/config/updateCheckCache.ts +63 -0
  14. package/src/core/attachments.ts +0 -0
  15. package/src/core/auth/codexAuth.ts +0 -0
  16. package/src/core/channel.ts +23 -0
  17. package/src/core/cleanupFastFail.ts +0 -0
  18. package/src/core/clipboard.ts +0 -0
  19. package/src/core/codex.ts +0 -0
  20. package/src/core/codexExecArgs.ts +0 -0
  21. package/src/core/codexLaunch.ts +0 -0
  22. package/src/core/codexPrompt.ts +0 -0
  23. package/src/core/executables/claudeExecutable.ts +0 -0
  24. package/src/core/executables/codexExecutable.ts +1 -0
  25. package/src/core/executables/executableResolver.ts +61 -43
  26. package/src/core/executables/geminiExecutable.ts +0 -0
  27. package/src/core/githubDiagnostics.ts +0 -0
  28. package/src/core/hollowResponseFormat.ts +0 -0
  29. package/src/core/inputDebug.ts +0 -0
  30. package/src/core/launchContext.ts +33 -33
  31. package/src/core/models/codexCapabilities.ts +0 -0
  32. package/src/core/models/codexModelCapabilities.ts +0 -0
  33. package/src/core/models/modelSpecs.ts +0 -0
  34. package/src/core/perf/profiler.ts +0 -0
  35. package/src/core/perf/renderDebug.ts +0 -0
  36. package/src/core/planStorage.ts +0 -0
  37. package/src/core/process/CommandRunner.ts +0 -0
  38. package/src/core/process/processValidation.ts +9 -5
  39. package/src/core/projectInstructions.ts +0 -0
  40. package/src/core/providerLauncher/launcher.ts +59 -42
  41. package/src/core/providerLauncher/registry.ts +0 -0
  42. package/src/core/providerLauncher/types.ts +0 -0
  43. package/src/core/providerLauncher/workspaceConfig.ts +0 -0
  44. package/src/core/providerRuntime/anthropic.ts +0 -0
  45. package/src/core/providerRuntime/capabilityProfile.ts +0 -0
  46. package/src/core/providerRuntime/claudeCodeDiscovery.ts +0 -0
  47. package/src/core/providerRuntime/contextMetadata.ts +0 -0
  48. package/src/core/providerRuntime/gemini.ts +0 -0
  49. package/src/core/providerRuntime/lmstudio.ts +0 -0
  50. package/src/core/providerRuntime/local.ts +0 -0
  51. package/src/core/providerRuntime/models.ts +0 -0
  52. package/src/core/providerRuntime/reasoning.ts +0 -0
  53. package/src/core/providerRuntime/registry.ts +0 -0
  54. package/src/core/providerRuntime/types.ts +0 -0
  55. package/src/core/providers/codexJsonStream.ts +0 -0
  56. package/src/core/providers/codexSubprocess.ts +0 -0
  57. package/src/core/providers/codexTranscript.ts +0 -0
  58. package/src/core/providers/openaiNative.ts +0 -0
  59. package/src/core/providers/registry.ts +0 -0
  60. package/src/core/providers/types.ts +0 -0
  61. package/src/core/terminal/startupClear.ts +20 -0
  62. package/src/core/terminal/terminalCapabilities.ts +0 -0
  63. package/src/core/terminal/terminalControl.ts +0 -0
  64. package/src/core/terminal/terminalSanitize.ts +0 -0
  65. package/src/core/terminal/terminalSelection.ts +0 -0
  66. package/src/core/terminal/terminalTitle.ts +0 -0
  67. package/src/core/updateCheck.ts +188 -0
  68. package/src/core/workspaceActivity.ts +0 -0
  69. package/src/core/workspaceGuard.ts +0 -0
  70. package/src/core/workspaceRoot.ts +0 -0
  71. package/src/exec.ts +0 -0
  72. package/src/headless/execArgs.ts +1 -0
  73. package/src/headless/execRunner.ts +0 -0
  74. package/src/index.tsx +13 -9
  75. package/src/session/appSession.ts +0 -0
  76. package/src/session/chatLifecycle.ts +0 -0
  77. package/src/session/liveRenderScheduler.ts +0 -0
  78. package/src/session/planFlow.ts +0 -0
  79. package/src/session/planTranscript.ts +0 -0
  80. package/src/session/promptRunSchedule.ts +0 -0
  81. package/src/session/types.ts +2 -1
  82. package/src/test/runtimeTestUtils.ts +0 -0
  83. package/src/types/react-dom.d.ts +0 -0
  84. package/src/ui/ActionRequiredBlock.tsx +5 -5
  85. package/src/ui/ActivityBars.tsx +3 -3
  86. package/src/ui/ActivityIndicator.tsx +6 -6
  87. package/src/ui/AgentBlock.tsx +6 -6
  88. package/src/ui/AnimatedStatusText.tsx +1 -1
  89. package/src/ui/AppShell.tsx +13 -5
  90. package/src/ui/AttachmentImportPanel.tsx +8 -8
  91. package/src/ui/AuthPanel.tsx +20 -20
  92. package/src/ui/BackendPicker.tsx +0 -0
  93. package/src/ui/BottomComposer.tsx +117 -78
  94. package/src/ui/DashCard.tsx +3 -3
  95. package/src/ui/Markdown.tsx +17 -17
  96. package/src/ui/ModePicker.tsx +0 -0
  97. package/src/ui/ModelPicker.tsx +0 -0
  98. package/src/ui/ModelPickerScreen.tsx +10 -10
  99. package/src/ui/ModelReasoningPicker.tsx +14 -14
  100. package/src/ui/Panel.tsx +3 -3
  101. package/src/ui/PermissionsPanel.tsx +0 -0
  102. package/src/ui/PlanActionPicker.tsx +6 -6
  103. package/src/ui/PlanReviewPanel.tsx +8 -8
  104. package/src/ui/ProviderPicker.tsx +18 -18
  105. package/src/ui/ReasoningPicker.tsx +0 -0
  106. package/src/ui/RunFooter.tsx +3 -3
  107. package/src/ui/SelectionPanel.tsx +4 -4
  108. package/src/ui/SettingsPanel.tsx +8 -8
  109. package/src/ui/Spinner.tsx +1 -1
  110. package/src/ui/StaticIntroItem.tsx +0 -0
  111. package/src/ui/StaticTranscriptItem.tsx +0 -0
  112. package/src/ui/TextEntryPanel.tsx +10 -10
  113. package/src/ui/ThemePicker.tsx +0 -0
  114. package/src/ui/ThinkingBlock.tsx +8 -8
  115. package/src/ui/Timeline.tsx +21 -17
  116. package/src/ui/TopHeader.tsx +421 -257
  117. package/src/ui/TurnGroup.tsx +31 -31
  118. package/src/ui/UpdateAvailableCard.tsx +41 -0
  119. package/src/ui/UpdatePromptPanel.tsx +203 -0
  120. package/src/ui/busyStatusAnimation.ts +0 -0
  121. package/src/ui/commandNormalize.ts +0 -0
  122. package/src/ui/diffRenderer.ts +0 -0
  123. package/src/ui/focus.ts +3 -0
  124. package/src/ui/inputBuffer.ts +0 -0
  125. package/src/ui/layout.ts +1 -1
  126. package/src/ui/logoVariants.ts +107 -0
  127. package/src/ui/modeDisplay.ts +12 -12
  128. package/src/ui/outputPipeline.ts +0 -0
  129. package/src/ui/progressEntries.ts +0 -0
  130. package/src/ui/runActivityView.ts +0 -0
  131. package/src/ui/runtimeDisplay.ts +100 -0
  132. package/src/ui/slashCommands.ts +0 -0
  133. package/src/ui/terminalAnswerFormat.ts +0 -0
  134. package/src/ui/textLayout.ts +0 -0
  135. package/src/ui/theme.tsx +274 -395
  136. package/src/ui/themeFlow.ts +0 -0
  137. package/src/ui/timelineMeasure.ts +29 -19
  138. package/src/ui/useThrottledValue.ts +0 -0
  139. package/scripts/audit-codexa-capabilities.mjs +0 -466
  140. package/scripts/smoke-terminal-bench.mjs +0 -35
  141. package/src/appRenderStability.test.ts +0 -131
  142. package/src/commands/handler.test.ts +0 -643
  143. package/src/config/launchArgs.test.ts +0 -158
  144. package/src/config/layeredConfig.test.ts +0 -143
  145. package/src/config/persistence.test.ts +0 -110
  146. package/src/config/runtimeConfig.test.ts +0 -218
  147. package/src/config/settings.test.ts +0 -155
  148. package/src/config/trustStore.test.ts +0 -29
  149. package/src/core/attachments.test.ts +0 -155
  150. package/src/core/auth/codexAuth.test.ts +0 -68
  151. package/src/core/cleanupFastFail.test.ts +0 -76
  152. package/src/core/codexExecArgs.test.ts +0 -195
  153. package/src/core/codexLaunch.test.ts +0 -205
  154. package/src/core/codexPrompt.test.ts +0 -252
  155. package/src/core/executables/codexExecutable.test.ts +0 -212
  156. package/src/core/executables/executableResolver.test.ts +0 -129
  157. package/src/core/executables/geminiExecutable.test.ts +0 -116
  158. package/src/core/executables/pathSanityScan.test.ts +0 -47
  159. package/src/core/githubDiagnostics.test.ts +0 -92
  160. package/src/core/hollowResponseFormat.test.ts +0 -58
  161. package/src/core/launchContext.test.ts +0 -157
  162. package/src/core/models/codexCapabilities.test.ts +0 -45
  163. package/src/core/models/codexModelCapabilities.test.ts +0 -246
  164. package/src/core/models/modelSpecs.test.ts +0 -283
  165. package/src/core/perf/renderDebug.test.ts +0 -230
  166. package/src/core/planStorage.test.ts +0 -143
  167. package/src/core/process/CommandRunner.test.ts +0 -105
  168. package/src/core/projectInstructions.test.ts +0 -50
  169. package/src/core/providerLauncher/launcher.test.ts +0 -238
  170. package/src/core/providerLauncher/registry.test.ts +0 -324
  171. package/src/core/providerLauncher/workspaceConfig.test.ts +0 -638
  172. package/src/core/providerRuntime/anthropic.test.ts +0 -1120
  173. package/src/core/providerRuntime/capabilityProfile.test.ts +0 -311
  174. package/src/core/providerRuntime/contextMetadata.test.ts +0 -468
  175. package/src/core/providerRuntime/gemini.test.ts +0 -437
  176. package/src/core/providerRuntime/lmstudio.test.ts +0 -168
  177. package/src/core/providerRuntime/local.test.ts +0 -787
  178. package/src/core/providerRuntime/registry.test.ts +0 -233
  179. package/src/core/providers/codexJsonStream.test.ts +0 -148
  180. package/src/core/providers/codexSubprocess.test.ts +0 -68
  181. package/src/core/providers/codexTranscript.test.ts +0 -284
  182. package/src/core/terminal/terminalCapabilities.test.ts +0 -93
  183. package/src/core/terminal/terminalControl.test.ts +0 -75
  184. package/src/core/terminal/terminalSanitize.test.ts +0 -22
  185. package/src/core/terminal/terminalSelection.test.ts +0 -42
  186. package/src/core/terminal/terminalTitle.test.ts +0 -328
  187. package/src/core/workspaceActivity.test.ts +0 -163
  188. package/src/core/workspaceGuard.test.ts +0 -151
  189. package/src/core/workspaceRoot.test.ts +0 -23
  190. package/src/exec.test.ts +0 -13
  191. package/src/headless/execArgs.test.ts +0 -147
  192. package/src/headless/execRunner.test.ts +0 -434
  193. package/src/index.test.tsx +0 -618
  194. package/src/session/appSession.test.ts +0 -897
  195. package/src/session/chatLifecycle.test.ts +0 -64
  196. package/src/session/liveRenderScheduler.test.ts +0 -201
  197. package/src/session/planFlow.test.ts +0 -103
  198. package/src/session/planTranscript.test.ts +0 -65
  199. package/src/session/promptRunSchedule.test.ts +0 -36
  200. package/src/ui/ActivityIndicator.test.tsx +0 -58
  201. package/src/ui/AgentBlock.test.ts +0 -6
  202. package/src/ui/AnimatedStatusText.test.ts +0 -16
  203. package/src/ui/AppShell.test.tsx +0 -1739
  204. package/src/ui/AttachmentImportPanel.test.tsx +0 -204
  205. package/src/ui/BottomComposer.test.ts +0 -674
  206. package/src/ui/CodexLogo.tsx +0 -55
  207. package/src/ui/Markdown.test.ts +0 -157
  208. package/src/ui/ModelPickerProviderScope.test.tsx +0 -411
  209. package/src/ui/ModelPickerScreen.test.tsx +0 -99
  210. package/src/ui/ModelPickerState.test.tsx +0 -151
  211. package/src/ui/ModelReasoningPicker.test.tsx +0 -447
  212. package/src/ui/PlanReviewPanel.test.tsx +0 -267
  213. package/src/ui/PromptCardBorder.test.tsx +0 -161
  214. package/src/ui/ProviderPicker.test.tsx +0 -289
  215. package/src/ui/ProviderShortcut.test.tsx +0 -143
  216. package/src/ui/SettingsPanel.test.tsx +0 -233
  217. package/src/ui/Timeline.test.ts +0 -2067
  218. package/src/ui/TimelineNavigation.test.tsx +0 -201
  219. package/src/ui/TopHeader.test.tsx +0 -239
  220. package/src/ui/TurnGroup.test.tsx +0 -365
  221. package/src/ui/busyStatusAnimation.test.ts +0 -30
  222. package/src/ui/commandNormalize.test.ts +0 -142
  223. package/src/ui/diffRenderer.test.ts +0 -102
  224. package/src/ui/focusFlow.test.tsx +0 -1098
  225. package/src/ui/inputBuffer.test.ts +0 -151
  226. package/src/ui/layout.test.ts +0 -145
  227. package/src/ui/modeDisplay.test.ts +0 -42
  228. package/src/ui/runActivityView.test.ts +0 -89
  229. package/src/ui/runLifecycleView.test.tsx +0 -237
  230. package/src/ui/statusRenderIsolation.test.tsx +0 -654
  231. package/src/ui/terminalAnswerFormat.test.ts +0 -19
  232. package/src/ui/textLayout.test.ts +0 -18
  233. package/src/ui/themeFlow.test.ts +0 -53
  234. package/src/ui/timelineMeasureCache.test.ts +0 -986
@@ -1,129 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { ChildProcess } from "node:child_process";
4
- import { mkdirSync, rmSync, writeFileSync } from "node:fs";
5
- import { tmpdir } from "node:os";
6
- import { join } from "node:path";
7
- import { runCommand, type CommandResult } from "../process/CommandRunner.js";
8
- import { resolveExecutable, buildSpawnSpec } from "./executableResolver.js";
9
-
10
- function commandResult(overrides: Partial<CommandResult>): CommandResult {
11
- return {
12
- status: "completed",
13
- exitCode: 0,
14
- signal: null,
15
- stdout: "",
16
- stderr: "",
17
- startedAt: 0,
18
- endedAt: 0,
19
- durationMs: 0,
20
- userMessage: "Command completed.",
21
- ...overrides,
22
- };
23
- }
24
-
25
- function mockRunCommand(result: CommandResult, onCall?: (spec: Parameters<typeof runCommand>[0]) => void): typeof runCommand {
26
- return ((spec) => {
27
- onCall?.(spec);
28
- return {
29
- child: null as unknown as ChildProcess,
30
- result: Promise.resolve(result),
31
- cancel: () => undefined,
32
- };
33
- }) as typeof runCommand;
34
- }
35
-
36
- test("resolver: uses configuredPath", async () => {
37
- const resolved = await resolveExecutable({
38
- commandNames: ["test"],
39
- label: "test",
40
- configuredPath: process.execPath,
41
- });
42
- assert.equal(resolved, process.execPath);
43
- });
44
-
45
- test("resolver: uses environment override", async () => {
46
- const original = process.env.TEST_EXECUTABLE;
47
- process.env.TEST_EXECUTABLE = "custom-test";
48
- try {
49
- const resolved = await resolveExecutable({
50
- commandNames: ["test"],
51
- label: "test",
52
- envOverrides: ["TEST_EXECUTABLE"],
53
- });
54
- assert.equal(resolved, "custom-test");
55
- } finally {
56
- if (original === undefined) delete process.env.TEST_EXECUTABLE;
57
- else process.env.TEST_EXECUTABLE = original;
58
- }
59
- });
60
-
61
- test("resolver: rejects environment override with shell metacharacters", async () => {
62
- const original = process.env.TEST_EXECUTABLE;
63
- process.env.TEST_EXECUTABLE = "custom-test & calc";
64
- try {
65
- await assert.rejects(
66
- () => resolveExecutable({
67
- commandNames: ["test"],
68
- label: "test",
69
- envOverrides: ["TEST_EXECUTABLE"],
70
- }),
71
- /shell metacharacters|single executable name/i,
72
- );
73
- } finally {
74
- if (original === undefined) delete process.env.TEST_EXECUTABLE;
75
- else process.env.TEST_EXECUTABLE = original;
76
- }
77
- });
78
-
79
- test("resolver: rejects configured executable values that include arguments", async () => {
80
- await assert.rejects(
81
- () => resolveExecutable({
82
- commandNames: ["test"],
83
- label: "test",
84
- configuredPath: "test --version",
85
- }),
86
- /single executable name/i,
87
- );
88
- });
89
-
90
- test("resolver: accepts quoted executable paths with spaces", async () => {
91
- const tempRoot = join(tmpdir(), `codexa resolver ${Date.now()}`);
92
- const executablePath = join(tempRoot, "tool with spaces.exe");
93
- mkdirSync(tempRoot, { recursive: true });
94
- writeFileSync(executablePath, "");
95
- try {
96
- const resolved = await resolveExecutable({
97
- commandNames: ["test"],
98
- label: "test",
99
- configuredPath: `"${executablePath}"`,
100
- });
101
- assert.equal(resolved, executablePath);
102
- } finally {
103
- rmSync(tempRoot, { recursive: true, force: true });
104
- }
105
- });
106
-
107
- test("resolver: falls back to bare name if not found", async () => {
108
- const mockImpl = mockRunCommand(commandResult({ status: "failed", exitCode: 1 }));
109
- const resolved = await resolveExecutable({
110
- runCommandImpl: mockImpl,
111
- commandNames: ["mytest"],
112
- label: "test",
113
- });
114
- assert.equal(resolved, "mytest");
115
- });
116
-
117
- test("buildSpawnSpec: wraps .cmd files in cmd.exe on Windows", async () => {
118
- if (process.platform !== "win32") return;
119
- const spec = buildSpawnSpec("test.cmd", ["arg1"]);
120
- assert.equal(spec.executable, "cmd.exe");
121
- assert.deepEqual(spec.args, ["/d", "/s", "/c", "call", "test.cmd", "arg1"]);
122
- });
123
-
124
- test("buildSpawnSpec: does not wrap .exe files", async () => {
125
- if (process.platform !== "win32") return;
126
- const spec = buildSpawnSpec("test.exe", ["arg1"]);
127
- assert.equal(spec.executable, "test.exe");
128
- assert.deepEqual(spec.args, ["arg1"]);
129
- });
@@ -1,116 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import type { ChildProcess } from "node:child_process";
4
- import { mkdirSync, rmSync, writeFileSync } from "node:fs";
5
- import { tmpdir } from "node:os";
6
- import { join } from "node:path";
7
- import { buildGeminiSpawnSpec, resetGeminiExecutableCacheForTests, resolveGeminiExecutable } from "./geminiExecutable.js";
8
- import { runCommand, type CommandResult } from "../process/CommandRunner.js";
9
-
10
- function commandResult(overrides: Partial<CommandResult>): CommandResult {
11
- return {
12
- status: "completed",
13
- exitCode: 0,
14
- signal: null,
15
- stdout: "",
16
- stderr: "",
17
- startedAt: 0,
18
- endedAt: 0,
19
- durationMs: 0,
20
- userMessage: "Command completed.",
21
- ...overrides,
22
- };
23
- }
24
-
25
- function mockRunCommand(onCall: (spec: Parameters<typeof runCommand>[0]) => CommandResult): typeof runCommand {
26
- return ((spec) => ({
27
- child: null as unknown as ChildProcess,
28
- result: Promise.resolve(onCall(spec)),
29
- cancel: () => undefined,
30
- })) as typeof runCommand;
31
- }
32
-
33
- async function withEnv<T>(env: Partial<NodeJS.ProcessEnv>, callback: () => Promise<T>): Promise<T> {
34
- const originalExecutable = process.env.GEMINI_EXECUTABLE;
35
- const originalCliPath = process.env.GEMINI_CLI_PATH;
36
- const originalAppData = process.env.APPDATA;
37
- try {
38
- if ("GEMINI_EXECUTABLE" in env) process.env.GEMINI_EXECUTABLE = env.GEMINI_EXECUTABLE;
39
- else delete process.env.GEMINI_EXECUTABLE;
40
- if ("GEMINI_CLI_PATH" in env) process.env.GEMINI_CLI_PATH = env.GEMINI_CLI_PATH;
41
- else delete process.env.GEMINI_CLI_PATH;
42
- if ("APPDATA" in env) process.env.APPDATA = env.APPDATA;
43
- resetGeminiExecutableCacheForTests();
44
- return await callback();
45
- } finally {
46
- if (originalExecutable === undefined) delete process.env.GEMINI_EXECUTABLE;
47
- else process.env.GEMINI_EXECUTABLE = originalExecutable;
48
- if (originalCliPath === undefined) delete process.env.GEMINI_CLI_PATH;
49
- else process.env.GEMINI_CLI_PATH = originalCliPath;
50
- if (originalAppData === undefined) delete process.env.APPDATA;
51
- else process.env.APPDATA = originalAppData;
52
- resetGeminiExecutableCacheForTests();
53
- }
54
- }
55
-
56
- test("Gemini resolver: env GEMINI_EXECUTABLE wins over PATH", async () => {
57
- await withEnv({ GEMINI_EXECUTABLE: "env-gemini.cmd" }, async () => {
58
- let whereCalled = false;
59
- const resolved = await resolveGeminiExecutable({
60
- runCommandImpl: mockRunCommand((spec) => {
61
- if (spec.executable === "where.exe") whereCalled = true;
62
- return commandResult({ stdout: "C:\\Tools\\gemini.cmd\n" });
63
- }),
64
- });
65
-
66
- assert.equal(resolved, "env-gemini.cmd");
67
- assert.equal(whereCalled, false);
68
- });
69
- });
70
-
71
- test("Gemini resolver: APPDATA npm shim fallback works", async () => {
72
- if (process.platform !== "win32") return;
73
- const tempRoot = join(tmpdir(), `codexa-gemini-${Date.now()}`);
74
- const npmDir = join(tempRoot, "npm");
75
- const shim = join(npmDir, "gemini.cmd");
76
- mkdirSync(npmDir, { recursive: true });
77
- writeFileSync(shim, "@echo off\r\n", "utf-8");
78
-
79
- try {
80
- await withEnv({ APPDATA: tempRoot }, async () => {
81
- const resolved = await resolveGeminiExecutable({
82
- runCommandImpl: mockRunCommand(() => commandResult({ status: "failed", exitCode: 1 })),
83
- });
84
- assert.equal(resolved, shim);
85
- });
86
- } finally {
87
- rmSync(tempRoot, { recursive: true, force: true });
88
- }
89
- });
90
-
91
- test("Gemini resolver: PowerShell function text is not accepted as executable path", async () => {
92
- if (process.platform !== "win32") return;
93
- await withEnv({}, async () => {
94
- const tempRoot = join(tmpdir(), `codexa-gemini-real-${Date.now()}`);
95
- const npmDir = join(tempRoot, "npm");
96
- const shim = join(npmDir, "gemini.cmd");
97
- mkdirSync(npmDir, { recursive: true });
98
- writeFileSync(shim, "@echo off\r\n", "utf-8");
99
- process.env.APPDATA = tempRoot;
100
- try {
101
- const resolved = await resolveGeminiExecutable({
102
- runCommandImpl: mockRunCommand(() => commandResult({ stdout: "function gemini { param($p) }\n" })),
103
- });
104
- assert.equal(resolved, shim);
105
- } finally {
106
- rmSync(tempRoot, { recursive: true, force: true });
107
- }
108
- });
109
- });
110
-
111
- test("Gemini spawn spec bypasses PowerShell and targets the resolved executable", () => {
112
- const spec = buildGeminiSpawnSpec("C:\\Users\\Example\\AppData\\Roaming\\npm\\gemini.cmd", ["-p", "Respond with READY only."]);
113
- assert.equal(spec.executable, "C:\\Users\\Example\\AppData\\Roaming\\npm\\gemini.cmd");
114
- assert.deepEqual(spec.args, ["-p", "Respond with READY only."]);
115
- assert.equal(spec.shell, undefined);
116
- });
@@ -1,47 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { readdirSync, readFileSync, statSync } from "node:fs";
4
- import { join } from "node:path";
5
-
6
- function collectTsFiles(dir: string): string[] {
7
- const result: string[] = [];
8
- for (const entry of readdirSync(dir)) {
9
- const full = join(dir, entry);
10
- if (statSync(full).isDirectory()) {
11
- result.push(...collectTsFiles(full));
12
- } else if (full.endsWith(".ts")) {
13
- result.push(full);
14
- }
15
- }
16
- return result;
17
- }
18
-
19
- const SRC_ROOT = join(import.meta.dirname, "../../..");
20
-
21
- // Guard against personal user-specific paths appearing in source.
22
- // Patterns are split across array entries to prevent THIS file from matching itself.
23
- const BANNED_FRAGMENTS: Array<[string, string]> = [
24
- ["C", ":\\\\Users\\\\jorda"],
25
- ["C", ":/Users/jorda"],
26
- ];
27
-
28
- test("no source files contain personal hardcoded user paths", { timeout: 30_000 }, () => {
29
- const patterns = BANNED_FRAGMENTS.map(([a, b]) => new RegExp(a + b));
30
- const files = collectTsFiles(SRC_ROOT);
31
- const violations: string[] = [];
32
-
33
- for (const file of files) {
34
- const content = readFileSync(file, "utf-8");
35
- for (const pattern of patterns) {
36
- if (pattern.test(content)) {
37
- violations.push(`${file}: matches ${pattern}`);
38
- }
39
- }
40
- }
41
-
42
- assert.deepEqual(
43
- violations,
44
- [],
45
- `Personal hardcoded user paths found in source:\n${violations.join("\n")}`,
46
- );
47
- });
@@ -1,92 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import {
4
- parseRepoIdentity,
5
- classifyDiagnostics,
6
- type DiagnosticResult,
7
- type RepoIdentity
8
- } from "./githubDiagnostics.js";
9
-
10
- test("parseRepoIdentity: handles various GitHub URL formats", () => {
11
- const cases = [
12
- {
13
- url: "https://github.com/owner/repo.git",
14
- expected: { owner: "owner", repo: "repo", provider: "github" }
15
- },
16
- {
17
- url: "git@github.com:owner/repo.git",
18
- expected: { owner: "owner", repo: "repo", provider: "github" }
19
- },
20
- {
21
- url: "https://github.com/owner/repo",
22
- expected: { owner: "owner", repo: "repo", provider: "github" }
23
- },
24
- {
25
- url: "ssh://git@github.com/owner/repo.git",
26
- expected: { owner: "owner", repo: "repo", provider: "github" }
27
- }
28
- ];
29
-
30
- for (const { url, expected } of cases) {
31
- const result = parseRepoIdentity(url);
32
- assert.ok(result);
33
- assert.equal(result.owner, expected.owner);
34
- assert.equal(result.repo, expected.repo);
35
- assert.equal(result.provider, expected.provider);
36
- }
37
- });
38
-
39
- test("parseRepoIdentity: handles missing or non-GitHub origins", () => {
40
- assert.equal(parseRepoIdentity(null), null);
41
- assert.equal(parseRepoIdentity(""), null);
42
-
43
- const other = parseRepoIdentity("https://gitlab.com/owner/repo.git");
44
- assert.ok(other);
45
- assert.equal(other.provider, "other");
46
- });
47
-
48
- test("classifyDiagnostics: handles all status combinations", () => {
49
- const repo: RepoIdentity = { owner: "o", repo: "r", provider: "github", remoteUrl: "..." };
50
-
51
- const pass: DiagnosticResult = { path: "p", status: "PASS", evidence: "e", blocker: null, recommendedUse: false };
52
- const fail: DiagnosticResult = { path: "p", status: "FAIL", evidence: "e", blocker: "b", recommendedUse: false };
53
- const partial: DiagnosticResult = { path: "p", status: "PARTIAL", evidence: "e", blocker: "b", recommendedUse: false };
54
-
55
- // 1. All PASS -> Local Git + GH CLI
56
- assert.equal(
57
- classifyDiagnostics(repo, pass, pass, pass, fail),
58
- "Local Git + GH CLI"
59
- );
60
-
61
- // 2. GH CLI missing but connector OK -> Connector-only
62
- assert.equal(
63
- classifyDiagnostics(repo, fail, pass, pass, pass),
64
- "Local Git + connector PR creation"
65
- );
66
-
67
- // 3. .git write fails but connector OK -> Connector-only
68
- assert.equal(
69
- classifyDiagnostics(repo, pass, pass, fail, pass),
70
- "Connector-only"
71
- );
72
-
73
- // 4. Connector read OK but write unknown (treated as PASS if not auth error)
74
- assert.equal(
75
- classifyDiagnostics(repo, fail, fail, fail, pass),
76
- "Connector-only"
77
- );
78
-
79
- // 5. Connector permission rejected
80
- const connectorRejected: DiagnosticResult = { path: "p", status: "PARTIAL", evidence: "e", blocker: "auth error", recommendedUse: false };
81
- assert.equal(
82
- classifyDiagnostics(repo, fail, fail, fail, connectorRejected),
83
- "Cannot publish yet"
84
- );
85
-
86
- // 6. Non-GitHub repo
87
- const otherRepo: RepoIdentity = { owner: "", repo: "", provider: "other", remoteUrl: "..." };
88
- assert.equal(
89
- classifyDiagnostics(otherRepo, pass, pass, pass, pass),
90
- "Cannot publish yet"
91
- );
92
- });
@@ -1,58 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { formatHollowResponse } from "./hollowResponseFormat.js";
4
- import type { HollowResponseResult } from "./codexPrompt.js";
5
-
6
- function makeResult(kind: HollowResponseResult["kind"], reason = ""): HollowResponseResult {
7
- return { isHollow: true, kind, reason };
8
- }
9
-
10
- test("greeting produces exactly 2 lines", () => {
11
- const out = formatHollowResponse(makeResult("greeting"));
12
- const lines = out.split("\n");
13
- assert.equal(lines.length, 2);
14
- assert.match(lines[0]!, /generic greeting/);
15
- assert.match(lines[1]!, /Retry/);
16
- });
17
-
18
- test("filler produces exactly 2 lines", () => {
19
- const out = formatHollowResponse(makeResult("filler"));
20
- const lines = out.split("\n");
21
- assert.equal(lines.length, 2);
22
- assert.match(lines[0]!, /acknowledged without acting/);
23
- });
24
-
25
- test("clarification produces exactly 2 lines", () => {
26
- const out = formatHollowResponse(makeResult("clarification"));
27
- const lines = out.split("\n");
28
- assert.equal(lines.length, 2);
29
- assert.match(lines[0]!, /clarification/);
30
- assert.match(lines[1]!, /suggest mode/);
31
- });
32
-
33
- test("short-no-action produces exactly 2 lines", () => {
34
- const out = formatHollowResponse(makeResult("short-no-action"));
35
- const lines = out.split("\n");
36
- assert.equal(lines.length, 2);
37
- assert.match(lines[0]!, /too brief/);
38
- });
39
-
40
- test("no emoji or warning symbols in any output", () => {
41
- for (const kind of ["greeting", "filler", "clarification", "short-no-action"] as const) {
42
- const out = formatHollowResponse(makeResult(kind));
43
- assert.doesNotMatch(out, /⚠/);
44
- assert.doesNotMatch(out, /---/);
45
- }
46
- });
47
-
48
- test("verbose=false omits raw response", () => {
49
- const out = formatHollowResponse(makeResult("greeting"), "Hello.");
50
- assert.doesNotMatch(out, /Hello\./);
51
- });
52
-
53
- test("verbose=true appends raw response", () => {
54
- const out = formatHollowResponse(makeResult("greeting"), "Hello.", true);
55
- assert.match(out, /Backend response: Hello\./);
56
- const lines = out.split("\n");
57
- assert.equal(lines.length, 4); // 2 message lines + blank + backend response
58
- });
@@ -1,157 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "fs";
3
- import { tmpdir } from "os";
4
- import { join } from "path";
5
- import test from "node:test";
6
- import {
7
- buildWorkspaceStatusMessage,
8
- createWorkspaceRelaunchPlan,
9
- guardWorkspaceRelaunch,
10
- resolveLaunchContext,
11
- } from "./launchContext.js";
12
- import { normalizeWorkspaceRoot } from "./workspaceRoot.js";
13
-
14
- function createTempWorkspace(): string {
15
- return mkdtempSync(join(tmpdir(), "codexa-launch-context-"));
16
- }
17
-
18
- test("uses installed launcher metadata when available", () => {
19
- const context = resolveLaunchContext({
20
- workspaceRoot: "C:/target",
21
- packageRoot: "C:/repo",
22
- execPath: "C:/Program Files/nodejs/node.exe",
23
- env: {
24
- CODEXA_LAUNCH_KIND: "installed-bin",
25
- CODEXA_PACKAGE_ROOT: "C:/repo",
26
- CODEXA_LAUNCHER_SCRIPT: "C:/repo/bin/codexa.js",
27
- CODEXA_RELAUNCH_EXECUTABLE: "C:/Program Files/nodejs/node.exe",
28
- CODEXA_RELAUNCH_ARGS: JSON.stringify(["C:/repo/bin/codexa.js", "--profile", "review"]),
29
- },
30
- });
31
-
32
- assert.equal(context.launchKind, "installed-bin");
33
- assert.equal(context.packageRoot, "C:\\repo");
34
- assert.equal(context.launcherScriptPath, "C:/repo/bin/codexa.js");
35
- assert.equal(context.relaunchExecutable, "C:/Program Files/nodejs/node.exe");
36
- assert.deepEqual(context.relaunchArgs, ["C:/repo/bin/codexa.js", "--profile", "review"]);
37
- });
38
-
39
- test("falls back to bun repo launch metadata when no installed launcher env exists", () => {
40
- const context = resolveLaunchContext({
41
- workspaceRoot: "C:/repo",
42
- packageRoot: "C:/repo",
43
- execPath: "C:/tools/bun.exe",
44
- hasBunRuntime: true,
45
- forwardArgs: ["--profile", "review", "--config", "model=\"gpt-5.4\""],
46
- env: {},
47
- });
48
-
49
- assert.equal(context.launchKind, "dev-run");
50
- assert.equal(context.relaunchExecutable, "C:/tools/bun.exe");
51
- assert.deepEqual(context.relaunchArgs, [
52
- "run",
53
- "--silent",
54
- join("C:\\repo", "src", "index.tsx"),
55
- "--",
56
- "--profile",
57
- "review",
58
- "--config",
59
- "model=\"gpt-5.4\"",
60
- ]);
61
- });
62
-
63
- test("creates an installed-bin relaunch plan with normalized target cwd and env", () => {
64
- const root = createTempWorkspace();
65
- try {
66
- const nextWorkspace = join(root, "next");
67
- mkdirSync(nextWorkspace);
68
-
69
- const context = resolveLaunchContext({
70
- workspaceRoot: root,
71
- packageRoot: "C:/repo",
72
- execPath: "C:/Program Files/nodejs/node.exe",
73
- env: {
74
- CODEXA_LAUNCH_KIND: "installed-bin",
75
- CODEXA_PACKAGE_ROOT: "C:/repo",
76
- CODEXA_LAUNCHER_SCRIPT: "C:/repo/bin/codexa.js",
77
- CODEXA_RELAUNCH_EXECUTABLE: "C:/Program Files/nodejs/node.exe",
78
- CODEXA_RELAUNCH_ARGS: JSON.stringify(["C:/repo/bin/codexa.js", "--profile", "review"]),
79
- },
80
- });
81
-
82
- const result = createWorkspaceRelaunchPlan(".\\next", context, {});
83
- assert.equal(result.ok, true);
84
- if (!result.ok) return;
85
-
86
- assert.equal(result.plan.executable, "C:/Program Files/nodejs/node.exe");
87
- assert.deepEqual(result.plan.args, ["C:/repo/bin/codexa.js", "--profile", "review"]);
88
- assert.equal(result.plan.cwd, normalizeWorkspaceRoot(nextWorkspace));
89
- assert.equal(result.plan.env.CODEX_WORKSPACE_ROOT, normalizeWorkspaceRoot(nextWorkspace));
90
- assert.equal(result.plan.env.CODEXA_LAUNCH_KIND, "installed-bin");
91
- } finally {
92
- rmSync(root, { recursive: true, force: true });
93
- }
94
- });
95
-
96
- test("rejects relaunch targets that do not exist", () => {
97
- const root = createTempWorkspace();
98
- try {
99
- const context = resolveLaunchContext({
100
- workspaceRoot: root,
101
- packageRoot: root,
102
- execPath: "C:/tools/bun.exe",
103
- hasBunRuntime: true,
104
- env: {},
105
- });
106
-
107
- const result = createWorkspaceRelaunchPlan(".\\missing", context, {});
108
- assert.equal(result.ok, false);
109
- if (result.ok) return;
110
- assert.match(result.message, /does not exist/i);
111
- } finally {
112
- rmSync(root, { recursive: true, force: true });
113
- }
114
- });
115
-
116
- test("rejects relaunch targets that are files instead of directories", () => {
117
- const root = createTempWorkspace();
118
- try {
119
- const fileTarget = join(root, "notes.txt");
120
- writeFileSync(fileTarget, "hello", "utf8");
121
- const context = resolveLaunchContext({
122
- workspaceRoot: root,
123
- packageRoot: root,
124
- execPath: "C:/tools/bun.exe",
125
- hasBunRuntime: true,
126
- env: {},
127
- });
128
-
129
- const result = createWorkspaceRelaunchPlan(".\\notes.txt", context, {});
130
- assert.equal(result.ok, false);
131
- if (result.ok) return;
132
- assert.match(result.message, /not a directory/i);
133
- } finally {
134
- rmSync(root, { recursive: true, force: true });
135
- }
136
- });
137
-
138
- test("blocks workspace relaunch while busy", () => {
139
- assert.deepEqual(guardWorkspaceRelaunch(false), { allowed: true });
140
- assert.equal(guardWorkspaceRelaunch(true).allowed, false);
141
- assert.match(guardWorkspaceRelaunch(true).message ?? "", /finish the current run/i);
142
- });
143
-
144
- test("describes dev launch mode with install guidance", () => {
145
- const message = buildWorkspaceStatusMessage(resolveLaunchContext({
146
- workspaceRoot: "C:/repo",
147
- packageRoot: "C:/repo",
148
- execPath: "C:/tools/bun.exe",
149
- hasBunRuntime: true,
150
- env: {},
151
- }));
152
-
153
- assert.match(message, /Launch mode: dev\/repo launch/i);
154
- assert.match(message, /npm link/i);
155
- assert.match(message, /where codexa/i);
156
- assert.match(message, /\/workspace relaunch <path>/i);
157
- });
@@ -1,45 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { parseCodexCliCapabilities } from "./codexCapabilities.js";
4
-
5
- test("detects exec help flags and strips ANSI noise", () => {
6
- const capabilities = parseCodexCliCapabilities(
7
- "\u001B[32mOptions:\u001B[0m\n --ask-for-approval <policy>\n --sandbox <mode>\n -c, --config <key=value>\n",
8
- "Global options:\n --ask-for-approval <policy>\n",
9
- );
10
-
11
- assert.deepEqual(capabilities, {
12
- askForApproval: true,
13
- sandbox: true,
14
- config: true,
15
- fullAuto: false,
16
- });
17
- });
18
-
19
- test("does not promote top-level approval flags into exec capabilities", () => {
20
- const capabilities = parseCodexCliCapabilities(
21
- "Usage: codex exec [OPTIONS]\n --sandbox <mode>\n --full-auto\n",
22
- "Global options:\n --ask-for-approval <policy>\n --sandbox <mode>\n --full-auto\n",
23
- );
24
-
25
- assert.deepEqual(capabilities, {
26
- askForApproval: false,
27
- sandbox: true,
28
- config: false,
29
- fullAuto: true,
30
- });
31
- });
32
-
33
- test("requires exact option tokens instead of prose guesses", () => {
34
- const capabilities = parseCodexCliCapabilities(
35
- "This build documents ask-for-approval compatibility in prose only.\nIt also mentions sandbox_mode in config docs.\n",
36
- "Use full auto mode for trusted environments.\n",
37
- );
38
-
39
- assert.deepEqual(capabilities, {
40
- askForApproval: false,
41
- sandbox: false,
42
- config: false,
43
- fullAuto: false,
44
- });
45
- });