lazyclaw 5.0.4 → 5.0.6
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 +30 -22
- package/package.json +1 -1
- package/tui/banner.generated.mjs +40 -40
- package/tui/splash.mjs +76 -55
- package/tui/wordmark.mjs +16 -0
package/cli.mjs
CHANGED
|
@@ -1011,6 +1011,8 @@ const SUBCOMMANDS = [
|
|
|
1011
1011
|
'agent', 'team', 'task',
|
|
1012
1012
|
// v5.0 Phase G — persona compose + cross-tool import (spec §9, §10)
|
|
1013
1013
|
'personality', 'migrate', 'hermes', 'openclaw',
|
|
1014
|
+
// v5.0.6 — arrow-key launcher menu (was the no-arg default in v5.0.5-)
|
|
1015
|
+
'menu',
|
|
1014
1016
|
// v5.0 Phase H1 — trajectory exporter (spec §2.7)
|
|
1015
1017
|
'trajectories',
|
|
1016
1018
|
];
|
|
@@ -1634,30 +1636,32 @@ function _renderBanner(version) {
|
|
|
1634
1636
|
];
|
|
1635
1637
|
}
|
|
1636
1638
|
|
|
1637
|
-
// v5 hero banner —
|
|
1638
|
-
//
|
|
1639
|
-
//
|
|
1639
|
+
// v5 hero banner — ANSI Shadow LAZYCLAW wordmark stacked on top of the
|
|
1640
|
+
// braille sloth (tui/banner.generated.mjs + tui/wordmark.mjs). Left-aligned
|
|
1641
|
+
// with a 2-cell margin so wide terminals don't push the art to the right.
|
|
1640
1642
|
// Opt out with LAZYCLAW_LEGACY_MENU=1 to fall back to the v4 figlet box.
|
|
1641
|
-
let
|
|
1642
|
-
async function
|
|
1643
|
-
if (
|
|
1643
|
+
let _bannerAssetsCache = null;
|
|
1644
|
+
async function _loadBannerAssets() {
|
|
1645
|
+
if (_bannerAssetsCache !== null) return _bannerAssetsCache;
|
|
1644
1646
|
try {
|
|
1645
1647
|
const { banner } = await import('./tui/banner.generated.mjs');
|
|
1646
|
-
|
|
1648
|
+
const { wordmark } = await import('./tui/wordmark.mjs');
|
|
1649
|
+
_bannerAssetsCache = { banner, wordmark };
|
|
1647
1650
|
} catch {
|
|
1648
|
-
|
|
1651
|
+
_bannerAssetsCache = null;
|
|
1649
1652
|
}
|
|
1650
|
-
return
|
|
1653
|
+
return _bannerAssetsCache;
|
|
1651
1654
|
}
|
|
1652
1655
|
|
|
1653
1656
|
async function _renderV5Banner(version) {
|
|
1654
|
-
const
|
|
1655
|
-
if (!
|
|
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));
|
|
1657
|
+
const a = await _loadBannerAssets();
|
|
1658
|
+
if (!a) return _renderBanner(version); // missing tarball asset → v4 figlet
|
|
1659
1659
|
const v = String(version || '?.?.?');
|
|
1660
|
-
rows
|
|
1660
|
+
const rows = [];
|
|
1661
|
+
for (const r of a.wordmark.rows) rows.push(_orange(' ' + r));
|
|
1662
|
+
rows.push('');
|
|
1663
|
+
for (const r of a.banner.rows) rows.push(_orange(' ' + r));
|
|
1664
|
+
rows.push(_orange(' ' + `lazyclaw v${v}`));
|
|
1661
1665
|
return rows;
|
|
1662
1666
|
}
|
|
1663
1667
|
|
|
@@ -6944,18 +6948,17 @@ async function main() {
|
|
|
6944
6948
|
const argv = process.argv.slice(2);
|
|
6945
6949
|
const cmd = argv[0];
|
|
6946
6950
|
const rest = parseArgs(argv.slice(1));
|
|
6947
|
-
// No subcommand at all: drop into
|
|
6948
|
-
//
|
|
6949
|
-
//
|
|
6950
|
-
// predictable.
|
|
6951
|
+
// No subcommand at all: drop into chat REPL (v5.0.6 default). The
|
|
6952
|
+
// arrow-key launcher menu is still available via `lazyclaw menu`.
|
|
6953
|
+
// Non-TTY callers (pipes, scripts) get the historical usage line.
|
|
6951
6954
|
if (cmd === undefined) {
|
|
6952
6955
|
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
6953
|
-
|
|
6954
|
-
return;
|
|
6956
|
+
process.argv.splice(2, 0, 'chat');
|
|
6957
|
+
return main();
|
|
6955
6958
|
}
|
|
6956
6959
|
console.error('Usage: lazyclaw <' + SUBCOMMANDS.join('|') + '> ...');
|
|
6957
6960
|
console.error('Run `lazyclaw help` for a one-line summary of each subcommand.');
|
|
6958
|
-
console.error('Tip: launch in an interactive terminal to
|
|
6961
|
+
console.error('Tip: launch in an interactive terminal to drop into chat.');
|
|
6959
6962
|
process.exit(2);
|
|
6960
6963
|
}
|
|
6961
6964
|
switch (cmd) {
|
|
@@ -7349,6 +7352,11 @@ async function main() {
|
|
|
7349
7352
|
cmdHelp(rest.positional[0]);
|
|
7350
7353
|
break;
|
|
7351
7354
|
}
|
|
7355
|
+
case 'menu': {
|
|
7356
|
+
// v5.0.6 — explicit arrow-key launcher (was the no-arg default in v5.0.5-).
|
|
7357
|
+
await cmdLauncher();
|
|
7358
|
+
break;
|
|
7359
|
+
}
|
|
7352
7360
|
default:
|
|
7353
7361
|
console.error('Usage: lazyclaw <' + SUBCOMMANDS.join('|') + '> ...');
|
|
7354
7362
|
console.error('Run `lazyclaw help` for a one-line summary of each subcommand.');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lazyclaw",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.6",
|
|
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",
|
package/tui/banner.generated.mjs
CHANGED
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// User-curated 48x35 dense braille sloth. Pasted verbatim from
|
|
2
|
+
// the operator review on 2026-06-05; replaces the chafa --invert
|
|
3
|
+
// output that read as negative-space silhouette instead of a
|
|
4
|
+
// recognizable creature.
|
|
5
5
|
export const banner = {
|
|
6
6
|
rows: [
|
|
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
|
-
"
|
|
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
|
+
"⣿⣷⡄⠀⠀⠀⠀⠀⠀⠀⣠⠄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸",
|
|
42
42
|
],
|
|
43
|
-
width:
|
|
43
|
+
width: 48,
|
|
44
44
|
height: 35,
|
|
45
45
|
fg: "#FFB347"
|
|
46
46
|
};
|
package/tui/splash.mjs
CHANGED
|
@@ -1,21 +1,32 @@
|
|
|
1
|
-
// tui/splash.mjs —
|
|
1
|
+
// tui/splash.mjs — Hermes-style hero splash.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
// - <Splash {...props} /> ink component for live REPL mount
|
|
5
|
-
// - renderSplashToString(props) pure string builder used by tests
|
|
6
|
-
// and by the non-TTY path.
|
|
3
|
+
// Layout (terminal-width responsive):
|
|
7
4
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
5
|
+
// ██╗ █████╗ ... ← wordmark (top, single-tone orange)
|
|
6
|
+
// ...
|
|
7
|
+
//
|
|
8
|
+
// ╭───── lazyclaw vX.Y.Z · trainer-split + FTS5 recall ─────────────╮
|
|
9
|
+
// │ [sloth braille] Available Tools │
|
|
10
|
+
// │ fs read · write · ... │
|
|
11
|
+
// │ Available Skills │
|
|
12
|
+
// │ N tools · M skills · /help for commands │
|
|
13
|
+
// ╰──────────────────────────────────────────────────────────────────╯
|
|
14
|
+
//
|
|
15
|
+
// provider · X · Y trainer · A · B
|
|
16
|
+
// /Users/o/cwd
|
|
17
|
+
// Session: 20260605_180543_2e0351
|
|
18
|
+
//
|
|
19
|
+
// Welcome to lazyclaw. Type your message or /help for commands.
|
|
20
|
+
// + Tip: ...
|
|
11
21
|
import React from 'react';
|
|
12
22
|
import { Box, Text } from 'ink';
|
|
13
23
|
import stringWidth from 'string-width';
|
|
14
24
|
import { theme } from './theme.mjs';
|
|
15
25
|
import { banner } from './banner.generated.mjs';
|
|
26
|
+
import { wordmark } from './wordmark.mjs';
|
|
16
27
|
|
|
17
|
-
const
|
|
18
|
-
const
|
|
28
|
+
const LMARGIN = ' ';
|
|
29
|
+
const TITLE = ' trainer-split · FTS5 recall · 6-backend sandbox ';
|
|
19
30
|
|
|
20
31
|
function fit(text, max) {
|
|
21
32
|
if (stringWidth(text) <= max) return text.padEnd(max);
|
|
@@ -35,74 +46,84 @@ function shortCwd(cwd) {
|
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
function toolRow({ category, sensitive, verbs }) {
|
|
38
|
-
const label = sensitive ?
|
|
39
|
-
const labelWidth = sensitive ? 20 : 9;
|
|
49
|
+
const label = sensitive ? `${category}*` : category;
|
|
40
50
|
const tail = verbs.slice(0, 6).join(' · ');
|
|
41
|
-
|
|
42
|
-
return `${fit(label, labelWidth)}${tail}${more}`;
|
|
51
|
+
return `${label.padEnd(12)} ${tail}`;
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
function skillRow({ group, names }) {
|
|
46
55
|
const tail = names.slice(0, 6).join(' · ');
|
|
47
|
-
|
|
48
|
-
return `${fit(group, 9)}${tail}${more}`;
|
|
56
|
+
return `${group.padEnd(12)} ${tail}`;
|
|
49
57
|
}
|
|
50
58
|
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
export function renderSplashToString(props, opts = {}) {
|
|
60
|
+
const cols = opts.columns || process.stdout.columns || 100;
|
|
61
|
+
const TERM = Math.max(80, cols);
|
|
62
|
+
const PANEL_W = TERM - LMARGIN.length * 2;
|
|
63
|
+
const INNER = PANEL_W - 4; // 2 border + 2 padding
|
|
64
|
+
const SLOTH_W = banner.width;
|
|
65
|
+
const RIGHT_W = Math.max(40, INNER - SLOTH_W - 2);
|
|
57
66
|
|
|
58
|
-
function buildToolsAndSkills(props) {
|
|
59
|
-
const { tools = [], skills = [] } = props;
|
|
60
|
-
const SECT_WIDTH = TERM_WIDTH - 4;
|
|
61
67
|
const lines = [];
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
for (const
|
|
65
|
-
if (tools.length > 8) lines.push(` ${fit(`... and ${tools.length - 8} more tool groups`, SECT_WIDTH)}`);
|
|
68
|
+
|
|
69
|
+
// Wordmark
|
|
70
|
+
for (const r of wordmark.rows) lines.push(LMARGIN + r);
|
|
66
71
|
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)}`);
|
|
71
|
-
return lines;
|
|
72
|
-
}
|
|
73
72
|
|
|
74
|
-
|
|
73
|
+
// Panel top with title
|
|
74
|
+
const versionLabel = ` lazyclaw ${props.version || ''} ·${TITLE} `;
|
|
75
|
+
const dashLeft = '─'.repeat(8);
|
|
76
|
+
const dashRight = '─'.repeat(Math.max(2, PANEL_W - 2 - dashLeft.length - stringWidth(versionLabel)));
|
|
77
|
+
lines.push(`${LMARGIN}╭${dashLeft}${versionLabel}${dashRight}╮`);
|
|
78
|
+
|
|
79
|
+
// Right column content
|
|
80
|
+
const { tools = [], skills = [] } = props;
|
|
81
|
+
const right = [];
|
|
82
|
+
right.push('Available Tools');
|
|
83
|
+
for (const t of tools.slice(0, 8)) right.push(toolRow(t));
|
|
84
|
+
if (tools.length > 8) right.push(`(and ${tools.length - 8} more tool groups...)`);
|
|
85
|
+
right.push('');
|
|
86
|
+
right.push('Available Skills');
|
|
87
|
+
for (const s of skills.slice(0, 8)) right.push(skillRow(s));
|
|
88
|
+
if (skills.length > 8) right.push(`(and ${skills.length - 8} more skill groups...)`);
|
|
89
|
+
right.push('');
|
|
90
|
+
right.push(`${tools.length} tools · ${skills.length} skills · /help for commands`);
|
|
91
|
+
|
|
92
|
+
const sloth = banner.rows.slice();
|
|
93
|
+
while (sloth.length < right.length) sloth.push(' '.repeat(SLOTH_W));
|
|
94
|
+
while (right.length < sloth.length) right.push('');
|
|
95
|
+
|
|
96
|
+
for (let i = 0; i < sloth.length; i++) {
|
|
97
|
+
const l = sloth[i] || ' '.repeat(SLOTH_W);
|
|
98
|
+
const r = fit(right[i] || '', RIGHT_W);
|
|
99
|
+
lines.push(`${LMARGIN}│ ${l} ${r} │`);
|
|
100
|
+
}
|
|
101
|
+
lines.push(`${LMARGIN}╰${'─'.repeat(PANEL_W - 2)}╯`);
|
|
102
|
+
lines.push('');
|
|
103
|
+
|
|
104
|
+
// Provider / session info
|
|
75
105
|
const { provider, model, trainer = {}, sessionId, cwd } = props;
|
|
76
106
|
const tProv = trainer.provider || provider;
|
|
77
107
|
const tModel = trainer.model || model;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
fit(' hint · Shift+Enter newline · Ctrl-R recall · Esc interrupt', 78),
|
|
85
|
-
];
|
|
86
|
-
}
|
|
108
|
+
lines.push(`${LMARGIN}${provider} · ${model} · trainer ${tProv} · ${tModel}`);
|
|
109
|
+
lines.push(`${LMARGIN}${shortCwd(cwd || process.cwd())}`);
|
|
110
|
+
if (sessionId) lines.push(`${LMARGIN}Session: ${sessionId}`);
|
|
111
|
+
lines.push('');
|
|
112
|
+
lines.push(`${LMARGIN}Welcome to lazyclaw. Type your message or /help for commands.`);
|
|
113
|
+
lines.push(`${LMARGIN}+ Tip: trainer learns from your Claude Pro subscription at $0.`);
|
|
87
114
|
|
|
88
|
-
|
|
89
|
-
void columns; // splash is fixed-width 80
|
|
90
|
-
return [
|
|
91
|
-
...buildBanner(),
|
|
92
|
-
'',
|
|
93
|
-
...buildToolsAndSkills(props),
|
|
94
|
-
'',
|
|
95
|
-
...buildFooter(props),
|
|
96
|
-
].join('\n');
|
|
115
|
+
return lines.join('\n');
|
|
97
116
|
}
|
|
98
117
|
|
|
99
118
|
export function Splash(props) {
|
|
100
119
|
const lines = renderSplashToString(props).split('\n');
|
|
120
|
+
// Color the wordmark and sloth rows; everything else stays default.
|
|
121
|
+
const heroRowCount = wordmark.height + 1 + 1 + banner.height + 1; // word + blank + top + sloth + bottom
|
|
101
122
|
return React.createElement(
|
|
102
123
|
Box,
|
|
103
124
|
{ flexDirection: 'column' },
|
|
104
125
|
lines.map((line, i) =>
|
|
105
|
-
React.createElement(Text, { key: i, color: i <
|
|
126
|
+
React.createElement(Text, { key: i, color: i < heroRowCount ? theme.fg : undefined }, line)
|
|
106
127
|
)
|
|
107
128
|
);
|
|
108
129
|
}
|
package/tui/wordmark.mjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// LAZYCLAW wordmark — ANSI Shadow style block-character rendering, shared
|
|
2
|
+
// between the chat splash and the no-arg launcher so the two entry points
|
|
3
|
+
// present identical visual identity. 67 cells × 6 rows.
|
|
4
|
+
export const wordmark = {
|
|
5
|
+
rows: [
|
|
6
|
+
"██╗ █████╗ ███████╗██╗ ██╗ ██████╗██╗ █████╗ ██╗ ██╗",
|
|
7
|
+
"██║ ██╔══██╗╚══███╔╝╚██╗ ██╔╝██╔════╝██║ ██╔══██╗██║ ██║",
|
|
8
|
+
"██║ ███████║ ███╔╝ ╚████╔╝ ██║ ██║ ███████║██║ █╗ ██║",
|
|
9
|
+
"██║ ██╔══██║ ███╔╝ ╚██╔╝ ██║ ██║ ██╔══██║██║███╗██║",
|
|
10
|
+
"███████╗██║ ██║███████╗ ██║ ╚██████╗███████╗██║ ██║╚███╔███╔╝",
|
|
11
|
+
"╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝ ",
|
|
12
|
+
],
|
|
13
|
+
width: 67,
|
|
14
|
+
height: 6,
|
|
15
|
+
fg: "#FFB347"
|
|
16
|
+
};
|