wispy-cli 2.0.2 → 2.1.1

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
@@ -1102,7 +1102,8 @@ if (serveMode || telegramMode || discordMode || slackMode) {
1102
1102
  const isInteractiveStart = !args.some(a =>
1103
1103
  ["--serve", "--telegram", "--discord", "--slack", "--server",
1104
1104
  "status", "setup", "init", "connect", "disconnect", "deploy",
1105
- "cron", "audit", "log", "server", "node", "channel", "sync", "tui"].includes(a)
1105
+ "cron", "audit", "log", "server", "node", "channel", "sync", "tui",
1106
+ "ws", "trust", "where", "handoff", "skill", "teach", "improve", "dry"].includes(a)
1106
1107
  );
1107
1108
 
1108
1109
  if (isInteractiveStart) {
@@ -331,6 +331,41 @@ ${bold("Wispy Commands:")}
331
331
  ${cyan("/memories")} List all memory files
332
332
  ${cyan("/recall")} <query> Search memories
333
333
 
334
+ ${bold("Workstream (v1.4):")}
335
+ ${cyan("/ws")} List workstreams
336
+ ${cyan("/ws")} <name> Switch to workstream
337
+ ${cyan("/ws new")} <name> Create new workstream
338
+ ${cyan("/ws archive")} <name> Archive workstream
339
+ ${cyan("/ws status")} All workstreams status
340
+ ${cyan("/ws search")} <q> Cross-workstream search
341
+
342
+ ${bold("Trust (v1.4):")}
343
+ ${cyan("/trust")} Show security + recent approvals
344
+ ${cyan("/trust")} <level> Change level (careful/balanced/yolo)
345
+ ${cyan("/trust log")} Audit log
346
+ ${cyan("/receipt")} Show last receipt
347
+ ${cyan("/dry")} Toggle dry-run mode
348
+
349
+ ${bold("Continuity (v1.4):")}
350
+ ${cyan("/where")} Show current mode
351
+ ${cyan("/handoff")} Sync + context summary
352
+ ${cyan("/sync")} Trigger sync
353
+
354
+ ${bold("Skills (v1.4):")}
355
+ ${cyan("/skills")} List skills
356
+ ${cyan("/teach")} <name> Create skill from this conversation
357
+ ${cyan("/improve")} <name> Improve skill
358
+ ${cyan("/<skill-name>")} Run any learned skill directly
359
+
360
+ ${bold("Quick shortcuts:")}
361
+ ${cyan("/o")} Overview (alias for /ws status)
362
+ ${cyan("/w")} Workstream list
363
+ ${cyan("/a")} Agents list
364
+ ${cyan("/m")} Memories
365
+ ${cyan("/t")} Timeline (recent actions)
366
+ ${cyan("/s")} Sync status
367
+ ${cyan("/d")} Toggle dry-run
368
+
334
369
  ${bold("Sub-agent Commands (v0.9):")}
335
370
  ${cyan("/agents")} List active/recent sub-agents
336
371
  ${cyan("/agent")} <id> Show sub-agent details and result
@@ -399,7 +434,200 @@ ${bold("Permissions & Audit (v1.1):")}
399
434
  return true;
400
435
  }
401
436
 
