blyuecode 0.1.15 → 0.1.17
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/dist/ui/Banner.js +6 -7
- package/dist/ui/Chat.js +5 -2
- package/dist/ui/InputBar.js +3 -8
- package/dist/ui/MascotPrompt.js +17 -9
- package/dist/ui/RocketBar.js +3 -11
- package/dist/ui/Settings.js +3 -1
- package/dist/ui/pastel.js +30 -0
- package/dist/ui/slowTick.js +38 -0
- package/dist/ui/useSlowColor.js +11 -0
- package/package.json +1 -1
package/dist/ui/Banner.js
CHANGED
|
@@ -5,7 +5,7 @@ import { readFileSync } from 'fs';
|
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { dirname, join } from 'path';
|
|
7
7
|
import os from 'os';
|
|
8
|
-
import {
|
|
8
|
+
import { pastelAt } from './pastel.js';
|
|
9
9
|
function getVersion() {
|
|
10
10
|
try {
|
|
11
11
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -63,8 +63,6 @@ const CODE_SMALL = [
|
|
|
63
63
|
];
|
|
64
64
|
const BLYUE_WIDTH = 44;
|
|
65
65
|
const BLYUE_SMALL_WIDTH = 27;
|
|
66
|
-
// Single <Text> — was previously a fragment with two <Text> children,
|
|
67
|
-
// which React/Ink flagged as "list without keys" under the parent Text.
|
|
68
66
|
const MakerMarkInline = ({ color }) => (_jsx(Text, { color: color, bold: true, children: "\u25C6 L U M E N" }));
|
|
69
67
|
export const Banner = () => {
|
|
70
68
|
const { stdout } = useStdout();
|
|
@@ -77,14 +75,15 @@ export const Banner = () => {
|
|
|
77
75
|
const pad = ' '.repeat(leftPad);
|
|
78
76
|
const [animate] = useState(() => shouldAnimate());
|
|
79
77
|
const [t, setT] = useState(0);
|
|
78
|
+
// Slow pastel drift — 24s full cycle, matches the rest of the TUI.
|
|
80
79
|
useEffect(() => {
|
|
81
80
|
if (!animate)
|
|
82
81
|
return;
|
|
83
|
-
const interval = setInterval(() => setT((v) => (v + 1 /
|
|
82
|
+
const interval = setInterval(() => setT((v) => (v + 1 / 200) % 1), 120);
|
|
84
83
|
return () => clearInterval(interval);
|
|
85
84
|
}, [animate]);
|
|
86
|
-
const baseHue = animate ? t : 0.
|
|
87
|
-
const brand =
|
|
85
|
+
const baseHue = animate ? t : 0.08;
|
|
86
|
+
const brand = pastelAt(baseHue);
|
|
88
87
|
const version = getVersion();
|
|
89
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [pad, _jsx(MakerMarkInline, { color: brand })] }), _jsx(Text, { children: " " }), blyue.map((line, i) => (_jsx(Text, { color:
|
|
88
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [pad, _jsx(MakerMarkInline, { color: brand })] }), _jsx(Text, { children: " " }), blyue.map((line, i) => (_jsx(Text, { color: pastelAt((baseHue + i * 0.035) % 1), bold: true, children: pad + line }, 'b' + i))), code.map((line, i) => (_jsx(Text, { color: pastelAt((baseHue + i * 0.035 + 0.18) % 1), bold: true, children: pad + line }, 'c' + i))), _jsx(Text, { children: " " }), _jsxs(Text, { children: [pad, _jsx(Text, { color: "gray", children: 'a coding agent · v' + version })] }), _jsx(Text, { children: " " })] }));
|
|
90
89
|
};
|
package/dist/ui/Chat.js
CHANGED
|
@@ -12,13 +12,15 @@ import { Settings } from './Settings.js';
|
|
|
12
12
|
import { PermissionPrompt } from './PermissionPrompt.js';
|
|
13
13
|
import { Spinner } from './Spinner.js';
|
|
14
14
|
import { toolToState } from './MascotPrompt.js';
|
|
15
|
+
import { useSlowColor } from './useSlowColor.js';
|
|
16
|
+
import { pastelAt } from './pastel.js';
|
|
15
17
|
import { bumpActivity } from './activity.js';
|
|
16
18
|
import { theme } from './theme.js';
|
|
17
19
|
let nextId = 0;
|
|
18
20
|
const mkItem = (kind, text) => ({ kind, text, id: nextId++ });
|
|
19
21
|
const HistoryLine = ({ item }) => {
|
|
20
22
|
if (item.kind === 'user') {
|
|
21
|
-
return (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color:
|
|
23
|
+
return (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: pastelAt(0.08), bold: true, children: '❯ ' }), _jsx(Text, { color: theme.text, bold: true, children: item.text })] }));
|
|
22
24
|
}
|
|
23
25
|
if (item.kind === 'error') {
|
|
24
26
|
return (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.error, children: '❯ ' }), _jsx(Text, { color: theme.error, children: item.text })] }));
|
|
@@ -93,6 +95,7 @@ export const Chat = ({ resumedCount = 0 }) => {
|
|
|
93
95
|
setMascotState(state);
|
|
94
96
|
setTimeout(() => setMascotState(undefined), ms);
|
|
95
97
|
};
|
|
98
|
+
const slowColor = useSlowColor();
|
|
96
99
|
useInput((inputChar, key) => {
|
|
97
100
|
bumpActivity();
|
|
98
101
|
if (permReq)
|
|
@@ -245,5 +248,5 @@ export const Chat = ({ resumedCount = 0 }) => {
|
|
|
245
248
|
const shortModel = currentModel.length > maxModelLen
|
|
246
249
|
? '…' + currentModel.slice(-(maxModelLen - 1))
|
|
247
250
|
: currentModel || 'unset';
|
|
248
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: history, children: (item) => _jsx(HistoryLine, { item: item }, item.id) }), streaming ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.text, children: streaming }) })) : loading ? (_jsx(Box, { marginTop: 1, children: _jsx(Spinner, {}) })) : null, _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(InputBar, { value: input, onChange: setInput, onSubmit: handleSubmit, loading: loading, mascotState: mascotState, elapsedSec: elapsedSec }), _jsxs(Text, { children: [_jsx(Text, { color:
|
|
251
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: history, children: (item) => _jsx(HistoryLine, { item: item }, item.id) }), streaming ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.text, children: streaming }) })) : loading ? (_jsx(Box, { marginTop: 1, children: _jsx(Spinner, {}) })) : null, _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(InputBar, { value: input, onChange: setInput, onSubmit: handleSubmit, loading: loading, mascotState: mascotState, elapsedSec: elapsedSec }), _jsxs(Text, { children: [_jsx(Text, { color: slowColor, children: " /model" }), _jsx(Text, { color: theme.muted, children: " \u00B7 " }), _jsx(Text, { color: slowColor, children: "/settings" }), _jsx(Text, { color: theme.muted, children: " \u00B7 " }), _jsx(Text, { color: slowColor, children: "/compact" }), _jsx(Text, { color: theme.muted, children: " \u00B7 " }), _jsx(Text, { color: theme.text, children: shortModel })] })] })] }));
|
|
249
252
|
};
|
package/dist/ui/InputBar.js
CHANGED
|
@@ -6,10 +6,12 @@ import { Placeholder } from './Placeholder.js';
|
|
|
6
6
|
import { RocketBar } from './RocketBar.js';
|
|
7
7
|
import { MascotPrompt } from './MascotPrompt.js';
|
|
8
8
|
import { theme } from './theme.js';
|
|
9
|
+
import { useSlowColor } from './useSlowColor.js';
|
|
9
10
|
const DOTS = ['●○○○', '○●○○', '○○●○', '○○○●'];
|
|
10
11
|
export const InputBar = ({ value, onChange, onSubmit, loading, mascotState, awaitingPermission, elapsedSec = 0, }) => {
|
|
11
12
|
const { stdout } = useStdout();
|
|
12
13
|
const width = Math.max(24, stdout?.columns ?? 50);
|
|
14
|
+
const pastel = useSlowColor();
|
|
13
15
|
const [dotI, setDotI] = useState(0);
|
|
14
16
|
useEffect(() => {
|
|
15
17
|
if (!loading)
|
|
@@ -18,13 +20,6 @@ export const InputBar = ({ value, onChange, onSubmit, loading, mascotState, awai
|
|
|
18
20
|
return () => clearInterval(int);
|
|
19
21
|
}, [loading]);
|
|
20
22
|
const hasText = value.length > 0;
|
|
21
|
-
// Priority:
|
|
22
|
-
// explicit mascotState
|
|
23
|
-
// → permission waiting
|
|
24
|
-
// → impatient if running > 8s
|
|
25
|
-
// → thinking if loading
|
|
26
|
-
// → typing if has text
|
|
27
|
-
// → idle
|
|
28
23
|
const state = mascotState ??
|
|
29
24
|
(awaitingPermission ? 'permission' :
|
|
30
25
|
loading && elapsedSec > 8 ? 'impatient' :
|
|
@@ -33,5 +28,5 @@ export const InputBar = ({ value, onChange, onSubmit, loading, mascotState, awai
|
|
|
33
28
|
const label = awaitingPermission ? 'waiting' : loading ? 'thinking' : 'blyue';
|
|
34
29
|
const status = loading ? ' ' + DOTS[dotI] + ' ' : '';
|
|
35
30
|
const botLen = Math.max(0, width - 2 - status.length);
|
|
36
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(MascotPrompt, { state: state }), _jsx(RocketBar, { label: label, loading: loading, width: width }), _jsxs(Box, { children: [_jsx(Text, { color:
|
|
31
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(MascotPrompt, { state: state }), _jsx(RocketBar, { label: label, loading: loading, width: width }), _jsxs(Box, { children: [_jsx(Text, { color: pastel, bold: true, children: '❯ ' }), hasText ? (_jsx(TextInput, { value: value, onChange: onChange, onSubmit: onSubmit })) : (_jsxs(_Fragment, { children: [_jsx(Placeholder, {}), _jsx(TextInput, { value: value, onChange: onChange, onSubmit: onSubmit })] }))] }), _jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '╰' + '─'.repeat(botLen) }), loading && _jsx(Text, { color: pastel, children: status }), _jsx(Text, { color: theme.muted, children: "\u256F" })] })] }));
|
|
37
32
|
};
|
package/dist/ui/MascotPrompt.js
CHANGED
|
@@ -3,6 +3,7 @@ import { useState, useEffect } from 'react';
|
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
4
|
import { theme } from './theme.js';
|
|
5
5
|
import { useIdle, defaultIdleThresholds, idleDisabled } from './useIdle.js';
|
|
6
|
+
import { useSlowColor } from './useSlowColor.js';
|
|
6
7
|
import { Zzz } from './SleepZs.js';
|
|
7
8
|
const FACES = {
|
|
8
9
|
idle: ['◕‿◕', '◕‿◕', '◕_◕', '◕‿◕', '◕‿◕', '◕‿◕', '◕_◕', '◕‿◕'],
|
|
@@ -30,8 +31,6 @@ const SPEEDS = {
|
|
|
30
31
|
};
|
|
31
32
|
const DROWSY_FACE = '–_–';
|
|
32
33
|
const SLEEPING_FACE = '-‿-';
|
|
33
|
-
// Reserved vertical rows above the face. Every state renders exactly
|
|
34
|
-
// this many rows so Ink never has to reflow the layout below us.
|
|
35
34
|
const TOP_ROWS = 2;
|
|
36
35
|
export function toolToState(tool) {
|
|
37
36
|
if (tool === 'read_file')
|
|
@@ -49,6 +48,20 @@ export function toolToState(tool) {
|
|
|
49
48
|
return 'thinking';
|
|
50
49
|
}
|
|
51
50
|
const BlankRows = () => (_jsx(_Fragment, { children: Array.from({ length: TOP_ROWS }, (_, i) => (_jsx(Text, { children: " " }, i))) }));
|
|
51
|
+
// States whose color is semantic (meaning-bearing) — these stay their
|
|
52
|
+
// theme colors. Everything else drifts through the pastel palette.
|
|
53
|
+
function isSemantic(state) {
|
|
54
|
+
return (state === 'error' || state === 'denied' ||
|
|
55
|
+
state === 'success' || state === 'granted' ||
|
|
56
|
+
state === 'permission' || state === 'impatient');
|
|
57
|
+
}
|
|
58
|
+
function semanticColor(state) {
|
|
59
|
+
if (state === 'error' || state === 'denied')
|
|
60
|
+
return theme.error;
|
|
61
|
+
if (state === 'success' || state === 'granted')
|
|
62
|
+
return theme.success;
|
|
63
|
+
return theme.warning; // permission / impatient
|
|
64
|
+
}
|
|
52
65
|
export const MascotPrompt = ({ state, keepAwake = false, }) => {
|
|
53
66
|
const frames = FACES[state];
|
|
54
67
|
const [i, setI] = useState(0);
|
|
@@ -57,6 +70,7 @@ export const MascotPrompt = ({ state, keepAwake = false, }) => {
|
|
|
57
70
|
...thresholds,
|
|
58
71
|
disabled: keepAwake || idleDisabled(),
|
|
59
72
|
});
|
|
73
|
+
const slowColor = useSlowColor();
|
|
60
74
|
useEffect(() => {
|
|
61
75
|
setI(0);
|
|
62
76
|
if (frames.length <= 1)
|
|
@@ -70,12 +84,6 @@ export const MascotPrompt = ({ state, keepAwake = false, }) => {
|
|
|
70
84
|
if (idle === 'drowsy') {
|
|
71
85
|
return (_jsxs(Box, { flexDirection: "column", height: TOP_ROWS + 1, children: [_jsx(BlankRows, {}), _jsx(Text, { color: theme.muted, bold: true, children: ' ' + DROWSY_FACE })] }));
|
|
72
86
|
}
|
|
73
|
-
const color = state
|
|
74
|
-
? theme.error
|
|
75
|
-
: state === 'success' || state === 'granted'
|
|
76
|
-
? theme.success
|
|
77
|
-
: state === 'permission' || state === 'impatient'
|
|
78
|
-
? theme.warning
|
|
79
|
-
: theme.accent;
|
|
87
|
+
const color = isSemantic(state) ? semanticColor(state) : slowColor;
|
|
80
88
|
return (_jsxs(Box, { flexDirection: "column", height: TOP_ROWS + 1, children: [_jsx(BlankRows, {}), _jsx(Text, { color: color, bold: true, children: ' ' + frames[i] })] }));
|
|
81
89
|
};
|
package/dist/ui/RocketBar.js
CHANGED
|
@@ -3,7 +3,7 @@ import { useState, useEffect } from 'react';
|
|
|
3
3
|
import { Text } from 'ink';
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import { theme } from './theme.js';
|
|
6
|
-
import {
|
|
6
|
+
import { useSlowColor } from './useSlowColor.js';
|
|
7
7
|
function shouldAnimate() {
|
|
8
8
|
if (process.env.BLYUE_ANIMATE === '1')
|
|
9
9
|
return true;
|
|
@@ -25,20 +25,13 @@ function shouldAnimate() {
|
|
|
25
25
|
}
|
|
26
26
|
export const RocketBar = ({ label, loading, width }) => {
|
|
27
27
|
const [animate] = useState(() => shouldAnimate());
|
|
28
|
-
const [t, setT] = useState(0);
|
|
29
28
|
const [pos, setPos] = useState(0);
|
|
30
|
-
|
|
31
|
-
if (!animate)
|
|
32
|
-
return;
|
|
33
|
-
const int = setInterval(() => setT((v) => (v + 0.00625) % 1), 120);
|
|
34
|
-
return () => clearInterval(int);
|
|
35
|
-
}, [animate]);
|
|
29
|
+
const hue = useSlowColor(); // shared pastel clock — matches banner + face
|
|
36
30
|
const fixed = 5 + label.length;
|
|
37
31
|
const midLen = Math.max(0, width - fixed);
|
|
38
32
|
useEffect(() => {
|
|
39
33
|
if (!loading || midLen < 6)
|
|
40
34
|
return;
|
|
41
|
-
// Static mode: no rocket motion, just fixed at 1/3 position
|
|
42
35
|
if (!animate) {
|
|
43
36
|
setPos(Math.floor(midLen / 3));
|
|
44
37
|
return;
|
|
@@ -47,9 +40,8 @@ export const RocketBar = ({ label, loading, width }) => {
|
|
|
47
40
|
const int = setInterval(() => setPos((p) => (p + 1) % track), 90);
|
|
48
41
|
return () => clearInterval(int);
|
|
49
42
|
}, [loading, midLen, animate]);
|
|
50
|
-
const hue = animate ? hueAt(t) : hueAt(0.1);
|
|
51
43
|
if (!loading || midLen < 6) {
|
|
52
|
-
return (_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '╭─ ' }), _jsx(Text, { color:
|
|
44
|
+
return (_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '╭─ ' }), _jsx(Text, { color: hue, bold: true, children: label }), _jsx(Text, { color: theme.muted, children: ' ' + '─'.repeat(midLen) + '╮' })] }));
|
|
53
45
|
}
|
|
54
46
|
const p = Math.min(pos, Math.max(0, midLen - 3));
|
|
55
47
|
const before = '─'.repeat(p);
|
package/dist/ui/Settings.js
CHANGED
|
@@ -92,7 +92,9 @@ export const Settings = ({ onClose, onModelChange }) => {
|
|
|
92
92
|
const inner = width - 4;
|
|
93
93
|
const top = (label) => (_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '╭─ ' }), _jsx(Text, { color: theme.accent, bold: true, children: label }), _jsx(Text, { color: theme.muted, children: ' ' + '─'.repeat(Math.max(1, inner - label.length - 3)) + '╮' })] }));
|
|
94
94
|
const bot = (_jsx(Text, { color: theme.muted, children: '╰' + '─'.repeat(inner) + '╯' }));
|
|
95
|
-
|
|
95
|
+
// ⚠️ Changed from <Text> to <Box flexDirection="row"> — callers pass a
|
|
96
|
+
// <Box> containing a prompt + <KeyInput>, and Ink forbids Box-in-Text.
|
|
97
|
+
const row = (content) => (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: theme.muted, children: '│ ' }), content] }));
|
|
96
98
|
// ---- Main screen ----
|
|
97
99
|
if (screen === 'main') {
|
|
98
100
|
const baseUrlIsLocal = (cfg.baseUrl || '').includes('localhost') ||
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Pastel/faded palette — Claude-Code-inspired soft muting.
|
|
2
|
+
// All colors sit at ~76% lightness / ~48% saturation, so they read as
|
|
3
|
+
// "faded" on both dark and light terminals.
|
|
4
|
+
function hslToHex(h, s, l) {
|
|
5
|
+
s /= 100;
|
|
6
|
+
l /= 100;
|
|
7
|
+
const k = (n) => (n + h / 30) % 12;
|
|
8
|
+
const a = s * Math.min(l, 1 - l);
|
|
9
|
+
const f = (n) => {
|
|
10
|
+
const c = l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
11
|
+
return Math.round(255 * c);
|
|
12
|
+
};
|
|
13
|
+
const toHex = (x) => x.toString(16).padStart(2, '0');
|
|
14
|
+
return '#' + toHex(f(0)) + toHex(f(8)) + toHex(f(4));
|
|
15
|
+
}
|
|
16
|
+
/** Full-hue pastel cycle. t in [0, 1). */
|
|
17
|
+
export function pastelAt(t) {
|
|
18
|
+
const h = (((t % 1) + 1) % 1) * 360;
|
|
19
|
+
return hslToHex(h, 48, 76);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Warm-only variant — rose → peach → gold → sage → back. If the full
|
|
23
|
+
* wheel feels too rainbow-y for your taste, swap pastelAt -> pastelWarmAt
|
|
24
|
+
* in the four call sites below.
|
|
25
|
+
*/
|
|
26
|
+
export function pastelWarmAt(t) {
|
|
27
|
+
const t2 = ((t % 1) + 1) % 1;
|
|
28
|
+
const h = (320 + t2 * 110) % 360; // 320° → 70°, wraps through warm
|
|
29
|
+
return hslToHex(h, 52, 76);
|
|
30
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// A single module-level tick shared by every component that wants to
|
|
2
|
+
// fade in sync. One interval, N subscribers, no per-component drift.
|
|
3
|
+
const PERIOD_MS = 24_000; // full pastel cycle
|
|
4
|
+
const TICK_MS = 120;
|
|
5
|
+
let t = 0;
|
|
6
|
+
const listeners = new Set();
|
|
7
|
+
let interval = null;
|
|
8
|
+
function start() {
|
|
9
|
+
if (interval)
|
|
10
|
+
return;
|
|
11
|
+
interval = setInterval(() => {
|
|
12
|
+
t = (t + TICK_MS / PERIOD_MS) % 1;
|
|
13
|
+
for (const l of listeners)
|
|
14
|
+
l();
|
|
15
|
+
}, TICK_MS);
|
|
16
|
+
// Don't keep the Node process alive just to animate colors.
|
|
17
|
+
const anyInt = interval;
|
|
18
|
+
anyInt.unref?.();
|
|
19
|
+
}
|
|
20
|
+
function stop() {
|
|
21
|
+
if (interval) {
|
|
22
|
+
clearInterval(interval);
|
|
23
|
+
interval = null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function subscribeSlowTick(fn) {
|
|
27
|
+
listeners.add(fn);
|
|
28
|
+
if (listeners.size === 1)
|
|
29
|
+
start();
|
|
30
|
+
return () => {
|
|
31
|
+
listeners.delete(fn);
|
|
32
|
+
if (listeners.size === 0)
|
|
33
|
+
stop();
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function getSlowTick() {
|
|
37
|
+
return t;
|
|
38
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react';
|
|
2
|
+
import { subscribeSlowTick, getSlowTick } from './slowTick.js';
|
|
3
|
+
import { pastelAt, pastelWarmAt } from './pastel.js';
|
|
4
|
+
/**
|
|
5
|
+
* Returns a pastel hex color that drifts slowly and stays in sync with
|
|
6
|
+
* every other component that uses this hook.
|
|
7
|
+
*/
|
|
8
|
+
export function useSlowColor(warm = false) {
|
|
9
|
+
const t = useSyncExternalStore(subscribeSlowTick, getSlowTick, getSlowTick);
|
|
10
|
+
return warm ? pastelWarmAt(t) : pastelAt(t);
|
|
11
|
+
}
|