lazyclaw 3.99.26 → 3.99.28

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/cli.mjs +148 -83
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -1153,6 +1153,7 @@ const HELP_SUMMARIES = {
1153
1153
  clear: 'Delete a persisted workflow state file (idempotent)',
1154
1154
  validate: 'Static-check a workflow file: shape, deps, cycles, parallelism',
1155
1155
  graph: 'Emit workflow DAG as Mermaid syntax (paste-ready for docs)',
1156
+ orchestrator: 'Multi-agent dispatch — planner decomposes, workers run, planner synthesises',
1156
1157
  };
1157
1158
 
1158
1159
  // Detailed usage per subcommand for `lazyclaw help <name>`. Kept as flat
@@ -1468,69 +1469,70 @@ function _attachGhostAutocomplete(rl) {
1468
1469
  // border off the box (which is exactly the bug v3.99.5 shipped:
1469
1470
  // two of the inner lines were 33 cols vs the others' 32, so the
1470
1471
  // ╮ rendered into the next line).
1471
- // v3.99.26canonical Big ASCII mascot from the v0.1 Claude Design
1472
- // handoff bundle. 12 rows. Claude's square body + lobster pincers (◂▸)
1473
- // + helmet (╔═╗) + asterisk-star tail. Sleepy slit eyes (│ │) by
1474
- // default name says lazyclaw.
1475
- //
1476
- // State variants live in _renderMascot(state). Big variant = banner.
1477
- // Inline 3-row Tiny variant lives in _renderMascotTiny(state).
1472
+ // v3.99.28 — Big ASCII mascot, rebuilt on a strict 17-wide canvas so
1473
+ // every row aligns (the v3.99.26 port copied the design handoff's
1474
+ // mixed 15/16/17 widths which made the helmet drift left of the
1475
+ // body in any monospace font). Layout: pincers ◂▸ at cols 2-3 and
1476
+ // 13-14, stems │ at cols 2 and 14, helmet box from col 1 to col 15
1477
+ // (13-wide interior), body box same width directly below, legs ┃ at
1478
+ // cols 4 and 12 symmetric on the central axis (col 8).
1479
+ const _MASCOT_W = 17;
1478
1480
  const _MASCOT_BIG = {
1479
1481
  idle: [
1480
- ' ◂▸ ◂▸ ',
1482
+ ' ◂▸ ◂▸ ',
1481
1483
  ' │ │ ',
1482
1484
  ' │ │ ',
1483
- '╔═════════════╗',
1484
- '║ ║',
1485
- '╚═════════════╝',
1486
- '┌─────────────┐',
1487
- '│ │ │ │',
1488
- '┤ │ │ ├',
1489
- '└─────────────┘',
1490
- ' ',
1491
- ' ',
1485
+ ' ╔═════════════╗ ',
1486
+ ' ║ ║ ',
1487
+ ' ╚═════════════╝ ',
1488
+ ' ┌─────────────┐ ',
1489
+ ' │ │ │ │ ',
1490
+ ' ┤ │ │ ├ ',
1491
+ ' └─────────────┘ ',
1492
+ ' ',
1493
+ ' ',
1492
1494
  ],
1493
1495
  working: [
1494
- ' ◂▸ ◂▸ ',
1496
+ ' ◂▸ ◂▸ ',
1495
1497
  ' │ ··· │ ',
1496
1498
  ' │ │ ',
1497
- '╔═════════════╗',
1498
- '║ * ║',
1499
- '╚═════════════╝',
1500
- '┌─────────────┐',
1501
- '│ · · │',
1502
- '┤ ├',
1503
- '└─────────────┘',
1504
- ' ',
1505
- ' ',
1499
+ ' ╔═════════════╗ ',
1500
+ ' * ',
1501
+ ' ╚═════════════╝ ',
1502
+ ' ┌─────────────┐ ',
1503
+ ' │ · · │ ',
1504
+ ' ',
1505
+ ' └─────────────┘ ',
1506
+ ' ',
1507
+ ' ',
1506
1508
  ],
1507
1509
  done: [
1508
- '✦ ◂▸ ◂▸ ✦',
1510
+ '✦ ◂▸ ◂▸ ✦',
1509
1511
  ' │ │ ',
1510
1512
  ' │ │ ',
1511
- '╔═════════════╗',
1512
- '║ ║',
1513
- '╚═════════════╝',
1514
- '┌─────────────┐',
1515
- '│ ^ ^ │',
1516
- '┤ ‿‿‿ ├',
1517
- '└─────────────┘',
1518
- ' ',
1519
- ' ',
1513
+ ' ╔═════════════╗ ',
1514
+ ' ║ ║ ',
1515
+ ' ╚═════════════╝ ',
1516
+ ' ┌─────────────┐ ',
1517
+ ' │ ^ ^ │ ',
1518
+ ' ‿‿‿‿‿ ',
1519
+ ' └─────────────┘ ',
1520
+ ' ',
1521
+ ' ',
1520
1522
  ],
1521
1523
  error: [
1522
- ' ▾ ▾ ',
1524
+ ' ▾ ▾ ',
1523
1525
  ' │ │ ',
1524
1526
  ' │ │ ',
1525
- '╔═════════════╗',
1526
- '║ ~ ║',
1527
- '╚═════════════╝',
1528
- '┌─────────────┐',
1529
- '│ × × │',
1530
- '┤ ├',
1531
- '└─────────────┘',
1532
- ' ',
1533
- ' ',
1527
+ ' ╔═════════════╗ ',
1528
+ ' ~ ',
1529
+ ' ╚═════════════╝ ',
1530
+ ' ┌─────────────┐ ',
1531
+ ' │ × × │ ',
1532
+ ' ⏜⏜⏜⏜⏜ ',
1533
+ ' └─────────────┘ ',
1534
+ ' ',
1535
+ ' ',
1534
1536
  ],
1535
1537
  };
1536
1538
  const _MASCOT_TINY = {
@@ -4528,25 +4530,48 @@ async function _dispatchMenuChoice(argv) {
4528
4530
  process.exit = (code) => { throw new _DispatchExit(code); };
4529
4531
  try {
4530
4532
  switch (sub) {
4531
- case 'chat': return await cmdChat({});
4532
- case 'agent': return await cmdAgent(rest[0] || '-', {});
4533
- case 'onboard': return await cmdOnboard({});
4534
- case 'setup': return await cmdSetup(undefined, rest, {});
4535
- case 'workspace': return await cmdWorkspace(rest[0], rest.slice(1), {});
4536
- case 'browse': return await cmdBrowse(rest[0], {});
4537
- case 'skills': return await cmdSkills(rest[0], rest.slice(1), {});
4538
- case 'sessions': return await cmdSessions(rest[0], rest.slice(1), {});
4539
- case 'providers': return await cmdProviders(rest[0], rest.slice(1), {});
4540
- case 'cron': return await cmdCron(rest[0], rest.slice(1), {});
4541
- case 'auth': return await cmdAuth(rest[0], rest.slice(1), {});
4542
- case 'pairing': return await cmdPairing(rest[0], rest.slice(1), {});
4543
- case 'nodes': return await cmdNodes(rest[0], rest.slice(1), {});
4544
- case 'message': return await cmdMessage(rest[0], rest.slice(1), {});
4545
- case 'doctor': return await cmdDoctor();
4546
- case 'status': return await cmdStatus();
4547
- case 'help': return cmdHelp();
4548
- case 'dashboard': return await cmdDashboard({});
4549
- default: throw new Error(`unknown menu choice: ${sub}`);
4533
+ case 'chat': return await cmdChat({});
4534
+ case 'agent': return await cmdAgent(rest[0] || '-', {});
4535
+ case 'onboard': return await cmdOnboard({});
4536
+ case 'setup': return await cmdSetup(undefined, rest, {});
4537
+ case 'workspace': return await cmdWorkspace(rest[0], rest.slice(1), {});
4538
+ case 'browse': return await cmdBrowse(rest[0], {});
4539
+ case 'skills': return await cmdSkills(rest[0], rest.slice(1), {});
4540
+ case 'sessions': return await cmdSessions(rest[0], rest.slice(1), {});
4541
+ case 'providers': return await cmdProviders(rest[0], rest.slice(1), {});
4542
+ case 'cron': return await cmdCron(rest[0], rest.slice(1), {});
4543
+ case 'auth': return await cmdAuth(rest[0], rest.slice(1), {});
4544
+ case 'pairing': return await cmdPairing(rest[0], rest.slice(1), {});
4545
+ case 'nodes': return await cmdNodes(rest[0], rest.slice(1), {});
4546
+ case 'message': return await cmdMessage(rest[0], rest.slice(1), {});
4547
+ case 'doctor': return await cmdDoctor();
4548
+ case 'status': return await cmdStatus();
4549
+ // v3.99.27 — fill the rest of the lazyclaw <subcommand> surface
4550
+ // so the no-arg launcher mirrors every entry in SUBCOMMANDS.
4551
+ case 'orchestrator': return await cmdOrchestrator(rest[0], rest.slice(1), {});
4552
+ case 'rates': return await cmdRates(rest[0], rest.slice(1), {});
4553
+ case 'config': {
4554
+ // Mirror the main switch's tiny dispatcher.
4555
+ const csub = rest[0];
4556
+ if (csub === 'list' || csub === undefined) return cmdConfigGet(undefined);
4557
+ if (csub === 'get') return cmdConfigGet(rest[1]);
4558
+ if (csub === 'set') return cmdConfigSet(rest[1], rest.slice(2).join(' '));
4559
+ if (csub === 'path') { process.stdout.write(configPath() + '\n'); return; }
4560
+ if (csub === 'edit') return await cmdConfigEdit();
4561
+ if (csub === 'validate') return await cmdConfigValidate();
4562
+ process.stderr.write('Usage: lazyclaw config <get|set|list|delete|path|edit|validate>\n');
4563
+ return;
4564
+ }
4565
+ case 'inspect': return await cmdInspect(rest[0], {});
4566
+ case 'export': return await cmdExport({});
4567
+ case 'version': return await cmdVersion();
4568
+ // help <cmd> is the safe fallback for commands that need real
4569
+ // arguments (run / resume / clear / validate / graph / daemon /
4570
+ // import / completion). Print the usage so the user can re-launch
4571
+ // with proper flags — the menu stays alive.
4572
+ case 'help': return cmdHelp(rest[0]);
4573
+ case 'dashboard': return await cmdDashboard({});
4574
+ default: throw new Error(`unknown menu choice: ${sub}`);
4550
4575
  }
4551
4576
  } catch (e) {
4552
4577
  if (e instanceof _DispatchExit) {
@@ -4568,22 +4593,57 @@ async function cmdLauncher() {
4568
4593
  await ensureRegistry();
4569
4594
  // Item table is fixed across iterations — only the dispatcher and
4570
4595
  // the per-iteration draw redraw on each loop tick.
4596
+ // Mirror every top-level `lazyclaw <subcommand>` here so the no-arg
4597
+ // launcher is a complete discovery surface. Commands that need
4598
+ // arguments (workflow runner, daemon, completion, import) route
4599
+ // through `help <cmd>` so the menu pick prints copy-pasteable usage
4600
+ // instead of erroring or blocking. Commands with a sensible default
4601
+ // ('list' / 'status') get dispatched directly.
4571
4602
  const items = [
4572
- { id: 'chat', label: 'Chat', desc: 'interactive REPL with the configured provider', argv: ['chat'] },
4573
- { id: 'agent', label: 'Agent', desc: 'one-shot prompt read text and exit', argv: ['agent'], promptForBody: true },
4574
- { id: 'dashboard', label: 'Dashboard', desc: 'open the lazyclaw web UI in your browser', argv: ['dashboard'] },
4575
- { id: 'setup', label: 'Setup', desc: 'multi-step provider / workspace / skill wizard', argv: ['setup'] },
4576
- { id: 'onboard', label: 'Onboard', desc: 'pick provider / model / api-key', argv: ['onboard'] },
4577
- { id: 'workspace', label: 'Workspace', desc: 'AGENTS.md / SOUL.md / TOOLS.md prompt bundles', argv: ['workspace', 'list'] },
4578
- { id: 'browse', label: 'Browse', desc: 'fetch a URL markdown', argv: ['browse'], promptForUrl: true },
4579
- { id: 'skills', label: 'Skills', desc: 'installed skill bundles', argv: ['skills', 'list'] },
4580
- { id: 'sessions', label: 'Sessions', desc: 'persisted chat sessions', argv: ['sessions', 'list'] },
4581
- { id: 'providers', label: 'Providers', desc: 'registered providers + reachability', argv: ['providers', 'list'] },
4582
- { id: 'cron', label: 'Cron', desc: 'recurring agent runs (launchd / crontab)', argv: ['cron', 'list'] },
4583
- { id: 'doctor', label: 'Doctor', desc: 'diagnostic — config, providers, workflows', argv: ['doctor'] },
4584
- { id: 'status', label: 'Status', desc: 'current provider / model / masked key', argv: ['status'] },
4585
- { id: 'help', label: 'Help', desc: 'one-line summary of every subcommand', argv: ['help'] },
4586
- { id: 'quit', label: 'Quit', desc: 'exit lazyclaw', argv: null },
4603
+ // Core interaction
4604
+ { id: 'chat', label: 'Chat', desc: 'interactive REPL with the configured provider', argv: ['chat'] },
4605
+ { id: 'agent', label: 'Agent', desc: 'one-shot prompt read text and exit', argv: ['agent'], promptForBody: true },
4606
+ { id: 'orchestrator', label: 'Orchestrator', desc: 'multi-agent dispatch planner + workers', argv: ['orchestrator', 'status'] },
4607
+ // UI & onboarding
4608
+ { id: 'dashboard', label: 'Dashboard', desc: 'open the lazyclaw web UI in your browser', argv: ['dashboard'] },
4609
+ { id: 'setup', label: 'Setup', desc: 'multi-step provider / workspace / skill wizard',argv: ['setup'] },
4610
+ { id: 'onboard', label: 'Onboard', desc: 'pick provider / model / api-key', argv: ['onboard'] },
4611
+ // Auth & config
4612
+ { id: 'providers', label: 'Providers', desc: 'registered providers + reachability', argv: ['providers', 'list'] },
4613
+ { id: 'auth', label: 'Auth', desc: 'multi-key rotation per provider', argv: ['help', 'auth'] },
4614
+ { id: 'config', label: 'Config', desc: 'cfg.json get/set/list/delete/path/edit', argv: ['config', 'list'] },
4615
+ { id: 'rates', label: 'Rates', desc: 'per-model input/output pricing cards', argv: ['rates', 'list'] },
4616
+ // Workspaces & assets
4617
+ { id: 'workspace', label: 'Workspace', desc: 'AGENTS.md / SOUL.md / TOOLS.md prompt bundles', argv: ['workspace', 'list'] },
4618
+ { id: 'skills', label: 'Skills', desc: 'installed skill bundles', argv: ['skills', 'list'] },
4619
+ { id: 'sessions', label: 'Sessions', desc: 'persisted chat sessions', argv: ['sessions', 'list'] },
4620
+ // Outbound & schedule
4621
+ { id: 'browse', label: 'Browse', desc: 'fetch a URL → markdown', argv: ['browse'], promptForUrl: true },
4622
+ { id: 'message', label: 'Message', desc: 'outbound webhook (Slack / Discord / generic)', argv: ['message', 'list'] },
4623
+ { id: 'cron', label: 'Cron', desc: 'recurring agent runs (launchd / crontab)', argv: ['cron', 'list'] },
4624
+ // Workflow runner (.mjs)
4625
+ { id: 'run', label: 'Run', desc: '.mjs workflow runner (needs session + file)', argv: ['help', 'run'] },
4626
+ { id: 'resume', label: 'Resume', desc: 're-enter a persisted workflow run', argv: ['help', 'resume'] },
4627
+ { id: 'inspect', label: 'Inspect', desc: 'list / drill into persisted workflow sessions', argv: ['inspect'] },
4628
+ { id: 'clear', label: 'Clear', desc: 'delete the state file for a session', argv: ['help', 'clear'] },
4629
+ { id: 'validate', label: 'Validate', desc: 'static-check a workflow.mjs (shape + deps)', argv: ['help', 'validate'] },
4630
+ { id: 'graph', label: 'Graph', desc: 'emit Mermaid graph TD / LR from a workflow', argv: ['help', 'graph'] },
4631
+ // Devices & process
4632
+ { id: 'pairing', label: 'Pairing', desc: 'sender allowlist for the messaging surface', argv: ['pairing', 'list'] },
4633
+ { id: 'nodes', label: 'Nodes', desc: 'companion device registry', argv: ['nodes', 'list'] },
4634
+ { id: 'daemon', label: 'Daemon', desc: 'localhost HTTP daemon (blocking — see usage)', argv: ['help', 'daemon'] },
4635
+ // Bundle
4636
+ { id: 'export', label: 'Export', desc: 'redacted config bundle → stdout', argv: ['export'] },
4637
+ { id: 'import', label: 'Import', desc: 'restore from a bundle on stdin', argv: ['help', 'import'] },
4638
+ // Tools
4639
+ { id: 'completion', label: 'Completion', desc: 'shell completion (bash | zsh)', argv: ['help', 'completion'] },
4640
+ { id: 'version', label: 'Version', desc: 'lazyclaw version + Node + platform', argv: ['version'] },
4641
+ // Diagnostics
4642
+ { id: 'doctor', label: 'Doctor', desc: 'diagnostic — config, providers, workflows', argv: ['doctor'] },
4643
+ { id: 'status', label: 'Status', desc: 'current provider / model / masked key', argv: ['status'] },
4644
+ // Meta
4645
+ { id: 'help', label: 'Help', desc: 'one-line summary of every subcommand', argv: ['help'] },
4646
+ { id: 'quit', label: 'Quit', desc: 'exit lazyclaw', argv: null },
4587
4647
  ];
4588
4648
 
4589
4649
  const accent = (s) => `\x1b[38;5;208m${s}\x1b[0m`;
@@ -4744,8 +4804,13 @@ async function cmdLauncher() {
4744
4804
  });
4745
4805
 
4746
4806
  if (!picked || picked.id === 'quit' || !picked.argv) {
4747
- // Plain return so main() can exit naturally.
4748
- return;
4807
+ // v3.99.28 break out of the while loop, fall through the
4808
+ // finally (stdin cleanup), then hit the explicit process.exit(0)
4809
+ // at the function tail. Previously this was `return`, which
4810
+ // jumped over the explicit exit and left dangling timers /
4811
+ // sockets (ollama probe, registry retry, etc.) keeping the
4812
+ // event loop alive — visible to the user as "Quit didn't quit."
4813
+ break;
4749
4814
  }
4750
4815
 
4751
4816
  // Two menu items need a follow-up question before they can run:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lazyclaw",
3
- "version": "3.99.26",
3
+ "version": "3.99.28",
4
4
  "description": "Lazy, elegant terminal CLI for chatting with Claude / OpenAI / Gemini / Ollama and orchestrating multi-step LLM workflows. Banner-on-launch, slash-command ghost autocomplete, persistent sessions, local HTTP gateway.",
5
5
  "keywords": [
6
6
  "claude",