ralph-cli-sandboxed 0.3.0 → 0.4.0

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 (55) hide show
  1. package/dist/commands/action.d.ts +7 -0
  2. package/dist/commands/action.js +276 -0
  3. package/dist/commands/chat.js +95 -7
  4. package/dist/commands/config.js +6 -18
  5. package/dist/commands/fix-config.d.ts +4 -0
  6. package/dist/commands/fix-config.js +388 -0
  7. package/dist/commands/help.js +17 -0
  8. package/dist/commands/init.js +89 -2
  9. package/dist/commands/listen.js +50 -9
  10. package/dist/commands/prd.js +2 -2
  11. package/dist/config/languages.json +4 -0
  12. package/dist/index.js +4 -0
  13. package/dist/providers/telegram.d.ts +6 -2
  14. package/dist/providers/telegram.js +68 -2
  15. package/dist/templates/macos-scripts.d.ts +42 -0
  16. package/dist/templates/macos-scripts.js +448 -0
  17. package/dist/tui/ConfigEditor.d.ts +7 -0
  18. package/dist/tui/ConfigEditor.js +313 -0
  19. package/dist/tui/components/ArrayEditor.d.ts +22 -0
  20. package/dist/tui/components/ArrayEditor.js +193 -0
  21. package/dist/tui/components/BooleanToggle.d.ts +19 -0
  22. package/dist/tui/components/BooleanToggle.js +43 -0
  23. package/dist/tui/components/EditorPanel.d.ts +50 -0
  24. package/dist/tui/components/EditorPanel.js +232 -0
  25. package/dist/tui/components/HelpPanel.d.ts +13 -0
  26. package/dist/tui/components/HelpPanel.js +69 -0
  27. package/dist/tui/components/JsonSnippetEditor.d.ts +24 -0
  28. package/dist/tui/components/JsonSnippetEditor.js +380 -0
  29. package/dist/tui/components/KeyValueEditor.d.ts +34 -0
  30. package/dist/tui/components/KeyValueEditor.js +261 -0
  31. package/dist/tui/components/ObjectEditor.d.ts +23 -0
  32. package/dist/tui/components/ObjectEditor.js +227 -0
  33. package/dist/tui/components/PresetSelector.d.ts +23 -0
  34. package/dist/tui/components/PresetSelector.js +58 -0
  35. package/dist/tui/components/Preview.d.ts +18 -0
  36. package/dist/tui/components/Preview.js +190 -0
  37. package/dist/tui/components/ScrollableContainer.d.ts +38 -0
  38. package/dist/tui/components/ScrollableContainer.js +77 -0
  39. package/dist/tui/components/SectionNav.d.ts +31 -0
  40. package/dist/tui/components/SectionNav.js +130 -0
  41. package/dist/tui/components/StringEditor.d.ts +21 -0
  42. package/dist/tui/components/StringEditor.js +29 -0
  43. package/dist/tui/hooks/useConfig.d.ts +16 -0
  44. package/dist/tui/hooks/useConfig.js +89 -0
  45. package/dist/tui/hooks/useTerminalSize.d.ts +21 -0
  46. package/dist/tui/hooks/useTerminalSize.js +48 -0
  47. package/dist/tui/utils/presets.d.ts +52 -0
  48. package/dist/tui/utils/presets.js +191 -0
  49. package/dist/tui/utils/validation.d.ts +49 -0
  50. package/dist/tui/utils/validation.js +198 -0
  51. package/dist/utils/chat-client.d.ts +31 -1
  52. package/dist/utils/chat-client.js +27 -1
  53. package/dist/utils/config.d.ts +7 -2
  54. package/docs/MACOS-DEVELOPMENT.md +435 -0
  55. package/package.json +1 -1
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Execute an action from config.json - works both inside and outside containers.
3
+ *
4
+ * Inside container: Uses file-based message queue to communicate with host daemon
5
+ * Outside container: Executes the command directly
6
+ */
7
+ export declare function action(args: string[]): Promise<void>;
@@ -0,0 +1,276 @@
1
+ import { spawn } from "child_process";
2
+ import { existsSync } from "fs";
3
+ import { loadConfig, isRunningInContainer } from "../utils/config.js";
4
+ import { getMessagesPath, sendMessage, waitForResponse, } from "../utils/message-queue.js";
5
+ /**
6
+ * Execute an action from config.json - works both inside and outside containers.
7
+ *
8
+ * Inside container: Uses file-based message queue to communicate with host daemon
9
+ * Outside container: Executes the command directly
10
+ */
11
+ export async function action(args) {
12
+ // Parse arguments
13
+ let actionName;
14
+ let actionArgs = [];
15
+ let debug = false;
16
+ let showList = false;
17
+ for (let i = 0; i < args.length; i++) {
18
+ const arg = args[i];
19
+ if (arg === "--debug" || arg === "-d") {
20
+ debug = true;
21
+ }
22
+ else if (arg === "--help" || arg === "-h") {
23
+ showHelp();
24
+ return;
25
+ }
26
+ else if (arg === "--list" || arg === "-l") {
27
+ showList = true;
28
+ }
29
+ else if (!arg.startsWith("-")) {
30
+ if (!actionName) {
31
+ actionName = arg;
32
+ }
33
+ else {
34
+ // Collect remaining args as action arguments
35
+ actionArgs = args.slice(i);
36
+ break;
37
+ }
38
+ }
39
+ }
40
+ // Load config
41
+ let config;
42
+ try {
43
+ config = loadConfig();
44
+ }
45
+ catch {
46
+ console.error("Failed to load config. Run 'ralph init' first.");
47
+ process.exit(1);
48
+ }
49
+ // Get available actions from daemon config
50
+ const daemonActions = config.daemon?.actions || {};
51
+ const actionNames = Object.keys(daemonActions);
52
+ // If --list or no action specified, show available actions
53
+ if (showList || !actionName) {
54
+ if (actionNames.length === 0) {
55
+ console.log("No actions configured.");
56
+ console.log("");
57
+ console.log("Configure actions in .ralph/config.json:");
58
+ console.log(' {');
59
+ console.log(' "daemon": {');
60
+ console.log(' "actions": {');
61
+ console.log(' "build": {');
62
+ console.log(' "command": "./scripts/build.sh",');
63
+ console.log(' "description": "Build the project"');
64
+ console.log(' }');
65
+ console.log(' }');
66
+ console.log(' }');
67
+ console.log(' }');
68
+ }
69
+ else {
70
+ console.log("Available actions:");
71
+ console.log("");
72
+ for (const [name, actionConfig] of Object.entries(daemonActions)) {
73
+ const desc = actionConfig.description || actionConfig.command;
74
+ console.log(` ${name.padEnd(20)} ${desc}`);
75
+ }
76
+ console.log("");
77
+ console.log("Run an action: ralph action <name> [args...]");
78
+ }
79
+ return;
80
+ }
81
+ // Validate action exists
82
+ if (!daemonActions[actionName]) {
83
+ console.error(`Unknown action: ${actionName}`);
84
+ console.error("");
85
+ if (actionNames.length > 0) {
86
+ console.error(`Available actions: ${actionNames.join(", ")}`);
87
+ }
88
+ else {
89
+ console.error("No actions configured in .ralph/config.json");
90
+ }
91
+ process.exit(1);
92
+ }
93
+ const actionConfig = daemonActions[actionName];
94
+ const inContainer = isRunningInContainer();
95
+ if (debug) {
96
+ console.log(`[action] Name: ${actionName}`);
97
+ console.log(`[action] Args: ${actionArgs.join(" ") || "(none)"}`);
98
+ console.log(`[action] Command: ${actionConfig.command}`);
99
+ console.log(`[action] In container: ${inContainer}`);
100
+ }
101
+ if (inContainer) {
102
+ // Inside container - use file-based message queue to execute on host
103
+ await executeViaQueue(actionName, actionArgs, debug);
104
+ }
105
+ else {
106
+ // Outside container - execute directly
107
+ await executeDirectly(actionConfig.command, actionArgs, debug);
108
+ }
109
+ }
110
+ /**
111
+ * Execute action via message queue (when running inside container).
112
+ */
113
+ async function executeViaQueue(actionName, args, debug) {
114
+ const messagesPath = getMessagesPath(true);
115
+ if (!existsSync(messagesPath)) {
116
+ const ralphDir = "/workspace/.ralph";
117
+ if (!existsSync(ralphDir)) {
118
+ console.error("Error: .ralph directory not mounted in container.");
119
+ console.error("Make sure the container is started with 'ralph docker run'.");
120
+ process.exit(1);
121
+ }
122
+ }
123
+ // Send message via file queue
124
+ const messageId = sendMessage(messagesPath, "sandbox", actionName, args.length > 0 ? args : undefined);
125
+ if (debug) {
126
+ console.log(`[action] Sent message: ${messageId}`);
127
+ }
128
+ console.log(`Executing action: ${actionName}`);
129
+ console.log("Waiting for daemon response...");
130
+ // Wait for response with longer timeout for actions that may take time
131
+ const response = await waitForResponse(messagesPath, messageId, 60000);
132
+ if (!response) {
133
+ console.error("No response from daemon (timeout).");
134
+ console.error("");
135
+ console.error("Make sure the daemon is running on the host:");
136
+ console.error(" ralph daemon start");
137
+ process.exit(1);
138
+ }
139
+ if (debug) {
140
+ console.log(`[action] Response: ${JSON.stringify(response)}`);
141
+ }
142
+ // Display output
143
+ if (response.output) {
144
+ console.log("");
145
+ console.log(response.output);
146
+ }
147
+ if (response.success) {
148
+ console.log("");
149
+ console.log(`Action '${actionName}' completed successfully.`);
150
+ }
151
+ else {
152
+ console.error("");
153
+ console.error(`Action '${actionName}' failed: ${response.error || "Unknown error"}`);
154
+ process.exit(1);
155
+ }
156
+ }
157
+ /**
158
+ * Execute action directly on host (when running outside container).
159
+ */
160
+ async function executeDirectly(command, args, debug) {
161
+ return new Promise((resolve, reject) => {
162
+ // Build full command with arguments
163
+ let fullCommand = command;
164
+ if (args.length > 0) {
165
+ fullCommand = `${command} ${args.map(a => `"${a.replace(/"/g, '\\"')}"`).join(" ")}`;
166
+ }
167
+ if (debug) {
168
+ console.log(`[action] Executing: ${fullCommand}`);
169
+ }
170
+ console.log(`Executing: ${fullCommand}`);
171
+ console.log("");
172
+ const proc = spawn(fullCommand, [], {
173
+ stdio: ["inherit", "pipe", "pipe"],
174
+ shell: true,
175
+ cwd: process.cwd(),
176
+ });
177
+ // Stream stdout in real-time
178
+ proc.stdout.on("data", (data) => {
179
+ process.stdout.write(data);
180
+ });
181
+ // Stream stderr in real-time
182
+ proc.stderr.on("data", (data) => {
183
+ process.stderr.write(data);
184
+ });
185
+ proc.on("close", (code) => {
186
+ if (code === 0) {
187
+ console.log("");
188
+ console.log("Action completed successfully.");
189
+ resolve();
190
+ }
191
+ else {
192
+ console.error("");
193
+ console.error(`Action failed with exit code: ${code}`);
194
+ process.exit(code || 1);
195
+ }
196
+ });
197
+ proc.on("error", (err) => {
198
+ console.error(`Failed to execute action: ${err.message}`);
199
+ reject(err);
200
+ });
201
+ });
202
+ }
203
+ function showHelp() {
204
+ console.log(`
205
+ ralph action - Execute host actions from config.json
206
+
207
+ USAGE:
208
+ ralph action [name] [args...] Execute an action
209
+ ralph action --list List available actions
210
+ ralph action --help Show this help
211
+
212
+ OPTIONS:
213
+ -l, --list List all configured actions
214
+ -d, --debug Show debug output
215
+ -h, --help Show this help message
216
+
217
+ DESCRIPTION:
218
+ This command executes actions defined in the daemon.actions section of
219
+ .ralph/config.json. When running inside a container, the action is
220
+ executed on the host via the daemon. When running on the host directly,
221
+ the action is executed locally.
222
+
223
+ Actions are useful for triggering host operations like:
224
+ - Building Xcode projects (requires host Xcode installation)
225
+ - Running deployment scripts
226
+ - Executing platform-specific tools not available in the container
227
+
228
+ CONFIGURATION:
229
+ Define actions in .ralph/config.json:
230
+
231
+ {
232
+ "daemon": {
233
+ "actions": {
234
+ "build": {
235
+ "command": "./scripts/build.sh",
236
+ "description": "Build the project"
237
+ },
238
+ "deploy": {
239
+ "command": "./scripts/deploy.sh --env staging",
240
+ "description": "Deploy to staging"
241
+ },
242
+ "gen_xcode": {
243
+ "command": "swift package generate-xcodeproj",
244
+ "description": "Generate Xcode project from Swift package"
245
+ }
246
+ }
247
+ }
248
+ }
249
+
250
+ EXAMPLES:
251
+ # List available actions
252
+ ralph action --list
253
+ ralph action
254
+
255
+ # Execute an action
256
+ ralph action build
257
+ ralph action deploy --env production
258
+ ralph action gen_xcode
259
+
260
+ # Execute with arguments
261
+ ralph action build --release
262
+ ralph action deploy staging
263
+
264
+ SETUP FOR CONTAINER USAGE:
265
+ 1. Define actions in .ralph/config.json
266
+ 2. Start the daemon on the host: ralph daemon start
267
+ 3. Run the container: ralph docker run
268
+ 4. From inside the container: ralph action build
269
+
270
+ NOTES:
271
+ - Actions configured in daemon.actions are automatically available to the daemon
272
+ - When in a container, the daemon must be running to process action requests
273
+ - Actions have a 60-second timeout when executed via the daemon
274
+ - Output is streamed in real-time when executing directly on host
275
+ `);
276
+ }
@@ -7,7 +7,7 @@ import { join, basename } from "path";
7
7
  import { spawn } from "child_process";
