@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.
Files changed (226) hide show
  1. package/README.md +320 -0
  2. package/bin/codexa.js +445 -0
  3. package/package.json +45 -0
  4. package/scripts/audit-codexa-capabilities.mjs +466 -0
  5. package/scripts/smoke-terminal-bench.mjs +35 -0
  6. package/src/app.tsx +4561 -0
  7. package/src/appRenderStability.test.ts +131 -0
  8. package/src/commands/handler.test.ts +643 -0
  9. package/src/commands/handler.ts +875 -0
  10. package/src/config/launchArgs.test.ts +158 -0
  11. package/src/config/launchArgs.ts +186 -0
  12. package/src/config/layeredConfig.test.ts +143 -0
  13. package/src/config/layeredConfig.ts +836 -0
  14. package/src/config/persistence.test.ts +110 -0
  15. package/src/config/persistence.ts +311 -0
  16. package/src/config/runtimeConfig.test.ts +218 -0
  17. package/src/config/runtimeConfig.ts +554 -0
  18. package/src/config/settings.test.ts +155 -0
  19. package/src/config/settings.ts +401 -0
  20. package/src/config/toml-serialize.ts +98 -0
  21. package/src/config/trustStore.test.ts +29 -0
  22. package/src/config/trustStore.ts +68 -0
  23. package/src/core/attachments.test.ts +155 -0
  24. package/src/core/attachments.ts +71 -0
  25. package/src/core/auth/codexAuth.test.ts +68 -0
  26. package/src/core/auth/codexAuth.ts +359 -0
  27. package/src/core/cleanupFastFail.test.ts +76 -0
  28. package/src/core/cleanupFastFail.ts +67 -0
  29. package/src/core/clipboard.ts +24 -0
  30. package/src/core/codex.ts +124 -0
  31. package/src/core/codexExecArgs.test.ts +195 -0
  32. package/src/core/codexExecArgs.ts +152 -0
  33. package/src/core/codexLaunch.test.ts +205 -0
  34. package/src/core/codexLaunch.ts +162 -0
  35. package/src/core/codexPrompt.test.ts +252 -0
  36. package/src/core/codexPrompt.ts +428 -0
  37. package/src/core/executables/claudeExecutable.ts +63 -0
  38. package/src/core/executables/codexExecutable.test.ts +212 -0
  39. package/src/core/executables/codexExecutable.ts +159 -0
  40. package/src/core/executables/executableResolver.test.ts +129 -0
  41. package/src/core/executables/executableResolver.ts +138 -0
  42. package/src/core/executables/geminiExecutable.test.ts +116 -0
  43. package/src/core/executables/geminiExecutable.ts +78 -0
  44. package/src/core/executables/pathSanityScan.test.ts +47 -0
  45. package/src/core/githubDiagnostics.test.ts +92 -0
  46. package/src/core/githubDiagnostics.ts +222 -0
  47. package/src/core/hollowResponseFormat.test.ts +58 -0
  48. package/src/core/hollowResponseFormat.ts +39 -0
  49. package/src/core/inputDebug.ts +51 -0
  50. package/src/core/launchContext.test.ts +157 -0
  51. package/src/core/launchContext.ts +266 -0
  52. package/src/core/models/codexCapabilities.test.ts +45 -0
  53. package/src/core/models/codexCapabilities.ts +95 -0
  54. package/src/core/models/codexModelCapabilities.test.ts +246 -0
  55. package/src/core/models/codexModelCapabilities.ts +571 -0
  56. package/src/core/models/modelSpecs.test.ts +283 -0
  57. package/src/core/models/modelSpecs.ts +300 -0
  58. package/src/core/perf/profiler.ts +125 -0
  59. package/src/core/perf/renderDebug.test.ts +230 -0
  60. package/src/core/perf/renderDebug.ts +373 -0
  61. package/src/core/planStorage.test.ts +143 -0
  62. package/src/core/planStorage.ts +141 -0
  63. package/src/core/process/CommandRunner.test.ts +105 -0
  64. package/src/core/process/CommandRunner.ts +269 -0
  65. package/src/core/process/processValidation.ts +101 -0
  66. package/src/core/projectInstructions.test.ts +50 -0
  67. package/src/core/projectInstructions.ts +54 -0
  68. package/src/core/providerLauncher/launcher.test.ts +238 -0
  69. package/src/core/providerLauncher/launcher.ts +203 -0
  70. package/src/core/providerLauncher/registry.test.ts +324 -0
  71. package/src/core/providerLauncher/registry.ts +253 -0
  72. package/src/core/providerLauncher/types.ts +84 -0
  73. package/src/core/providerLauncher/workspaceConfig.test.ts +638 -0
  74. package/src/core/providerLauncher/workspaceConfig.ts +407 -0
  75. package/src/core/providerRuntime/anthropic.test.ts +1120 -0
  76. package/src/core/providerRuntime/anthropic.ts +576 -0
  77. package/src/core/providerRuntime/capabilityProfile.test.ts +311 -0
  78. package/src/core/providerRuntime/capabilityProfile.ts +288 -0
  79. package/src/core/providerRuntime/claudeCodeDiscovery.ts +446 -0
  80. package/src/core/providerRuntime/contextMetadata.test.ts +468 -0
  81. package/src/core/providerRuntime/contextMetadata.ts +409 -0
  82. package/src/core/providerRuntime/gemini.test.ts +437 -0
  83. package/src/core/providerRuntime/gemini.ts +784 -0
  84. package/src/core/providerRuntime/lmstudio.test.ts +168 -0
  85. package/src/core/providerRuntime/lmstudio.ts +118 -0
  86. package/src/core/providerRuntime/local.test.ts +787 -0
  87. package/src/core/providerRuntime/local.ts +754 -0
  88. package/src/core/providerRuntime/models.ts +150 -0
  89. package/src/core/providerRuntime/reasoning.ts +17 -0
  90. package/src/core/providerRuntime/registry.test.ts +233 -0
  91. package/src/core/providerRuntime/registry.ts +203 -0
  92. package/src/core/providerRuntime/types.ts +103 -0
  93. package/src/core/providers/codexJsonStream.test.ts +148 -0
  94. package/src/core/providers/codexJsonStream.ts +305 -0
  95. package/src/core/providers/codexSubprocess.test.ts +68 -0
  96. package/src/core/providers/codexSubprocess.ts +372 -0
  97. package/src/core/providers/codexTranscript.test.ts +284 -0
  98. package/src/core/providers/codexTranscript.ts +695 -0
  99. package/src/core/providers/openaiNative.ts +13 -0
  100. package/src/core/providers/registry.ts +21 -0
  101. package/src/core/providers/types.ts +59 -0
  102. package/src/core/terminal/terminalCapabilities.test.ts +93 -0
  103. package/src/core/terminal/terminalCapabilities.ts +100 -0
  104. package/src/core/terminal/terminalControl.test.ts +75 -0
  105. package/src/core/terminal/terminalControl.ts +147 -0
  106. package/src/core/terminal/terminalSanitize.test.ts +22 -0
  107. package/src/core/terminal/terminalSanitize.ts +147 -0
  108. package/src/core/terminal/terminalSelection.test.ts +42 -0
  109. package/src/core/terminal/terminalSelection.ts +66 -0
  110. package/src/core/terminal/terminalTitle.test.ts +328 -0
  111. package/src/core/terminal/terminalTitle.ts +483 -0
  112. package/src/core/workspaceActivity.test.ts +163 -0
  113. package/src/core/workspaceActivity.ts +380 -0
  114. package/src/core/workspaceGuard.test.ts +151 -0
  115. package/src/core/workspaceGuard.ts +288 -0
  116. package/src/core/workspaceRoot.test.ts +23 -0
  117. package/src/core/workspaceRoot.ts +47 -0
  118. package/src/exec.test.ts +13 -0
  119. package/src/exec.ts +72 -0
  120. package/src/headless/execArgs.test.ts +147 -0
  121. package/src/headless/execArgs.ts +294 -0
  122. package/src/headless/execRunner.test.ts +434 -0
  123. package/src/headless/execRunner.ts +304 -0
  124. package/src/index.test.tsx +618 -0
  125. package/src/index.tsx +296 -0
  126. package/src/session/appSession.test.ts +897 -0
  127. package/src/session/appSession.ts +761 -0
  128. package/src/session/chatLifecycle.test.ts +64 -0
  129. package/src/session/chatLifecycle.ts +951 -0
  130. package/src/session/liveRenderScheduler.test.ts +201 -0
  131. package/src/session/liveRenderScheduler.ts +214 -0
  132. package/src/session/planFlow.test.ts +103 -0
  133. package/src/session/planFlow.ts +149 -0
  134. package/src/session/planTranscript.test.ts +65 -0
  135. package/src/session/planTranscript.ts +15 -0
  136. package/src/session/promptRunSchedule.test.ts +36 -0
  137. package/src/session/promptRunSchedule.ts +26 -0
  138. package/src/session/types.ts +228 -0
  139. package/src/test/runtimeTestUtils.ts +14 -0
  140. package/src/types/react-dom.d.ts +3 -0
  141. package/src/ui/ActionRequiredBlock.tsx +38 -0
  142. package/src/ui/ActivityBars.tsx +68 -0
  143. package/src/ui/ActivityIndicator.test.tsx +58 -0
  144. package/src/ui/ActivityIndicator.tsx +58 -0
  145. package/src/ui/AgentBlock.test.ts +6 -0
  146. package/src/ui/AgentBlock.tsx +130 -0
  147. package/src/ui/AnimatedStatusText.test.ts +16 -0
  148. package/src/ui/AnimatedStatusText.tsx +69 -0
  149. package/src/ui/AppShell.test.tsx +1739 -0
  150. package/src/ui/AppShell.tsx +698 -0
  151. package/src/ui/AttachmentImportPanel.test.tsx +204 -0
  152. package/src/ui/AttachmentImportPanel.tsx +98 -0
  153. package/src/ui/AuthPanel.tsx +113 -0
  154. package/src/ui/BackendPicker.tsx +28 -0
  155. package/src/ui/BottomComposer.test.ts +674 -0
  156. package/src/ui/BottomComposer.tsx +1028 -0
  157. package/src/ui/CodexLogo.tsx +55 -0
  158. package/src/ui/DashCard.tsx +82 -0
  159. package/src/ui/Markdown.test.ts +157 -0
  160. package/src/ui/Markdown.tsx +310 -0
  161. package/src/ui/ModePicker.tsx +27 -0
  162. package/src/ui/ModelPicker.tsx +31 -0
  163. package/src/ui/ModelPickerProviderScope.test.tsx +411 -0
  164. package/src/ui/ModelPickerScreen.test.tsx +99 -0
  165. package/src/ui/ModelPickerScreen.tsx +416 -0
  166. package/src/ui/ModelPickerState.test.tsx +151 -0
  167. package/src/ui/ModelReasoningPicker.test.tsx +447 -0
  168. package/src/ui/ModelReasoningPicker.tsx +458 -0
  169. package/src/ui/Panel.tsx +51 -0
  170. package/src/ui/PermissionsPanel.tsx +78 -0
  171. package/src/ui/PlanActionPicker.tsx +119 -0
  172. package/src/ui/PlanReviewPanel.test.tsx +267 -0
  173. package/src/ui/PlanReviewPanel.tsx +212 -0
  174. package/src/ui/PromptCardBorder.test.tsx +161 -0
  175. package/src/ui/ProviderPicker.test.tsx +289 -0
  176. package/src/ui/ProviderPicker.tsx +321 -0
  177. package/src/ui/ProviderShortcut.test.tsx +143 -0
  178. package/src/ui/ReasoningPicker.tsx +46 -0
  179. package/src/ui/RunFooter.tsx +65 -0
  180. package/src/ui/SelectionPanel.tsx +67 -0
  181. package/src/ui/SettingsPanel.test.tsx +233 -0
  182. package/src/ui/SettingsPanel.tsx +156 -0
  183. package/src/ui/Spinner.tsx +25 -0
  184. package/src/ui/StaticIntroItem.tsx +54 -0
  185. package/src/ui/StaticTranscriptItem.tsx +56 -0
  186. package/src/ui/TextEntryPanel.tsx +139 -0
  187. package/src/ui/ThemePicker.tsx +31 -0
  188. package/src/ui/ThinkingBlock.tsx +100 -0
  189. package/src/ui/Timeline.test.ts +2067 -0
  190. package/src/ui/Timeline.tsx +1472 -0
  191. package/src/ui/TimelineNavigation.test.tsx +201 -0
  192. package/src/ui/TopHeader.test.tsx +239 -0
  193. package/src/ui/TopHeader.tsx +257 -0
  194. package/src/ui/TurnGroup.test.tsx +365 -0
  195. package/src/ui/TurnGroup.tsx +657 -0
  196. package/src/ui/busyStatusAnimation.test.ts +30 -0
  197. package/src/ui/busyStatusAnimation.ts +11 -0
  198. package/src/ui/commandNormalize.test.ts +142 -0
  199. package/src/ui/commandNormalize.ts +66 -0
  200. package/src/ui/diffRenderer.test.ts +102 -0
  201. package/src/ui/diffRenderer.ts +116 -0
  202. package/src/ui/focus.ts +61 -0
  203. package/src/ui/focusFlow.test.tsx +1098 -0
  204. package/src/ui/inputBuffer.test.ts +151 -0
  205. package/src/ui/inputBuffer.ts +203 -0
  206. package/src/ui/layout.test.ts +145 -0
  207. package/src/ui/layout.ts +287 -0
  208. package/src/ui/modeDisplay.test.ts +42 -0
  209. package/src/ui/modeDisplay.ts +52 -0
  210. package/src/ui/outputPipeline.ts +64 -0
  211. package/src/ui/progressEntries.ts +156 -0
  212. package/src/ui/runActivityView.test.ts +89 -0
  213. package/src/ui/runActivityView.ts +37 -0
  214. package/src/ui/runLifecycleView.test.tsx +237 -0
  215. package/src/ui/slashCommands.ts +41 -0
  216. package/src/ui/statusRenderIsolation.test.tsx +654 -0
  217. package/src/ui/terminalAnswerFormat.test.ts +19 -0
  218. package/src/ui/terminalAnswerFormat.ts +128 -0
  219. package/src/ui/textLayout.test.ts +18 -0
  220. package/src/ui/textLayout.ts +338 -0
  221. package/src/ui/theme.tsx +395 -0
  222. package/src/ui/themeFlow.test.ts +53 -0
  223. package/src/ui/themeFlow.ts +41 -0
  224. package/src/ui/timelineMeasure.ts +3088 -0
  225. package/src/ui/timelineMeasureCache.test.ts +986 -0
  226. package/src/ui/useThrottledValue.ts +31 -0
