wispy-cli 0.1.1 → 0.1.2

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 (2) hide show
  1. package/lib/wispy-repl.mjs +74 -92
  2. package/package.json +1 -1
@@ -132,111 +132,93 @@ ${bold("macOS Keychain (auto-detected):")}
132
132
 
133
133
  async function runOnboarding() {
134
134
  const { createInterface: createRL } = await import("node:readline");
135
+ const { execSync } = await import("node:child_process");
135
136
 
136
- console.log(`
137
- ${bold("🌿 Welcome to Wispy!")}
138
- ${dim("Let's get you set up in 30 seconds.")}
139
- `);
140
-
141
- const rl = createRL({ input: process.stdin, output: process.stdout });
142
- const ask = (q) => new Promise(r => rl.question(q, r));
137
+ console.log(`\n${bold("🌿 Welcome to Wispy!")}\n`);
143
138
 
144
- // Step 1: Choose provider
145
- console.log(`${bold("Step 1:")} Choose your AI provider\n`);
146
- console.log(` ${green("1")} Google AI (Gemini) ${dim("← free, recommended")}`)
147
- console.log(` ${cyan("2")} Anthropic (Claude) ${dim("paid")}`)
148
- console.log(` ${cyan("3")} OpenAI (GPT-4o) ${dim("paid")}`)
149
- console.log(` ${cyan("4")} OpenRouter ${dim("any model, some free")}`)
150
- console.log(` ${cyan("5")} Groq ${dim("free, fast")}`)
151
- console.log(` ${cyan("6")} DeepSeek ${dim("cheap")}`)
152
- console.log(` ${cyan("7")} Ollama ${dim("local, no key needed")}`)
153
- console.log("");
139
+ // Auto-detect 1: Ollama running locally?
140
+ try {
141
+ const resp = await fetch("http://localhost:11434/api/tags", { signal: AbortSignal.timeout(2000) });
142
+ if (resp.ok) {
143
+ console.log(`${green("")} Found Ollama running locally — no API key needed!`);
144
+ const configPath = path.join(WISPY_DIR, "config.json");
145
+ await mkdir(WISPY_DIR, { recursive: true });
146
+ const config = JSON.parse(await readFileOr(configPath, "{}"));
147
+ config.provider = "ollama";
148
+ await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
149
+ process.env.OLLAMA_HOST = "http://localhost:11434";
150
+ console.log(dim(" Using local Ollama. Run wispy to start.\n"));
151
+ return;
152
+ }
153
+ } catch { /* not running */ }
154
154
 
155
- const choice = await ask(green(" Pick a number (1-7): "));
156
- const providerMap = { "1": "google", "2": "anthropic", "3": "openai", "4": "openrouter", "5": "groq", "6": "deepseek", "7": "ollama" };
157
- const chosenProvider = providerMap[choice.trim()] ?? "google";
158
- const chosenConfig = PROVIDERS[chosenProvider];
159
-
160
- let apiKey = null;
161
-
162
- if (chosenProvider === "ollama") {
163
- console.log(`\n${green("✅")} Ollama selected — no API key needed!`);
164
- console.log(dim(" Make sure Ollama is running: ollama serve"));
165
- const host = await ask(green(" Ollama host (enter for default localhost:11434): "));
166
- const ollamaHost = host.trim() || "http://localhost:11434";
167
-
168
- // Save config
169
- const configPath = path.join(WISPY_DIR, "config.json");
170
- await mkdir(WISPY_DIR, { recursive: true });
171
- const existing = await readFileOr(configPath, "{}");
172
- const config = JSON.parse(existing);
173
- config.provider = "ollama";
174
- config.ollamaHost = ollamaHost;
175
- await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
176
-
177
- process.env.OLLAMA_HOST = ollamaHost;
178
- } else {
179
- // Step 2: Get API key
180
- console.log(`\n${bold("Step 2:")} Enter your API key`);
181
- if (chosenConfig.signupUrl) {
182
- console.log(dim(` Get one here: ${chosenConfig.signupUrl}`));
155
+ // Auto-detect 2: macOS Keychain
156
+ const keychainProviders = [
157
+ { service: "google-ai-key", provider: "google", label: "Google AI" },
158
+ { service: "anthropic-api-key", provider: "anthropic", label: "Anthropic" },
159
+ { service: "openai-api-key", provider: "openai", label: "OpenAI" },
160
+ ];
161
+ for (const kc of keychainProviders) {
162
+ const key = await tryKeychainKey(kc.service);
163
+ if (key) {
164
+ console.log(`${green("✅")} Found ${kc.label} key in macOS Keychain — using it automatically!`);
165
+ const configPath = path.join(WISPY_DIR, "config.json");
166
+ await mkdir(WISPY_DIR, { recursive: true });
167
+ const config = JSON.parse(await readFileOr(configPath, "{}"));
168
+ config.provider = kc.provider;
169
+ await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
170
+ process.env[PROVIDERS[kc.provider].envKeys[0]] = key;
171
+ console.log(dim(" Ready to go. Run wispy to start.\n"));
172
+ return;
183
173
  }
184
- console.log("");
174
+ }
185
175
 
186
- apiKey = await ask(green(" API key: "));
187
- apiKey = apiKey.trim();
176
+ // Nothing auto-detected minimal setup: open browser, paste key
177
+ console.log(`${dim("No API key found. Let's set one up — takes 10 seconds.")}\n`);
188
178
 
189
- if (!apiKey) {
190
- console.log(red("\n No key entered. Run wispy again when you have one."));
191
- rl.close();
192
- process.exit(1);
193
- }
179
+ const rl = createRL({ input: process.stdin, output: process.stdout });
180
+ const ask = (q) => new Promise(r => rl.question(q, r));
194
181
 
195
- // Save to config
196
- const configPath = path.join(WISPY_DIR, "config.json");
197
- await mkdir(WISPY_DIR, { recursive: true });
198
- const existing = await readFileOr(configPath, "{}");
199
- const config = JSON.parse(existing);
200
- config.provider = chosenProvider;
201
- config.apiKey = apiKey;
202
- await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
203
-
204
- // Set env for current session
205
- process.env[chosenConfig.envKeys[0]] = apiKey;
206
- }
207
-
208
- // Step 3: Name (optional)
209
- console.log(`\n${bold("Step 3:")} What should I call you? ${dim("(enter to skip)")}`);
210
- const userName = await ask(green(" Your name: "));
211
- if (userName.trim()) {
212
- await mkdir(path.join(WISPY_DIR, "memory"), { recursive: true });
213
- await appendFile(
214
- path.join(WISPY_DIR, "memory", "user.md"),
215
- `\n- [${new Date().toISOString().slice(0,16)}] Name: ${userName.trim()}\n`,
216
- "utf8",
217
- );
182
+ console.log(` The easiest way: ${bold("Google AI")} (free, no credit card)\n`);
183
+
184
+ // Auto-open browser
185
+ try {
186
+ execSync('open "https://aistudio.google.com/apikey" 2>/dev/null || xdg-open "https://aistudio.google.com/apikey" 2>/dev/null', { stdio: "ignore" });
187
+ console.log(` ${green("→")} Opened browser to get your free API key`);
188
+ console.log(` ${dim(" Click 'Create API Key' and paste it here:")}\n`);
189
+ } catch {
190
+ console.log(` ${dim(" Go to:")} ${cyan("https://aistudio.google.com/apikey")}`);
191
+ console.log(` ${dim(" Click 'Create API Key' and paste it here:")}\n`);
218
192
  }
219
193
 
220
- rl.close();
194
+ const apiKey = (await ask(green(" API key: "))).trim();
221
195
 
222
- console.log(`
223
- ${green(" Setup complete!")}
196
+ if (!apiKey) {
197
+ console.log(`\n ${dim("No key? You can also use Ollama for free local AI:")}`);
198
+ console.log(` ${cyan("brew install ollama && ollama serve")}`);
199
+ console.log(` ${dim("Then run wispy again.\n")}`);
200
+ rl.close();
201
+ process.exit(0);
202
+ }
224
203
 
225
- ${bold("You're ready to go:")}
226
- ${cyan("wispy")} Start chatting
227
- ${cyan('wispy "hello"')} Quick message
228
- ${cyan("wispy -w project")} Use a workstream
229
- ${cyan("wispy --help")} All options
204
+ // Detect provider from key format
205
+ let chosenProvider = "google"; // default
206
+ if (apiKey.startsWith("sk-ant-")) chosenProvider = "anthropic";
207
+ else if (apiKey.startsWith("sk-")) chosenProvider = "openai";
208
+ else if (apiKey.startsWith("sk-or-")) chosenProvider = "openrouter";
209
+ else if (apiKey.startsWith("gsk_")) chosenProvider = "groq";
230
210
 
231
- ${dim("🌿 Enjoy Wispy!")}
232
- `);
211
+ // Save
212
+ const configPath = path.join(WISPY_DIR, "config.json");
213
+ await mkdir(WISPY_DIR, { recursive: true });
214
+ const config = JSON.parse(await readFileOr(configPath, "{}"));
215
+ config.provider = chosenProvider;
216
+ config.apiKey = apiKey;
217
+ await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
218
+ process.env[PROVIDERS[chosenProvider].envKeys[0]] = apiKey;
233
219
 
234
- // Re-detect provider with new config
235
- const newDetected = await detectProvider();
236
- if (newDetected) {
237
- // Update globals — but since they're const, we need to restart
238
- // For this session, just continue with the values we set
239
- }
220
+ rl.close();
221
+ console.log(`\n${green("✅")} Ready! Using ${PROVIDERS[chosenProvider].label}.\n`);
240
222
  }
241
223
 
242
224
  let detected = await detectProvider();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wispy-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "🌿 Wispy — AI workspace assistant with multi-agent orchestration",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Minseo & Poropo",