wispy-cli 0.1.2 โ 0.2.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/lib/wispy-repl.mjs +96 -29
- package/package.json +1 -1
package/lib/wispy-repl.mjs
CHANGED
|
@@ -134,78 +134,112 @@ async function runOnboarding() {
|
|
|
134
134
|
const { createInterface: createRL } = await import("node:readline");
|
|
135
135
|
const { execSync } = await import("node:child_process");
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
// Splash
|
|
138
|
+
console.log("");
|
|
139
|
+
console.log(box([
|
|
140
|
+
"",
|
|
141
|
+
`${bold("๐ฟ W I S P Y")}`,
|
|
142
|
+
"",
|
|
143
|
+
`${dim("AI workspace assistant")}`,
|
|
144
|
+
`${dim("with multi-agent orchestration")}`,
|
|
145
|
+
"",
|
|
146
|
+
]));
|
|
147
|
+
console.log("");
|
|
138
148
|
|
|
139
149
|
// Auto-detect 1: Ollama running locally?
|
|
150
|
+
process.stdout.write(dim(" Checking environment..."));
|
|
140
151
|
try {
|
|
141
152
|
const resp = await fetch("http://localhost:11434/api/tags", { signal: AbortSignal.timeout(2000) });
|
|
142
153
|
if (resp.ok) {
|
|
143
|
-
console.log(
|
|
154
|
+
console.log(green(" found Ollama! โ\n"));
|
|
155
|
+
console.log(box([
|
|
156
|
+
`${green("โ")} Using local Ollama ${dim("โ no API key needed")}`,
|
|
157
|
+
"",
|
|
158
|
+
` ${dim("Your AI runs entirely on your machine.")}`,
|
|
159
|
+
` ${dim("No data leaves your computer.")}`,
|
|
160
|
+
]));
|
|
144
161
|
const configPath = path.join(WISPY_DIR, "config.json");
|
|
145
162
|
await mkdir(WISPY_DIR, { recursive: true });
|
|
146
163
|
const config = JSON.parse(await readFileOr(configPath, "{}"));
|
|
147
164
|
config.provider = "ollama";
|
|
148
165
|
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
149
166
|
process.env.OLLAMA_HOST = "http://localhost:11434";
|
|
150
|
-
console.log(
|
|
167
|
+
console.log("");
|
|
151
168
|
return;
|
|
152
169
|
}
|
|
153
170
|
} catch { /* not running */ }
|
|
154
171
|
|
|
155
172
|
// Auto-detect 2: macOS Keychain
|
|
156
173
|
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" },
|
|
174
|
+
{ service: "google-ai-key", provider: "google", label: "Google AI (Gemini)" },
|
|
175
|
+
{ service: "anthropic-api-key", provider: "anthropic", label: "Anthropic (Claude)" },
|
|
176
|
+
{ service: "openai-api-key", provider: "openai", label: "OpenAI (GPT)" },
|
|
160
177
|
];
|
|
161
178
|
for (const kc of keychainProviders) {
|
|
162
179
|
const key = await tryKeychainKey(kc.service);
|
|
163
180
|
if (key) {
|
|
164
|
-
console.log(
|
|
181
|
+
console.log(green(` found ${kc.label} key! โ\n`));
|
|
182
|
+
console.log(box([
|
|
183
|
+
`${green("โ")} ${kc.label} ${dim("โ auto-detected from Keychain")}`,
|
|
184
|
+
"",
|
|
185
|
+
` ${dim("Ready to go. No setup needed.")}`,
|
|
186
|
+
]));
|
|
165
187
|
const configPath = path.join(WISPY_DIR, "config.json");
|
|
166
188
|
await mkdir(WISPY_DIR, { recursive: true });
|
|
167
189
|
const config = JSON.parse(await readFileOr(configPath, "{}"));
|
|
168
190
|
config.provider = kc.provider;
|
|
169
191
|
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
170
192
|
process.env[PROVIDERS[kc.provider].envKeys[0]] = key;
|
|
171
|
-
console.log(
|
|
193
|
+
console.log("");
|
|
172
194
|
return;
|
|
173
195
|
}
|
|
174
196
|
}
|
|
175
197
|
|
|
176
|
-
|
|
177
|
-
console.log(`${dim("No API key found. Let's set one up โ takes 10 seconds.")}\n`);
|
|
198
|
+
console.log(dim(" no existing config found.\n"));
|
|
178
199
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
200
|
+
// Nothing auto-detected โ elegant key setup
|
|
201
|
+
console.log(box([
|
|
202
|
+
`${bold("Quick Setup")} ${dim("โ one step, 10 seconds")}`,
|
|
203
|
+
"",
|
|
204
|
+
` Wispy needs an AI provider to work.`,
|
|
205
|
+
` The easiest: ${bold("Google AI")} ${dim("(free, no credit card)")}`,
|
|
206
|
+
]));
|
|
207
|
+
console.log("");
|
|
183
208
|
|
|
184
209
|
// Auto-open browser
|
|
185
210
|
try {
|
|
186
211
|
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("โ")}
|
|
188
|
-
console.log(` ${dim(" Click 'Create API Key' and paste it here:")}\n`);
|
|
212
|
+
console.log(` ${green("โ")} Browser opened to ${underline("aistudio.google.com/apikey")}`);
|
|
189
213
|
} catch {
|
|
190
|
-
console.log(` ${
|
|
191
|
-
console.log(` ${dim(" Click 'Create API Key' and paste it here:")}\n`);
|
|
214
|
+
console.log(` ${green("โ")} Visit: ${underline("https://aistudio.google.com/apikey")}`);
|
|
192
215
|
}
|
|
216
|
+
console.log(` ${dim(' Click "Create API Key" โ copy โ paste below')}`);
|
|
217
|
+
console.log("");
|
|
193
218
|
|
|
194
|
-
const
|
|
219
|
+
const rl = createRL({ input: process.stdin, output: process.stdout });
|
|
220
|
+
const ask = (q) => new Promise(r => rl.question(q, r));
|
|
221
|
+
|
|
222
|
+
const apiKey = (await ask(` ${green("API key")} ${dim("(paste here)")}: `)).trim();
|
|
195
223
|
|
|
196
224
|
if (!apiKey) {
|
|
197
|
-
console.log(
|
|
198
|
-
console.log(
|
|
199
|
-
|
|
225
|
+
console.log("");
|
|
226
|
+
console.log(box([
|
|
227
|
+
`${dim("No key? Try local AI instead:")}`,
|
|
228
|
+
"",
|
|
229
|
+
` ${cyan("brew install ollama")}`,
|
|
230
|
+
` ${cyan("ollama serve")}`,
|
|
231
|
+
` ${cyan("wispy")} ${dim("โ will auto-detect")}`,
|
|
232
|
+
]));
|
|
233
|
+
console.log("");
|
|
200
234
|
rl.close();
|
|
201
235
|
process.exit(0);
|
|
202
236
|
}
|
|
203
237
|
|
|
204
|
-
//
|
|
205
|
-
let chosenProvider = "google";
|
|
238
|
+
// Auto-detect provider from key format
|
|
239
|
+
let chosenProvider = "google";
|
|
206
240
|
if (apiKey.startsWith("sk-ant-")) chosenProvider = "anthropic";
|
|
207
|
-
else if (apiKey.startsWith("sk-")) chosenProvider = "openai";
|
|
208
241
|
else if (apiKey.startsWith("sk-or-")) chosenProvider = "openrouter";
|
|
242
|
+
else if (apiKey.startsWith("sk-")) chosenProvider = "openai";
|
|
209
243
|
else if (apiKey.startsWith("gsk_")) chosenProvider = "groq";
|
|
210
244
|
|
|
211
245
|
// Save
|
|
@@ -218,7 +252,17 @@ async function runOnboarding() {
|
|
|
218
252
|
process.env[PROVIDERS[chosenProvider].envKeys[0]] = apiKey;
|
|
219
253
|
|
|
220
254
|
rl.close();
|
|
221
|
-
|
|
255
|
+
|
|
256
|
+
console.log("");
|
|
257
|
+
console.log(box([
|
|
258
|
+
`${green("โ")} Connected to ${bold(PROVIDERS[chosenProvider].label)}`,
|
|
259
|
+
"",
|
|
260
|
+
` ${cyan("wispy")} ${dim("start chatting")}`,
|
|
261
|
+
` ${cyan('wispy "do something"')} ${dim("quick command")}`,
|
|
262
|
+
` ${cyan("wispy -w project")} ${dim("use a workstream")}`,
|
|
263
|
+
` ${cyan("wispy --help")} ${dim("all options")}`,
|
|
264
|
+
]));
|
|
265
|
+
console.log("");
|
|
222
266
|
}
|
|
223
267
|
|
|
224
268
|
let detected = await detectProvider();
|
|
@@ -237,6 +281,29 @@ const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
|
237
281
|
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
238
282
|
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
239
283
|
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
284
|
+
const magenta = (s) => `\x1b[35m${s}\x1b[0m`;
|
|
285
|
+
const bgGreen = (s) => `\x1b[42m\x1b[30m${s}\x1b[0m`;
|
|
286
|
+
const underline = (s) => `\x1b[4m${s}\x1b[0m`;
|
|
287
|
+
|
|
288
|
+
function box(lines, { padding = 1, border = "rounded" } = {}) {
|
|
289
|
+
const chars = border === "rounded"
|
|
290
|
+
? { tl: "โญ", tr: "โฎ", bl: "โฐ", br: "โฏ", h: "โ", v: "โ" }
|
|
291
|
+
: { tl: "โ", tr: "โ", bl: "โ", br: "โ", h: "โ", v: "โ" };
|
|
292
|
+
|
|
293
|
+
// Strip ANSI for width calculation
|
|
294
|
+
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
295
|
+
const maxW = Math.max(...lines.map(l => stripAnsi(l).length)) + padding * 2;
|
|
296
|
+
const pad = " ".repeat(padding);
|
|
297
|
+
|
|
298
|
+
const top = ` ${chars.tl}${chars.h.repeat(maxW)}${chars.tr}`;
|
|
299
|
+
const bot = ` ${chars.bl}${chars.h.repeat(maxW)}${chars.br}`;
|
|
300
|
+
const mid = lines.map(l => {
|
|
301
|
+
const visible = stripAnsi(l).length;
|
|
302
|
+
const rightPad = " ".repeat(Math.max(0, maxW - padding * 2 - visible));
|
|
303
|
+
return ` ${chars.v}${pad}${l}${rightPad}${pad}${chars.v}`;
|
|
304
|
+
});
|
|
305
|
+
return [top, ...mid, bot].join("\n");
|
|
306
|
+
}
|
|
240
307
|
|
|
241
308
|
// ---------------------------------------------------------------------------
|
|
242
309
|
// File helpers
|
|
@@ -1812,11 +1879,11 @@ ${bold("Wispy Commands:")}
|
|
|
1812
1879
|
// ---------------------------------------------------------------------------
|
|
1813
1880
|
|
|
1814
1881
|
async function runRepl() {
|
|
1815
|
-
const wsLabel = ACTIVE_WORKSTREAM === "default" ? "" : `
|
|
1882
|
+
const wsLabel = ACTIVE_WORKSTREAM === "default" ? "" : ` ${dim("ยท")} ${cyan(ACTIVE_WORKSTREAM)}`;
|
|
1816
1883
|
const providerLabel = PROVIDERS[PROVIDER]?.label ?? PROVIDER;
|
|
1817
1884
|
console.log(`
|
|
1818
|
-
${bold("๐ฟ Wispy")}${wsLabel} ${dim(
|
|
1819
|
-
${dim(
|
|
1885
|
+
${bold("๐ฟ Wispy")}${wsLabel} ${dim(`ยท ${MODEL}`)}
|
|
1886
|
+
${dim(`${providerLabel} ยท /help for commands ยท Ctrl+C to exit`)}
|
|
1820
1887
|
`);
|
|
1821
1888
|
|
|
1822
1889
|
const systemPrompt = await buildSystemPrompt();
|