wispy-cli 0.1.0 → 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 +112 -7
  2. package/package.json +1 -1
@@ -130,10 +130,101 @@ ${bold("macOS Keychain (auto-detected):")}
130
130
  `);
131
131
  }
132
132
 
133
- const detected = await detectProvider();
134
- const PROVIDER = detected?.provider ?? "none";
135
- const API_KEY = detected?.key ?? null;
136
- const MODEL = detected?.model ?? "unknown";
133
+ async function runOnboarding() {
134
+ const { createInterface: createRL } = await import("node:readline");
135
+ const { execSync } = await import("node:child_process");
136
+
137
+ console.log(`\n${bold("🌿 Welcome to Wispy!")}\n`);
138
+
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
+
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;
173
+ }
174
+ }
175
+
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`);
178
+
179
+ const rl = createRL({ input: process.stdin, output: process.stdout });
180
+ const ask = (q) => new Promise(r => rl.question(q, r));
181
+
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`);
192
+ }
193
+
194
+ const apiKey = (await ask(green(" API key: "))).trim();
195
+
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
+ }
203
+
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";
210
+
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;
219
+
220
+ rl.close();
221
+ console.log(`\n${green("✅")} Ready! Using ${PROVIDERS[chosenProvider].label}.\n`);
222
+ }
223
+
224
+ let detected = await detectProvider();
225
+ let PROVIDER = detected?.provider ?? "none";
226
+ let API_KEY = detected?.key ?? null;
227
+ let MODEL = detected?.model ?? "unknown";
137
228
  const MAX_CONTEXT_CHARS = 40_000;
138
229
 
139
230
  // ---------------------------------------------------------------------------
@@ -1991,10 +2082,24 @@ if (args[0] && operatorCommands.has(args[0])) {
1991
2082
  process.exit(0);
1992
2083
  }
1993
2084
 
1994
- // Check API key
2085
+ // Check API key — if none found, run interactive onboarding
1995
2086
  if (!API_KEY && PROVIDER !== "ollama") {
1996
- printSetupGuide();
1997
- process.exit(1);
2087
+ await runOnboarding();
2088
+ // Re-detect after onboarding saved config
2089
+ const redetected = await detectProvider();
2090
+ if (redetected) {
2091
+ // Patch module-level state for this session
2092
+ Object.assign(detected, redetected);
2093
+ }
2094
+ if (redetected) {
2095
+ PROVIDER = redetected.provider;
2096
+ API_KEY = redetected.key;
2097
+ MODEL = redetected.model;
2098
+ }
2099
+ if (!API_KEY && PROVIDER !== "ollama") {
2100
+ console.log(dim("\nRun wispy again to start chatting!"));
2101
+ process.exit(0);
2102
+ }
1998
2103
  }
1999
2104
 
2000
2105
  // Auto-start server before entering REPL or one-shot
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wispy-cli",
3
- "version": "0.1.0",
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",