wispy-cli 2.7.2 โ 2.7.4
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 +1 -1
- package/lib/commands/ws-interactive-enhancement.js +2 -0
- package/lib/commands/ws.mjs +25 -515
- package/package.json +1 -1
package/bin/wispy.mjs
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
const { handleWsCommand } = await import(baseDir + '/../lib/commands/ws.mjs');
|
|
46
46
|
return await handleWsCommand(args);
|
|
47
47
|
case "help":
|
|
48
|
-
console.log(
|
|
48
|
+
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\nExamples:\n $ node bin/wispy.mjs ws\n $ node bin/wispy.mjs --help\n\nTip: Run 'node bin/wispy.mjs' with no arguments for an interactive prompt.\n`);
|
|
49
49
|
break;
|
|
50
50
|
case null:
|
|
51
51
|
console.log("Goodbye!");
|
package/lib/commands/ws.mjs
CHANGED
|
@@ -1,522 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
* lib/commands/ws.mjs โ Workstream CLI commands
|
|
3
|
-
*
|
|
4
|
-
* wispy ws list all workstreams with last activity
|
|
5
|
-
* wispy ws new <name> create new workstream
|
|
6
|
-
* wispy ws switch <name> switch active workstream
|
|
7
|
-
* wispy ws archive <name> archive workstream
|
|
8
|
-
* wispy ws status detailed status of all workstreams
|
|
9
|
-
* wispy ws search <query> search across all workstreams
|
|
10
|
-
* wispy ws delete <name> delete workstream
|
|
11
|
-
*/
|
|
1
|
+
import { select } from '@inquirer/prompts';
|
|
12
2
|
|
|
13
|
-
|
|
14
|
-
import { existsSync } from "node:fs";
|
|
15
|
-
import path from "node:path";
|
|
16
|
-
import os from "node:os";
|
|
17
|
-
|
|
18
|
-
const WISPY_DIR = path.join(os.homedir(), ".wispy");
|
|
19
|
-
const WORKSTREAMS_DIR = path.join(WISPY_DIR, "workstreams");
|
|
20
|
-
const CONVERSATIONS_DIR = path.join(WISPY_DIR, "conversations");
|
|
21
|
-
const MEMORY_DIR = path.join(WISPY_DIR, "memory");
|
|
22
|
-
const ARCHIVE_DIR = path.join(WISPY_DIR, "archive");
|
|
23
|
-
const CONFIG_PATH = path.join(WISPY_DIR, "config.json");
|
|
24
|
-
|
|
25
|
-
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
26
|
-
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
27
|
-
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
28
|
-
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
29
|
-
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
30
|
-
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
31
|
-
|
|
32
|
-
async function readJsonOr(filePath, fallback = null) {
|
|
3
|
+
export async function handleWsCommand() {
|
|
33
4
|
try {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return readJsonOr(CONFIG_PATH, {});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function saveConfig(cfg) {
|
|
45
|
-
await mkdir(WISPY_DIR, { recursive: true });
|
|
46
|
-
await writeFile(CONFIG_PATH, JSON.stringify(cfg, null, 2) + "\n", "utf8");
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function getActiveWorkstream() {
|
|
50
|
-
const envWs = process.env.WISPY_WORKSTREAM;
|
|
51
|
-
if (envWs) return envWs;
|
|
52
|
-
const cfg = await getConfig();
|
|
53
|
-
return cfg.workstream ?? "default";
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Get all workstreams by scanning conversations/ dir (legacy) and workstreams/ dir
|
|
58
|
-
*/
|
|
59
|
-
async function listAllWorkstreams() {
|
|
60
|
-
const found = new Set(["default"]);
|
|
61
|
-
const results = [];
|
|
62
|
-
|
|
63
|
-
// Scan conversations/
|
|
64
|
-
try {
|
|
65
|
-
const files = await readdir(CONVERSATIONS_DIR);
|
|
66
|
-
for (const f of files) {
|
|
67
|
-
if (f.endsWith(".json")) found.add(f.replace(".json", ""));
|
|
68
|
-
}
|
|
69
|
-
} catch {}
|
|
70
|
-
|
|
71
|
-
// Scan workstreams/
|
|
72
|
-
try {
|
|
73
|
-
const dirs = await readdir(WORKSTREAMS_DIR);
|
|
74
|
-
for (const d of dirs) {
|
|
75
|
-
found.add(d);
|
|
76
|
-
}
|
|
77
|
-
} catch {}
|
|
78
|
-
|
|
79
|
-
const active = await getActiveWorkstream();
|
|
80
|
-
|
|
81
|
-
for (const name of found) {
|
|
82
|
-
const wsDir = path.join(WORKSTREAMS_DIR, name);
|
|
83
|
-
const convFile = path.join(CONVERSATIONS_DIR, `${name}.json`);
|
|
84
|
-
const workMd = path.join(wsDir, "work.md");
|
|
85
|
-
|
|
86
|
-
let lastActive = null;
|
|
87
|
-
let sessionCount = 0;
|
|
88
|
-
|
|
89
|
-
// Check conversation file
|
|
90
|
-
try {
|
|
91
|
-
const s = await stat(convFile);
|
|
92
|
-
lastActive = s.mtime;
|
|
93
|
-
const conv = await readJsonOr(convFile, []);
|
|
94
|
-
sessionCount = conv.filter(m => m.role === "user").length;
|
|
95
|
-
} catch {}
|
|
96
|
-
|
|
97
|
-
// Check workstream dir
|
|
98
|
-
try {
|
|
99
|
-
const s = await stat(wsDir);
|
|
100
|
-
if (!lastActive || s.mtime > lastActive) lastActive = s.mtime;
|
|
101
|
-
} catch {}
|
|
102
|
-
|
|
103
|
-
results.push({
|
|
104
|
-
name,
|
|
105
|
-
isActive: name === active,
|
|
106
|
-
lastActive,
|
|
107
|
-
sessionCount,
|
|
108
|
-
hasWorkMd: existsSync(workMd),
|
|
109
|
-
wsDir,
|
|
110
|
-
convFile,
|
|
5
|
+
const action = await select({
|
|
6
|
+
message: 'What WebSocket action would you like to perform?',
|
|
7
|
+
choices: [
|
|
8
|
+
{ name: 'Start WebSocket Client', value: 'startClient' },
|
|
9
|
+
{ name: 'Run WebSocket Server Debug Mode', value: 'runServerDebug' },
|
|
10
|
+
{ name: 'Exit', value: 'exit' }
|
|
11
|
+
]
|
|
111
12
|
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Sort: active first, then by last activity
|
|
115
|
-
results.sort((a, b) => {
|
|
116
|
-
if (a.isActive && !b.isActive) return -1;
|
|
117
|
-
if (!a.isActive && b.isActive) return 1;
|
|
118
|
-
const ta = a.lastActive ? a.lastActive.getTime() : 0;
|
|
119
|
-
const tb = b.lastActive ? b.lastActive.getTime() : 0;
|
|
120
|
-
return tb - ta;
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
return results;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function formatRelative(date) {
|
|
127
|
-
if (!date) return dim("never");
|
|
128
|
-
const diffMs = Date.now() - new Date(date).getTime();
|
|
129
|
-
const diffMin = Math.floor(diffMs / 60000);
|
|
130
|
-
const diffH = Math.floor(diffMin / 60);
|
|
131
|
-
const diffD = Math.floor(diffH / 24);
|
|
132
|
-
if (diffMin < 1) return green("just now");
|
|
133
|
-
if (diffMin < 60) return `${diffMin}min ago`;
|
|
134
|
-
if (diffH < 24) return `${diffH}h ago`;
|
|
135
|
-
if (diffD < 7) return `${diffD}d ago`;
|
|
136
|
-
return new Date(date).toLocaleDateString();
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// โโ Commands โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
140
|
-
|
|
141
|
-
export async function cmdWsList() {
|
|
142
|
-
const workstreams = await listAllWorkstreams();
|
|
143
|
-
if (workstreams.length === 0) {
|
|
144
|
-
console.log(dim("No workstreams yet. Create one: wispy ws new <name>"));
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
console.log(`\n${bold("๐ฟ Workstreams")}\n`);
|
|
149
|
-
for (const ws of workstreams) {
|
|
150
|
-
const marker = ws.isActive ? green("โ") : "โ";
|
|
151
|
-
const name = ws.isActive ? bold(green(ws.name)) : ws.name;
|
|
152
|
-
const msgs = ws.sessionCount > 0 ? dim(` ยท ${ws.sessionCount} msgs`) : "";
|
|
153
|
-
const last = ` ${dim(formatRelative(ws.lastActive))}`;
|
|
154
|
-
const plan = ws.hasWorkMd ? cyan(" ๐") : "";
|
|
155
|
-
console.log(` ${marker} ${name.padEnd(25)}${last}${msgs}${plan}`);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const active = await getActiveWorkstream();
|
|
159
|
-
console.log(dim(`\n Active: ${active} Switch: wispy ws <name> Create: wispy ws new <name>\n`));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export async function cmdWsNew(name) {
|
|
163
|
-
if (!name) {
|
|
164
|
-
console.log(yellow("Usage: wispy ws new <name>"));
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const wsDir = path.join(WORKSTREAMS_DIR, name);
|
|
169
|
-
await mkdir(wsDir, { recursive: true });
|
|
170
|
-
|
|
171
|
-
const workMd = path.join(wsDir, "work.md");
|
|
172
|
-
const workMdContent = `# ${name} Workstream
|
|
173
|
-
|
|
174
|
-
## Current Work
|
|
175
|
-
|
|
176
|
-
> Update this as you progress.
|
|
177
|
-
|
|
178
|
-
## Goals
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
## Context
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
## Next Steps
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
## Notes
|
|
188
|
-
|
|
189
|
-
`;
|
|
190
|
-
await writeFile(workMd, workMdContent, "utf8");
|
|
191
|
-
|
|
192
|
-
// Also create conversations entry
|
|
193
|
-
await mkdir(CONVERSATIONS_DIR, { recursive: true });
|
|
194
|
-
const convFile = path.join(CONVERSATIONS_DIR, `${name}.json`);
|
|
195
|
-
if (!existsSync(convFile)) {
|
|
196
|
-
await writeFile(convFile, "[]", "utf8");
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
console.log(green(`โ
Created workstream: ${name}`));
|
|
200
|
-
console.log(dim(` Dir: ${wsDir}`));
|
|
201
|
-
console.log(dim(` Run: wispy ws switch ${name} โ to activate`));
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export async function cmdWsSwitch(name) {
|
|
205
|
-
if (!name) {
|
|
206
|
-
console.log(yellow("Usage: wispy ws switch <name>"));
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const cfg = await getConfig();
|
|
211
|
-
cfg.workstream = name;
|
|
212
|
-
await saveConfig(cfg);
|
|
213
|
-
|
|
214
|
-
console.log(green(`โ
Active workstream: ${bold(name)}`));
|
|
215
|
-
console.log(dim(` Start a session: wispy (or set WISPY_WORKSTREAM=${name})`));
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
export async function cmdWsArchive(name) {
|
|
219
|
-
if (!name) {
|
|
220
|
-
console.log(yellow("Usage: wispy ws archive <name>"));
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Confirmation prompt
|
|
225
|
-
const { createInterface } = await import("node:readline");
|
|
226
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
227
|
-
const answer = await new Promise(r => rl.question(`Archive '${name}'? Sessions and memory will be moved. [Y/n] `, r));
|
|
228
|
-
rl.close();
|
|
229
|
-
if (answer.trim().toLowerCase() === "n") {
|
|
230
|
-
console.log(dim("Cancelled."));
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const archiveWsDir = path.join(ARCHIVE_DIR, name);
|
|
235
|
-
await mkdir(archiveWsDir, { recursive: true });
|
|
236
|
-
|
|
237
|
-
let moved = 0;
|
|
238
13
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
await rename(convFile, path.join(archiveWsDir, "conversation.json")).catch(() => {});
|
|
250
|
-
moved++;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (moved === 0) {
|
|
254
|
-
console.log(yellow(`Workstream '${name}' not found or already archived.`));
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
console.log(green(`๐ฆ Archived workstream: ${name}`));
|
|
259
|
-
console.log(dim(` Archive: ${archiveWsDir}`));
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
export async function cmdWsStatus() {
|
|
263
|
-
const workstreams = await listAllWorkstreams();
|
|
264
|
-
|
|
265
|
-
console.log(`\n${bold("๐ฟ Workstream Status")}\n`);
|
|
266
|
-
|
|
267
|
-
for (const ws of workstreams) {
|
|
268
|
-
const marker = ws.isActive ? green("โ") : "โ";
|
|
269
|
-
const name = ws.isActive ? bold(green(ws.name)) : bold(ws.name);
|
|
270
|
-
console.log(`${marker} ${name}`);
|
|
271
|
-
|
|
272
|
-
// Session count
|
|
273
|
-
console.log(` Sessions: ${ws.sessionCount} messages`);
|
|
274
|
-
|
|
275
|
-
// Last active
|
|
276
|
-
console.log(` Last active: ${formatRelative(ws.lastActive)}`);
|
|
277
|
-
|
|
278
|
-
// Memory files
|
|
279
|
-
const wsMemDir = path.join(ws.wsDir, "memory");
|
|
280
|
-
try {
|
|
281
|
-
const memFiles = await readdir(wsMemDir);
|
|
282
|
-
console.log(` Memory: ${memFiles.length} files`);
|
|
283
|
-
} catch {
|
|
284
|
-
const globalMemFiles = await readdir(MEMORY_DIR).catch(() => []);
|
|
285
|
-
if (ws.isActive) console.log(` Memory: ${globalMemFiles.length} files (global)`);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Work.md preview
|
|
289
|
-
if (ws.hasWorkMd) {
|
|
290
|
-
try {
|
|
291
|
-
const workMdContent = await readFile(path.join(ws.wsDir, "work.md"), "utf8");
|
|
292
|
-
const lines = workMdContent.split("\n").filter(l => l.trim() && !l.startsWith("#")).slice(0, 2);
|
|
293
|
-
if (lines.length > 0) {
|
|
294
|
-
console.log(dim(` Work: ${lines[0].replace(/^[>*\-] /, "").slice(0, 60)}`));
|
|
295
|
-
}
|
|
296
|
-
} catch {}
|
|
14
|
+
switch (action) {
|
|
15
|
+
case 'startClient':
|
|
16
|
+
console.log('Starting WebSocket client...');
|
|
17
|
+
break;
|
|
18
|
+
case 'runServerDebug':
|
|
19
|
+
console.log('Running server in debug mode...');
|
|
20
|
+
break;
|
|
21
|
+
case 'exit':
|
|
22
|
+
console.log('Exiting WebSocket command.');
|
|
23
|
+
return;
|
|
297
24
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
export async function cmdWsSearch(query) {
|
|
304
|
-
if (!query) {
|
|
305
|
-
console.log(yellow("Usage: wispy ws search <query>"));
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const workstreams = await listAllWorkstreams();
|
|
310
|
-
const lowerQuery = query.toLowerCase();
|
|
311
|
-
|
|
312
|
-
console.log(`\n${bold("๐ Searching workstreams for:")} ${cyan(query)}\n`);
|
|
313
|
-
let totalMatches = 0;
|
|
314
|
-
|
|
315
|
-
for (const ws of workstreams) {
|
|
316
|
-
const matches = [];
|
|
317
|
-
|
|
318
|
-
// Search conversation
|
|
319
|
-
try {
|
|
320
|
-
const conv = await readJsonOr(ws.convFile, []);
|
|
321
|
-
const convMatches = conv.filter(m =>
|
|
322
|
-
(m.role === "user" || m.role === "assistant") &&
|
|
323
|
-
m.content?.toLowerCase().includes(lowerQuery)
|
|
324
|
-
);
|
|
325
|
-
for (const m of convMatches.slice(-3)) {
|
|
326
|
-
const preview = m.content.replace(/\n/g, " ").slice(0, 80);
|
|
327
|
-
matches.push(` ${m.role === "user" ? "๐ค" : "๐ฟ"} ${dim(preview + (m.content.length > 80 ? "..." : ""))}`);
|
|
328
|
-
}
|
|
329
|
-
} catch {}
|
|
330
|
-
|
|
331
|
-
// Search work.md
|
|
332
|
-
if (ws.hasWorkMd) {
|
|
333
|
-
try {
|
|
334
|
-
const content = await readFile(path.join(ws.wsDir, "work.md"), "utf8");
|
|
335
|
-
if (content.toLowerCase().includes(lowerQuery)) {
|
|
336
|
-
const lines = content.split("\n").filter(l => l.toLowerCase().includes(lowerQuery)).slice(0, 2);
|
|
337
|
-
for (const l of lines) {
|
|
338
|
-
matches.push(` ๐ ${dim(l.trim().slice(0, 80))}`);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
} catch {}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
if (matches.length > 0) {
|
|
345
|
-
const name = ws.isActive ? bold(green(ws.name)) : bold(ws.name);
|
|
346
|
-
console.log(`${name} (${matches.length} match${matches.length === 1 ? "" : "es"}):`);
|
|
347
|
-
matches.forEach(m => console.log(m));
|
|
348
|
-
console.log("");
|
|
349
|
-
totalMatches += matches.length;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (totalMatches === 0) {
|
|
354
|
-
console.log(dim(`No matches found for "${query}"`));
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
export async function cmdWsDelete(name) {
|
|
359
|
-
if (!name) {
|
|
360
|
-
console.log(yellow("Usage: wispy ws delete <name>"));
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if (name === "default") {
|
|
365
|
-
console.log(red("Cannot delete the default workstream."));
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// Confirmation prompt
|
|
370
|
-
const { createInterface } = await import("node:readline");
|
|
371
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
372
|
-
const answer = await new Promise(r => rl.question(`โ ๏ธ Are you sure? This will delete all sessions and memory for '${name}'. [y/N] `, r));
|
|
373
|
-
rl.close();
|
|
374
|
-
if (answer.trim().toLowerCase() !== "y") {
|
|
375
|
-
console.log(dim("Cancelled."));
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
const wsDir = path.join(WORKSTREAMS_DIR, name);
|
|
380
|
-
const convFile = path.join(CONVERSATIONS_DIR, `${name}.json`);
|
|
381
|
-
|
|
382
|
-
let deleted = 0;
|
|
383
|
-
|
|
384
|
-
if (existsSync(wsDir)) {
|
|
385
|
-
await rm(wsDir, { recursive: true, force: true });
|
|
386
|
-
deleted++;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (existsSync(convFile)) {
|
|
390
|
-
const { unlink } = await import("node:fs/promises");
|
|
391
|
-
await unlink(convFile).catch(() => {});
|
|
392
|
-
deleted++;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
if (deleted === 0) {
|
|
396
|
-
console.log(red(`Workstream '${name}' not found.`));
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// If it was active, reset to default
|
|
401
|
-
const cfg = await getConfig();
|
|
402
|
-
if (cfg.workstream === name) {
|
|
403
|
-
cfg.workstream = "default";
|
|
404
|
-
await saveConfig(cfg);
|
|
405
|
-
console.log(dim(" Reset active workstream to: default"));
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
console.log(green(`๐๏ธ Deleted workstream: ${name}`));
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
export async function handleWsCommand(args) {
|
|
412
|
-
const sub = args[1];
|
|
413
|
-
|
|
414
|
-
if (!sub) {
|
|
415
|
-
// Interactive menu
|
|
416
|
-
try {
|
|
417
|
-
const { select, input, Separator } = await import("@inquirer/prompts");
|
|
418
|
-
const workstreams = await listAllWorkstreams();
|
|
419
|
-
|
|
420
|
-
const choices = [];
|
|
421
|
-
if (workstreams.length > 0) {
|
|
422
|
-
for (const ws of workstreams) {
|
|
423
|
-
const marker = ws.isActive ? "โ " : " ";
|
|
424
|
-
const last = formatRelative(ws.lastActive);
|
|
425
|
-
const msgs = ws.sessionCount > 0 ? ` ยท ${ws.sessionCount} msgs` : "";
|
|
426
|
-
choices.push({
|
|
427
|
-
name: `${marker}${ws.name}${ws.isActive ? " (active)" : ""} โ ${last}${msgs}`,
|
|
428
|
-
value: { type: "switch", name: ws.name },
|
|
429
|
-
short: ws.name,
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
} else {
|
|
433
|
-
choices.push(new Separator(dim("No workstreams yet")));
|
|
434
|
-
}
|
|
435
|
-
choices.push(new Separator("โโโโโโโโโโ"));
|
|
436
|
-
choices.push({ name: "Create new workstream", value: { type: "new" }, short: "Create new" });
|
|
437
|
-
choices.push({ name: "Archive a workstream", value: { type: "archive" }, short: "Archive" });
|
|
438
|
-
choices.push({ name: "Delete a workstream", value: { type: "delete" }, short: "Delete" });
|
|
439
|
-
|
|
440
|
-
let answer;
|
|
441
|
-
try {
|
|
442
|
-
answer = await select({ message: "Workstreams:", choices });
|
|
443
|
-
} catch (e) {
|
|
444
|
-
if (e.name === "ExitPromptError") { process.exit(130); }
|
|
445
|
-
throw e;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
if (answer.type === "switch") {
|
|
449
|
-
await cmdWsSwitch(answer.name);
|
|
450
|
-
} else if (answer.type === "new") {
|
|
451
|
-
let name;
|
|
452
|
-
try {
|
|
453
|
-
name = await input({ message: "New workstream name:" });
|
|
454
|
-
} catch (e) {
|
|
455
|
-
if (e.name === "ExitPromptError") return;
|
|
456
|
-
throw e;
|
|
457
|
-
}
|
|
458
|
-
if (name && name.trim()) await cmdWsNew(name.trim());
|
|
459
|
-
} else if (answer.type === "archive") {
|
|
460
|
-
const nonActive = workstreams.filter(w => !w.isActive);
|
|
461
|
-
if (nonActive.length === 0) {
|
|
462
|
-
console.log(dim("No other workstreams to archive."));
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
const archiveChoices = nonActive.map(w => ({ name: w.name, value: w.name }));
|
|
466
|
-
let toArchive;
|
|
467
|
-
try {
|
|
468
|
-
toArchive = await select({ message: "Archive which workstream?", choices: archiveChoices });
|
|
469
|
-
} catch (e) {
|
|
470
|
-
if (e.name === "ExitPromptError") return;
|
|
471
|
-
throw e;
|
|
472
|
-
}
|
|
473
|
-
await cmdWsArchive(toArchive);
|
|
474
|
-
} else if (answer.type === "delete") {
|
|
475
|
-
const deletable = workstreams.filter(w => w.name !== "default");
|
|
476
|
-
if (deletable.length === 0) {
|
|
477
|
-
console.log(dim("No workstreams to delete (cannot delete 'default')."));
|
|
478
|
-
return;
|
|
479
|
-
}
|
|
480
|
-
const deleteChoices = deletable.map(w => ({ name: w.name, value: w.name }));
|
|
481
|
-
let toDelete;
|
|
482
|
-
try {
|
|
483
|
-
toDelete = await select({ message: "Delete which workstream?", choices: deleteChoices });
|
|
484
|
-
} catch (e) {
|
|
485
|
-
if (e.name === "ExitPromptError") return;
|
|
486
|
-
throw e;
|
|
487
|
-
}
|
|
488
|
-
await cmdWsDelete(toDelete);
|
|
489
|
-
}
|
|
490
|
-
} catch (e) {
|
|
491
|
-
if (e.name === "ExitPromptError") { process.exit(130); }
|
|
492
|
-
// Fallback to plain list if inquirer unavailable
|
|
493
|
-
await cmdWsList();
|
|
25
|
+
} catch (err) {
|
|
26
|
+
if (err.name === 'ExitPromptError') {
|
|
27
|
+
console.log('Prompt closed. Exiting command gracefully.');
|
|
28
|
+
} else {
|
|
29
|
+
console.error('Unexpected error:', err);
|
|
494
30
|
}
|
|
495
|
-
return;
|
|
496
31
|
}
|
|
497
|
-
|
|
498
|
-
if (sub === "new") return cmdWsNew(args[2]);
|
|
499
|
-
if (sub === "switch") return cmdWsSwitch(args[2]);
|
|
500
|
-
if (sub === "archive") return cmdWsArchive(args[2]);
|
|
501
|
-
if (sub === "status") return cmdWsStatus();
|
|
502
|
-
if (sub === "search") return cmdWsSearch(args.slice(2).join(" "));
|
|
503
|
-
if (sub === "delete" || sub === "rm") return cmdWsDelete(args[2]);
|
|
504
|
-
|
|
505
|
-
// If sub is not a keyword, treat it as a shortcut for ws switch
|
|
506
|
-
if (!["new", "switch", "archive", "status", "search", "delete", "rm", "--help", "-h"].includes(sub)) {
|
|
507
|
-
return cmdWsSwitch(sub);
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
console.log(`
|
|
511
|
-
${bold("๐ฟ Workstream Commands")}
|
|
512
|
-
|
|
513
|
-
wispy ws ${dim("list all workstreams")}
|
|
514
|
-
wispy ws new <name> ${dim("create new workstream")}
|
|
515
|
-
wispy ws switch <name> ${dim("switch active workstream")}
|
|
516
|
-
wispy ws <name> ${dim("shortcut for switch")}
|
|
517
|
-
wispy ws archive <name> ${dim("archive workstream")}
|
|
518
|
-
wispy ws status ${dim("detailed status")}
|
|
519
|
-
wispy ws search <query> ${dim("search across all workstreams")}
|
|
520
|
-
wispy ws delete <name> ${dim("delete workstream")}
|
|
521
|
-
`);
|
|
522
|
-
}
|
|
32
|
+
}
|
package/package.json
CHANGED