8
8
  import { loadConfig, getRalphDir, isRunningInContainer } from "../utils/config.js";
9
9
  import { createTelegramClient } from "../providers/telegram.js";
10
- import { generateProjectId, formatStatusMessage, } from "../utils/chat-client.js";
10
+ import { generateProjectId, formatStatusMessage, formatStatusForChat, } from "../utils/chat-client.js";
11
11
  import { getMessagesPath, sendMessage, waitForResponse, } from "../utils/message-queue.js";
12
12
  const CHAT_STATE_FILE = "chat-state.json";
13
13
  /**
@@ -74,6 +74,35 @@ function getPrdStatus() {
74
74
  return { complete: 0, total: 0, incomplete: 0 };
75
75
  }
76
76
  }
77
+ /**
78
+ * Get open (incomplete) categories from the PRD.
79
+ * Returns unique categories that have at least one incomplete task.
80
+ */
81
+ function getOpenCategories() {
82
+ const ralphDir = getRalphDir();
83
+ const prdPath = join(ralphDir, "prd.json");
84
+ if (!existsSync(prdPath)) {
85
+ return [];
86
+ }
87
+ try {
88
+ const content = readFileSync(prdPath, "utf-8");
89
+ const items = JSON.parse(content);
90
+ if (!Array.isArray(items)) {
91
+ return [];
92
+ }
93
+ // Get unique categories that have incomplete tasks
94
+ const openCategories = new Set();
95
+ for (const item of items) {
96
+ if (item.passes !== true && item.category) {
97
+ openCategories.add(item.category);
98
+ }
99
+ }
100
+ return Array.from(openCategories);
101
+ }
102
+ catch {
103
+ return [];
104
+ }
105
+ }
77
106
  /**
78
107
  * Add a new task to the PRD.
79
108
  */
