lazyclaw 5.0.8 → 5.1.0
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/README.md +29 -8
- package/cli.mjs +12 -2
- package/daemon.mjs +377 -4
- package/package.json +1 -1
- package/tui/splash.mjs +283 -38
- package/tui/wordmark.mjs +21 -3
- package/web/dashboard.html +383 -24
package/tui/splash.mjs
CHANGED
|
@@ -1,23 +1,12 @@
|
|
|
1
|
-
// tui/splash.mjs — Hermes-style hero splash
|
|
1
|
+
// tui/splash.mjs — Hermes-style hero splash with gradient wordmark,
|
|
2
|
+
// subcommand catalog, tool registry, skill index, and a bottom status bar.
|
|
2
3
|
//
|
|
3
|
-
// Layout (terminal-width responsive):
|
|
4
|
+
// Layout (terminal-width responsive across four tiers):
|
|
4
5
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
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: ...
|
|
6
|
+
// WIDE (cols >= 140) — full wordmark + panel + sloth + 2-col right side
|
|
7
|
+
// MEDIUM ( 90 <= cols < 140) — compact headline, panel + sloth + wrapped right column
|
|
8
|
+
// NARROW ( 60 <= cols < 90) — single column, no sloth/panel, truncated verb lists
|
|
9
|
+
// MINIMAL (cols < 60) — headline + provider + cwd + /help line only
|
|
21
10
|
import React from 'react';
|
|
22
11
|
import { Box, Text } from 'ink';
|
|
23
12
|
import stringWidth from 'string-width';
|
|
@@ -28,6 +17,27 @@ import { wordmark } from './wordmark.mjs';
|
|
|
28
17
|
const LMARGIN = ' ';
|
|
29
18
|
const TITLE = ' trainer-split · FTS5 recall · 6-backend sandbox ';
|
|
30
19
|
|
|
20
|
+
// Tier breakpoints. Wordmark is 120 cols wide + LMARGIN(2)*2 = 124 minimum;
|
|
21
|
+
// the user constraint pins WIDE at >=140 to give comfortable slack. Below
|
|
22
|
+
// 90 the sloth (48 cols) leaves <40 cols for the right column, so we drop
|
|
23
|
+
// it and go single-column. Below 60 we emit only a minimal headline.
|
|
24
|
+
const WORDMARK_BREAKPOINT = 140; // drop wordmark below this
|
|
25
|
+
const PANEL_BREAKPOINT = 90; // drop sloth+panel below this
|
|
26
|
+
const MINIMAL_BREAKPOINT = 60; // drop everything but headline below this
|
|
27
|
+
|
|
28
|
+
// Subcommand catalog — grouped for the splash so a new user sees the
|
|
29
|
+
// surface area at a glance. Mirrors SUBCOMMANDS in cli.mjs.
|
|
30
|
+
export const SUBCOMMAND_GROUPS = [
|
|
31
|
+
['core', ['chat', 'agent', 'orchestrator', 'dashboard', 'menu']],
|
|
32
|
+
['workflow', ['run', 'resume', 'inspect', 'clear', 'validate', 'graph']],
|
|
33
|
+
['config', ['config', 'auth', 'rates', 'providers', 'setup', 'onboard']],
|
|
34
|
+
['state', ['sessions', 'skills', 'workspace', 'memory', 'status', 'doctor']],
|
|
35
|
+
['runtime', ['daemon', 'cron', 'loop', 'loops', 'goal']],
|
|
36
|
+
['channels', ['slack', 'telegram', 'matrix', 'channels', 'message', 'pairing']],
|
|
37
|
+
['v5', ['sandbox', 'personality', 'migrate', 'hermes', 'openclaw', 'trajectories']],
|
|
38
|
+
['utility', ['browse', 'version', 'completion', 'help', 'export', 'import', 'nodes']],
|
|
39
|
+
];
|
|
40
|
+
|
|
31
41
|
function fit(text, max) {
|
|
32
42
|
if (stringWidth(text) <= max) return text.padEnd(max);
|
|
33
43
|
let lo = 0, hi = text.length;
|
|
@@ -56,38 +66,87 @@ function skillRow({ group, names }) {
|
|
|
56
66
|
return `${group.padEnd(12)} ${tail}`;
|
|
57
67
|
}
|
|
58
68
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
function subcommandRow([label, verbs]) {
|
|
70
|
+
return `${label.padEnd(12)} ${verbs.join(' · ')}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Wrap a labeled verb list onto multiple rows when it would overflow maxWidth.
|
|
74
|
+
// First row: '<label.padEnd(12)> verb · verb'; continuations: ' verb · verb'.
|
|
75
|
+
function wrapVerbs(label, verbs, maxWidth) {
|
|
76
|
+
const pad = ' '.repeat(13); // label.padEnd(12) + ' ' = 13 cells
|
|
77
|
+
const rows = [];
|
|
78
|
+
let current = label.padEnd(12) + ' ';
|
|
79
|
+
let firstOnRow = true;
|
|
80
|
+
for (const v of verbs) {
|
|
81
|
+
const candidate = firstOnRow ? current + v : current + ' · ' + v;
|
|
82
|
+
if (stringWidth(candidate) > maxWidth) {
|
|
83
|
+
if (firstOnRow) {
|
|
84
|
+
// even a single verb overflows — emit it anyway (truncated) to make progress.
|
|
85
|
+
rows.push(fit(candidate, maxWidth).trimEnd());
|
|
86
|
+
current = pad;
|
|
87
|
+
firstOnRow = true;
|
|
88
|
+
} else {
|
|
89
|
+
rows.push(current.trimEnd());
|
|
90
|
+
current = pad + v;
|
|
91
|
+
firstOnRow = false;
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
current = candidate;
|
|
95
|
+
firstOnRow = false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (current.trim()) rows.push(current.trimEnd());
|
|
99
|
+
return rows;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Crush-style truncation for NARROW tier — take first N verbs, append '…' if more.
|
|
103
|
+
function truncateRow(label, verbs, maxWidth, take = 3) {
|
|
104
|
+
const head = label.padEnd(12) + ' ';
|
|
105
|
+
const tail = verbs.slice(0, take).join(' · ');
|
|
106
|
+
let line = head + tail;
|
|
107
|
+
if (verbs.length > take) line += ' …';
|
|
108
|
+
if (stringWidth(line) <= maxWidth) return line;
|
|
109
|
+
return fit(line, maxWidth).trimEnd();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Wide tier — original v5.0.9 layout, kept verbatim.
|
|
113
|
+
function renderWide(props, cols) {
|
|
114
|
+
const PANEL_W = cols - LMARGIN.length * 2;
|
|
115
|
+
const INNER = PANEL_W - 4;
|
|
64
116
|
const SLOTH_W = banner.width;
|
|
65
117
|
const RIGHT_W = Math.max(40, INNER - SLOTH_W - 2);
|
|
66
118
|
|
|
67
119
|
const lines = [];
|
|
68
120
|
|
|
69
|
-
//
|
|
121
|
+
// 1) wordmark
|
|
70
122
|
for (const r of wordmark.rows) lines.push(LMARGIN + r);
|
|
71
123
|
lines.push('');
|
|
72
124
|
|
|
73
|
-
//
|
|
125
|
+
// 2) panel top with inset title
|
|
74
126
|
const versionLabel = ` lazyclaw ${props.version || ''} ·${TITLE} `;
|
|
75
127
|
const dashLeft = '─'.repeat(8);
|
|
76
128
|
const dashRight = '─'.repeat(Math.max(2, PANEL_W - 2 - dashLeft.length - stringWidth(versionLabel)));
|
|
77
129
|
lines.push(`${LMARGIN}╭${dashLeft}${versionLabel}${dashRight}╮`);
|
|
78
130
|
|
|
79
|
-
//
|
|
131
|
+
// 3) right column content
|
|
80
132
|
const { tools = [], skills = [] } = props;
|
|
81
133
|
const right = [];
|
|
134
|
+
right.push('Subcommands');
|
|
135
|
+
for (const g of SUBCOMMAND_GROUPS) right.push(subcommandRow(g));
|
|
136
|
+
right.push('');
|
|
82
137
|
right.push('Available Tools');
|
|
83
|
-
for (const t of tools.slice(0,
|
|
84
|
-
if (tools.length >
|
|
138
|
+
for (const t of tools.slice(0, 14)) right.push(toolRow(t));
|
|
139
|
+
if (tools.length > 14) right.push(`(and ${tools.length - 14} more...)`);
|
|
85
140
|
right.push('');
|
|
86
141
|
right.push('Available Skills');
|
|
87
|
-
|
|
88
|
-
|
|
142
|
+
if (skills.length === 0) right.push('(none installed)');
|
|
143
|
+
else {
|
|
144
|
+
for (const s of skills.slice(0, 8)) right.push(skillRow(s));
|
|
145
|
+
if (skills.length > 8) right.push(`(and ${skills.length - 8} more skill groups...)`);
|
|
146
|
+
}
|
|
89
147
|
right.push('');
|
|
90
|
-
|
|
148
|
+
const subcmdCount = SUBCOMMAND_GROUPS.reduce((n, [, v]) => n + v.length, 0);
|
|
149
|
+
right.push(`${subcmdCount} subcommands · ${tools.length} tool groups · ${skills.length} skills · /help for commands`);
|
|
91
150
|
|
|
92
151
|
const sloth = banner.rows.slice();
|
|
93
152
|
while (sloth.length < right.length) sloth.push(' '.repeat(SLOTH_W));
|
|
@@ -101,7 +160,97 @@ export function renderSplashToString(props, opts = {}) {
|
|
|
101
160
|
lines.push(`${LMARGIN}╰${'─'.repeat(PANEL_W - 2)}╯`);
|
|
102
161
|
lines.push('');
|
|
103
162
|
|
|
104
|
-
//
|
|
163
|
+
// 4) provider / session info
|
|
164
|
+
const { provider, model, trainer = {}, sessionId, cwd } = props;
|
|
165
|
+
const tProv = trainer.provider || provider;
|
|
166
|
+
const tModel = trainer.model || model;
|
|
167
|
+
lines.push(`${LMARGIN}${provider} · ${model} · trainer ${tProv} · ${tModel}`);
|
|
168
|
+
lines.push(`${LMARGIN}${shortCwd(cwd || process.cwd())}`);
|
|
169
|
+
if (sessionId) lines.push(`${LMARGIN}Session: ${sessionId}`);
|
|
170
|
+
lines.push('');
|
|
171
|
+
lines.push(`${LMARGIN}Welcome to lazyclaw. Type your message or /help for commands.`);
|
|
172
|
+
lines.push(`${LMARGIN}+ Tip: trainer learns from your Claude Pro subscription at $0.`);
|
|
173
|
+
lines.push('');
|
|
174
|
+
|
|
175
|
+
// 5) status bar (Hermes-style separator)
|
|
176
|
+
const sep = '─'.repeat(PANEL_W);
|
|
177
|
+
lines.push(LMARGIN + sep);
|
|
178
|
+
const ctx = props.ctxUsed != null && props.ctxTotal != null
|
|
179
|
+
? `[${'░'.repeat(20)}] ${props.ctxUsed}/${props.ctxTotal}`
|
|
180
|
+
: `[${'░'.repeat(20)}] --`;
|
|
181
|
+
lines.push(`${LMARGIN} ${provider} · ${model} | ctx -- | ${ctx} | 0s`);
|
|
182
|
+
lines.push(LMARGIN + sep);
|
|
183
|
+
|
|
184
|
+
return lines;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Medium tier — no wordmark, compact panel title, sloth + wrapped right column.
|
|
188
|
+
function renderMedium(props, cols) {
|
|
189
|
+
const PANEL_W = cols - LMARGIN.length * 2;
|
|
190
|
+
const INNER = PANEL_W - 4;
|
|
191
|
+
const SLOTH_W = banner.width;
|
|
192
|
+
const RIGHT_W = Math.max(20, INNER - SLOTH_W - 2);
|
|
193
|
+
|
|
194
|
+
const lines = [];
|
|
195
|
+
|
|
196
|
+
// 1) compact headline (no wordmark)
|
|
197
|
+
lines.push(`${LMARGIN}lazyclaw ${props.version || ''}`.trimEnd());
|
|
198
|
+
lines.push('');
|
|
199
|
+
|
|
200
|
+
// 2) compact panel top — drop the TITLE chain, just version
|
|
201
|
+
const versionLabel = ` lazyclaw ${props.version || ''} `;
|
|
202
|
+
const dashLeft = '─'.repeat(4);
|
|
203
|
+
const dashRight = '─'.repeat(Math.max(2, PANEL_W - 2 - dashLeft.length - stringWidth(versionLabel)));
|
|
204
|
+
lines.push(`${LMARGIN}╭${dashLeft}${versionLabel}${dashRight}╮`);
|
|
205
|
+
|
|
206
|
+
// 3) build right column with wrapping
|
|
207
|
+
const { tools = [], skills = [] } = props;
|
|
208
|
+
const right = [];
|
|
209
|
+
right.push('Subcommands');
|
|
210
|
+
for (const [label, verbs] of SUBCOMMAND_GROUPS) {
|
|
211
|
+
for (const r of wrapVerbs(label, verbs, RIGHT_W)) right.push(r);
|
|
212
|
+
}
|
|
213
|
+
right.push('');
|
|
214
|
+
right.push('Available Tools');
|
|
215
|
+
for (const t of tools.slice(0, 14)) {
|
|
216
|
+
const label = t.sensitive ? `${t.category}*` : t.category;
|
|
217
|
+
for (const r of wrapVerbs(label, t.verbs.slice(0, 6), RIGHT_W)) right.push(r);
|
|
218
|
+
}
|
|
219
|
+
if (tools.length > 14) right.push(`(and ${tools.length - 14} more...)`);
|
|
220
|
+
right.push('');
|
|
221
|
+
right.push('Available Skills');
|
|
222
|
+
if (skills.length === 0) right.push('(none installed)');
|
|
223
|
+
else {
|
|
224
|
+
for (const s of skills.slice(0, 8)) {
|
|
225
|
+
for (const r of wrapVerbs(s.group, s.names.slice(0, 6), RIGHT_W)) right.push(r);
|
|
226
|
+
}
|
|
227
|
+
if (skills.length > 8) right.push(`(and ${skills.length - 8} more skill groups...)`);
|
|
228
|
+
}
|
|
229
|
+
right.push('');
|
|
230
|
+
const subcmdCount = SUBCOMMAND_GROUPS.reduce((n, [, v]) => n + v.length, 0);
|
|
231
|
+
const summary = `${subcmdCount} subcommands · ${tools.length} tool groups · ${skills.length} skills · /help`;
|
|
232
|
+
if (stringWidth(summary) > RIGHT_W) {
|
|
233
|
+
right.push(`${subcmdCount} subcmds · ${tools.length} tools · ${skills.length} skills`);
|
|
234
|
+
right.push('/help for commands');
|
|
235
|
+
} else {
|
|
236
|
+
right.push(summary);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const sloth = banner.rows.slice();
|
|
240
|
+
while (sloth.length < right.length) sloth.push(' '.repeat(SLOTH_W));
|
|
241
|
+
while (right.length < sloth.length) right.push('');
|
|
242
|
+
|
|
243
|
+
for (let i = 0; i < sloth.length; i++) {
|
|
244
|
+
const l = sloth[i] || ' '.repeat(SLOTH_W);
|
|
245
|
+
// pad (no ellipsis) — wrapVerbs already guarantees width <= RIGHT_W
|
|
246
|
+
const raw = right[i] || '';
|
|
247
|
+
const r = raw + ' '.repeat(Math.max(0, RIGHT_W - stringWidth(raw)));
|
|
248
|
+
lines.push(`${LMARGIN}│ ${l} ${r} │`);
|
|
249
|
+
}
|
|
250
|
+
lines.push(`${LMARGIN}╰${'─'.repeat(PANEL_W - 2)}╯`);
|
|
251
|
+
lines.push('');
|
|
252
|
+
|
|
253
|
+
// 4) provider / session info
|
|
105
254
|
const { provider, model, trainer = {}, sessionId, cwd } = props;
|
|
106
255
|
const tProv = trainer.provider || provider;
|
|
107
256
|
const tModel = trainer.model || model;
|
|
@@ -111,19 +260,115 @@ export function renderSplashToString(props, opts = {}) {
|
|
|
111
260
|
lines.push('');
|
|
112
261
|
lines.push(`${LMARGIN}Welcome to lazyclaw. Type your message or /help for commands.`);
|
|
113
262
|
lines.push(`${LMARGIN}+ Tip: trainer learns from your Claude Pro subscription at $0.`);
|
|
263
|
+
lines.push('');
|
|
264
|
+
|
|
265
|
+
// 5) status bar
|
|
266
|
+
const sep = '─'.repeat(PANEL_W);
|
|
267
|
+
lines.push(LMARGIN + sep);
|
|
268
|
+
const ctx = props.ctxUsed != null && props.ctxTotal != null
|
|
269
|
+
? `[${'░'.repeat(20)}] ${props.ctxUsed}/${props.ctxTotal}`
|
|
270
|
+
: `[${'░'.repeat(20)}] --`;
|
|
271
|
+
lines.push(`${LMARGIN} ${provider} · ${model} | ctx -- | ${ctx} | 0s`);
|
|
272
|
+
lines.push(LMARGIN + sep);
|
|
273
|
+
|
|
274
|
+
return lines;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Narrow tier — single column, no sloth, no panel, truncated verb lists.
|
|
278
|
+
function renderNarrow(props, cols) {
|
|
279
|
+
const W = cols - LMARGIN.length * 2;
|
|
280
|
+
const lines = [];
|
|
281
|
+
|
|
282
|
+
// 1) headline
|
|
283
|
+
lines.push(`${LMARGIN}lazyclaw ${props.version || ''}`.trimEnd());
|
|
284
|
+
lines.push('');
|
|
285
|
+
|
|
286
|
+
const { tools = [], skills = [], provider, model, trainer = {}, sessionId, cwd } = props;
|
|
114
287
|
|
|
288
|
+
// 2) subcommands
|
|
289
|
+
lines.push(`${LMARGIN}Subcommands`);
|
|
290
|
+
for (const [label, verbs] of SUBCOMMAND_GROUPS) {
|
|
291
|
+
lines.push(LMARGIN + truncateRow(label, verbs, W));
|
|
292
|
+
}
|
|
293
|
+
lines.push('');
|
|
294
|
+
|
|
295
|
+
// 3) tools
|
|
296
|
+
lines.push(`${LMARGIN}Available Tools`);
|
|
297
|
+
for (const t of tools.slice(0, 14)) {
|
|
298
|
+
const label = t.sensitive ? `${t.category}*` : t.category;
|
|
299
|
+
lines.push(LMARGIN + truncateRow(label, t.verbs, W));
|
|
300
|
+
}
|
|
301
|
+
if (tools.length > 14) lines.push(`${LMARGIN}(and ${tools.length - 14} more...)`);
|
|
302
|
+
lines.push('');
|
|
303
|
+
|
|
304
|
+
// 4) skills
|
|
305
|
+
lines.push(`${LMARGIN}Available Skills`);
|
|
306
|
+
if (skills.length === 0) lines.push(`${LMARGIN}(none installed)`);
|
|
307
|
+
else {
|
|
308
|
+
for (const s of skills.slice(0, 8)) {
|
|
309
|
+
lines.push(LMARGIN + truncateRow(s.group, s.names, W));
|
|
310
|
+
}
|
|
311
|
+
if (skills.length > 8) lines.push(`${LMARGIN}(and ${skills.length - 8} more skill groups...)`);
|
|
312
|
+
}
|
|
313
|
+
lines.push('');
|
|
314
|
+
|
|
315
|
+
// 5) provider / session info
|
|
316
|
+
const tProv = trainer.provider || provider;
|
|
317
|
+
const tModel = trainer.model || model;
|
|
318
|
+
lines.push(fit(`${LMARGIN}${provider} · ${model} · trainer ${tProv} · ${tModel}`, cols).trimEnd());
|
|
319
|
+
lines.push(fit(`${LMARGIN}${shortCwd(cwd || process.cwd())}`, cols).trimEnd());
|
|
320
|
+
if (sessionId) lines.push(fit(`${LMARGIN}Session: ${sessionId}`, cols).trimEnd());
|
|
321
|
+
lines.push('');
|
|
322
|
+
lines.push(fit(`${LMARGIN}Welcome to lazyclaw. /help for commands.`, cols).trimEnd());
|
|
323
|
+
lines.push('');
|
|
324
|
+
|
|
325
|
+
// 6) compact status — single line, no separator dashes
|
|
326
|
+
const ctx = props.ctxUsed != null && props.ctxTotal != null
|
|
327
|
+
? `${props.ctxUsed}/${props.ctxTotal}`
|
|
328
|
+
: '--';
|
|
329
|
+
lines.push(fit(`${LMARGIN}${provider} · ${model} · ctx ${ctx}`, cols).trimEnd());
|
|
330
|
+
|
|
331
|
+
return lines;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Minimal tier — bare-bones fallback for cols < 60.
|
|
335
|
+
function renderMinimal(props) {
|
|
336
|
+
const { version, provider, model, sessionId, cwd } = props;
|
|
337
|
+
const lines = [];
|
|
338
|
+
lines.push(`lazyclaw ${version || ''}`.trimEnd());
|
|
339
|
+
lines.push(`${provider} · ${model}`);
|
|
340
|
+
lines.push(shortCwd(cwd || process.cwd()));
|
|
341
|
+
if (sessionId) lines.push(`Session: ${sessionId}`);
|
|
342
|
+
lines.push('/help for commands');
|
|
343
|
+
return lines;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function renderSplashToString(props, opts = {}) {
|
|
347
|
+
const cols = opts.columns || process.stdout.columns || 100;
|
|
348
|
+
let lines;
|
|
349
|
+
if (cols < MINIMAL_BREAKPOINT) lines = renderMinimal(props);
|
|
350
|
+
else if (cols < PANEL_BREAKPOINT) lines = renderNarrow(props, cols);
|
|
351
|
+
else if (cols < WORDMARK_BREAKPOINT) lines = renderMedium(props, cols);
|
|
352
|
+
else lines = renderWide(props, cols);
|
|
115
353
|
return lines.join('\n');
|
|
116
354
|
}
|
|
117
355
|
|
|
118
356
|
export function Splash(props) {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
const
|
|
357
|
+
const cols = process.stdout.columns || 100;
|
|
358
|
+
const out = renderSplashToString(props, { columns: cols });
|
|
359
|
+
const lines = out.split('\n');
|
|
360
|
+
const palette = wordmark.palette;
|
|
361
|
+
const gradient = wordmark.gradient;
|
|
362
|
+
const showWordmark = cols >= WORDMARK_BREAKPOINT;
|
|
363
|
+
|
|
122
364
|
return React.createElement(
|
|
123
365
|
Box,
|
|
124
366
|
{ flexDirection: 'column' },
|
|
125
|
-
lines.map((line, i) =>
|
|
126
|
-
|
|
127
|
-
|
|
367
|
+
lines.map((line, i) => {
|
|
368
|
+
let color;
|
|
369
|
+
if (showWordmark && i < wordmark.height) color = palette[gradient[i] ?? 1];
|
|
370
|
+
else if (showWordmark && i < wordmark.height + 1 + 1 + banner.height) color = theme.fg;
|
|
371
|
+
return React.createElement(Text, { key: i, color }, line);
|
|
372
|
+
})
|
|
128
373
|
);
|
|
129
374
|
}
|
package/tui/wordmark.mjs
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// "Larry 3D"-style LAZYCLAW wordmark — 13 rows × 120 cols, shared between
|
|
2
|
+
// chat splash and launcher. Gradient palette maps each row to one of four
|
|
3
|
+
// warm-orange shades; top rows = brightest, bottom rows = shadow-dark.
|
|
4
|
+
//
|
|
5
|
+
// The string-output renderer (renderSplashToString) emits the raw rows;
|
|
6
|
+
// the ink/launcher color path picks PALETTE[GRADIENT[row]] when ANSI is
|
|
7
|
+
// available.
|
|
8
|
+
|
|
9
|
+
export const PALETTE = [
|
|
10
|
+
'#FFD580', // 0 top — warm white-gold
|
|
11
|
+
'#FFB347', // 1 highlight (current single-tone reference)
|
|
12
|
+
'#E08020', // 2 midtone — amber
|
|
13
|
+
'#A05010', // 3 bottom — burnt shadow
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
// 13 rows; bias toward bottom for shadow depth.
|
|
17
|
+
export const GRADIENT = [0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3];
|
|
18
|
+
|
|
3
19
|
export const wordmark = {
|
|
4
20
|
rows: [
|
|
5
21
|
" ____ ____ _____ _____ _____ _____ ____ ____ _____ ",
|
|
@@ -18,5 +34,7 @@ export const wordmark = {
|
|
|
18
34
|
],
|
|
19
35
|
width: 120,
|
|
20
36
|
height: 13,
|
|
21
|
-
fg: "#FFB347"
|
|
37
|
+
fg: "#FFB347",
|
|
38
|
+
gradient: GRADIENT,
|
|
39
|
+
palette: PALETTE,
|
|
22
40
|
};
|