@@ -0,0 +1,37 @@
1
+ import type { RunEvent } from "../session/types.js";
2
+
3
+ const MAX_VISIBLE_ACTIVE_ACTIVITY = 6;
4
+ const MAX_VISIBLE_SUMMARY_ACTIVITY = 4;
5
+ export function selectVisibleRunActivity(event: RunEvent) {
6
+ const source = event.status === "running"
7
+ ? event.activity
8
+ : event.activitySummary?.recent ?? event.activity;
9
+ const limit = event.status === "running" ? MAX_VISIBLE_ACTIVE_ACTIVITY : MAX_VISIBLE_SUMMARY_ACTIVITY;
10
+ const visible = source.slice(-limit);
11
+ return {
12
+ visible,
13
+ hiddenCount: Math.max(0, source.length - visible.length),
14
+ };
15
+ }
16
+
17
+ export function shouldShowRawOutputFallback(event: RunEvent): boolean {
18
+ void event;
19
+ return false;
20
+ }
21
+
22
+ export function getVisibleRawOutputLines(event: RunEvent): string[] {
23
+ void event;
24
+ return [];
25
+ }
26
+
27
+ export function formatRunActivityStats(event: RunEvent): string | null {
28
+ const summary = event.activitySummary;
29
+ if (!summary || event.touchedFileCount === 0) return null;
30
+
31
+ return [
32
+ `${event.touchedFileCount} file${event.touchedFileCount === 1 ? "" : "s"} touched`,
33
+ `+${summary.created}`,
34
+ `~${summary.modified}`,
35
+ `-${summary.deleted}`,
36
+ ].join(" ");
37
+ }
@@ -0,0 +1,237 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import React from "react";
4
+ import { PassThrough } from "node:stream";
5
+ import { Box, render } from "ink";
6
+ import type { UIState } from "../session/types.js";
7
+ import { isBusy } from "../session/types.js";
8
+ import { BottomComposer } from "./BottomComposer.js";
9
+ import { createLayoutSnapshot } from "./layout.js";
10
+ import { getRunFooterStatus, RunFooter } from "./RunFooter.js";
11
+ import { ThemeProvider } from "./theme.js";
12
+
13
+ class TestInput extends PassThrough {
14
+ readonly isTTY = true;
15
+
16
+ setRawMode(): this {
17
+ return this;
18
+ }
19
+
20
+ override resume(): this {
21
+ return this;
22
+ }
23
+
24
+ override pause(): this {
25
+ return this;
26
+ }
27
+
28
+ ref(): this {
29
+ return this;
30
+ }
31
+
32
+ unref(): this {
33
+ return this;
34
+ }
35
+ }
36
+
37
+ class TestOutput extends PassThrough {
38
+ readonly isTTY = true;
39
+ columns = 120;
40
+ rows = 40;
41
+ }
42
+
43
+ function stripAnsi(value: string): string {
44
+ return value.replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, "");
45
+ }
46
+
47
+ function sleep(ms = 50): Promise<void> {
48
+ return new Promise((resolve) => setTimeout(resolve, ms));
49
+ }
50
+
51
+ const TEST_LAYOUT = createLayoutSnapshot(120, 40);
52
+
53
+ test("idle footer status is empty by contract", () => {
54
+ assert.equal(getRunFooterStatus({ kind: "IDLE" }), "");
55
+ });
56
+
57
+ function LifecycleHarness({ uiState, value, showBusyLoader = true }: { uiState: UIState; value: string; showBusyLoader?: boolean }) {
58
+ const showComposer = !isBusy(uiState);
59
+
60
+ return (
61
+ <ThemeProvider theme="purple">
62
+ <Box flexDirection="column" width="100%">
63
+ {showComposer ? (
64
+ <BottomComposer
65
+ layout={TEST_LAYOUT}
66
+ uiState={uiState}
67
+ mode="suggest"
68
+ model="gpt-5.4"
69
+ reasoningLevel="balanced"
70
+ tokensUsed={100}
71
+ value={value}
72
+ cursor={value.length}
73
+ onChangeInput={() => {}}
74
+ onSubmit={() => {}}
75
+ onCancel={() => {}}
76
+ onChangeValue={() => {}}
77
+ onChangeCursor={() => {}}
78
+ onHistoryUp={() => {}}
79
+ onHistoryDown={() => {}}
80
+ onOpenBackendPicker={() => {}}
81
+ onOpenModelPicker={() => {}}
82
+ onOpenModePicker={() => {}}
83
+ onOpenThemePicker={() => {}}
84
+ onOpenAuthPanel={() => {}}
85
+ onTogglePlanMode={() => {}}
86
+ onClear={() => {}}
87
+ onCycleMode={() => {}}
88
+ onQuit={() => {}}
89
+ />
90
+ ) : (
91
+ <RunFooter uiState={uiState} showBusyLoader={showBusyLoader} onCancel={() => {}} onQuit={() => {}} />
92
+ )}
93
+ </Box>
94
+ </ThemeProvider>
95
+ );
96
+ }
97
+
98
+ test("collapses composer during thinking so input buffer artifacts are removed", async () => {
99
+ const stdin = new TestInput();
100
+ const stdout = new TestOutput();
101
+ let output = "";
102
+ stdout.on("data", (chunk) => {
103
+ output += chunk.toString();
104
+ });
105
+
106
+ const instance = render(
107
+ <LifecycleHarness uiState={{ kind: "IDLE" }} value="draft prompt" />,
108
+ {
109
+ stdin: stdin as unknown as NodeJS.ReadStream,
110
+ stdout: stdout as unknown as NodeJS.WriteStream,
111
+ stderr: stdout as unknown as NodeJS.WriteStream,
112
+ debug: true,
113
+ exitOnCtrlC: false,
114
+ },
115
+ );
116
+
117
+ await sleep();
118
+ let frame = stripAnsi(output);
119
+ assert.match(frame, /draft prompt/i);
120
+
121
+ output = "";
122
+ instance.rerender(<LifecycleHarness uiState={{ kind: "THINKING", turnId: 1 }} value="draft prompt" />);
123
+ await sleep();
124
+ frame = stripAnsi(output);
125
+ assert.doesNotMatch(frame, /draft prompt/i);
126
+ assert.match(frame, /Codexa is thinking/i);
127
+
128
+ instance.unmount();
129
+ });
130
+
131
+ test("busy footer advances from local status state without a parent rerender", async () => {
132
+ const stdin = new TestInput();
133
+ const stdout = new TestOutput();
134
+ let output = "";
135
+
136
+ stdout.on("data", (chunk) => {
137
+ output += chunk.toString();
138
+ });
139
+
140
+ const instance = render(
141
+ <LifecycleHarness uiState={{ kind: "THINKING", turnId: 1 }} value="" />,
142
+ {
143
+ stdin: stdin as unknown as NodeJS.ReadStream,
144
+ stdout: stdout as unknown as NodeJS.WriteStream,
145
+ stderr: stdout as unknown as NodeJS.WriteStream,
146
+ debug: true,
147
+ exitOnCtrlC: false,
148
+ },
149
+ );
150
+
151
+ await sleep();
152
+ let frame = stripAnsi(output);
153
+ assert.match(frame, /Codexa is thinking \./);
154
+
155
+ output = "";
156
+ await sleep(950);
157
+ frame = stripAnsi(output);
158
+ assert.match(frame, /Codexa is thinking \.\./);
159
+
160
+ instance.unmount();
161
+ });
162
+
163
+ test("busy footer omits loader frames when disabled", async () => {
164
+ const stdin = new TestInput();
165
+ const stdout = new TestOutput();
166
+ let output = "";
167
+
168
+ stdout.on("data", (chunk) => {
169
+ output += chunk.toString();
170
+ });
171
+
172
+ const instance = render(
173
+ <LifecycleHarness uiState={{ kind: "THINKING", turnId: 1 }} value="" showBusyLoader={false} />,
174
+ {
175
+ stdin: stdin as unknown as NodeJS.ReadStream,
176
+ stdout: stdout as unknown as NodeJS.WriteStream,
177
+ stderr: stdout as unknown as NodeJS.WriteStream,
178
+ debug: true,
179
+ exitOnCtrlC: false,
180
+ },
181
+ );
182
+
183
+ try {
184
+ await sleep();
185
+ let frame = stripAnsi(output);
186
+ assert.match(frame, /Codexa is thinking/);
187
+ assert.doesNotMatch(frame, /Codexa is thinking \./);
188
+
189
+ output = "";
190
+ await sleep(950);
191
+ frame = stripAnsi(output);
192
+ assert.equal(frame, "");
193
+ } finally {
194
+ instance.unmount();
195
+ }
196
+ });
197
+
198
+ test("static status debug flag reserves status text without dot ticks", async () => {
199
+ const previous = process.env.CODEXA_DEBUG_STATIC_STATUS;
200
+ process.env.CODEXA_DEBUG_STATIC_STATUS = "1";
201
+ const stdin = new TestInput();
202
+ const stdout = new TestOutput();
203
+ let output = "";
204
+
205
+ stdout.on("data", (chunk) => {
206
+ output += chunk.toString();
207
+ });
208
+
209
+ const instance = render(
210
+ <LifecycleHarness uiState={{ kind: "THINKING", turnId: 1 }} value="" />,
211
+ {
212
+ stdin: stdin as unknown as NodeJS.ReadStream,
213
+ stdout: stdout as unknown as NodeJS.WriteStream,
214
+ stderr: stdout as unknown as NodeJS.WriteStream,
215
+ debug: true,
216
+ exitOnCtrlC: false,
217
+ },
218
+ );
219
+
220
+ try {
221
+ await sleep();
222
+ let frame = stripAnsi(output);
223
+ assert.match(frame, /Codexa is thinking \.\.\./);
224
+
225
+ output = "";
226
+ await sleep(950);
227
+ frame = stripAnsi(output);
228
+ assert.equal(frame, "");
229
+ } finally {
230
+ instance.unmount();
231
+ if (previous === undefined) {
232
+ delete process.env.CODEXA_DEBUG_STATIC_STATUS;
233
+ } else {
234
+ process.env.CODEXA_DEBUG_STATIC_STATUS = previous;
235
+ }
236
+ }
237
+ });
@@ -0,0 +1,41 @@
1
+ export interface SlashCommandSuggestion {
2
+ cmd: string;
3
+ desc: string;
4
+ aliases?: readonly string[];
5
+ }
6
+
7
+ export const SLASH_COMMANDS = [
8
+ { cmd: "/help", desc: "Show available commands" },
9
+ { cmd: "/clear", desc: "Clear chat and cancel active run" },
10
+ { cmd: "/providers", desc: "Open AI provider picker", aliases: ["/provider"] },
11
+ { cmd: "/model", desc: "Change active model" },
12
+ { cmd: "/models", desc: "Open model picker" },
13
+ { cmd: "/mode", desc: "Change execution mode" },
14
+ { cmd: "/route", desc: "Show active chat route" },
15
+ { cmd: "/backend", desc: "Change active backend" },
16
+ { cmd: "/reasoning", desc: "Change reasoning level" },
17
+ { cmd: "/plan", desc: "Show or toggle session plan mode" },
18
+ { cmd: "/settings", desc: "Open the settings picker", aliases: ["/setting"] },
19
+ { cmd: "/status", desc: "Show effective runtime configuration" },
20
+ { cmd: "/permissions", desc: "Inspect or update permissions and sandbox controls" },
21
+ { cmd: "/runtime", desc: "Compatibility runtime policy controls" },
22
+ { cmd: "/themes", desc: "Open visual theme picker" },
23
+ { cmd: "/verbose", desc: "Toggle verbose mode (detailed processing info)" },
24
+ { cmd: "/auth", desc: "Manage authentication" },
25
+ { cmd: "/workspace", desc: "Show the locked workspace" },
26
+ { cmd: "/copy", desc: "Copy the full conversation transcript to clipboard" },
27
+ { cmd: "/mouse", desc: "Toggle mouse capture for wheel scrolling (off by default)" },
28
+ { cmd: "/exit", desc: "Quit the application" },
29
+ ] as const satisfies readonly SlashCommandSuggestion[];
30
+
31
+ export type CommandSuggestion = (typeof SLASH_COMMANDS)[number];
32
+
33
+ function matchesPrefix(suggestion: SlashCommandSuggestion, prefix: string): boolean {
34
+ if (suggestion.cmd.startsWith(prefix)) return true;
35
+ return suggestion.aliases?.some((alias) => alias.startsWith(prefix)) ?? false;
36
+ }
37
+
38
+ export function getSlashCommandSuggestions(prefix: string): readonly CommandSuggestion[] {
39
+ const normalized = prefix.toLowerCase();
40
+ return SLASH_COMMANDS.filter((command) => matchesPrefix(command, normalized)).slice(0, 5);
41
+ }