lazyclaw 5.0.5 → 5.0.7
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 +65 -23
- package/package.json +1 -1
- 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
|
|
|
@@ -2414,11 +2418,45 @@ async function cmdChat(flags = {}) {
|
|
|
2414
2418
|
const { renderSplashToString } = await import('./tui/splash.mjs');
|
|
2415
2419
|
// narrow-terminal fallback: <60 cols falls back to v4
|
|
2416
2420
|
if ((process.stdout.columns || 80) < 60) throw new Error('narrow-terminal');
|
|
2421
|
+
|
|
2422
|
+
// Tool groups — read the v5 registry and collapse to one row per category.
|
|
2423
|
+
let toolGroups = [];
|
|
2424
|
+
try {
|
|
2425
|
+
const registry = await import('./mas/tools/registry.mjs');
|
|
2426
|
+
const byCat = registry.byCategory();
|
|
2427
|
+
toolGroups = Object.entries(byCat).map(([category, items]) => ({
|
|
2428
|
+
category,
|
|
2429
|
+
sensitive: items.some(t => t.sensitive),
|
|
2430
|
+
verbs: items.map(t => t.name.replace(/^[a-z]+_/, '')).slice(0, 6),
|
|
2431
|
+
})).sort((a, b) => a.category.localeCompare(b.category));
|
|
2432
|
+
} catch { /* registry unavailable → empty list */ }
|
|
2433
|
+
|
|
2434
|
+
// Skill groups — group installed skills by filename hyphen-prefix
|
|
2435
|
+
// (canonical C5 fallback: <group>-<name>.md → group; bare names → 'general').
|
|
2436
|
+
let skillGroups = [];
|
|
2437
|
+
try {
|
|
2438
|
+
const { listSkills } = await import('./skills.mjs');
|
|
2439
|
+
const flat = listSkills();
|
|
2440
|
+
const byGroup = new Map();
|
|
2441
|
+
for (const s of flat) {
|
|
2442
|
+
const i = s.name.indexOf('-');
|
|
2443
|
+
const group = i > 0 ? s.name.slice(0, i) : 'general';
|
|
2444
|
+
const sub = i > 0 ? s.name.slice(i + 1) : s.name;
|
|
2445
|
+
if (!byGroup.has(group)) byGroup.set(group, []);
|
|
2446
|
+
byGroup.get(group).push(sub);
|
|
2447
|
+
}
|
|
2448
|
+
skillGroups = [...byGroup.entries()]
|
|
2449
|
+
.map(([group, names]) => ({ group, names: names.slice(0, 6) }))
|
|
2450
|
+
.sort((a, b) => a.group.localeCompare(b.group));
|
|
2451
|
+
} catch { /* skills dir unavailable → empty list */ }
|
|
2452
|
+
|
|
2417
2453
|
const splashProps = {
|
|
2418
2454
|
provider: activeProvName, model: activeModel,
|
|
2419
2455
|
trainer: {}, sessionId: flags.session || '',
|
|
2420
2456
|
cwd: process.cwd(),
|
|
2421
|
-
|
|
2457
|
+
version: readVersionFromRepo(),
|
|
2458
|
+
tools: toolGroups,
|
|
2459
|
+
skills: skillGroups,
|
|
2422
2460
|
};
|
|
2423
2461
|
void renderSplashToString; // surfaced for tests; runtime uses <Splash/>
|
|
2424
2462
|
const ink = render(/* @__PURE__ */ React.createElement(ReplApp, {
|
|
@@ -6944,18 +6982,17 @@ async function main() {
|
|
|
6944
6982
|
const argv = process.argv.slice(2);
|
|
6945
6983
|
const cmd = argv[0];
|
|
6946
6984
|
const rest = parseArgs(argv.slice(1));
|
|
6947
|
-
// No subcommand at all: drop into
|
|
6948
|
-
//
|
|
6949
|
-
//
|
|
6950
|
-
// predictable.
|
|
6985
|
+
// No subcommand at all: drop into chat REPL (v5.0.6 default). The
|
|
6986
|
+
// arrow-key launcher menu is still available via `lazyclaw menu`.
|
|
6987
|
+
// Non-TTY callers (pipes, scripts) get the historical usage line.
|
|
6951
6988
|
if (cmd === undefined) {
|
|
6952
6989
|
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
6953
|
-
|
|
6954
|
-
return;
|
|
6990
|
+
process.argv.splice(2, 0, 'chat');
|
|
6991
|
+
return main();
|
|
6955
6992
|
}
|
|
6956
6993
|
console.error('Usage: lazyclaw <' + SUBCOMMANDS.join('|') + '> ...');
|
|
6957
6994
|
console.error('Run `lazyclaw help` for a one-line summary of each subcommand.');
|
|
6958
|
-
console.error('Tip: launch in an interactive terminal to
|
|
6995
|
+
console.error('Tip: launch in an interactive terminal to drop into chat.');
|
|
6959
6996
|
process.exit(2);
|
|
6960
6997
|
}
|
|
6961
6998
|
switch (cmd) {
|
|
@@ -7349,6 +7386,11 @@ async function main() {
|
|
|
7349
7386
|
cmdHelp(rest.positional[0]);
|
|
7350
7387
|
break;
|
|
7351
7388
|
}
|
|
7389
|
+
case 'menu': {
|
|
7390
|
+
// v5.0.6 — explicit arrow-key launcher (was the no-arg default in v5.0.5-).
|
|
7391
|
+
await cmdLauncher();
|
|
7392
|
+
break;
|
|
7393
|
+
}
|
|
7352
7394
|
default:
|
|
7353
7395
|
console.error('Usage: lazyclaw <' + SUBCOMMANDS.join('|') + '> ...');
|
|
7354
7396
|
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.7",
|
|
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/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
|
+
};
|