402
- if (cmd === "/workstreams" || cmd === "/ws") {
437
+ // ── Quick single-letter shortcuts ────────────────────────────────────────────
438
+ if (cmd === "/o") {
439
+ // Overview — alias for /ws status
440
+ const { cmdWsStatus } = await import("./commands/ws.mjs");
441
+ await cmdWsStatus();
442
+ return true;
443
+ }
444
+
445
+ if (cmd === "/w") {
446
+ // Workstream list
447
+ const { cmdWsList } = await import("./commands/ws.mjs");
448
+ await cmdWsList();
449
+ return true;
450
+ }
451
+
452
+ if (cmd === "/a") {
453
+ // Agents — handled below (delegates to /agents)
454
+ const inMemory = engine.subagents.list();
455
+ const history = await engine.subagents.listHistory(10);
456
+ const inMemoryIds = new Set(inMemory.map(a => a.id));
457
+ const all = [
458
+ ...inMemory.map(a => a.toJSON()),
459
+ ...history.filter(h => !inMemoryIds.has(h.id)),
460
+ ].slice(0, 20);
461
+ if (all.length === 0) {
462
+ console.log(dim("No sub-agents yet."));
463
+ } else {
464
+ console.log(bold(`\n🤖 Agents (${all.length}):\n`));
465
+ for (const a of all) {
466
+ const icon = { completed: green("✓"), failed: red("✗"), running: cyan("⟳"), killed: dim("✕") }[a.status] ?? "?";
467
+ console.log(` ${icon} ${bold(a.id)} ${dim("·")} ${a.label}`);
468
+ }
469
+ }
470
+ return true;
471
+ }
472
+
473
+ if (cmd === "/m") {
474
+ // Memories — alias for /memories
475
+ const keys = await engine.memory.list();
476
+ if (keys.length === 0) {
477
+ console.log(dim("No memories stored yet. Use /remember <text>"));
478
+ } else {
479
+ console.log(bold(`\n🧠 Memories (${keys.length}):\n`));
480
+ for (const k of keys) {
481
+ console.log(` ${cyan(k.key.padEnd(30))} ${dim(k.preview ?? "")}`);
482
+ }
483
+ }
484
+ return true;
485
+ }
486
+
487
+ if (cmd === "/t") {
488
+ // Timeline — recent audit events
489
+ const events = await engine.audit.getRecent(15);
490
+ if (events.length === 0) {
491
+ console.log(dim("No recent activity."));
492
+ } else {
493
+ console.log(bold(`\n⏱ Timeline (last ${events.length} actions):\n`));
494
+ for (const evt of events) {
495
+ const ts = new Date(evt.timestamp).toLocaleTimeString();
496
+ const icons = {
497
+ tool_call: "🔧", tool_result: "✅", approval_requested: "⚠️ ",
498
+ approval_granted: "✅", approval_denied: "❌", message_sent: "🌿",
499
+ subagent_spawned: "🤖", error: "🚨",
500
+ };
501
+ const icon = icons[evt.type] ?? "•";
502
+ const detail = evt.tool ? ` ${cyan(evt.tool)}` : evt.content ? ` ${dim(evt.content.slice(0, 50))}` : "";
503
+ console.log(` ${dim(ts)} ${icon} ${evt.type}${detail}`);
504
+ }
505
+ }
506
+ return true;
507
+ }
508
+
509
+ if (cmd === "/s") {
510
+ // Sync status
511
+ try {
512
+ const { SyncManager } = await import("../core/sync.mjs");
513
+ const cfg = await SyncManager.loadConfig();
514
+ if (!cfg.remoteUrl) {
515
+ console.log(dim("No remote sync configured. Use: wispy sync auto --remote <url>"));
516
+ } else {
517
+ console.log(bold(`\n🔄 Sync Status\n`));
518
+ console.log(` Remote: ${cyan(cfg.remoteUrl)}`);
519
+ const lastSync = cfg.lastSync ? new Date(cfg.lastSync).toLocaleString() : "never";
520
+ console.log(` Last sync: ${dim(lastSync)}`);
521
+ console.log(` Auto-sync: ${cfg.autoSync ? green("enabled") : dim("disabled")}`);
522
+ }
523
+ } catch {
524
+ console.log(dim("Sync not available."));
525
+ }
526
+ return true;
527
+ }
528
+
529
+ if (cmd === "/d") {
530
+ // Toggle dry-run
531
+ if (engine.dryRunMode) {
532
+ engine.dryRunMode = false;
533
+ console.log(green("🌿 Dry-run OFF — tools will execute normally"));
534
+ } else {
535
+ engine.dryRunMode = true;
536
+ console.log(yellow("🔍 Dry-run ON — all tool calls will be previewed only"));
537
+ }
538
+ return true;
539
+ }
540
+
541
+ // ── /ws <subcommand> ─────────────────────────────────────────────────────────
542
+ if (cmd === "/ws") {
543
+ const { handleWsCommand } = await import("./commands/ws.mjs");
544
+ // Convert from /ws args to ws args format
545
+ const wsArgs = ["ws", ...parts.slice(1)];
546
+ await handleWsCommand(wsArgs);
547
+ return true;
548
+ }
549
+
550
+ // ── /trust <subcommand> ──────────────────────────────────────────────────────
551
+ if (cmd === "/trust") {
552
+ const { handleTrustCommand } = await import("./commands/trust.mjs");
553
+ const trustArgs = ["trust", ...parts.slice(1)];
554
+ await handleTrustCommand(trustArgs);
555
+ return true;
556
+ }
557
+
558
+ // ── /receipt ─────────────────────────────────────────────────────────────────
559
+ if (cmd === "/receipt") {
560
+ const { cmdTrustReceipt } = await import("./commands/trust.mjs");
561
+ const sessionId = parts[1] ?? engine.sessions?.list?.()?.find(Boolean)?.id;
562
+ await cmdTrustReceipt(sessionId);
563
+ return true;
564
+ }
565
+
566
+ // ── /dry (toggle dry-run) ────────────────────────────────────────────────────
567
+ if (cmd === "/dry") {
568
+ engine.dryRunMode = !engine.dryRunMode;
569
+ if (engine.dryRunMode) {
570
+ console.log(yellow("🔍 Dry-run ON — all tool calls will be previewed only"));
571
+ } else {
572
+ console.log(green("🌿 Dry-run OFF — tools will execute normally"));
573
+ }
574
+ return true;
575
+ }
576
+
577
+ // ── /where ────────────────────────────────────────────────────────────────────
578
+ if (cmd === "/where") {
579
+ const { cmdWhere } = await import("./commands/continuity.mjs");
580
+ await cmdWhere();
581
+ return true;
582
+ }
583
+
584
+ // ── /handoff ──────────────────────────────────────────────────────────────────
585
+ if (cmd === "/handoff") {
586
+ const { handleContinuityCommand } = await import("./commands/continuity.mjs");
587
+ const sub = parts[1] ?? "cloud";
588
+ await handleContinuityCommand(["handoff", sub]);
589
+ return true;
590
+ }
591
+
592
+ // ── /sync ─────────────────────────────────────────────────────────────────────
593
+ if (cmd === "/sync") {
594
+ try {
595
+ const { SyncManager } = await import("../core/sync.mjs");
596
+ const cfg = await SyncManager.loadConfig();
597
+ if (!cfg.remoteUrl) {
598
+ console.log(dim("No remote sync configured. Run: wispy sync auto --remote <url>"));
599
+ } else {
600
+ process.stdout.write(dim(`🔄 Syncing with ${cfg.remoteUrl}...`));
601
+ const mgr = new SyncManager({ remoteUrl: cfg.remoteUrl, token: cfg.token, strategy: "newer-wins" });
602
+ const result = await mgr.sync(cfg.remoteUrl, cfg.token, {});
603
+ console.log(green(` ✓ pushed ${result.pushed}, pulled ${result.pulled}`));
604
+ }
605
+ } catch (err) {
606
+ console.log(red(`✗ Sync failed: ${err.message.slice(0, 80)}`));
607
+ }
608
+ return true;
609
+ }
610
+
611
+ // ── /teach <name> ─────────────────────────────────────────────────────────────
612
+ if (cmd === "/teach") {
613
+ const skillName = parts[1];
614
+ if (!skillName) { console.log(yellow("Usage: /teach <name>")); return true; }
615
+ const { cmdTeach } = await import("./commands/skills-cmd.mjs");
616
+ await cmdTeach(skillName);
617
+ return true;
618
+ }
619
+
620
+ // ── /improve <name> ───────────────────────────────────────────────────────────
621
+ if (cmd === "/improve") {
622
+ const skillName = parts[1];
623
+ const feedback = parts.slice(2).join(" ").replace(/^["']|["']$/g, "");
624
+ if (!skillName || !feedback) { console.log(yellow('Usage: /improve <name> "feedback"')); return true; }
625
+ const { cmdImproveSkill } = await import("./commands/skills-cmd.mjs");
626
+ await cmdImproveSkill(skillName, feedback);
627
+ return true;
628
+ }
629
+
630
+ if (cmd === "/workstreams" || (cmd === "/ws" && !parts[1])) {
403
631
  const wsList = await listWorkstreams();
404
632
  if (wsList.length === 0) { console.log(dim("No workstreams yet.")); return true; }
405
633
  console.log(bold("\n📋 Workstreams:\n"));
@@ -857,9 +1085,15 @@ ${bold("Permissions & Audit (v1.1):")}
857
1085
  // ---------------------------------------------------------------------------
858
1086
 
859
1087
  async function runRepl(engine) {
1088
+ // Initialize dry-run from env flag (set by `wispy dry "..."`)
1089
+ if (process.env.WISPY_DRY_RUN === "1") {
1090
+ engine.dryRunMode = true;
1091
+ }
1092
+
860
1093
  const wsLabel = ACTIVE_WORKSTREAM === "default" ? "" : ` ${dim("·")} ${cyan(ACTIVE_WORKSTREAM)}`;
1094
+ const dryLabel = engine.dryRunMode ? ` ${yellow("· dry-run")}` : "";
861
1095
  console.log(`
862
- ${bold("🌿 Wispy")}${wsLabel} ${dim(`· ${engine.model}`)}
1096
+ ${bold("🌿 Wispy")}${wsLabel}${dryLabel} ${dim(`· ${engine.model}`)}
863
1097
  ${dim(`${engine.provider} · /help for commands · Ctrl+C to exit`)}
864
1098
  `);
865
1099
 
@@ -872,6 +1106,13 @@ async function runRepl(engine) {
872
1106
  historySize: 100,
873
1107
  });
874
1108
 
1109
+ // Dynamic prompt — shows workstream and dry-run status
1110
+ function updatePrompt() {
1111
+ const ws = ACTIVE_WORKSTREAM !== "default" ? `${cyan(ACTIVE_WORKSTREAM)} ` : "";
1112
+ const dry = engine.dryRunMode ? yellow("(dry) ") : "";
1113
+ rl.setPrompt(ws + dry + green("› "));
1114
+ }
1115
+ updatePrompt();
875
1116
  rl.prompt();
876
1117
 
877
1118
  rl.on("line", async (line) => {
@@ -880,7 +1121,7 @@ async function runRepl(engine) {
880
1121
 
881
1122
  if (input.startsWith("/")) {
882
1123
  const handled = await handleSlashCommand(input, engine, conversation);
883
- if (handled) { rl.prompt(); return; }
1124
+ if (handled) { updatePrompt(); rl.prompt(); return; }
884
1125
  }
885
1126
 
886
1127
  conversation.push({ role: "user", content: input });
@@ -895,6 +1136,7 @@ async function runRepl(engine) {
895
1136
  onChunk: (chunk) => process.stdout.write(chunk),
896
1137
  systemPrompt: await engine._buildSystemPrompt(input),
897
1138
  noSave: true,
1139
+ dryRun: engine.dryRunMode ?? false,
898
1140
  onSkillLearned: (skill) => {
899
1141
  console.log(cyan(`\n💡 Learned new skill: '${skill.name}' — use /${skill.name} next time`));
900
1142
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wispy-cli",
3
- "version": "2.0.2",
3
+ "version": "2.1.1",
4
4
  "description": "🌿 Wispy — AI workspace assistant with trustworthy execution (harness, receipts, approvals, diffs)",
5
5
  "license": "MIT",
6
6
  "author": "Minseo & Poropo",
@@ -57,7 +57,6 @@
57
57
  "LICENSE"
58
58
  ],
59
59
  "scripts": {
60
- "postinstall": "node scripts/postinstall.mjs",
61
60
  "test": "node --test --test-force-exit --test-timeout=15000 tests/*.test.mjs",
62
61
  "test:basic": "node --test --test-force-exit test/basic.test.mjs",
63
62
  "test:verbose": "node --test --test-force-exit --test-timeout=15000 --test-reporter=spec tests/*.test.mjs"