bingocode 1.1.112 → 1.1.113
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/.claude/settings.local.json +7 -0
- package/package.json +2 -1
- package/src/manager/CliMenuManager.tsx +1238 -1234
- package/src/manager/CliMenuUi.tsx +22 -9
- package/src/manager/TopToolbar.tsx +137 -111
|
@@ -266,16 +266,29 @@ export const TopBar: React.FC<{
|
|
|
266
266
|
compactLogo: React.ReactNode;
|
|
267
267
|
toolbar?: React.ReactNode;
|
|
268
268
|
ip?: string;
|
|
269
|
-
}> = memo(({ ready, page, width = 80, height = 5, homeLogo, compactLogo, toolbar, ip }) =>
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
269
|
+
}> = memo(({ ready, page, width = 80, height = 5, homeLogo, compactLogo, toolbar, ip }) => {
|
|
270
|
+
const isHome = page === null;
|
|
271
|
+
return (
|
|
272
|
+
<Panel width={width} height={height} borderStyle="round" paddingX={1} paddingY={0}>
|
|
273
|
+
<Box width={width - 4} flexDirection="row" alignItems="flex-start">
|
|
274
|
+
{/* Left Section: Welcome Text & IP */}
|
|
275
|
+
<Box flexDirection="column" width={20} marginRight={2}>
|
|
276
|
+
<Text bold color="cyan">{isHome ? 'Welcome Bingo\nCode' : 'Bingo Code'}</Text>
|
|
277
|
+
{ip ? (
|
|
278
|
+
<Box marginTop={1}>
|
|
279
|
+
<Text color="green">IP: {ip.replace(' ','\n ')}</Text>
|
|
280
|
+
</Box>
|
|
281
|
+
) : null}
|
|
282
|
+
</Box>
|
|
283
|
+
|
|
284
|
+
{/* Center/Right Section: Logo + Toolbar (Dynamic) */}
|
|
285
|
+
<Box flexGrow={1} flexDirection="column">
|
|
286
|
+
{toolbar}
|
|
287
|
+
</Box>
|
|
274
288
|
</Box>
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
));
|
|
289
|
+
</Panel>
|
|
290
|
+
);
|
|
291
|
+
});
|
|
279
292
|
|
|
280
293
|
// InfoPair (Label fixed width for column alignment)
|
|
281
294
|
export const InfoPair: React.FC<{ label: string; value: string; labelColor?: string; valueColor?: string; labelWidth?: number }> = memo(({
|
|
@@ -1,111 +1,137 @@
|
|
|
1
|
-
//@C:M ID=M.UI.TopToolbar;K=M;V=1.
|
|
2
|
-
import React, { memo, useMemo } from 'react';
|
|
3
|
-
import { Box } from 'ink';
|
|
4
|
-
import { Chip
|
|
5
|
-
import { useTheme } from '../components/design-system/ThemeProvider.
|
|
6
|
-
import { getGlobalConfig, getCurrentProjectConfig
|
|
7
|
-
import { getCwd } from '../utils/cwd.js';
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
1
|
+
//@C:M ID=M.UI.TopToolbar;K=M;V=1.3;P=top toolbar;D=CLI;M=cli;S=ui
|
|
2
|
+
import React, { memo, useMemo } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import { Chip } from './CliMenuUi.tsx';
|
|
5
|
+
import { useTheme } from '../components/design-system/ThemeProvider.tsx';
|
|
6
|
+
import { getGlobalConfig, getCurrentProjectConfig } from '../utils/config.ts';
|
|
7
|
+
import { getCwd } from '../utils/cwd.js';
|
|
8
|
+
import type { ClawdPose } from '../components/LogoV2/Clawd.tsx';
|
|
9
|
+
import { Clawd } from '../components/LogoV2/Clawd.tsx';
|
|
10
|
+
import { AnimatedClawd } from '../components/LogoV2/AnimatedClawd.tsx';
|
|
11
|
+
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import os from 'os';
|
|
15
|
+
|
|
16
|
+
type Props = {
|
|
17
|
+
ready: boolean;
|
|
18
|
+
page: string | null;
|
|
19
|
+
animEnabled: boolean;
|
|
20
|
+
tipsEnabled: boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function countLeases(): number {
|
|
24
|
+
try {
|
|
25
|
+
const LEASES_DIR = path.join(os.homedir(), '.claude-cli', 'runtime', 'leases');
|
|
26
|
+
if (!fs.existsSync(LEASES_DIR)) return 0;
|
|
27
|
+
const files = fs.readdirSync(LEASES_DIR);
|
|
28
|
+
let alive = 0;
|
|
29
|
+
for (const f of files) {
|
|
30
|
+
try {
|
|
31
|
+
const p = path.join(LEASES_DIR, f);
|
|
32
|
+
const raw = fs.readFileSync(p, 'utf-8');
|
|
33
|
+
const data = JSON.parse(raw);
|
|
34
|
+
if (data?.pid) {
|
|
35
|
+
try {
|
|
36
|
+
process.kill(data.pid, 0);
|
|
37
|
+
alive++;
|
|
38
|
+
} catch {
|
|
39
|
+
// PID not alive
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch {}
|
|
43
|
+
}
|
|
44
|
+
return alive;
|
|
45
|
+
} catch {
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function basename(p: string) {
|
|
51
|
+
if (!p) return '';
|
|
52
|
+
const parts = p.split(/[/\\]/).filter(Boolean);
|
|
53
|
+
return parts[parts.length - 1] || p;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Compact 1/6 size Mini-Clawd mascot string.
|
|
58
|
+
* Approx 3x2 characters.
|
|
59
|
+
*/
|
|
60
|
+
const MiniClawd = () => (
|
|
61
|
+
<Box flexDirection="column" marginRight={1}>
|
|
62
|
+
<Text color="cyan">▟█▙</Text>
|
|
63
|
+
<Text color="cyan">▜██</Text>
|
|
64
|
+
</Box>
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
export const TopToolbar: React.FC<Props> = memo(({ ready, page, animEnabled, tipsEnabled }) => {
|
|
68
|
+
const [theme] = useTheme();
|
|
69
|
+
|
|
70
|
+
// Polling for window count
|
|
71
|
+
const [windowCount, setWindowCount] = React.useState(0);
|
|
72
|
+
React.useEffect(() => {
|
|
73
|
+
const update = () => setWindowCount(countLeases());
|
|
74
|
+
update();
|
|
75
|
+
const id = setInterval(update, 10000); // 10s poll
|
|
76
|
+
return () => clearInterval(id);
|
|
77
|
+
}, []);
|
|
78
|
+
|
|
79
|
+
// Only read config when ready
|
|
80
|
+
const { projectName } = useMemo(() => {
|
|
81
|
+
if (!ready) {
|
|
82
|
+
return { projectName: '' };
|
|
83
|
+
}
|
|
84
|
+
const _cwd = getCwd();
|
|
85
|
+
let _projectName = '';
|
|
86
|
+
try {
|
|
87
|
+
const prj = getCurrentProjectConfig();
|
|
88
|
+
_projectName = (prj && (prj.name || prj.projectName || prj.id)) || (typeof _cwd === 'string' ? basename(_cwd) : '');
|
|
89
|
+
} catch {
|
|
90
|
+
_projectName = typeof _cwd === 'string' ? basename(_cwd) : '';
|
|
91
|
+
}
|
|
92
|
+
return { projectName: _projectName };
|
|
93
|
+
}, [ready]);
|
|
94
|
+
|
|
95
|
+
// Theme name
|
|
96
|
+
const themeLabel = String(theme || (ready ? (getGlobalConfig()?.theme ?? 'system') : '…'));
|
|
97
|
+
|
|
98
|
+
// Static Clawd pose
|
|
99
|
+
const clawdPose: ClawdPose = useMemo(() => {
|
|
100
|
+
if (!ready) return 'default';
|
|
101
|
+
if (page === null) return animEnabled ? 'arms-up' : 'default';
|
|
102
|
+
return tipsEnabled ? 'look-left' : 'look-right';
|
|
103
|
+
}, [ready, page, animEnabled, tipsEnabled]);
|
|
104
|
+
|
|
105
|
+
const uiTone = (String(theme) === 'dark') ? 'accent' : (String(theme) === 'highContrast' ? 'warning' : 'info');
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<Box flexDirection="row" width="100%" alignItems="flex-start" justifyContent="space-between">
|
|
109
|
+
{/* Left: Main Mascot + Compact Grid */}
|
|
110
|
+
<Box alignItems="flex-start">
|
|
111
|
+
<Box marginRight={2}>
|
|
112
|
+
{animEnabled ? <AnimatedClawd /> : <Clawd pose={clawdPose} />}
|
|
113
|
+
</Box>
|
|
114
|
+
|
|
115
|
+
{/* Visual Window Grid (Mini-mascots 1/6 size) */}
|
|
116
|
+
<Box flexDirection="row" flexWrap="wrap" maxWidth={40}>
|
|
117
|
+
{Array.from({ length: windowCount }).map((_, i) => (
|
|
118
|
+
<MiniClawd key={i} />
|
|
119
|
+
))}
|
|
120
|
+
</Box>
|
|
121
|
+
</Box>
|
|
122
|
+
|
|
123
|
+
{/* Right: Status Info Chips (Vertical Stack) */}
|
|
124
|
+
<Box flexDirection="column" alignItems="flex-end">
|
|
125
|
+
<Chip label="Theme" value={themeLabel} tone="accent" />
|
|
126
|
+
<Chip label="Project" value={projectName || '—'} tone="info" />
|
|
127
|
+
<Chip
|
|
128
|
+
label="UI"
|
|
129
|
+
value={tipsEnabled ? 'Tips On' : 'Tips Off'}
|
|
130
|
+
tone={uiTone as any}
|
|
131
|
+
/>
|
|
132
|
+
</Box>
|
|
133
|
+
</Box>
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
export default TopToolbar;
|