@@ -163,18 +192,22 @@ async function handleCommand(command, client, config, state, debug) {
163
192
  }
164
193
  switch (cmd) {
165
194
  case "run": {
195
+ // Check for optional category filter
196
+ const category = args.length > 0 ? args[0] : undefined;
166
197
  // Check PRD status first (from host)
167
198
  const prdStatus = getPrdStatus();
168
199
  if (prdStatus.incomplete === 0) {
169
200
  await client.sendMessage(chatId, `${state.projectName}: All tasks already complete (${prdStatus.complete}/${prdStatus.total})`);
170
201
  return;
171
202
  }
172
- await client.sendMessage(chatId, `${state.projectName}: Starting ralph run (${prdStatus.incomplete} tasks remaining)...`);
173
- // Send run command to sandbox
174
- const response = await sendToSandbox("run", [], debug, 10000);
203
+ const categoryInfo = category ? ` (category: ${category})` : "";
204
+ await client.sendMessage(chatId, `${state.projectName}: Starting ralph run${categoryInfo} (${prdStatus.incomplete} tasks remaining)...`);
205
+ // Send run command to sandbox with optional category argument
206
+ const runArgs = category ? [category] : [];
207
+ const response = await sendToSandbox("run", runArgs, debug, 10000);
175
208
  if (response) {
176
209
  if (response.success) {
177
- await client.sendMessage(chatId, `${state.projectName}: Ralph run started in sandbox`);
210
+ await client.sendMessage(chatId, `${state.projectName}: Ralph run started in sandbox${categoryInfo}`);
178
211
  }
179
212
  else {
180
213
  await client.sendMessage(chatId, `${state.projectName}: Failed to start: ${response.error}`);
@@ -190,16 +223,32 @@ async function handleCommand(command, client, config, state, debug) {
190
223
  case "status": {
191
224
  // Try sandbox first, fall back to host
192
225
  const response = await sendToSandbox("status", [], debug, 5000);
226
+ let statusMessage;
193
227
  if (response?.success && response.output) {
194
- await client.sendMessage(chatId, `${state.projectName}:\n${response.output}`);
228
+ // Strip ANSI codes and progress bar for clean chat output
229
+ const cleanedOutput = formatStatusForChat(response.output);
230
+ statusMessage = `${state.projectName}:\n${cleanedOutput}`;
195
231
  }
196
232
  else {
197
233
  // Fall back to host status
198
234
  const prdStatus = getPrdStatus();
199
235
  const status = prdStatus.incomplete === 0 ? "completed" : "idle";
200
236
  const details = `Progress: ${prdStatus.complete}/${prdStatus.total} tasks complete`;
201
- await client.sendMessage(chatId, formatStatusMessage(state.projectName, status, details));
237
+ statusMessage = formatStatusMessage(state.projectName, status, details);
238
+ }
239
+ // Get open categories and create inline buttons (max 4)
240
+ const openCategories = getOpenCategories();
241
+ let inlineKeyboard;
242
+ if (openCategories.length > 0 && openCategories.length <= 4) {
243
+ // Create a row of buttons, one per category
244
+ inlineKeyboard = [
245
+ openCategories.map((category) => ({
246
+ text: `▶ Run ${category}`,
247
+ callbackData: `/run ${category}`,
248
+ })),
249
+ ];
202
250
  }
251
+ await client.sendMessage(chatId, statusMessage, { inlineKeyboard });
203
252
  break;
204
253
  }
205
254
  case "add": {
@@ -295,6 +344,41 @@ async function handleCommand(command, client, config, state, debug) {
295
344
  }
296
345
  break;
297
346
  }
347
+ case "claude": {
348
+ if (args.length === 0) {
349
+ await client.sendMessage(chatId, `${state.projectName}: Usage: /claude [prompt]`);
350
+ return;
351
+ }
352
+ const prompt = args.join(" ");
353
+ await client.sendMessage(chatId, `⏳ ${state.projectName}: Running Claude Code...\n(this may take a few minutes)`);
354
+ // Send claude command to sandbox with longer timeout (5 minutes)
355
+ const response = await sendToSandbox("claude", args, debug, 300000);
356
+ if (response) {
357
+ let output = response.output || response.error || "(no output)";
358
+ // Truncate long output
359
+ if (output.length > 2000) {
360
+ output = output.substring(0, 2000) + "\n...(truncated)";
361
+ }
362
+ if (response.success) {
363
+ await client.sendMessage(chatId, `✅ ${state.projectName}: Claude Code DONE\n\n${output}`);
364
+ }
365
+ else {
366
+ // Check for version mismatch (sandbox has old version without /claude support)
367
+ if (response.error?.includes("Unknown action: claude")) {
368
+ await client.sendMessage(chatId, `❌ ${state.projectName}: Claude Code failed - sandbox needs update.\n` +
369
+ `The sandbox listener doesn't support /claude. Rebuild your Docker container:\n` +
370
+ ` ralph docker build --no-cache`);
371
+ }
372
+ else {
373
+ await client.sendMessage(chatId, `❌ ${state.projectName}: Claude Code FAILED\n\n${output}`);
374
+ }
375
+ }
376
+ }
377
+ else {
378
+ await client.sendMessage(chatId, `❌ ${state.projectName}: No response from sandbox. Is 'ralph listen' running?`);
379
+ }
380
+ break;
381
+ }
298
382
  case "help": {
299
383
  const helpText = `
300
384
  /run - Start automation
@@ -302,6 +386,7 @@ async function handleCommand(command, client, config, state, debug) {
302
386
  /add [desc] - Add task
303
387
  /exec [cmd] - Shell command
304
388
  /action [name] - Run action
389
+ /claude [prompt] - Run Claude Code
305
390
  /help - This help
306
391
  `.trim();
307
392
  await client.sendMessage(chatId, helpText);
@@ -365,6 +450,7 @@ async function startChat(config, debug) {
365
450
  console.log(" /add ... - Add new task to PRD");
366
451
  console.log(" /exec ... - Execute shell command");
367
452
  console.log(" /action ... - Run daemon action");
453
+ console.log(" /claude ... - Run Claude Code with prompt (YOLO mode)");
368
454
  console.log(" /help - Show help");
369
455
  console.log("");
370
456
  console.log("Press Ctrl+C to stop the daemon.");
@@ -538,6 +624,7 @@ CHAT COMMANDS:
538
624
  /add [desc] - Add new task to PRD
539
625
  /exec [cmd] - Execute shell command
540
626
  /action [name] - Run daemon action (e.g., /action build)
627
+ /claude [prompt] - Run Claude Code with prompt in YOLO mode
541
628
  /stop - Stop running ralph process
542
629
  /help - Show help
543
630
 
@@ -580,6 +667,7 @@ EXAMPLES:
580
667
  /exec npm test # Run npm test
581
668
  /action build # Run build action
582
669
  /action deploy # Run deploy action
670
+ /claude Fix the login bug # Run Claude Code with prompt
583
671
  `);
584
672
  return;
585
673
  }
@@ -1,11 +1,8 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { render, Box, Text } from "ink";
3
- import { loadConfig, getPaths } from "../utils/config.js";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render } from "ink";
3
+ import { getPaths } from "../utils/config.js";
4
4
  import { existsSync } from "fs";
5
- // Placeholder Ink app component
6
- function ConfigEditorApp({ config }) {
7
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { color: "cyan", bold: true, children: "ralph config" }), _jsx(Text, { children: "TUI Config Editor (work in progress)" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Language: " }), _jsx(Text, { children: config.language })] }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Check: " }), _jsx(Text, { children: config.checkCommand })] }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Test: " }), _jsx(Text, { children: config.testCommand })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press Ctrl+C to exit" }) })] }));
8
- }
5
+ import { ConfigEditor } from "../tui/ConfigEditor.js";
9
6
  export async function config(args) {
10
7
  const subcommand = args[0];
11
8
  if (subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
@@ -18,17 +15,8 @@ export async function config(args) {
18
15
  console.error("Error: .ralph/config.json not found. Run 'ralph init' first.");
19
16
  process.exit(1);
20
17
  }
21
- // Load configuration
22
- let configData;
23
- try {
24
- configData = loadConfig();
25
- }
26
- catch (error) {
27
- console.error("Error loading config:", error instanceof Error ? error.message : "Unknown error");
28
- process.exit(1);
29
- }
30
- // Render Ink app
31
- const { waitUntilExit } = render(_jsx(ConfigEditorApp, { config: configData }));
18
+ // Render Ink app with ConfigEditor
19
+ const { waitUntilExit } = render(_jsx(ConfigEditor, {}));
32
20
  await waitUntilExit();
33
21
  }
34
22
  function showConfigHelp() {
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Main fix-config command handler.
3
+ */
4
+ export declare function fixConfig(args: string[]): Promise<void>;