woopcode 0.1.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 (67) hide show
  1. package/CONTRIBUTING.md +329 -0
  2. package/LICENSE +21 -0
  3. package/README.md +582 -0
  4. package/cli.ts +17 -0
  5. package/commands/agent.tsx +139 -0
  6. package/commands/agentController.ts +138 -0
  7. package/commands/models.ts +21 -0
  8. package/commands/providers/index.ts +12 -0
  9. package/commands/providers/listProviders.ts +28 -0
  10. package/commands/providers/login.ts +28 -0
  11. package/commands/providers/logout.ts +33 -0
  12. package/commands/providers/setProvider.ts +34 -0
  13. package/config/authProvider.ts +54 -0
  14. package/config/client.ts +163 -0
  15. package/config/config.ts +118 -0
  16. package/config/conversation.json +1 -0
  17. package/config/models.json +50 -0
  18. package/config/paths.ts +96 -0
  19. package/config/providers.json +21 -0
  20. package/config/runtime.ts +130 -0
  21. package/config/systemPrompt.ts +54 -0
  22. package/config/types.ts +88 -0
  23. package/onboarding/FLOW.md +291 -0
  24. package/onboarding/index.ts +56 -0
  25. package/onboarding/providers.ts +47 -0
  26. package/onboarding/setupWizard.tsx +193 -0
  27. package/onboarding/test-reset.ts +55 -0
  28. package/package.json +86 -0
  29. package/tools/createFile.ts +24 -0
  30. package/tools/editFile.ts +85 -0
  31. package/tools/findFiles.ts +94 -0
  32. package/tools/grep.ts +68 -0
  33. package/tools/index.ts +26 -0
  34. package/tools/listFiles.ts +49 -0
  35. package/tools/readFile.ts +53 -0
  36. package/tools/runTests.ts +29 -0
  37. package/tools/terminal.ts +37 -0
  38. package/tools/writeFile.ts +75 -0
  39. package/tui/package.json +0 -0
  40. package/tui/src/app.tsx +60 -0
  41. package/tui/src/components/ApprovalFooter.tsx +29 -0
  42. package/tui/src/components/AsciiLogo.tsx +78 -0
  43. package/tui/src/components/BootScreen.tsx +76 -0
  44. package/tui/src/components/CapabilityRow.tsx +17 -0
  45. package/tui/src/components/CodeBlock.tsx +70 -0
  46. package/tui/src/components/DiffPreview.tsx +46 -0
  47. package/tui/src/components/DiffViewer.tsx +36 -0
  48. package/tui/src/components/HomeFooter.tsx +11 -0
  49. package/tui/src/components/HomeScreen.tsx +71 -0
  50. package/tui/src/components/InlineCode.tsx +13 -0
  51. package/tui/src/components/LogoReveal.tsx +158 -0
  52. package/tui/src/components/Markdown.tsx +280 -0
  53. package/tui/src/components/MessageRenderer.tsx +84 -0
  54. package/tui/src/components/PromptCard.tsx +52 -0
  55. package/tui/src/components/StreamingCursor.tsx +13 -0
  56. package/tui/src/components/ThinkingIndicator.tsx +14 -0
  57. package/tui/src/components/ToolStatus.tsx +26 -0
  58. package/tui/src/header.tsx +25 -0
  59. package/tui/src/hooks/useBootAnimation.ts +67 -0
  60. package/tui/src/hooks/useLogoAnimation.ts +114 -0
  61. package/tui/src/index.ts +5 -0
  62. package/tui/src/prompt.tsx +66 -0
  63. package/tui/src/statusBar.tsx +83 -0
  64. package/tui/src/store/ui-store.ts +234 -0
  65. package/tui/src/store/useUIStore.ts +9 -0
  66. package/tui/src/timeline.tsx +96 -0
  67. package/tui/src/types.ts +40 -0
