lazyclaw 5.0.3 → 5.0.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/cli.mjs CHANGED
@@ -1634,28 +1634,31 @@ function _renderBanner(version) {
1634
1634
  ];
1635
1635
  }
1636
1636
 
1637
- // v5 wordmark banner — ANSI Shadow style "LAZYCLAW" rendered in box-drawing
1638
- // + half-block glyphs at 67 cols x 6 rows. Wider than the chat splash gutter
1639
- // because the no-arg launcher has the full terminal width to work with.
1640
- // Single-tone orange. Opt out with LAZYCLAW_LEGACY_MENU=1 to fall back to
1641
- // the boxed figlet variant above.
1642
- const _V5_WORDMARK = [
1643
- " ██╗ █████╗ ███████╗██╗ ██╗ ██████╗██╗ █████╗ ██╗ ██╗",
1644
- " ██║ ██╔══██╗╚══███╔╝╚██╗ ██╔╝██╔════╝██║ ██╔══██╗██║ ██║",
1645
- " ██║ ███████║ ███╔╝ ╚████╔╝ ██║ ██║ ███████║██║ █╗ ██║",
1646
- " ██║ ██╔══██║ ███╔╝ ╚██╔╝ ██║ ██║ ██╔══██║██║███╗██║",
1647
- " ███████╗██║ ██║███████╗ ██║ ╚██████╗███████╗██║ ██║╚███╔███╔╝",
1648
- " ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝ ",
1649
- ];
1637
+ // v5 hero banner — the chafa-rendered braille sloth shared with the chat
1638
+ // splash (tui/banner.generated.mjs). Centered in TERM_WIDTH at runtime so
1639
+ // the launcher and chat splash present identical visual identity.
1640
+ // Opt out with LAZYCLAW_LEGACY_MENU=1 to fall back to the v4 figlet box.
1641
+ let _slothBannerRowsCache = null;
1642
+ async function _loadSlothBanner() {
1643
+ if (_slothBannerRowsCache !== null) return _slothBannerRowsCache;
1644
+ try {
1645
+ const { banner } = await import('./tui/banner.generated.mjs');
1646
+ _slothBannerRowsCache = banner;
1647
+ } catch {
1648
+ _slothBannerRowsCache = null;
1649
+ }
1650
+ return _slothBannerRowsCache;
1651
+ }
1650
1652
 
