wispy-cli 2.7.4 → 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.
- package/README.md +354 -439
- package/bin/wispy-mjs +3 -0
- package/bin/wispy.mjs +331 -50
- package/lib/commands/ws.mjs +36 -27
- package/package.json +1 -1
package/bin/wispy-mjs
ADDED
package/bin/wispy.mjs
CHANGED
|
@@ -1,60 +1,341 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* wispy-cli
|
|
5
|
-
*
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
13
|
+
|
|
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";
|
|
18
|
+
|
|
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");
|
|
34
|
+
}
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
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);
|
|
130
|
+
}
|
|
131
|
+
} catch (err) {
|
|
132
|
+
console.error("Config error:", err.message);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
process.exit(0);
|
|
136
|
+
}
|
|
137
|
+
|
|
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}`);
|
|
37
285
|
}
|
|
38
|
-
process.exit(0);
|
|
39
286
|
}
|
|
40
|
-
|
|
287
|
+
console.log("");
|
|
41
288
|
}
|
|
289
|
+
} catch (err) {
|
|
290
|
+
console.error("Model error:", err.message);
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
process.exit(0);
|
|
294
|
+
}
|
|
42
295
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
console.log("Goodbye!");
|
|
52
|
-
break;
|
|
53
|
-
default:
|
|
54
|
-
console.error(`Unknown command: ${command}`);
|
|
55
|
-
console.log("Try one of the available commands: ws, help");
|
|
56
|
-
}
|
|
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);
|
|
57
304
|
}
|
|
305
|
+
// TUI handles its own lifecycle
|
|
306
|
+
}
|
|
58
307
|
|
|
59
|
-
|
|
60
|
-
|
|
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/lib/commands/ws.mjs
CHANGED
|
@@ -1,32 +1,41 @@
|
|
|
1
1
|
import { select } from '@inquirer/prompts';
|
|
2
2
|
|
|
3
|
-
export async function handleWsCommand() {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
export async function handleWsCommand(args = []) {
|
|
4
|
+
try {
|
|
5
|
+
if (args.length > 0 && !['startClient', 'runServerDebug'].includes(args[0])) {
|
|
6
|
+
console.error(`\nError: Invalid WebSocket subcommand '${args[0]}'.\n`);
|
|
7
|
+
console.log('Available subcommands:');
|
|
8
|
+
console.log(' startClient Start WebSocket Client');
|
|
9
|
+
console.log(' runServerDebug Run WebSocket Server Debug Mode');
|
|
10
|
+
console.log('\nTip: Use no arguments to enter interactive mode.\n');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
14
|
+
const action = args[0] || await select({
|
|
15
|
+
message: 'What WebSocket action would you like to perform?',
|
|
16
|
+
choices: [
|
|
17
|
+
{ name: 'Start WebSocket Client', value: 'startClient' },
|
|
18
|
+
{ name: 'Run WebSocket Server Debug Mode', value: 'runServerDebug' },
|
|
19
|
+
{ name: 'Exit', value: 'exit' }
|
|
20
|
+
]
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
switch (action) {
|
|
24
|
+
case 'startClient':
|
|
25
|
+
console.log('Starting WebSocket client...');
|
|
26
|
+
break;
|
|
27
|
+
case 'runServerDebug':
|
|
28
|
+
console.log('Running server in debug mode...');
|
|
29
|
+
break;
|
|
30
|
+
case 'exit':
|
|
31
|
+
console.log('Exiting WebSocket command.');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
} catch (err) {
|
|
35
|
+
if (err.name === 'ExitPromptError') {
|
|
36
|
+
console.log('Prompt closed. Exiting command gracefully.');
|
|
37
|
+
} else {
|
|
38
|
+
console.error('Unexpected error:', err);
|
|
39
|
+
}
|
|
30
40
|
}
|
|
31
|
-
}
|
|
32
41
|
}
|