lazyclaw 3.99.25 → 3.99.27

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 +198 -66
  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,40 +1469,111 @@ 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
- function _renderBanner(version) {
1472
- // Rebuilt from the Claude Design handoff bundle (v0.1 mascot sheet):
1473
- // Claude's asterisk star wearing a crab/crustacean helmet with two
1474
- // antenna-claws. 10-line "big ASCII" form fits a terminal banner
1475
- // without zoom and reads at any monospace font that has the
1476
- // box-drawing + geometric-shape glyphs.
1477
- //
1478
- // Palette (CLAUDE ORIGINAL): helmet body #c33d2a, helmet shadow
1479
- // #7a1f15, star body #d97757, star shadow #a04f32, eyes ink
1480
- // #c7bca6 / muted slits. Truecolor ANSI; degrades gracefully on
1481
- // terminals that ignore it.
1482
- const helmet = (s) => `\x1b[38;2;195;61;42m${s}\x1b[0m`; // #c33d2a
1483
- const star = (s) => `\x1b[38;2;217;119;87m${s}\x1b[0m`; // #d97757 — Claude orange
1484
- const ink = (s) => `\x1b[38;2;241;234;217m${s}\x1b[0m`; // #f1ead9 paper-ink
1485
- const dim = (s) => `\x1b[2m${s}\x1b[0m`;
1486
- const muted = (s) => `\x1b[38;2;122;110;95m${s}\x1b[0m`; // #7a6e5f
1472
+ // v3.99.26 — canonical Big ASCII mascot from the v0.1 Claude Design
1473
+ // handoff bundle. 12 rows. Claude's square body + lobster pincers (◂▸)
1474
+ // + helmet (╔═╗) + asterisk-star tail. Sleepy slit eyes (│ │) by
1475
+ // defaultname says lazyclaw.
1476
+ //
1477
+ // State variants live in _renderMascot(state). Big variant = banner.
1478
+ // Inline 3-row Tiny variant lives in _renderMascotTiny(state).
1479
+ const _MASCOT_BIG = {
1480
+ idle: [
1481
+ ' ◂▸ ◂▸ ',
1482
+ ' │ │ ',
1483
+ ' │ │ ',
1484
+ '╔═════════════╗',
1485
+ '║ ║',
1486
+ '╚═════════════╝',
1487
+ '┌─────────────┐',
1488
+ '│ │ │ │',
1489
+ '┤ │ │ ├',
1490
+ '└─────────────┘',
1491
+ ' ┃ ┃ ',
1492
+ ' ┃ ┃ ',
1493
+ ],
1494
+ working: [
1495
+ ' ◂▸ ◂▸ ',
1496
+ ' │ ··· │ ',
1497
+ ' │ │ ',
1498
+ '╔═════════════╗',
1499
+ '║ * ║',
1500
+ '╚═════════════╝',
1501
+ '┌─────────────┐',
1502
+ '│ · · │',
1503
+ '┤ ├',
1504
+ '└─────────────┘',
1505
+ ' ┃ ┃ ',
1506
+ ' ┃ ┃ ',
1507
+ ],
1508
+ done: [
1509
+ '✦ ◂▸ ◂▸ ✦',
1510
+ ' │ │ ',
1511
+ ' │ │ ',
1512
+ '╔═════════════╗',
1513
+ '║ ║',
1514
+ '╚═════════════╝',
1515
+ '┌─────────────┐',
1516
+ '│ ^ ^ │',
1517
+ '┤ ‿‿‿ ├',
1518
+ '└─────────────┘',
1519
+ ' ┃ ┃ ',
1520
+ ' ┃ ┃ ',
1521
+ ],
1522
+ error: [
1523
+ ' ▾ ▾ ',
1524
+ ' │ │ ',
1525
+ ' │ │ ',
1526
+ '╔═════════════╗',
1527
+ '║ ~ ║',
1528
+ '╚═════════════╝',
1529
+ '┌─────────────┐',
1530
+ '│ × × │',
1531
+ '┤ ⏜ ├',
1532
+ '└─────────────┘',
1533
+ ' ┃ ┃ ',
1534
+ ' ┃ ┃ ',
1535
+ ],
1536
+ };
1537
+ const _MASCOT_TINY = {
1538
+ idle: '◂▸ ◂▸\n[│ │]\n ┃ ┃ ',
1539
+ working: '◂▸ ◂▸\n[· ·] ···\n ┃ ┃ ',
1540
+ done: '◂▸ ◂▸\n[^ ^] ✓\n ┃ ┃ ',
1541
+ error: '▾ ▾ \n[× ×] !\n ┃ ┃ ',
1542
+ };
1543
+
1544
+ // Ink helpers. State picks a primary colour; the banner caller layers
1545
+ // a secondary "wordmark" right column.
1546
+ function _mascotInkers(state) {
1547
+ const helmet = (s) => `\x1b[38;2;195;61;42m${s}\x1b[0m`;
1548
+ const helmetDim = (s) => `\x1b[38;2;122;31;21m${s}\x1b[0m`;
1549
+ const star = (s) => `\x1b[38;2;217;119;87m${s}\x1b[0m`;
1550
+ const ok = (s) => `\x1b[38;2;111;185;143m${s}\x1b[0m`;
1551
+ const err = (s) => `\x1b[38;2;230;57;70m${s}\x1b[0m`;
1552
+ if (state === 'done') return (s) => ok(s);
1553
+ if (state === 'error') return (s) => err(s);
1554
+ if (state === 'working') return (s) => helmet(s);
1555
+ return (s) => helmet(s);
1556
+ }
1557
+
1558
+ function _renderMascot(state) {
1559
+ const rows = _MASCOT_BIG[state] || _MASCOT_BIG.idle;
1560
+ const ink = _mascotInkers(state);
1561
+ return rows.map((r) => ink(r));
1562
+ }
1487
1563
 
1564
+ // Tiny inline mascot — picked up by chat/agent helpers when they want
1565
+ // to flash a one-line status without re-rendering the whole banner.
1566
+ // Returns a string; callers add their own newline.
1567
+ function _renderMascotTiny(state) {
1568
+ const ink = _mascotInkers(state);
1569
+ return ink((_MASCOT_TINY[state] || _MASCOT_TINY.idle));
1570
+ }
1571
+
1572
+ function _renderBanner(version) {
1573
+ const ink = (s) => `\x1b[38;2;241;234;217m${s}\x1b[0m`;
1574
+ const dim = (s) => `\x1b[2m${s}\x1b[0m`;
1488
1575
  const v = String(version || '?.?.?');
1489
- // Left column — sprite. Right column — wordmark + tagline, aligned
1490
- // to the helmet's eye-row / brim. The trailing spaces preserve the
1491
- // grid so any caller padding the lines (or copying them) doesn't
1492
- // get ragged edges.
1493
- const left = [
1494
- ` ${helmet('▲')} ${helmet('▲')} `,
1495
- ` ${helmet('│')} ${helmet('│')} `,
1496
- ` ${helmet('╔══════════╗')} `,
1497
- ` ${helmet('║')} ${helmet('║')} `,
1498
- ` ${helmet('║')} ${muted('──')} ${muted('──')} ${helmet('║')} `,
1499
- ` ${helmet('║')} ${helmet('║')} `,
1500
- ` ${helmet('╚══════════╝')} `,
1501
- ` ${star('✦')} `,
1502
- ` ${star('╱|╲')} `,
1503
- ` ${star('╱ | ╲')} `,
1504
- ];
1576
+ const left = _renderMascot('idle');
1505
1577
  const right = [
1506
1578
  '',
1507
1579
  '',
@@ -1513,6 +1585,8 @@ function _renderBanner(version) {
1513
1585
  '',
1514
1586
  '',
1515
1587
  '',
1588
+ '',
1589
+ '',
1516
1590
  ];
1517
1591
  return left.map((l, i) => ' ' + l + (right[i] || ''));
1518
1592
  }
@@ -4455,25 +4529,48 @@ async function _dispatchMenuChoice(argv) {
4455
4529
  process.exit = (code) => { throw new _DispatchExit(code); };
4456
4530
  try {
4457
4531
  switch (sub) {
4458
- case 'chat': return await cmdChat({});
4459
- case 'agent': return await cmdAgent(rest[0] || '-', {});
4460
- case 'onboard': return await cmdOnboard({});
4461
- case 'setup': return await cmdSetup(undefined, rest, {});
4462
- case 'workspace': return await cmdWorkspace(rest[0], rest.slice(1), {});
4463
- case 'browse': return await cmdBrowse(rest[0], {});
4464
- case 'skills': return await cmdSkills(rest[0], rest.slice(1), {});
4465
- case 'sessions': return await cmdSessions(rest[0], rest.slice(1), {});
4466
- case 'providers': return await cmdProviders(rest[0], rest.slice(1), {});
4467
- case 'cron': return await cmdCron(rest[0], rest.slice(1), {});
4468
- case 'auth': return await cmdAuth(rest[0], rest.slice(1), {});
4469
- case 'pairing': return await cmdPairing(rest[0], rest.slice(1), {});
4470
- case 'nodes': return await cmdNodes(rest[0], rest.slice(1), {});
4471
- case 'message': return await cmdMessage(rest[0], rest.slice(1), {});
4472
- case 'doctor': return await cmdDoctor();
4473
- case 'status': return await cmdStatus();
4474
- case 'help': return cmdHelp();
4475
- case 'dashboard': return await cmdDashboard({});
4476
- default: throw new Error(`unknown menu choice: ${sub}`);
4532
+ case 'chat': return await cmdChat({});
4533
+ case 'agent': return await cmdAgent(rest[0] || '-', {});
4534
+ case 'onboard': return await cmdOnboard({});
4535
+ case 'setup': return await cmdSetup(undefined, rest, {});
4536
+ case 'workspace': return await cmdWorkspace(rest[0], rest.slice(1), {});
4537
+ case 'browse': return await cmdBrowse(rest[0], {});
4538
+ case 'skills': return await cmdSkills(rest[0], rest.slice(1), {});
4539
+ case 'sessions': return await cmdSessions(rest[0], rest.slice(1), {});
4540
+ case 'providers': return await cmdProviders(rest[0], rest.slice(1), {});
4541
+ case 'cron': return await cmdCron(rest[0], rest.slice(1), {});
4542
+ case 'auth': return await cmdAuth(rest[0], rest.slice(1), {});
4543
+ case 'pairing': return await cmdPairing(rest[0], rest.slice(1), {});
4544
+ case 'nodes': return await cmdNodes(rest[0], rest.slice(1), {});
4545
+ case 'message': return await cmdMessage(rest[0], rest.slice(1), {});
4546
+ case 'doctor': return await cmdDoctor();
4547
+ case 'status': return await cmdStatus();
4548
+ // v3.99.27 — fill the rest of the lazyclaw <subcommand> surface
4549
+ // so the no-arg launcher mirrors every entry in SUBCOMMANDS.
4550
+ case 'orchestrator': return await cmdOrchestrator(rest[0], rest.slice(1), {});
4551
+ case 'rates': return await cmdRates(rest[0], rest.slice(1), {});
4552
+ case 'config': {
4553
+ // Mirror the main switch's tiny dispatcher.
4554
+ const csub = rest[0];
4555
+ if (csub === 'list' || csub === undefined) return cmdConfigGet(undefined);
4556
+ if (csub === 'get') return cmdConfigGet(rest[1]);
4557
+ if (csub === 'set') return cmdConfigSet(rest[1], rest.slice(2).join(' '));
4558
+ if (csub === 'path') { process.stdout.write(configPath() + '\n'); return; }
4559
+ if (csub === 'edit') return await cmdConfigEdit();
4560
+ if (csub === 'validate') return await cmdConfigValidate();
4561
+ process.stderr.write('Usage: lazyclaw config <get|set|list|delete|path|edit|validate>\n');
4562
+ return;
4563
+ }
4564
+ case 'inspect': return await cmdInspect(rest[0], {});
4565
+ case 'export': return await cmdExport({});
4566
+ case 'version': return await cmdVersion();
4567
+ // help <cmd> is the safe fallback for commands that need real
4568
+ // arguments (run / resume / clear / validate / graph / daemon /
4569
+ // import / completion). Print the usage so the user can re-launch
4570
+ // with proper flags — the menu stays alive.
4571
+ case 'help': return cmdHelp(rest[0]);
4572
+ case 'dashboard': return await cmdDashboard({});
4573
+ default: throw new Error(`unknown menu choice: ${sub}`);
4477
4574
  }
4478
4575
  } catch (e) {
4479
4576
  if (e instanceof _DispatchExit) {
@@ -4495,22 +4592,57 @@ async function cmdLauncher() {
4495
4592
  await ensureRegistry();
4496
4593
  // Item table is fixed across iterations — only the dispatcher and
4497
4594
  // the per-iteration draw redraw on each loop tick.
4595
+ // Mirror every top-level `lazyclaw <subcommand>` here so the no-arg
4596
+ // launcher is a complete discovery surface. Commands that need
4597
+ // arguments (workflow runner, daemon, completion, import) route
4598
+ // through `help <cmd>` so the menu pick prints copy-pasteable usage
4599
+ // instead of erroring or blocking. Commands with a sensible default
4600
+ // ('list' / 'status') get dispatched directly.
4498
4601
  const items = [
4499
- { id: 'chat', label: 'Chat', desc: 'interactive REPL with the configured provider', argv: ['chat'] },
4500
- { id: 'agent', label: 'Agent', desc: 'one-shot prompt read text and exit', argv: ['agent'], promptForBody: true },
4501
- { id: 'dashboard', label: 'Dashboard', desc: 'open the lazyclaw web UI in your browser', argv: ['dashboard'] },
4502
- { id: 'setup', label: 'Setup', desc: 'multi-step provider / workspace / skill wizard', argv: ['setup'] },
4503
- { id: 'onboard', label: 'Onboard', desc: 'pick provider / model / api-key', argv: ['onboard'] },
4504
- { id: 'workspace', label: 'Workspace', desc: 'AGENTS.md / SOUL.md / TOOLS.md prompt bundles', argv: ['workspace', 'list'] },
4505
- { id: 'browse', label: 'Browse', desc: 'fetch a URL markdown', argv: ['browse'], promptForUrl: true },
4506
- { id: 'skills', label: 'Skills', desc: 'installed skill bundles', argv: ['skills', 'list'] },
4507
- { id: 'sessions', label: 'Sessions', desc: 'persisted chat sessions', argv: ['sessions', 'list'] },
4508
- { id: 'providers', label: 'Providers', desc: 'registered providers + reachability', argv: ['providers', 'list'] },
4509
- { id: 'cron', label: 'Cron', desc: 'recurring agent runs (launchd / crontab)', argv: ['cron', 'list'] },
4510
- { id: 'doctor', label: 'Doctor', desc: 'diagnostic — config, providers, workflows', argv: ['doctor'] },
4511
- { id: 'status', label: 'Status', desc: 'current provider / model / masked key', argv: ['status'] },
4512
- { id: 'help', label: 'Help', desc: 'one-line summary of every subcommand', argv: ['help'] },
4513
- { id: 'quit', label: 'Quit', desc: 'exit lazyclaw', argv: null },
4602
+ // Core interaction
4603
+ { id: 'chat', label: 'Chat', desc: 'interactive REPL with the configured provider', argv: ['chat'] },
4604
+ { id: 'agent', label: 'Agent', desc: 'one-shot prompt read text and exit', argv: ['agent'], promptForBody: true },
4605
+ { id: 'orchestrator', label: 'Orchestrator', desc: 'multi-agent dispatch planner + workers', argv: ['orchestrator', 'status'] },
4606
+ // UI & onboarding
4607
+ { id: 'dashboard', label: 'Dashboard', desc: 'open the lazyclaw web UI in your browser', argv: ['dashboard'] },
4608
+ { id: 'setup', label: 'Setup', desc: 'multi-step provider / workspace / skill wizard',argv: ['setup'] },
4609
+ { id: 'onboard', label: 'Onboard', desc: 'pick provider / model / api-key', argv: ['onboard'] },
4610
+ // Auth & config
4611
+ { id: 'providers', label: 'Providers', desc: 'registered providers + reachability', argv: ['providers', 'list'] },
4612
+ { id: 'auth', label: 'Auth', desc: 'multi-key rotation per provider', argv: ['help', 'auth'] },
4613
+ { id: 'config', label: 'Config', desc: 'cfg.json get/set/list/delete/path/edit', argv: ['config', 'list'] },
4614
+ { id: 'rates', label: 'Rates', desc: 'per-model input/output pricing cards', argv: ['rates', 'list'] },
4615
+ // Workspaces & assets
4616
+ { id: 'workspace', label: 'Workspace', desc: 'AGENTS.md / SOUL.md / TOOLS.md prompt bundles', argv: ['workspace', 'list'] },
4617
+ { id: 'skills', label: 'Skills', desc: 'installed skill bundles', argv: ['skills', 'list'] },
4618
+ { id: 'sessions', label: 'Sessions', desc: 'persisted chat sessions', argv: ['sessions', 'list'] },
4619
+ // Outbound & schedule
4620
+ { id: 'browse', label: 'Browse', desc: 'fetch a URL → markdown', argv: ['browse'], promptForUrl: true },
4621
+ { id: 'message', label: 'Message', desc: 'outbound webhook (Slack / Discord / generic)', argv: ['message', 'list'] },
4622
+ { id: 'cron', label: 'Cron', desc: 'recurring agent runs (launchd / crontab)', argv: ['cron', 'list'] },
4623
+ // Workflow runner (.mjs)
4624
+ { id: 'run', label: 'Run', desc: '.mjs workflow runner (needs session + file)', argv: ['help', 'run'] },
4625
+ { id: 'resume', label: 'Resume', desc: 're-enter a persisted workflow run', argv: ['help', 'resume'] },
4626
+ { id: 'inspect', label: 'Inspect', desc: 'list / drill into persisted workflow sessions', argv: ['inspect'] },
4627
+ { id: 'clear', label: 'Clear', desc: 'delete the state file for a session', argv: ['help', 'clear'] },
4628
+ { id: 'validate', label: 'Validate', desc: 'static-check a workflow.mjs (shape + deps)', argv: ['help', 'validate'] },
4629
+ { id: 'graph', label: 'Graph', desc: 'emit Mermaid graph TD / LR from a workflow', argv: ['help', 'graph'] },
4630
+ // Devices & process
4631
+ { id: 'pairing', label: 'Pairing', desc: 'sender allowlist for the messaging surface', argv: ['pairing', 'list'] },
4632
+ { id: 'nodes', label: 'Nodes', desc: 'companion device registry', argv: ['nodes', 'list'] },
4633
+ { id: 'daemon', label: 'Daemon', desc: 'localhost HTTP daemon (blocking — see usage)', argv: ['help', 'daemon'] },
4634
+ // Bundle
4635
+ { id: 'export', label: 'Export', desc: 'redacted config bundle → stdout', argv: ['export'] },
4636
+ { id: 'import', label: 'Import', desc: 'restore from a bundle on stdin', argv: ['help', 'import'] },
4637
+ // Tools
4638
+ { id: 'completion', label: 'Completion', desc: 'shell completion (bash | zsh)', argv: ['help', 'completion'] },
4639
+ { id: 'version', label: 'Version', desc: 'lazyclaw version + Node + platform', argv: ['version'] },
4640
+ // Diagnostics
4641
+ { id: 'doctor', label: 'Doctor', desc: 'diagnostic — config, providers, workflows', argv: ['doctor'] },
4642
+ { id: 'status', label: 'Status', desc: 'current provider / model / masked key', argv: ['status'] },
4643
+ // Meta
4644
+ { id: 'help', label: 'Help', desc: 'one-line summary of every subcommand', argv: ['help'] },
4645
+ { id: 'quit', label: 'Quit', desc: 'exit lazyclaw', argv: null },
4514
4646
  ];
4515
4647
 
4516
4648
  const accent = (s) => `\x1b[38;5;208m${s}\x1b[0m`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lazyclaw",
3
- "version": "3.99.25",
3
+ "version": "3.99.27",
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",