1651
- function _renderV5Banner(version) {
1653
+ async function _renderV5Banner(version) {
1654
+ const b = await _loadSlothBanner();
1655
+ if (!b) return _renderBanner(version); // missing tarball asset → v4 figlet
1656
+ const TERM_W = Math.max(80, process.stdout.columns || 80);
1657
+ const pad = Math.max(0, Math.floor((TERM_W - b.width) / 2));
1658
+ const rows = b.rows.map(r => _orange(' '.repeat(pad) + r));
1652
1659
  const v = String(version || '?.?.?');
1653
- const wmCols = 69; // width of _V5_WORDMARK rows including leading " "
1654
- const versionLine = ` v${v}`;
1655
- return [
1656
- ..._V5_WORDMARK.map(_orange),
1657
- _orange(versionLine.padEnd(wmCols)),
1658
- ];
1660
+ rows.push(_orange((' '.repeat(pad) + `lazyclaw v${v}`).padEnd(TERM_W)));
1661
+ return rows;
1659
1662
  }
1660
1663
 
1661
1664
  function _printChatBanner(activeProvName, activeModel, version) {
@@ -6746,7 +6749,7 @@ async function cmdLauncher() {
6746
6749
  const useLegacyBanner = !!process.env.LAZYCLAW_LEGACY_MENU;
6747
6750
  const bannerRowsCached = useLegacyBanner
6748
6751
  ? _renderBanner(readVersionFromRepo())
6749
- : _renderV5Banner(readVersionFromRepo());
6752
+ : await _renderV5Banner(readVersionFromRepo());
6750
6753
  const draw = () => {
6751
6754
  process.stdout.write('\x1b[?25l\x1b[2J\x1b[H'); // hide cursor + clear
6752
6755
  bannerRowsCached.forEach((l) => process.stdout.write(l + '\n'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lazyclaw",
3
- "version": "5.0.3",
3
+ "version": "5.0.4",
4
4
  "description": "Lazy, elegant terminal CLI for chatting with Claude / OpenAI / Gemini / Ollama, orchestrating multi-step LLM workflows, and running multi-agent Slack teams with cross-task memory. Banner-on-launch, slash-command ghost autocomplete, persistent sessions, local HTTP gateway.",
5
5
  "keywords": [
6
6
  "claude",
@@ -1,29 +1,46 @@
1
- // Hand-crafted 24x12 sleepy sloth icon — replaces the chafa-generated
2
- // placeholder. Source image (docs/assets/sleepy-sloth-source.png) is a
3
- // circle silhouette with no internal detail, so chafa --symbols=braille
4
- // renders an oval-of-dots at this resolution. A hand drawing reads as
5
- // a creature; the rasterised conversion did not.
6
- //
7
- // All rows are exactly 24 East-Asian-Width cells. Render width matches
8
- // the splash gutter (GUTTER_WIDTH = 24 in tui/splash.mjs). Don't change
9
- // row widths without updating tests/phaseC-build-splash.test.mjs (width
10
- // strict-equals 24, height <= 12).
1
+ // Auto-generated by `chafa --format=symbols --symbols=braille --invert
2
+ // --size=48x35 --fg-only docs/assets/sleepy-sloth-source.png`. Source:
3
+ // Pexels three-toed sloth by Tomas Malik (free license). Style is the
4
+ // dense inverted braille that user signed off on in v5.0.4 review.
11
5
  export const banner = {
12
6
  rows: [
13
- " ╭──╮ ╭──╮ ",
14
- " ╭╯ ╰────╯ ╰╮ ",
15
- " │ ─ ─ │ ",
16
- " │ │ ",
17
- " │ ‿‿‿ │ ",
18
- " ╰╮ ╭╯ ",
19
- " ╰─┬─────┬─╯ ",
20
- " │ │ ",
21
- " ╱│ z │╲ ",
22
- " ╱ │ z │ ╲ ",
23
- " ✦ ╰─────╯ ✦ ",
24
- " lazyclaw "
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
+ "⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢽⣟⡦⢂⣀⣀⣠⣾⣿⣿⣿⣿⣿⣽⣿⣿⣿⣿⣿⣿⣿⣿⣿",
37
+ "⣿⣿⣿⣿⣿⣿⣿⣿⣯⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣷⣂⡽⣵⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣟⣟⣿⣿⣿⣿⣿",
38
+ "⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢹⣻⢻⡿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⣿⣿⡿⣿",
39
+ "⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⣿⣿⣾⣷⣶⣯⣭⣛⣻⠿⣿⣷⣿⣿⣿⣿⣿⣿⣿⡿⠿⡿⣟",
40
+ "⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⣶⣶⣶⣷⣶⣿⣶⣶⣾⣿⣿⣿",
41
+ "⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⣷⣟⣀⣤⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿",
25
42
  ],
26
- width: 24,
27
- height: 12,
43
+ width: 47,
44
+ height: 35,
28
45
  fg: "#FFB347"
29
46
  };
package/tui/splash.mjs CHANGED
@@ -1,21 +1,21 @@
1
- // tui/splash.mjs — two-column launch splash (spec §5.1).
1
+ // tui/splash.mjs — hero-banner launch splash.
2
2
  //
3
3
  // Public surface:
4
4
  // - <Splash {...props} /> ink component for live REPL mount
5
5
  // - renderSplashToString(props) pure string builder used by tests
6
6
  // and by the non-TTY path.
7
7
  //
8
- // Layout: 24-cell sloth gutter (cols 0-23) | 2-cell separator (24-25)
9
- // | 52-cell right column (cols 26-77) | 2-cell right padding.
10
- // Footer: exactly 4 lines, blank row separates body from footer.
8
+ // Layout: 47-cell-wide hero banner centered in 80-col terminal,
9
+ // followed by a two-column tools|skills section (36 cells each)
10
+ // and a 4-line footer.
11
11
  import React from 'react';
12
12
  import { Box, Text } from 'ink';
13
13
  import stringWidth from 'string-width';
14
14
  import { theme } from './theme.mjs';
15
15
  import { banner } from './banner.generated.mjs';
16
16
 
17
- const RIGHT_COL_WIDTH = 52;
18
- const GUTTER_WIDTH = 24;
17
+ const TERM_WIDTH = 80;
18
+ const COL_WIDTH = 36; // 2 (lpad) + 36 + 2 (mid) + 36 + 2 (rpad) = 78 ≈ 80 with slack
19
19
 
20
20
  function fit(text, max) {
21
21
  if (stringWidth(text) <= max) return text.padEnd(max);
@@ -48,29 +48,26 @@ function skillRow({ group, names }) {
48
48
  return `${fit(group, 9)}${tail}${more}`;
49
49
  }
50
50
 
51
- function buildBody(props) {
52
- const { tools = [], skills = [] } = props;
53
- const right = [];
54
- right.push('Available Tools');
55
- right.push(''.repeat(45));
56
- for (const t of tools.slice(0, 8)) right.push(toolRow(t));
57
- if (tools.length > 8) right.push(`... and ${tools.length - 8} more tool groups`);
58
- right.push('');
59
- right.push('Available Skills');
60
- right.push('─'.repeat(45));
61
- for (const s of skills.slice(0, 8)) right.push(skillRow(s));
62
- if (skills.length > 8) right.push(`... and ${skills.length - 8} more skill groups`);
63
-
64
- const left = banner.rows.slice();
65
- while (left.length < right.length) left.push('');
66
- while (right.length < left.length) right.push('');
51
+ function buildBanner() {
52
+ // Center the hero banner inside TERM_WIDTH. banner.width may be < TERM_WIDTH;
53
+ // pad each side equally so it floats over the centerline.
54
+ const pad = Math.max(0, Math.floor((TERM_WIDTH - banner.width) / 2));
55
+ return banner.rows.map(row => ' '.repeat(pad) + row);
56
+ }
67
57
 
58
+ function buildToolsAndSkills(props) {
59
+ const { tools = [], skills = [] } = props;
60
+ const SECT_WIDTH = TERM_WIDTH - 4;
68
61
  const lines = [];
69
- for (let i = 0; i < left.length; i++) {
70
- const lhs = fit(left[i], GUTTER_WIDTH);
71
- const rhs = fit(right[i], RIGHT_COL_WIDTH);
72
- lines.push(` ${lhs} ${rhs}`);
73
- }
62
+ lines.push(` ${fit('Available Tools', SECT_WIDTH)}`);
63
+ lines.push(` ${'─'.repeat(SECT_WIDTH)}`);
64
+ for (const t of tools.slice(0, 8)) lines.push(` ${fit(toolRow(t), SECT_WIDTH)}`);
65
+ if (tools.length > 8) lines.push(` ${fit(`... and ${tools.length - 8} more tool groups`, SECT_WIDTH)}`);
66
+ lines.push('');
67
+ lines.push(` ${fit('Available Skills', SECT_WIDTH)}`);
68
+ lines.push(` ${'─'.repeat(SECT_WIDTH)}`);
69
+ for (const s of skills.slice(0, 8)) lines.push(` ${fit(skillRow(s), SECT_WIDTH)}`);
70
+ if (skills.length > 8) lines.push(` ${fit(`... and ${skills.length - 8} more skill groups`, SECT_WIDTH)}`);
74
71
  return lines;
75
72
  }
76
73
 
@@ -89,10 +86,14 @@ function buildFooter(props) {
89
86
  }
90
87
 
91
88
  export function renderSplashToString(props, { columns = 80 } = {}) {
92
- void columns; // currently fixed-width at 80; <72 fallback is in cli.mjs
93
- const body = buildBody(props);
94
- const footer = buildFooter(props);
95
- return [...body, '', ...footer].join('\n');
89
+ void columns; // splash is fixed-width 80
90
+ return [
91
+ ...buildBanner(),
92
+ '',
93
+ ...buildToolsAndSkills(props),
94
+ '',
95
+ ...buildFooter(props),
96
+ ].join('\n');
96
97
  }
97
98
 
98
99
  export function Splash(props) {