@@ -0,0 +1,291 @@
1
+ # Onboarding Flow Diagram
2
+
3
+ ## User Journey
4
+
5
+ ```
6
+ ┌─────────────────────────────────────────────────────────────┐
7
+ │ User runs: woopcode │
8
+ └─────────────────────┬───────────────────────────────────────┘
9
+
10
+
11
+ ┌─────────────────────┐
12
+ │ Check configuration │
13
+ │ (ensureProvider │
14
+ │ Configured()) │
15
+ └──────────┬──────────┘
16
+
17
+ ┌──────────┴──────────┐
18
+ │ │
19
+ ┌───────▼────────┐ ┌───────▼────────┐
20
+ │ Has API Key? │ │ Has API Key? │
21
+ │ YES │ │ NO │
22
+ └───────┬────────┘ └───────┬────────┘
23
+ │ │
24
+ │ ┌──────▼──────┐
25
+ │ │ Welcome │
26
+ │ │ Screen │
27
+ │ └──────┬──────┘
28
+ │ │
29
+ │ ┌──────▼──────────┐
30
+ │ │ Select │
31
+ │ │ Provider │
32
+ │ │ (Arrow Keys) │
33
+ │ └──────┬──────────┘
34
+ │ │
35
+ │ ┌──────▼──────────┐
36
+ │ │ Show API Key │
37
+ │ │ URL │
38
+ │ └──────┬──────────┘
39
+ │ │
40
+ │ ┌──────▼──────────┐
41
+ │ │ Enter API Key │
42
+ │ │ (Masked Input) │
43
+ │ └──────┬──────────┘
44
+ │ │
45
+ │ ┌──────▼──────────┐
46
+ │ │ Validate Key │
47
+ │ │ (Spinner) │
48
+ │ └──────┬──────────┘
49
+ │ │
50
+ │ ┌──────┴──────┐
51
+ │ │ │
52
+ │ ┌────▼────┐ ┌────▼────┐
53
+ │ │ Invalid │ │ Valid │
54
+ │ │ Key │ │ Key │
55
+ │ └────┬────┘ └────┬────┘
56
+ │ │ │
57
+ │ ┌────▼────────┐ │
58
+ │ │ Show Error │ │
59
+ │ │ Return to │ │
60
+ │ │ Input │ │
61
+ │ └────┬────────┘ │
62
+ │ │ │
63
+ │ └────────┬───┘
64
+ │ │
65
+ │ ┌──────▼──────┐
66
+ │ │ Save Config │
67
+ │ └──────┬──────┘
68
+ │ │
69
+ │ ┌──────▼──────┐
70
+ │ │ Success │
71
+ │ │ Message │
72
+ │ └──────┬──────┘
73
+ │ │
74
+ └───────────────────────┘
75
+
76
+ ┌──────▼──────┐
77
+ │ Launch │
78
+ │ Main Chat │
79
+ │ Interface │
80
+ └─────────────┘
81
+ ```
82
+
83
+ ## State Machine
84
+
85
+ ```
86
+ ┌─────────┐ Enter ┌────────────────┐ ↑↓ ┌──────────────┐
87
+ │ welcome ├──────────────►│select-provider ├───────────►│ highlighting │
88
+ └─────────┘ └────────┬───────┘ │ provider │
89
+ │ └──────┬───────┘
90
+ │ Enter │
91
+ ┌───────▼──────┐ │
92
+ │api-key-info │◄───────────────────┘
93
+ └───────┬──────┘
94
+ │ Enter
95
+ ┌───────▼──────┐
96
+ │ enter-key │
97
+ └───────┬──────┘
98
+ │ Submit
99
+ ┌───────▼──────┐
100
+ │ validating │
101
+ └───────┬──────┘
102
+
103
+ ┌───────────┴───────────┐
104
+ │ │
105
+ ┌────▼────┐ ┌───▼─────┐
106
+ │ Error │ │Complete │
107
+ └────┬────┘ └───┬─────┘
108
+ │ │
109
+ Back to Continue to
110
+ enter-key main app
111
+ ```
112
+
113
+ ## Error Handling Flow
114
+
115
+ ```
116
+ ┌──────────────┐
117
+ │ API Call │
118
+ └──────┬───────┘
119
+
120
+ ├─────► Network Error ──────► Show "Check connection" ──► Retry
121
+
122
+ ├─────► Invalid Key ──────► Show "Invalid key" ──► Retry
123
+
124
+ ├─────► Timeout ──────► Show "Request timeout" ──► Retry
125
+
126
+ ├─────► 401/403 ──────► Show "Invalid key" ──► Retry
127
+
128
+ ├─────► 500 ──────► Show "Provider error" ──► Retry
129
+
130
+ └─────► Success ──────► Save & Continue
131
+ ```
132
+
133
+ ## Component Tree
134
+
135
+ ```
136
+ <SetupWizard>
137
+
138
+ ├─ step: "welcome"
139
+ │ └─ <Box>
140
+ │ ├─ Welcome message
141
+ │ ├─ Instructions
142
+ │ └─ "Press Enter" hint
143
+
144
+ ├─ step: "select-provider"
145
+ │ └─ <Box>
146
+ │ ├─ Provider list (map)
147
+ │ │ └─ Each provider:
148
+ │ │ ├─ Highlight if selected
149
+ │ │ └─ Show description
150
+ │ └─ Keyboard hints
151
+
152
+ ├─ step: "api-key-info"
153
+ │ └─ <Box>
154
+ │ ├─ Provider name
155
+ │ ├─ API key URL
156
+ │ └─ "Press Enter" hint
157
+
158
+ ├─ step: "enter-key"
159
+ │ └─ <Box>
160
+ │ ├─ Prompt
161
+ │ ├─ <TextInput mask="*" />
162
+ │ └─ Submit hint
163
+
164
+ ├─ step: "validating"
165
+ │ └─ <Box>
166
+ │ ├─ <Spinner />
167
+ │ └─ "Validating..." text
168
+
169
+ └─ step: "complete"
170
+ └─ <Box>
171
+ ├─ ✓ API key verified
172
+ ├─ ✓ Configuration saved
173
+ └─ Starting message
174
+ ```
175
+
176
+ ## Data Flow
177
+
178
+ ```
179
+ ┌─────────────┐
180
+ │ User │
181
+ │ Input │
182
+ └──────┬──────┘
183
+
184
+
185
+ ┌────────────────┐
186
+ │ SetupWizard │ ──► state: { step, selectedIndex,
187
+ │ Component │ selectedProvider, apiKey }
188
+ └────────┬───────┘
189
+
190
+
191
+ ┌────────────────┐
192
+ │ handleKeySubmit│
193
+ └────────┬───────┘
194
+
195
+
196
+ ┌────────────────┐
197
+ │ loginProvider()│ ──► Reuses existing validation
198
+ └────────┬───────┘ from config/authProvider.ts
199
+
200
+ ├─► Invalid ─► onError() ─► Show error & retry
201
+
202
+ └─► Valid ───┐
203
+
204
+
205
+ ┌────────────────────┐
206
+ │ getConfig() │
207
+ │ config.provider = X│
208
+ │ config.apiKey = Y │
209
+ │ saveConfig() │
210
+ └────────┬───────────┘
211
+
212
+
213
+ ┌────────────────┐
214
+ │ onComplete() │ ──► Unmount wizard
215
+ └────────┬───────┘ Continue to app
216
+
217
+
218
+ ┌────────────────┐
219
+ │ Main App │
220
+ │ (agent.tsx) │
221
+ └────────────────┘
222
+ ```
223
+
224
+ ## Keyboard Shortcuts
225
+
226
+ ```
227
+ ┌──────────────┬─────────────────────────────────┐
228
+ │ Key │ Action │
229
+ ├──────────────┼─────────────────────────────────┤
230
+ │ Enter │ Confirm / Next step │
231
+ │ ↑ │ Previous provider (select step) │
232
+ │ ↓ │ Next provider (select step) │
233
+ │ Ctrl+C │ Cancel onboarding, exit │
234
+ │ Type │ Input API key (masked) │
235
+ └──────────────┴─────────────────────────────────┘
236
+ ```
237
+
238
+ ## Integration Points
239
+
240
+ ```
241
+ cli.ts (Entry)
242
+
243
+
244
+ agent.tsx
245
+
246
+ ├─► ensureProviderConfigured() ──────┐
247
+ │ │
248
+ │ ┌───────────────────────────────┘
249
+ │ │
250
+ │ ▼
251
+ │ onboarding/index.ts
252
+ │ │
253
+ │ ├─► Check config exists? ───► Yes ───► Return (skip)
254
+ │ │
255
+ │ └─► No ───► Launch wizard
256
+ │ │
257
+ │ ▼
258
+ │ onboarding/setupWizard.tsx
259
+ │ │
260
+ │ ├─► providers.ts (data)
261
+ │ │
262
+ │ ├─► loginProvider() (validation)
263
+ │ │
264
+ │ └─► saveConfig() (persistence)
265
+
266
+
267
+ Continue to main app
268
+ ```
269
+
270
+ ## File Dependencies
271
+
272
+ ```
273
+ onboarding/index.ts
274
+ ├─► ink (render)
275
+ ├─► react
276
+ ├─► ./setupWizard (component)
277
+ └─► ../config/config (getConfig)
278
+
279
+ onboarding/setupWizard.tsx
280
+ ├─► react (useState)
281
+ ├─► ink (Box, Text, useInput)
282
+ ├─► ink-text-input (TextInput)
283
+ ├─► ink-spinner (Spinner)
284
+ ├─► chalk
285
+ ├─► ./providers (getEnabledProviders, ProviderInfo)
286
+ ├─► ../config/authProvider (loginProvider)
287
+ └─► ../config/config (getConfig, saveConfig)
288
+
289
+ onboarding/providers.ts
290
+ └─► No dependencies (pure data)
291
+ ```
@@ -0,0 +1,56 @@
1
+ import { render } from "ink";
2
+ import React from "react";
3
+ import { SetupWizard } from "./setupWizard";
4
+ import { getConfig } from "../config/config";
5
+
6
+ /**
7
+ * Ensures a provider is configured. If not, launches the onboarding wizard.
8
+ * This function is reusable across any command that requires authentication.
9
+ *
10
+ * @returns Promise that resolves when a provider is configured
11
+ * @throws If the user cancels onboarding (Ctrl+C)
12
+ */
13
+ export async function ensureProviderConfigured(): Promise<void> {
14
+ const config = await getConfig();
15
+ const provider = config.defaultProvider;
16
+ const apiKey = config.providers[provider]?.apiKey;
17
+
18
+ // Already configured
19
+ if (provider && apiKey) {
20
+ return;
21
+ }
22
+
23
+ // Launch onboarding
24
+ return new Promise<void>((resolve, reject) => {
25
+ let hasCompleted = false;
26
+
27
+ const { unmount, waitUntilExit } = render(
28
+ React.createElement(SetupWizard, {
29
+ onComplete: () => {
30
+ hasCompleted = true;
31
+ unmount();
32
+ resolve();
33
+ },
34
+ onError: (error: string) => {
35
+ console.error(`\n✖ ${error}\n`);
36
+ // Don't unmount - let user try again
37
+ },
38
+ }),
39
+ );
40
+
41
+ // Handle Ctrl+C gracefully
42
+ const handleExit = () => {
43
+ if (!hasCompleted) {
44
+ unmount();
45
+ console.log("\n\nSetup cancelled. Run 'woopcode' again when you're ready.\n");
46
+ process.exit(0);
47
+ }
48
+ };
49
+
50
+ process.once("SIGINT", handleExit);
51
+
52
+ waitUntilExit().then(() => {
53
+ process.off("SIGINT", handleExit);
54
+ });
55
+ });
56
+ }
@@ -0,0 +1,47 @@
1
+ // Provider registry for onboarding
2
+ export interface ProviderInfo {
3
+ id: string;
4
+ name: string;
5
+ enabled: boolean;
6
+ keyUrl: string;
7
+ description: string;
8
+ }
9
+
10
+ export const PROVIDERS: ProviderInfo[] = [
11
+ {
12
+ id: "google",
13
+ name: "Google Gemini",
14
+ enabled: true,
15
+ keyUrl: "https://aistudio.google.com/apikey",
16
+ description: "Fast and capable, with a generous free tier",
17
+ },
18
+ {
19
+ id: "openai",
20
+ name: "OpenAI",
21
+ enabled: false,
22
+ keyUrl: "https://platform.openai.com/api-keys",
23
+ description: "GPT-4 and GPT-3.5 models",
24
+ },
25
+ {
26
+ id: "anthropic",
27
+ name: "Anthropic Claude",
28
+ enabled: false,
29
+ keyUrl: "https://console.anthropic.com/settings/keys",
30
+ description: "Claude 3 family of models",
31
+ },
32
+ {
33
+ id: "groq",
34
+ name: "Groq",
35
+ enabled: false,
36
+ keyUrl: "https://console.groq.com/keys",
37
+ description: "Ultra-fast inference for open models",
38
+ },
39
+ ];
40
+
41
+ export function getEnabledProviders(): ProviderInfo[] {
42
+ return PROVIDERS.filter((p) => p.enabled);
43
+ }
44
+
45
+ export function getProviderInfo(id: string): ProviderInfo | undefined {
46
+ return PROVIDERS.find((p) => p.id === id);
47
+ }
@@ -0,0 +1,193 @@
1
+ import React, { useState } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+ import TextInput from "ink-text-input";
4
+ import Spinner from "ink-spinner";
5
+ import chalk from "chalk";
6
+ import { getEnabledProviders, type ProviderInfo } from "./providers";
7
+ import { loginProvider } from "../config/authProvider";
8
+ import { getConfig, saveConfig } from "../config/config";
9
+
10
+ type WizardStep =
11
+ | "welcome"
12
+ | "select-provider"
13
+ | "api-key-info"
14
+ | "enter-key"
15
+ | "validating"
16
+ | "complete";
17
+
18
+ interface SetupWizardProps {
19
+ onComplete: () => void;
20
+ onError: (error: string) => void;
21
+ }
22
+
23
+ export function SetupWizard({ onComplete, onError }: SetupWizardProps) {
24
+ const [step, setStep] = useState<WizardStep>("welcome");
25
+ const [selectedIndex, setSelectedIndex] = useState(0);
26
+ const [selectedProvider, setSelectedProvider] = useState<ProviderInfo | null>(
27
+ null,
28
+ );
29
+ const [apiKey, setApiKey] = useState("");
30
+
31
+ const enabledProviders = getEnabledProviders();
32
+
33
+ useInput((input, key) => {
34
+ if (step === "welcome") {
35
+ if (key.return) {
36
+ setStep("select-provider");
37
+ }
38
+ } else if (step === "select-provider") {
39
+ if (key.upArrow) {
40
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
41
+ } else if (key.downArrow) {
42
+ setSelectedIndex((prev) =>
43
+ Math.min(enabledProviders.length - 1, prev + 1),
44
+ );
45
+ } else if (key.return) {
46
+ setSelectedProvider(enabledProviders[selectedIndex]!);
47
+ setStep("api-key-info");
48
+ }
49
+ } else if (step === "api-key-info") {
50
+ if (key.return) {
51
+ setStep("enter-key");
52
+ }
53
+ }
54
+ });
55
+
56
+ const handleKeySubmit = async (value: string) => {
57
+ if (!selectedProvider || !value.trim()) {
58
+ return;
59
+ }
60
+
61
+ setApiKey(value);
62
+ setStep("validating");
63
+
64
+ try {
65
+ const isValid = await loginProvider(selectedProvider.id, value.trim());
66
+
67
+ if (!isValid) {
68
+ onError("Invalid API key. Please try again.");
69
+ setStep("enter-key");
70
+ setApiKey("");
71
+ return;
72
+ }
73
+
74
+ // Save configuration
75
+ const config = await getConfig();
76
+ config.defaultProvider = selectedProvider.id;
77
+ config.providers[selectedProvider.id].apiKey = value.trim();
78
+ await saveConfig(config);
79
+
80
+ setStep("complete");
81
+ setTimeout(onComplete, 1000);
82
+ } catch (error) {
83
+ onError(
84
+ error instanceof Error
85
+ ? `Validation failed: ${error.message}`
86
+ : "Network error. Please check your connection and try again.",
87
+ );
88
+ setStep("enter-key");
89
+ setApiKey("");
90
+ }
91
+ };
92
+
93
+ if (step === "welcome") {
94
+ return (
95
+ <Box flexDirection="column" paddingY={1}>
96
+ <Text bold color="cyan">
97
+ Welcome to Woopcode!
98
+ </Text>
99
+ <Text dimColor> </Text>
100
+ <Text>You'll need an AI provider to use Woopcode.</Text>
101
+ <Text>We'll only ask for this once.</Text>
102
+ <Text dimColor> </Text>
103
+ <Text dimColor>Press Enter to continue...</Text>
104
+ </Box>
105
+ );
106
+ }
107
+
108
+ if (step === "select-provider") {
109
+ return (
110
+ <Box flexDirection="column" paddingY={1}>
111
+ <Text bold>Select an AI provider:</Text>
112
+ <Text dimColor> </Text>
113
+ {enabledProviders.map((provider, index) => (
114
+ <Box key={provider.id} flexDirection="column">
115
+ <Text color={index === selectedIndex ? "cyan" : undefined}>
116
+ {index === selectedIndex ? "❯ " : " "}
117
+ {provider.name}
118
+ </Text>
119
+ {index === selectedIndex && (
120
+ <Text dimColor> {provider.description}</Text>
121
+ )}
122
+ </Box>
123
+ ))}
124
+ <Text dimColor> </Text>
125
+ <Text dimColor>Use ↑↓ arrows to select, Enter to confirm</Text>
126
+ </Box>
127
+ );
128
+ }
129
+
130
+ if (step === "api-key-info" && selectedProvider) {
131
+ return (
132
+ <Box flexDirection="column" paddingY={1}>
133
+ <Text bold color="cyan">
134
+ Setting up {selectedProvider.name}
135
+ </Text>
136
+ <Text dimColor> </Text>
137
+ <Text>You can create a free API key at:</Text>
138
+ <Text color="blue" underline>
139
+ {selectedProvider.keyUrl}
140
+ </Text>
141
+ <Text dimColor> </Text>
142
+ <Text dimColor>Press Enter once you have it...</Text>
143
+ </Box>
144
+ );
145
+ }
146
+
147
+ if (step === "enter-key" && selectedProvider) {
148
+ return (
149
+ <Box flexDirection="column" paddingY={1}>
150
+ <Text bold>Paste your {selectedProvider.name} API key:</Text>
151
+ <Text dimColor> </Text>
152
+ <Box>
153
+ <Text dimColor>Key: </Text>
154
+ <TextInput
155
+ value={apiKey}
156
+ onChange={setApiKey}
157
+ onSubmit={handleKeySubmit}
158
+ placeholder="Enter your API key..."
159
+ mask="*"
160
+ />
161
+ </Box>
162
+ <Text dimColor> </Text>
163
+ <Text dimColor>Press Enter to validate</Text>
164
+ </Box>
165
+ );
166
+ }
167
+
168
+ if (step === "validating") {
169
+ return (
170
+ <Box flexDirection="column" paddingY={1}>
171
+ <Box>
172
+ <Text color="cyan">
173
+ <Spinner type="dots" />
174
+ </Text>
175
+ <Text> Validating API key...</Text>
176
+ </Box>
177
+ </Box>
178
+ );
179
+ }
180
+
181
+ if (step === "complete") {
182
+ return (
183
+ <Box flexDirection="column" paddingY={1}>
184
+ <Text color="green">✓ API key verified</Text>
185
+ <Text color="green">✓ Configuration saved</Text>
186
+ <Text dimColor> </Text>
187
+ <Text>Starting Woopcode...</Text>
188
+ </Box>
189
+ );
190
+ }
191
+
192
+ return null;
193
+ }
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Utility script to reset provider configuration for testing onboarding.
4
+ *
5
+ * Usage:
6
+ * bun onboarding/test-reset.ts # Clear all API keys
7
+ * bun onboarding/test-reset.ts restore # Restore from backup
8
+ */
9
+
10
+ import { existsSync } from "fs";
11
+
12
+ const CONFIG_PATH = "./config/providers.json";
13
+ const BACKUP_PATH = "./config/providers.json.backup";
14
+
15
+ const command = process.argv[2];
16
+
17
+ async function clearKeys() {
18
+ // Backup current config
19
+ if (existsSync(CONFIG_PATH)) {
20
+ const current = await Bun.file(CONFIG_PATH).text();
21
+ await Bun.write(BACKUP_PATH, current);
22
+ console.log("✓ Backed up current configuration");
23
+ }
24
+
25
+ // Clear all API keys
26
+ const config = JSON.parse(await Bun.file(CONFIG_PATH).text());
27
+
28
+ for (const provider in config.providers) {
29
+ config.providers[provider].apiKey = "";
30
+ }
31
+
32
+ await Bun.write(CONFIG_PATH, JSON.stringify(config, null, 2));
33
+ console.log("✓ Cleared all API keys");
34
+ console.log("\nYou can now test the onboarding flow:");
35
+ console.log(" bun cli.ts");
36
+ console.log("\nTo restore your keys:");
37
+ console.log(" bun onboarding/test-reset.ts restore");
38
+ }
39
+
40
+ async function restore() {
41
+ if (!existsSync(BACKUP_PATH)) {
42
+ console.error("✖ No backup found");
43
+ process.exit(1);
44
+ }
45
+
46
+ const backup = await Bun.file(BACKUP_PATH).text();
47
+ await Bun.write(CONFIG_PATH, backup);
48
+ console.log("✓ Restored configuration from backup");
49
+ }
50
+
51
+ if (command === "restore") {
52
+ await restore();
53
+ } else {
54
+ await clearKeys();
55
+ }