wispy-cli 2.7.6 → 2.7.7

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/bin/wispy.mjs CHANGED
@@ -265,27 +265,102 @@ if (command === "handoff") {
265
265
  if (command === "model") {
266
266
  try {
267
267
  const { loadConfig, saveConfig, detectProvider, PROVIDERS } = await import(join(rootDir, "core/config.mjs"));
268
- const config = await loadConfig();
269
- const detected = await detectProvider();
270
268
 
271
- if (args[1]) {
272
- config.model = args[1];
273
- await saveConfig(config);
274
- console.log(`Model set to: ${args[1]}`);
275
- } else {
276
- console.log(`\n Provider: ${detected?.provider ?? "none"}`);
277
- console.log(` Model: ${detected?.model ?? "none"}`);
278
- console.log(`\n Override: wispy model <model-name>`);
279
- console.log(` Env var: WISPY_MODEL=<model-name>`);
280
-
281
- if (PROVIDERS) {
282
- console.log(`\n Available providers:`);
283
- for (const [key, p] of Object.entries(PROVIDERS)) {
284
- console.log(` ${key.padEnd(14)} ${p.defaultModel}`);
269
+ const sub = args[1];
270
+
271
+ // wispy model list → print all providers and their models
272
+ if (sub === "list") {
273
+ console.log("\n Available models by provider:\n");
274
+ for (const [key, p] of Object.entries(PROVIDERS)) {
275
+ console.log(` ${key}`);
276
+ const models = p.models ?? [p.defaultModel];
277
+ for (const m of models) {
278
+ const tag = m === p.defaultModel ? " (default)" : "";
279
+ console.log(` ${m}${tag}`);
285
280
  }
286
281
  }
287
282
  console.log("");
283
+ process.exit(0);
288
284
  }
285
+
286
+ // wispy model <name> → set directly
287
+ if (sub && sub !== "list") {
288
+ const config = await loadConfig();
289
+ config.model = sub;
290
+ await saveConfig(config);
291
+ console.log(`Model set to: ${sub}`);
292
+ process.exit(0);
293
+ }
294
+
295
+ // wispy model (no args) → show current + interactive picker
296
+ const { select } = await import("@inquirer/prompts");
297
+ const config = await loadConfig();
298
+ const detected = await detectProvider();
299
+
300
+ console.log(`\n Current provider: ${detected?.provider ?? "none"}`);
301
+ console.log(` Current model: ${detected?.model ?? "none"}\n`);
302
+
303
+ // Find providers with configured keys (or local providers)
304
+ const availableProviders = Object.entries(PROVIDERS).filter(([key, p]) => {
305
+ if (p.local) return true;
306
+ // Check env keys
307
+ for (const k of (p.envKeys ?? [])) {
308
+ if (process.env[k]) return true;
309
+ }
310
+ // Check config
311
+ if (config.provider === key && config.apiKey) return true;
312
+ if (config.providers?.[key]) return true;
313
+ return false;
314
+ });
315
+
316
+ let providerChoices = availableProviders.map(([key, p]) => ({
317
+ name: p.label ?? key,
318
+ value: key,
319
+ }));
320
+
321
+ // If no providers detected, show all
322
+ if (providerChoices.length === 0) {
323
+ providerChoices = Object.entries(PROVIDERS).map(([key, p]) => ({
324
+ name: p.label ?? key,
325
+ value: key,
326
+ }));
327
+ }
328
+
329
+ let selectedProvider;
330
+ try {
331
+ selectedProvider = await select({
332
+ message: "Select provider:",
333
+ choices: providerChoices,
334
+ default: detected?.provider,
335
+ });
336
+ } catch {
337
+ console.log(" Cancelled.");
338
+ process.exit(0);
339
+ }
340
+
341
+ const providerInfo = PROVIDERS[selectedProvider];
342
+ const modelList = providerInfo.models ?? [providerInfo.defaultModel];
343
+
344
+ let selectedModel;
345
+ try {
346
+ selectedModel = await select({
347
+ message: "Select model:",
348
+ choices: modelList.map((m) => ({
349
+ name: m === providerInfo.defaultModel ? `${m} (default)` : m,
350
+ value: m,
351
+ })),
352
+ default: providerInfo.defaultModel,
353
+ });
354
+ } catch {
355
+ console.log(" Cancelled.");
356
+ process.exit(0);
357
+ }
358
+
359
+ config.provider = selectedProvider;
360
+ config.model = selectedModel;
361
+ await saveConfig(config);
362
+ console.log(`\n Provider set to: ${selectedProvider}`);
363
+ console.log(` Model set to: ${selectedModel}\n`);
289
364
  } catch (err) {
290
365
  console.error("Model error:", err.message);
291
366
  process.exit(1);
package/core/config.mjs CHANGED
@@ -19,47 +19,74 @@ export const MEMORY_DIR = path.join(WISPY_DIR, "memory");
19
19
 
20
20
  export const PROVIDERS = {
21
21
  // ── Tier 0: Custom API providers ────────────────────────────────────────────
22
- google: { envKeys: ["GOOGLE_AI_KEY", "GOOGLE_GENERATIVE_AI_KEY", "GEMINI_API_KEY"], defaultModel: "gemini-2.5-flash", label: "Google AI (Gemini)", signupUrl: "https://aistudio.google.com/apikey" },
23
- anthropic: { envKeys: ["ANTHROPIC_API_KEY"], defaultModel: "claude-sonnet-4-20250514", label: "Anthropic (Claude)", signupUrl: "https://console.anthropic.com/settings/keys" },
22
+ google: { envKeys: ["GOOGLE_AI_KEY", "GOOGLE_GENERATIVE_AI_KEY", "GEMINI_API_KEY"], defaultModel: "gemini-2.5-flash", label: "Google AI (Gemini)", signupUrl: "https://aistudio.google.com/apikey",
23
+ models: ["gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.0-flash", "gemini-2.0-flash-lite", "gemini-1.5-pro", "gemini-1.5-flash"] },
24
+ anthropic: { envKeys: ["ANTHROPIC_API_KEY"], defaultModel: "claude-sonnet-4-20250514", label: "Anthropic (Claude)", signupUrl: "https://console.anthropic.com/settings/keys",
25
+ models: ["claude-opus-4-20250514", "claude-sonnet-4-20250514", "claude-3-5-sonnet-20241022", "claude-3-5-haiku-20241022", "claude-3-opus-20240229"] },
24
26
 
25
27
  // ── Tier 1: Popular OpenAI-compat ──────────────────────────────────────────
26
- openai: { envKeys: ["OPENAI_API_KEY"], defaultModel: "gpt-4o", label: "OpenAI", signupUrl: "https://platform.openai.com/api-keys" },
27
- xai: { envKeys: ["XAI_API_KEY"], defaultModel: "grok-3-mini", label: "xAI (Grok)", signupUrl: "https://console.x.ai/" },
28
- mistral: { envKeys: ["MISTRAL_API_KEY"], defaultModel: "mistral-large-latest", label: "Mistral", signupUrl: "https://console.mistral.ai/api-keys/" },
29
- together: { envKeys: ["TOGETHER_API_KEY"], defaultModel: "meta-llama/Llama-3.3-70B-Instruct-Turbo", label: "Together AI", signupUrl: "https://api.together.xyz/settings/api-keys" },
30
- nvidia: { envKeys: ["NVIDIA_API_KEY"], defaultModel: "meta/llama-3.3-70b-instruct", label: "NVIDIA NIM", signupUrl: "https://build.nvidia.com/" },
28
+ openai: { envKeys: ["OPENAI_API_KEY"], defaultModel: "gpt-4o", label: "OpenAI", signupUrl: "https://platform.openai.com/api-keys",
29
+ models: ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "o1", "o1-mini", "o3", "o3-mini", "o4-mini"] },
30
+ xai: { envKeys: ["XAI_API_KEY"], defaultModel: "grok-3-mini", label: "xAI (Grok)", signupUrl: "https://console.x.ai/",
31
+ models: ["grok-3", "grok-3-mini", "grok-3-fast", "grok-2", "grok-2-mini"] },
32
+ mistral: { envKeys: ["MISTRAL_API_KEY"], defaultModel: "mistral-large-latest", label: "Mistral", signupUrl: "https://console.mistral.ai/api-keys/",
33
+ models: ["mistral-large-latest", "mistral-medium-latest", "mistral-small-latest", "codestral-latest", "open-mistral-nemo"] },
34
+ together: { envKeys: ["TOGETHER_API_KEY"], defaultModel: "meta-llama/Llama-3.3-70B-Instruct-Turbo", label: "Together AI", signupUrl: "https://api.together.xyz/settings/api-keys",
35
+ models: ["meta-llama/Llama-3.3-70B-Instruct-Turbo", "meta-llama/Llama-3.1-8B-Instruct-Turbo", "mistralai/Mixtral-8x22B-Instruct-v0.1", "Qwen/Qwen2.5-72B-Instruct-Turbo", "deepseek-ai/deepseek-r1"] },
36
+ nvidia: { envKeys: ["NVIDIA_API_KEY"], defaultModel: "meta/llama-3.3-70b-instruct", label: "NVIDIA NIM", signupUrl: "https://build.nvidia.com/",
37
+ models: ["meta/llama-3.3-70b-instruct", "meta/llama-3.1-8b-instruct", "mistralai/mistral-7b-instruct-v0.3", "google/gemma-2-9b-it"] },
31
38
 
32
39
  // ── Tier 1: Fast/free ───────────────────────────────────────────────────────
33
- groq: { envKeys: ["GROQ_API_KEY"], defaultModel: "llama-3.3-70b-versatile", label: "Groq (fast inference)", signupUrl: "https://console.groq.com/keys" },
34
- deepseek: { envKeys: ["DEEPSEEK_API_KEY"], defaultModel: "deepseek-chat", label: "DeepSeek", signupUrl: "https://platform.deepseek.com/api_keys" },
35
- chutes: { envKeys: ["CHUTES_API_KEY"], defaultModel: "deepseek-ai/DeepSeek-V3-0324", label: "Chutes", signupUrl: "https://chutes.ai/" },
40
+ groq: { envKeys: ["GROQ_API_KEY"], defaultModel: "llama-3.3-70b-versatile", label: "Groq (fast inference)", signupUrl: "https://console.groq.com/keys",
41
+ models: ["llama-3.3-70b-versatile", "llama-3.1-8b-instant", "mixtral-8x7b-32768", "gemma2-9b-it", "llama-3.3-70b-specdec"] },
42
+ deepseek: { envKeys: ["DEEPSEEK_API_KEY"], defaultModel: "deepseek-chat", label: "DeepSeek", signupUrl: "https://platform.deepseek.com/api_keys",
43
+ models: ["deepseek-chat", "deepseek-coder", "deepseek-reasoner"] },
44
+ chutes: { envKeys: ["CHUTES_API_KEY"], defaultModel: "deepseek-ai/DeepSeek-V3-0324", label: "Chutes", signupUrl: "https://chutes.ai/",
45
+ models: ["deepseek-ai/DeepSeek-V3-0324", "deepseek-ai/DeepSeek-R1", "Qwen/Qwen3-235B-A22B"] },
36
46
 
37
47
  // ── Tier 1: Aggregators ─────────────────────────────────────────────────────
38
- openrouter: { envKeys: ["OPENROUTER_API_KEY"], defaultModel: "anthropic/claude-sonnet-4-20250514", label: "OpenRouter (multi-model)", signupUrl: "https://openrouter.ai/keys" },
39
- vercelai: { envKeys: ["VERCEL_AI_TOKEN"], defaultModel: "anthropic/claude-3-5-sonnet", label: "Vercel AI Gateway", signupUrl: "https://vercel.com/docs/ai-gateway" },
48
+ openrouter: { envKeys: ["OPENROUTER_API_KEY"], defaultModel: "anthropic/claude-sonnet-4-20250514", label: "OpenRouter (multi-model)", signupUrl: "https://openrouter.ai/keys",
49
+ models: ["anthropic/claude-sonnet-4-20250514", "anthropic/claude-opus-4", "openai/gpt-4o", "openai/o3", "google/gemini-2.5-pro", "google/gemini-2.5-flash", "meta-llama/llama-3.3-70b-instruct", "deepseek/deepseek-r1"] },
50
+ vercelai: { envKeys: ["VERCEL_AI_TOKEN"], defaultModel: "anthropic/claude-3-5-sonnet", label: "Vercel AI Gateway", signupUrl: "https://vercel.com/docs/ai-gateway",
51
+ models: ["anthropic/claude-3-5-sonnet", "anthropic/claude-3-5-haiku", "openai/gpt-4o", "openai/gpt-4o-mini"] },
40
52
 
41
53
  // ── Tier 2: Useful ──────────────────────────────────────────────────────────
42
- kimi: { envKeys: ["MOONSHOT_API_KEY", "KIMI_API_KEY"], defaultModel: "moonshot-v1-8k", label: "Kimi/Moonshot", signupUrl: "https://platform.moonshot.cn/console/api-keys" },
43
- minimax: { envKeys: ["MINIMAX_API_KEY"], defaultModel: "MiniMax-Text-01", label: "MiniMax", signupUrl: "https://www.minimaxi.com/" },
44
- venice: { envKeys: ["VENICE_API_KEY"], defaultModel: "llama-3.3-70b", label: "Venice AI", signupUrl: "https://venice.ai/chat/api" },
45
- huggingface: { envKeys: ["HF_TOKEN", "HUGGINGFACE_API_KEY"], defaultModel: "meta-llama/Llama-3.3-70B-Instruct", label: "Hugging Face", signupUrl: "https://huggingface.co/settings/tokens", special: "hf_model_in_url" },
46
- cloudflare: { envKeys: ["CF_API_TOKEN"], defaultModel: "@cf/meta/llama-3.3-70b-instruct-fp8-fast", label: "Cloudflare AI", signupUrl: "https://dash.cloudflare.com/", special: "cf_account_id" },
54
+ kimi: { envKeys: ["MOONSHOT_API_KEY", "KIMI_API_KEY"], defaultModel: "moonshot-v1-8k", label: "Kimi/Moonshot", signupUrl: "https://platform.moonshot.cn/console/api-keys",
55
+ models: ["moonshot-v1-8k", "moonshot-v1-32k", "moonshot-v1-128k"] },
56
+ minimax: { envKeys: ["MINIMAX_API_KEY"], defaultModel: "MiniMax-Text-01", label: "MiniMax", signupUrl: "https://www.minimaxi.com/",
57
+ models: ["MiniMax-Text-01", "abab6.5s-chat", "abab6.5g-chat"] },
58
+ venice: { envKeys: ["VENICE_API_KEY"], defaultModel: "llama-3.3-70b", label: "Venice AI", signupUrl: "https://venice.ai/chat/api",
59
+ models: ["llama-3.3-70b", "mistral-31-24b", "deepseek-r1-671b"] },
60
+ huggingface: { envKeys: ["HF_TOKEN", "HUGGINGFACE_API_KEY"], defaultModel: "meta-llama/Llama-3.3-70B-Instruct", label: "Hugging Face", signupUrl: "https://huggingface.co/settings/tokens", special: "hf_model_in_url",
61
+ models: ["meta-llama/Llama-3.3-70B-Instruct", "meta-llama/Llama-3.1-8B-Instruct", "mistralai/Mistral-7B-Instruct-v0.3", "Qwen/Qwen2.5-72B-Instruct"] },
62
+ cloudflare: { envKeys: ["CF_API_TOKEN"], defaultModel: "@cf/meta/llama-3.3-70b-instruct-fp8-fast", label: "Cloudflare AI", signupUrl: "https://dash.cloudflare.com/", special: "cf_account_id",
63
+ models: ["@cf/meta/llama-3.3-70b-instruct-fp8-fast", "@cf/mistral/mistral-7b-instruct-v0.2", "@cf/google/gemma-7b-it", "@cf/qwen/qwen1.5-14b-chat-awq"] },
47
64
 
48
65
  // ── Tier 3: Regional / niche ────────────────────────────────────────────────
49
- volcengine: { envKeys: ["VOLCENGINE_API_KEY", "ARK_API_KEY"], defaultModel: "doubao-pro-32k", label: "Volcengine (ByteDance)", signupUrl: "https://console.volcengine.com/ark" },
50
- byteplus: { envKeys: ["BYTEPLUS_API_KEY"], defaultModel: "doubao-pro-32k", label: "BytePlus", signupUrl: "https://console.byteplux.com/" },
51
- zai: { envKeys: ["ZAI_API_KEY", "GLM_API_KEY", "ZHIPU_API_KEY"], defaultModel: "glm-4-flash", label: "Z.AI / GLM", signupUrl: "https://open.bigmodel.cn/" },
52
- dashscope: { envKeys: ["DASHSCOPE_API_KEY"], defaultModel: "qwen-max", label: "DashScope (Alibaba)", signupUrl: "https://dashscope.aliyun.com/" },
53
- xiaomi: { envKeys: ["XIAOMI_API_KEY"], defaultModel: "MiMo-7B-RL", label: "Xiaomi AI", signupUrl: "https://ai.xiaomi.com/" },
66
+ volcengine: { envKeys: ["VOLCENGINE_API_KEY", "ARK_API_KEY"], defaultModel: "doubao-pro-32k", label: "Volcengine (ByteDance)", signupUrl: "https://console.volcengine.com/ark",
67
+ models: ["doubao-pro-32k", "doubao-pro-128k", "doubao-lite-32k"] },
68
+ byteplus: { envKeys: ["BYTEPLUS_API_KEY"], defaultModel: "doubao-pro-32k", label: "BytePlus", signupUrl: "https://console.byteplux.com/",
69
+ models: ["doubao-pro-32k", "doubao-lite-32k"] },
70
+ zai: { envKeys: ["ZAI_API_KEY", "GLM_API_KEY", "ZHIPU_API_KEY"], defaultModel: "glm-4-flash", label: "Z.AI / GLM", signupUrl: "https://open.bigmodel.cn/",
71
+ models: ["glm-4-flash", "glm-4-plus", "glm-4-long", "glm-z1-flash"] },
72
+ dashscope: { envKeys: ["DASHSCOPE_API_KEY"], defaultModel: "qwen-max", label: "DashScope (Alibaba)", signupUrl: "https://dashscope.aliyun.com/",
73
+ models: ["qwen-max", "qwen-plus", "qwen-turbo", "qwen-long", "qwen2.5-72b-instruct"] },
74
+ xiaomi: { envKeys: ["XIAOMI_API_KEY"], defaultModel: "MiMo-7B-RL", label: "Xiaomi AI", signupUrl: "https://ai.xiaomi.com/",
75
+ models: ["MiMo-7B-RL", "MiMo-7B-SFT"] },
54
76
 
55
77
  // ── OAuth / subscription-based ────────────────────────────────────────────
56
- "github-copilot": { envKeys: ["GITHUB_COPILOT_TOKEN"], defaultModel: "gpt-4o", label: "GitHub Copilot", signupUrl: "https://github.com/features/copilot", auth: "oauth" },
78
+ "github-copilot": { envKeys: ["GITHUB_COPILOT_TOKEN"], defaultModel: "gpt-4o", label: "GitHub Copilot", signupUrl: "https://github.com/features/copilot", auth: "oauth",
79
+ models: ["gpt-4o", "gpt-4o-mini", "claude-sonnet-4-20250514", "o3-mini"] },
57
80
 
58
81
  // ── Local / self-hosted ─────────────────────────────────────────────────────
59
- ollama: { envKeys: [], defaultModel: "llama3.2", label: "Ollama (local)", signupUrl: null, local: true },
60
- vllm: { envKeys: [], defaultModel: "meta-llama/Llama-3.3-70B-Instruct", label: "vLLM (self-hosted)", signupUrl: "https://docs.vllm.ai/", local: true },
61
- sglang: { envKeys: [], defaultModel: "meta-llama/Llama-3.3-70B-Instruct", label: "SGLang (self-hosted)", signupUrl: "https://docs.sglang.ai/", local: true },
62
- litellm: { envKeys: ["LITELLM_API_KEY"], defaultModel: "gpt-4o", label: "LiteLLM (proxy)", signupUrl: "https://docs.litellm.ai/", local: true },
82
+ ollama: { envKeys: [], defaultModel: "llama3.2", label: "Ollama (local)", signupUrl: null, local: true,
83
+ models: ["llama3.2", "llama3.1", "llama3.1:70b", "mistral", "mixtral", "deepseek-r1", "qwen2.5", "phi4", "gemma3"] },
84
+ vllm: { envKeys: [], defaultModel: "meta-llama/Llama-3.3-70B-Instruct", label: "vLLM (self-hosted)", signupUrl: "https://docs.vllm.ai/", local: true,
85
+ models: ["meta-llama/Llama-3.3-70B-Instruct", "meta-llama/Llama-3.1-8B-Instruct", "mistralai/Mistral-7B-Instruct-v0.3", "Qwen/Qwen2.5-72B-Instruct"] },
86
+ sglang: { envKeys: [], defaultModel: "meta-llama/Llama-3.3-70B-Instruct", label: "SGLang (self-hosted)", signupUrl: "https://docs.sglang.ai/", local: true,
87
+ models: ["meta-llama/Llama-3.3-70B-Instruct", "meta-llama/Llama-3.1-8B-Instruct", "deepseek-ai/DeepSeek-V3", "Qwen/Qwen2.5-72B-Instruct"] },
88
+ litellm: { envKeys: ["LITELLM_API_KEY"], defaultModel: "gpt-4o", label: "LiteLLM (proxy)", signupUrl: "https://docs.litellm.ai/", local: true,
89
+ models: ["gpt-4o", "claude-sonnet-4-20250514", "gemini/gemini-2.5-flash", "bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0"] },
63
90
  };
64
91
 
65
92
  async function tryKeychainKey(service) {
@@ -569,10 +569,31 @@ export class OnboardingWizard {
569
569
  const result = await validateApiKey(provKey, apiKey.trim());
570
570
 
571
571
  if (result.ok) {
572
- console.log(green(` ✅ Connected! (${result.model ?? getProviderDefaultModel(provKey)})`));
572
+ console.log(green(` ✅ Connected!`));
573
+
574
+ // Model selection step
575
+ const provInfo = PROVIDERS[provKey];
576
+ const modelList = provInfo?.models ?? [getProviderDefaultModel(provKey)];
577
+ let chosenModel = result.model ?? getProviderDefaultModel(provKey);
578
+ if (modelList.length > 1) {
579
+ try {
580
+ chosenModel = await select({
581
+ message: ` Select model for ${info?.label ?? provKey}:`,
582
+ choices: modelList.map((m) => ({
583
+ name: m === getProviderDefaultModel(provKey) ? `${m} (default)` : m,
584
+ value: m,
585
+ })),
586
+ default: chosenModel,
587
+ });
588
+ } catch {
589
+ // keep default
590
+ }
591
+ }
592
+ console.log(dim(` Using model: ${chosenModel}`));
593
+
573
594
  providers[provKey] = {
574
595
  apiKey: apiKey.trim(),
575
- model: result.model ?? getProviderDefaultModel(provKey),
596
+ model: chosenModel,
576
597
  };
577
598
  validated = true;
578
599
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wispy-cli",
3
- "version": "2.7.6",
3
+ "version": "2.7.7",
4
4
  "description": "🌿 Wispy — AI workspace assistant with trustworthy execution (harness, receipts, approvals, diffs)",
5
5
  "license": "MIT",
6
6
  "author": "Minseo & Poropo",