wispy-cli 2.7.5 → 2.7.6

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/bin/wispy.mjs +327 -109
  2. package/package.json +1 -1
package/bin/wispy.mjs CHANGED
@@ -1,123 +1,341 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * wispy-cli
5
- * Async refactored dispatcher avoids direct top-level awaits in Node modules.
4
+ * wispy-cli — main entry point
5
+ *
6
+ * Dispatches to the correct handler based on the first argument:
7
+ * - Operator commands (setup, config, doctor, etc.) → operator CLI or inline handler
8
+ * - Server commands → server manager
9
+ * - Everything else → REPL (interactive chat)
10
+ *
11
+ * v2.7.x — rewired to properly route all commands.
6
12
  */
7
- (async () => {
8
- const { fileURLToPath } = await import('url');
9
- const { dirname } = await import('path');
10
- const { readFile } = await import('fs/promises');
11
- const baseDir = dirname(fileURLToPath(import.meta.url));
12
13
 
13
- const args = process.argv.slice(2);
14
+ import { fileURLToPath } from "node:url";
15
+ import { dirname, join } from "node:path";
16
+ import { readFile } from "node:fs/promises";
17
+ import { existsSync } from "node:fs";
14
18
 
15
- if (args.includes('--help')) {
16
- console.log(`\nWispy CLI Help:\n\nCommands:\n ws - Execute WebSocket operations\n help - Interactive help guide\n --help - Show this message\n --version - Display version\n\n`);
17
- process.exit(0);
19
+ const __filename = fileURLToPath(import.meta.url);
20
+ const __dirname = dirname(__filename);
21
+ const rootDir = join(__dirname, "..");
22
+
23
+ const args = process.argv.slice(2);
24
+ const command = args[0];
25
+
26
+ // ── Flags ─────────────────────────────────────────────────────────────────────
27
+
28
+ if (args.includes("--version") || command === "--version" || command === "-v") {
29
+ try {
30
+ const pkg = JSON.parse(await readFile(join(rootDir, "package.json"), "utf8"));
31
+ console.log(`wispy-cli v${pkg.version}`);
32
+ } catch {
33
+ console.error("Failed to read version from package.json");
18
34
  }
35
+ process.exit(0);
36
+ }
19
37
 
20
- async function handleCommand(command) {
21
- const inquirer = (await import('inquirer')).default;
22
-
23
- if (!command) {
24
- let answers;
25
- try {
26
- answers = await inquirer.prompt([
27
- {
28
- type: 'list',
29
- name: 'selectedCommand',
30
- message: 'What would you like to do?',
31
- choices: [
32
- { name: 'Run WebSocket command - Execute and debug WebSocket operations', value: 'ws' },
33
- { name: 'Get help - Learn about available commands and usage', value: 'help' },
34
- { name: 'Exit - Close the interactive prompt', value: null }
35
- ],
36
- }
37
- ]);
38
- } catch (error) {
39
- console.error("TTY Prompt failed. Need help? Run 'node bin/wispy.mjs --help'. Restarting interactivity...");
40
- process.exit(0);
41
- }
42
- command = answers.selectedCommand;
38
+ if (args.includes("--help") || command === "--help" || command === "-h" || command === "help") {
39
+ const pkg = JSON.parse(await readFile(join(rootDir, "package.json"), "utf8"));
40
+ console.log(`
41
+ wispy-cli v${pkg.version}
42
+ AI workspace assistant — chat, automate, and orchestrate from the terminal.
43
+
44
+ Usage:
45
+ wispy Start interactive REPL
46
+ wispy <message> One-shot chat
47
+ wispy setup Run first-time setup wizard
48
+ wispy config [show|get|set|delete|reset|path|edit]
49
+ Manage configuration
50
+ wispy model Show or change AI model
51
+ wispy doctor Check system health
52
+ wispy trust [level|log] Security level & audit
53
+ wispy ws [start-client|run-debug]
54
+ WebSocket operations
55
+ wispy skill [list|run] Manage learned skills
56
+ wispy teach <name> Create skill from conversation
57
+ wispy improve <name> Improve a skill
58
+ wispy where Show current mode (local/cloud)
59
+ wispy handoff [cloud|local] Sync between environments
60
+ wispy server [start|stop|status]
61
+ Manage HTTP/WS server
62
+ wispy tui Launch full terminal UI
63
+ wispy overview Director view of workstreams
64
+
65
+ Options:
66
+ -w, --workstream <name> Set active workstream
67
+ --session <id> Resume a session
68
+ --model <name> Override AI model
69
+ --provider <name> Override AI provider
70
+ --help, -h Show this help
71
+ --version, -v Show version
72
+ `);
73
+ process.exit(0);
74
+ }
75
+
76
+ // ── Setup (first-run wizard) ──────────────────────────────────────────────────
77
+
78
+ if (command === "setup") {
79
+ try {
80
+ const { OnboardingWizard } = await import(join(rootDir, "core/onboarding.mjs"));
81
+ const wizard = new OnboardingWizard();
82
+ await wizard.run();
83
+ } catch (err) {
84
+ console.error("Setup failed:", err.message);
85
+ process.exit(1);
86
+ }
87
+ process.exit(0);
88
+ }
89
+
90
+ // ── Config ────────────────────────────────────────────────────────────────────
91
+
92
+ if (command === "config") {
93
+ try {
94
+ const { loadConfig, saveConfig, WISPY_DIR, CONFIG_PATH } = await import(join(rootDir, "core/config.mjs"));
95
+ const sub = args[1];
96
+ const config = await loadConfig();
97
+
98
+ if (!sub || sub === "show") {
99
+ console.log(JSON.stringify(config, null, 2));
100
+ } else if (sub === "path") {
101
+ console.log(CONFIG_PATH);
102
+ } else if (sub === "get") {
103
+ const key = args[2];
104
+ if (!key) { console.error("Usage: wispy config get <key>"); process.exit(1); }
105
+ console.log(config[key] !== undefined ? JSON.stringify(config[key], null, 2) : `Key '${key}' not found`);
106
+ } else if (sub === "set") {
107
+ const key = args[2], value = args[3];
108
+ if (!key || value === undefined) { console.error("Usage: wispy config set <key> <value>"); process.exit(1); }
109
+ try { config[key] = JSON.parse(value); } catch { config[key] = value; }
110
+ await saveConfig(config);
111
+ console.log(`Set ${key} = ${JSON.stringify(config[key])}`);
112
+ } else if (sub === "delete") {
113
+ const key = args[2];
114
+ if (!key) { console.error("Usage: wispy config delete <key>"); process.exit(1); }
115
+ delete config[key];
116
+ await saveConfig(config);
117
+ console.log(`Deleted '${key}'`);
118
+ } else if (sub === "reset") {
119
+ const { confirm } = await import("@inquirer/prompts");
120
+ const yes = await confirm({ message: "Reset all config to defaults?", default: false });
121
+ if (yes) { await saveConfig({}); console.log("Config reset to defaults."); }
122
+ } else if (sub === "edit") {
123
+ const editor = process.env.EDITOR || "nano";
124
+ const { execSync } = await import("node:child_process");
125
+ execSync(`${editor} "${CONFIG_PATH}"`, { stdio: "inherit" });
126
+ } else {
127
+ console.error(`Unknown config subcommand: ${sub}`);
128
+ console.log("Available: show, get, set, delete, reset, path, edit");
129
+ process.exit(1);
43
130
  }
131
+ } catch (err) {
132
+ console.error("Config error:", err.message);
133
+ process.exit(1);
134
+ }
135
+ process.exit(0);
136
+ }
44
137
 
45
- switch (command) {
46
- case "--version":
47
- try {
48
- const packageJson = JSON.parse(await readFile(`${baseDir}/../package.json`, 'utf8'));
49
- console.log(`Wispy CLI version: ${packageJson.version}`);
50
- } catch {
51
- console.error('Failed to read version information from package.json.');
52
- }
53
- break;
54
- case "ws":
55
- if (!args[1]) {
56
- let subcommand;
57
- try {
58
- const { subcommand: chosenSubcommand } = await inquirer.prompt([
59
- {
60
- type: 'list',
61
- name: 'subcommand',
62
- message: 'Choose a WebSocket subcommand:',
63
- choices: [
64
- { name: 'Start WebSocket Client', value: 'start-client' },
65
- { name: 'Run WebSocket Server Debug Mode', value: 'run-debug' }
66
- ]
67
- }
68
- ]);
69
- subcommand = chosenSubcommand;
70
- } catch (error) {
71
- console.error('Interactive prompt was cancelled or closed. Exiting gracefully.');
72
- process.exit(0);
73
- }
74
- args[1] = subcommand;
75
- } else if (!['start-client', 'run-debug'].includes(args[1])) {
76
- console.error(`Error: Unrecognized argument '${args[1]}'.`);
77
- const { retry } = await inquirer.prompt([
78
- {
79
- type: 'confirm',
80
- name: 'retry',
81
- message: 'Would you like to re-enter the WebSocket command?',
82
- default: true,
83
- }
84
- ]);
85
- if (retry) return await handleCommand('ws');
86
- process.exit(1);
87
- }
88
- const { handleWsCommand } = await import(baseDir + '/../lib/commands/ws.mjs');
89
- return await handleWsCommand(args);
90
- case "help":
91
- if (args.length > 1) {
92
- console.error(`Invalid arguments provided for 'help': ${args.slice(1).join(' ')}\n`);
93
- console.log(`Usage: node bin/wispy.mjs help\n\nAvailable Commands:\n ws - Run WebSocket command (debug operations)\n help - Show this message\n\nTip: Run 'node bin/wispy.mjs' with no args for interactive guidance!\n`);
94
- } else {
95
- console.log(`\nWispy CLI Help:\n\nCommands:\n ws - Run WebSocket command (e.g., 'node bin/wispy.mjs ws')\n help - Show this help message (e.g., 'node bin/wispy.mjs --help')\n --version - Show the CLI version (e.g., 'node bin/wispy.mjs --version')\n\nExamples:\n $ node bin/wispy.mjs ws\n $ node bin/wispy.mjs --help\n $ node bin/wispy.mjs --version\n\nTip: Use no args for full interactivity.\n`);
96
- }
97
- break;
98
- case null:
99
- console.log("Goodbye!");
100
- break;
101
- default:
102
- const { getCommandsWithSkills } = await import(baseDir + '/../lib/command-registry.mjs');
103
- const validCommands = getCommandsWithSkills(null);
104
- const prompt = (await import('inquirer')).default;
105
- console.error(`Unknown command: ${command}`);
106
- try {
107
- const { choice } = await prompt.prompt([{
108
- type: 'list',
109
- name: 'choice',
110
- message: 'Unknown command. Need help? Select a valid command:',
111
- choices: validCommands.map(c => `${c.cmd} - ${c.desc}`),
112
- }]);
113
- const selectedCmd = choice.split(' ')[0];
114
- console.log(`Executing '${selectedCmd}'...`);
115
- await handleCommand(selectedCmd);
116
- } catch (err) {
117
- console.log('Prompt exited. No command executed. Goodbye!');
138
+ // ── Doctor ────────────────────────────────────────────────────────────────────
139
+
140
+ if (command === "doctor") {
141
+ try {
142
+ const { loadConfig, detectProvider, WISPY_DIR } = await import(join(rootDir, "core/config.mjs"));
143
+ const config = await loadConfig();
144
+ const provider = await detectProvider();
145
+ const pkg = JSON.parse(await readFile(join(rootDir, "package.json"), "utf8"));
146
+
147
+ console.log(`\n wispy-cli v${pkg.version}`);
148
+ console.log(` Config dir: ${WISPY_DIR}`);
149
+ console.log(` Provider: ${provider?.provider ?? "none"}`);
150
+ console.log(` Model: ${provider?.model ?? "none"}`);
151
+ console.log(` API key: ${provider?.key ? "✓ set" : "✗ missing"}`);
152
+
153
+ // Check optional components
154
+ const checks = [
155
+ { name: "Config file", ok: existsSync(join(WISPY_DIR, "config.json")) },
156
+ { name: "Memory dir", ok: existsSync(join(WISPY_DIR, "memory")) },
157
+ { name: "Sessions dir", ok: existsSync(join(WISPY_DIR, "sessions")) },
158
+ { name: "MCP config", ok: existsSync(join(WISPY_DIR, "mcp.json")) },
159
+ ];
160
+ console.log("");
161
+ for (const c of checks) {
162
+ console.log(` ${c.ok ? "✓" : "✗"} ${c.name}`);
163
+ }
164
+ console.log("");
165
+
166
+ if (!provider?.key) {
167
+ console.log(" Run 'wispy setup' to configure an API key.\n");
168
+ } else {
169
+ console.log(" All good!\n");
170
+ }
171
+ } catch (err) {
172
+ console.error("Doctor error:", err.message);
173
+ process.exit(1);
174
+ }
175
+ process.exit(0);
176
+ }
177
+
178
+ // ── Trust ─────────────────────────────────────────────────────────────────────
179
+
180
+ if (command === "trust") {
181
+ try {
182
+ const { handleTrustCommand } = await import(join(rootDir, "lib/commands/trust.mjs"));
183
+ await handleTrustCommand(args.slice(1));
184
+ } catch (err) {
185
+ console.error("Trust command error:", err.message);
186
+ process.exit(1);
187
+ }
188
+ process.exit(0);
189
+ }
190
+
191
+ // ── WebSocket ─────────────────────────────────────────────────────────────────
192
+
193
+ if (command === "ws") {
194
+ try {
195
+ const { handleWsCommand } = await import(join(rootDir, "lib/commands/ws.mjs"));
196
+ await handleWsCommand(args);
197
+ } catch (err) {
198
+ console.error("WS command error:", err.message);
199
+ process.exit(1);
200
+ }
201
+ process.exit(0);
202
+ }
203
+
204
+ // ── Skills ────────────────────────────────────────────────────────────────────
205
+
206
+ if (command === "skill" || command === "skills") {
207
+ try {
208
+ const { handleSkillCommand } = await import(join(rootDir, "lib/commands/skills-cmd.mjs"));
209
+ await handleSkillCommand(args.slice(1));
210
+ } catch (err) {
211
+ console.error("Skill command error:", err.message);
212
+ process.exit(1);
213
+ }
214
+ process.exit(0);
215
+ }
216
+
217
+ if (command === "teach") {
218
+ try {
219
+ const { cmdTeach } = await import(join(rootDir, "lib/commands/skills-cmd.mjs"));
220
+ await cmdTeach(args[1]);
221
+ } catch (err) {
222
+ console.error("Teach error:", err.message);
223
+ process.exit(1);
224
+ }
225
+ process.exit(0);
226
+ }
227
+
228
+ if (command === "improve") {
229
+ try {
230
+ const { cmdImproveSkill } = await import(join(rootDir, "lib/commands/skills-cmd.mjs"));
231
+ await cmdImproveSkill(args[1], args.slice(2).join(" "));
232
+ } catch (err) {
233
+ console.error("Improve error:", err.message);
234
+ process.exit(1);
235
+ }
236
+ process.exit(0);
237
+ }
238
+
239
+ // ── Continuity ────────────────────────────────────────────────────────────────
240
+
241
+ if (command === "where") {
242
+ try {
243
+ const { cmdWhere } = await import(join(rootDir, "lib/commands/continuity.mjs"));
244
+ await cmdWhere();
245
+ } catch (err) {
246
+ console.error("Where error:", err.message);
247
+ process.exit(1);
248
+ }
249
+ process.exit(0);
250
+ }
251
+
252
+ if (command === "handoff") {
253
+ try {
254
+ const { handleContinuityCommand } = await import(join(rootDir, "lib/commands/continuity.mjs"));
255
+ await handleContinuityCommand(args.slice(1));
256
+ } catch (err) {
257
+ console.error("Handoff error:", err.message);
258
+ process.exit(1);
259
+ }
260
+ process.exit(0);
261
+ }
262
+
263
+ // ── Model ─────────────────────────────────────────────────────────────────────
264
+
265
+ if (command === "model") {
266
+ try {
267
+ const { loadConfig, saveConfig, detectProvider, PROVIDERS } = await import(join(rootDir, "core/config.mjs"));
268
+ const config = await loadConfig();
269
+ const detected = await detectProvider();
270
+
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}`);
118
285
  }
286
+ }
287
+ console.log("");
119
288
  }
289
+ } catch (err) {
290
+ console.error("Model error:", err.message);
291
+ process.exit(1);
292
+ }
293
+ process.exit(0);
294
+ }
295
+
296
+ // ── TUI ───────────────────────────────────────────────────────────────────────
297
+
298
+ if (command === "tui") {
299
+ try {
300
+ await import(join(rootDir, "bin/wispy-tui.mjs"));
301
+ } catch (err) {
302
+ console.error("TUI launch error:", err.message);
303
+ process.exit(1);
120
304
  }
305
+ // TUI handles its own lifecycle
306
+ }
121
307
 
122
- await handleCommand(args[0]);
123
- })();
308
+ // ── Overview ──────────────────────────────────────────────────────────────────
309
+
310
+ if (command === "overview") {
311
+ // Delegate to REPL with overview flag
312
+ process.env.WISPY_OVERVIEW = "1";
313
+ }
314
+
315
+ // ── Server ────────────────────────────────────────────────────────────────────
316
+
317
+ if (command === "server") {
318
+ // Delegate to REPL which has server handling built in
319
+ // (lib/wispy-repl.mjs handles server start/stop/status)
320
+ }
321
+
322
+ // ── Default: launch REPL ──────────────────────────────────────────────────────
323
+ // For no args, one-shot messages, or unrecognized commands that the REPL handles
324
+
325
+ if (command === "server" || command === "overview") {
326
+ // Already set up env flags above, fall through to REPL
327
+ }
328
+
329
+ // If we get here and it's not a recognized command above, go to REPL
330
+ // The REPL handles: interactive chat, one-shot "wispy <message>", server, overview
331
+ try {
332
+ await import(join(rootDir, "lib/wispy-repl.mjs"));
333
+ } catch (err) {
334
+ if (err.code === "ERR_MODULE_NOT_FOUND") {
335
+ console.error(`Module not found: ${err.message}`);
336
+ console.error("Try running: npm install");
337
+ } else {
338
+ console.error("Failed to start wispy:", err.message);
339
+ }
340
+ process.exit(1);
341
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wispy-cli",
3
- "version": "2.7.5",
3
+ "version": "2.7.6",
4
4
  "description": "🌿 Wispy — AI workspace assistant with trustworthy execution (harness, receipts, approvals, diffs)",
5
5
  "license": "MIT",
6
6
  "author": "Minseo & Poropo",