lazyclaw 5.0.3 → 5.0.5
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 +24 -21
- package/package.json +1 -1
- package/tui/banner.generated.mjs +41 -24
- package/tui/splash.mjs +32 -31
package/cli.mjs
CHANGED
|
@@ -1634,28 +1634,31 @@ function _renderBanner(version) {
|
|
|
1634
1634
|
];
|
|
1635
1635
|
}
|
|
1636
1636
|
|
|
1637
|
-
// v5
|
|
1638
|
-
//
|
|
1639
|
-
//
|
|
1640
|
-
//
|
|
1641
|
-
|
|
1642
|
-
|
|
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
|
-
|
|
1654
|
-
|
|
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
|
+
"version": "5.0.5",
|
|
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,29 +1,46 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
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
|
+
// 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.
|
|
11
5
|
export const banner = {
|
|
12
6
|
rows: [
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
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:
|
|
27
|
-
height:
|
|
43
|
+
width: 48,
|
|
44
|
+
height: 35,
|
|
28
45
|
fg: "#FFB347"
|
|
29
46
|
};
|
package/tui/splash.mjs
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
// tui/splash.mjs —
|
|
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:
|
|
9
|
-
//
|
|
10
|
-
//
|
|
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
|
|
18
|
-
const
|
|
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
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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; //
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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) {
|