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.
Files changed (2) hide show
  1. package/lib/wispy-repl.mjs +96 -29
  2. package/package.json +1 -1
@@ -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
- console.log(`\n${bold("๐ŸŒฟ Welcome to Wispy!")}\n`);
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(`${green("โœ…")} Found Ollama running locally โ€” no API key needed!`);
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(dim(" Using local Ollama. Run wispy to start.\n"));
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(`${green("โœ…")} Found ${kc.label} key in macOS Keychain โ€” using it automatically!`);
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(dim(" Ready to go. Run wispy to start.\n"));
193
+ console.log("");
172
194
  return;
173
195
  }
174
196
  }
175
197
 
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`);
198
+ console.log(dim(" no existing config found.\n"));
178
199
 
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`);
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("โ†’")} Opened browser to get your free API key`);
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(` ${dim(" Go to:")} ${cyan("https://aistudio.google.com/apikey")}`);
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 apiKey = (await ask(green(" API key: "))).trim();
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(`\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")}`);
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
- // Detect provider from key format
205
- let chosenProvider = "google"; // default
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
- console.log(`\n${green("โœ…")} Ready! Using ${PROVIDERS[chosenProvider].label}.\n`);
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" ? "" : ` [${cyan(ACTIVE_WORKSTREAM)}]`;
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(`โ€” ${providerLabel} (${MODEL})`)}
1819
- ${dim("Type a message to chat. /help for commands. Ctrl+C to exit.")}
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wispy-cli",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "๐ŸŒฟ Wispy โ€” AI workspace assistant with multi-agent orchestration",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Minseo & Poropo",