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.
- package/CONTRIBUTING.md +329 -0
- package/LICENSE +21 -0
- package/README.md +582 -0
- package/cli.ts +17 -0
- package/commands/agent.tsx +139 -0
- package/commands/agentController.ts +138 -0
- package/commands/models.ts +21 -0
- package/commands/providers/index.ts +12 -0
- package/commands/providers/listProviders.ts +28 -0
- package/commands/providers/login.ts +28 -0
- package/commands/providers/logout.ts +33 -0
- package/commands/providers/setProvider.ts +34 -0
- package/config/authProvider.ts +54 -0
- package/config/client.ts +163 -0
- package/config/config.ts +118 -0
- package/config/conversation.json +1 -0
- package/config/models.json +50 -0
- package/config/paths.ts +96 -0
- package/config/providers.json +21 -0
- package/config/runtime.ts +130 -0
- package/config/systemPrompt.ts +54 -0
- package/config/types.ts +88 -0
- package/onboarding/FLOW.md +291 -0
- package/onboarding/index.ts +56 -0
- package/onboarding/providers.ts +47 -0
- package/onboarding/setupWizard.tsx +193 -0
- package/onboarding/test-reset.ts +55 -0
- package/package.json +86 -0
- package/tools/createFile.ts +24 -0
- package/tools/editFile.ts +85 -0
- package/tools/findFiles.ts +94 -0
- package/tools/grep.ts +68 -0
- package/tools/index.ts +26 -0
- package/tools/listFiles.ts +49 -0
- package/tools/readFile.ts +53 -0
- package/tools/runTests.ts +29 -0
- package/tools/terminal.ts +37 -0
- package/tools/writeFile.ts +75 -0
- package/tui/package.json +0 -0
- package/tui/src/app.tsx +60 -0
- package/tui/src/components/ApprovalFooter.tsx +29 -0
- package/tui/src/components/AsciiLogo.tsx +78 -0
- package/tui/src/components/BootScreen.tsx +76 -0
- package/tui/src/components/CapabilityRow.tsx +17 -0
- package/tui/src/components/CodeBlock.tsx +70 -0
- package/tui/src/components/DiffPreview.tsx +46 -0
- package/tui/src/components/DiffViewer.tsx +36 -0
- package/tui/src/components/HomeFooter.tsx +11 -0
- package/tui/src/components/HomeScreen.tsx +71 -0
- package/tui/src/components/InlineCode.tsx +13 -0
- package/tui/src/components/LogoReveal.tsx +158 -0
- package/tui/src/components/Markdown.tsx +280 -0
- package/tui/src/components/MessageRenderer.tsx +84 -0
- package/tui/src/components/PromptCard.tsx +52 -0
- package/tui/src/components/StreamingCursor.tsx +13 -0
- package/tui/src/components/ThinkingIndicator.tsx +14 -0
- package/tui/src/components/ToolStatus.tsx +26 -0
- package/tui/src/header.tsx +25 -0
- package/tui/src/hooks/useBootAnimation.ts +67 -0
- package/tui/src/hooks/useLogoAnimation.ts +114 -0
- package/tui/src/index.ts +5 -0
- package/tui/src/prompt.tsx +66 -0
- package/tui/src/statusBar.tsx +83 -0
- package/tui/src/store/ui-store.ts +234 -0
- package/tui/src/store/useUIStore.ts +9 -0
- package/tui/src/timeline.tsx +96 -0
- 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
|
+
}
|