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.
- package/lib/wispy-repl.mjs +74 -92
- package/package.json +1 -1
package/lib/wispy-repl.mjs
CHANGED
|
@@ -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
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
174
|
+
}
|
|
185
175
|
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
190
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
194
|
+
const apiKey = (await ask(green(" API key: "))).trim();
|
|
221
195
|
|
|
222
|
-
|
|
223
|
-
${
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
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
|
-
|
|
235
|
-
|
|
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();
|