@bluehawks/cli 1.0.37 → 1.0.39
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/cli/app.d.ts.map +1 -1
- package/dist/cli/app.js +112 -134
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/components/ChatArea.d.ts +21 -0
- package/dist/cli/components/ChatArea.d.ts.map +1 -0
- package/dist/cli/components/ChatArea.js +37 -0
- package/dist/cli/components/ChatArea.js.map +1 -0
- package/dist/cli/components/Header.d.ts +7 -0
- package/dist/cli/components/Header.d.ts.map +1 -0
- package/dist/cli/components/Header.js +29 -0
- package/dist/cli/components/Header.js.map +1 -0
- package/dist/cli/components/Layout.d.ts +21 -0
- package/dist/cli/components/Layout.d.ts.map +1 -0
- package/dist/cli/components/Layout.js +9 -0
- package/dist/cli/components/Layout.js.map +1 -0
- package/dist/cli/components/Sidebar.d.ts +9 -0
- package/dist/cli/components/Sidebar.d.ts.map +1 -0
- package/dist/cli/components/Sidebar.js +24 -0
- package/dist/cli/components/Sidebar.js.map +1 -0
- package/dist/cli/components/types.d.ts +29 -0
- package/dist/cli/components/types.d.ts.map +1 -0
- package/dist/cli/components/types.js +5 -0
- package/dist/cli/components/types.js.map +1 -0
- package/dist/config/constants.d.ts +1 -1
- package/dist/config/constants.js +1 -1
- package/package.json +1 -1
- package/src/cli/app.tsx +216 -401
- package/src/cli/components/ChatArea.tsx +127 -0
- package/src/cli/components/Header.tsx +86 -0
- package/src/cli/components/Layout.tsx +60 -0
- package/src/cli/components/Sidebar.tsx +68 -0
- package/src/cli/components/types.ts +32 -0
- package/src/config/constants.ts +1 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import Spinner from 'ink-spinner';
|
|
4
|
+
import TextInput from 'ink-text-input';
|
|
5
|
+
import { COLORS } from '../../config/constants.js';
|
|
6
|
+
|
|
7
|
+
interface MessageDisplay {
|
|
8
|
+
role: 'user' | 'assistant' | 'tool' | 'system' | 'error';
|
|
9
|
+
content: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ChatAreaProps {
|
|
13
|
+
messages: MessageDisplay[];
|
|
14
|
+
streamingContent: string;
|
|
15
|
+
currentTool: string | null;
|
|
16
|
+
isProcessing: boolean;
|
|
17
|
+
input: string;
|
|
18
|
+
setInput: (v: string) => void;
|
|
19
|
+
onSubmit: (v: string) => void;
|
|
20
|
+
pendingApproval: { toolName: string; args: any } | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const ChatArea: React.FC<ChatAreaProps> = ({
|
|
24
|
+
messages,
|
|
25
|
+
streamingContent,
|
|
26
|
+
currentTool,
|
|
27
|
+
isProcessing,
|
|
28
|
+
input,
|
|
29
|
+
setInput,
|
|
30
|
+
onSubmit,
|
|
31
|
+
pendingApproval,
|
|
32
|
+
}) => {
|
|
33
|
+
const getRoleColor = (role: MessageDisplay['role']): string => {
|
|
34
|
+
switch (role) {
|
|
35
|
+
case 'user': return COLORS.primary;
|
|
36
|
+
case 'assistant': return COLORS.success;
|
|
37
|
+
case 'tool': return COLORS.info;
|
|
38
|
+
case 'system': return COLORS.warning;
|
|
39
|
+
case 'error': return COLORS.error;
|
|
40
|
+
default: return COLORS.muted;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Box flexDirection="column" borderStyle="single" borderColor="gray" flexGrow={1} paddingX={1} overflowY="hidden">
|
|
46
|
+
{/* Messages Area */}
|
|
47
|
+
<Box flexDirection="column" flexGrow={1} justifyContent="flex-end">
|
|
48
|
+
{messages.slice(-15).map((msg, i) => { // Show last 15 messages to fit
|
|
49
|
+
if (msg.role === 'tool') {
|
|
50
|
+
try {
|
|
51
|
+
const content = JSON.parse(msg.content);
|
|
52
|
+
if (content.type === 'tool_start') {
|
|
53
|
+
const jsonStr = JSON.stringify(content.args);
|
|
54
|
+
const displayArgs = jsonStr.length < 60 ? jsonStr : '...';
|
|
55
|
+
return (
|
|
56
|
+
<Box key={i} flexDirection="column" borderStyle="round" borderColor="magenta" paddingX={1} marginBottom={1}>
|
|
57
|
+
<Text color="magenta">⚡ TOOL: {content.name} <Text color="gray">{displayArgs}</Text></Text>
|
|
58
|
+
</Box>
|
|
59
|
+
);
|
|
60
|
+
} else if (content.type === 'tool_end') {
|
|
61
|
+
return (
|
|
62
|
+
<Box key={i} flexDirection="column" borderStyle="round" borderColor="green" paddingX={1} marginBottom={1}>
|
|
63
|
+
<Text color="green">✅ RESULT: {content.name}</Text>
|
|
64
|
+
</Box>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
return <Text key={i} color="gray">🔧 {msg.content}</Text>;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Box key={i} marginBottom={1} flexDirection="column">
|
|
74
|
+
<Text bold color={getRoleColor(msg.role)}>
|
|
75
|
+
{msg.role === 'user' ? '👤 YOU ' : msg.role === 'assistant' ? '🦅 BLUEHAWKS AI ' : 'ℹ️ SYSTEM '}
|
|
76
|
+
</Text>
|
|
77
|
+
<Box marginLeft={2}>
|
|
78
|
+
<Text color="white">
|
|
79
|
+
{(msg.role === 'user' || msg.role === 'assistant') ? '🔹 ' : ''}{msg.content}
|
|
80
|
+
</Text>
|
|
81
|
+
</Box>
|
|
82
|
+
</Box>
|
|
83
|
+
);
|
|
84
|
+
})}
|
|
85
|
+
|
|
86
|
+
{/* Streaming */}
|
|
87
|
+
{streamingContent && (
|
|
88
|
+
<Box marginBottom={1} flexDirection="column">
|
|
89
|
+
<Text bold color={COLORS.success}>🦅 BLUEHAWKS AI </Text>
|
|
90
|
+
<Box marginLeft={2}>
|
|
91
|
+
<Text color="white">🔹 {streamingContent}</Text>
|
|
92
|
+
</Box>
|
|
93
|
+
</Box>
|
|
94
|
+
)}
|
|
95
|
+
|
|
96
|
+
{/* Current Tool Spinner */}
|
|
97
|
+
{currentTool && !messages.some(m => m.role === 'tool' && m.content.includes(currentTool) && m.content.includes('tool_start')) && (
|
|
98
|
+
<Box marginBottom={1}>
|
|
99
|
+
<Spinner type="dots" />
|
|
100
|
+
<Text color="magenta"> Executing {currentTool}...</Text>
|
|
101
|
+
</Box>
|
|
102
|
+
)}
|
|
103
|
+
|
|
104
|
+
{/* Approval Prompt */}
|
|
105
|
+
{pendingApproval && (
|
|
106
|
+
<Box borderStyle="double" borderColor="yellow" padding={1} marginY={1}>
|
|
107
|
+
<Text color="yellow" bold>⚠️ APPROVAL REQUIRED: {pendingApproval.toolName}</Text>
|
|
108
|
+
<Text>Press Y to approve, N to deny</Text>
|
|
109
|
+
</Box>
|
|
110
|
+
)}
|
|
111
|
+
</Box>
|
|
112
|
+
|
|
113
|
+
{/* Input Area */}
|
|
114
|
+
{!pendingApproval && (
|
|
115
|
+
<Box borderStyle="single" borderTop borderColor="gray" paddingTop={0} marginTop={1}>
|
|
116
|
+
<Text color="blue">❯ </Text>
|
|
117
|
+
<TextInput
|
|
118
|
+
value={input}
|
|
119
|
+
onChange={setInput}
|
|
120
|
+
onSubmit={onSubmit}
|
|
121
|
+
placeholder={isProcessing ? "Processing..." : "Command..."}
|
|
122
|
+
/>
|
|
123
|
+
</Box>
|
|
124
|
+
)}
|
|
125
|
+
</Box>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { SystemEvent } from './types.js';
|
|
4
|
+
|
|
5
|
+
const Branding = () => (
|
|
6
|
+
<Box flexDirection="column" borderStyle="round" borderColor="cyan" paddingX={1} width={30} height={10}>
|
|
7
|
+
<Text color="cyan" bold>
|
|
8
|
+
{`
|
|
9
|
+
___ __ __ __
|
|
10
|
+
| _ )| | | || |
|
|
11
|
+
| _ \\| |__| || |__
|
|
12
|
+
|___/|____|__||____|
|
|
13
|
+
_ _ __ __ __ __
|
|
14
|
+
| || | / \\ \\ \\/ /| _\\
|
|
15
|
+
| __ || || | \\ / | _\\
|
|
16
|
+
|_||_||_||_| \\/ |__/
|
|
17
|
+
`}
|
|
18
|
+
</Text>
|
|
19
|
+
<Text color="blue" dimColor>-------------------------</Text>
|
|
20
|
+
<Text color="cyan"> OPERATIONAL</Text>
|
|
21
|
+
</Box>
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const SystemMonitor: React.FC<{ events: SystemEvent[] }> = ({ events }) => {
|
|
25
|
+
return (
|
|
26
|
+
<Box flexDirection="column" borderStyle="round" borderColor="green" flexGrow={1} height={10} marginLeft={1}>
|
|
27
|
+
<Box borderStyle="single" borderBottom borderColor="green" paddingX={1}>
|
|
28
|
+
<Text color="green">⚡ SYSTEM LOG [Live]</Text>
|
|
29
|
+
</Box>
|
|
30
|
+
<Box flexDirection="column" paddingX={1}>
|
|
31
|
+
{events.slice(-6).map((event) => (
|
|
32
|
+
<Text key={event.id} wrap="truncate">
|
|
33
|
+
<Text color="gray">[{event.timestamp.split('T')[1]?.split('.')[0]}] </Text>
|
|
34
|
+
<Text color={event.type === 'error' ? 'red' : event.type === 'success' ? 'green' : 'white'}>
|
|
35
|
+
{event.type === 'success' ? '✓ ' : event.type === 'error' ? '✗ ' : 'ℹ '}
|
|
36
|
+
{event.message}
|
|
37
|
+
</Text>
|
|
38
|
+
</Text>
|
|
39
|
+
))}
|
|
40
|
+
</Box>
|
|
41
|
+
</Box>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const NetworkActivity: React.FC<{ tps: number }> = ({ tps }) => {
|
|
46
|
+
// Simulated sparkline or bar chart
|
|
47
|
+
const [history, setHistory] = useState<number[]>(new Array(10).fill(0));
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
setHistory(prev => [...prev.slice(1), tps]);
|
|
51
|
+
}, [tps]);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Box flexDirection="column" borderStyle="round" borderColor="magenta" width={30} height={10} marginLeft={1}>
|
|
55
|
+
<Box borderStyle="single" borderBottom borderColor="magenta" paddingX={1}>
|
|
56
|
+
<Text color="magenta">🧠 NEURAL VELOCITY</Text>
|
|
57
|
+
</Box>
|
|
58
|
+
<Box flexDirection="column" paddingX={1} paddingTop={1}>
|
|
59
|
+
<Box justifyContent="space-between">
|
|
60
|
+
<Text>Speed:</Text>
|
|
61
|
+
<Text color="magenta" bold>{tps.toFixed(1)} t/s</Text>
|
|
62
|
+
</Box>
|
|
63
|
+
<Box marginTop={1} alignItems="flex-end" height={3}>
|
|
64
|
+
{history.map((val, i) => {
|
|
65
|
+
const height = Math.min(3, Math.ceil(val / 10)); // Scale roughly
|
|
66
|
+
return (
|
|
67
|
+
<Box key={i} flexDirection="column" marginRight={1}>
|
|
68
|
+
<Text color="magenta">
|
|
69
|
+
{height === 0 ? '_' : height === 1 ? ' ' : height === 2 ? '▂' : '▃'}
|
|
70
|
+
</Text>
|
|
71
|
+
</Box>
|
|
72
|
+
);
|
|
73
|
+
})}
|
|
74
|
+
</Box>
|
|
75
|
+
</Box>
|
|
76
|
+
</Box>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const Header: React.FC<{ events: SystemEvent[]; tps: number }> = ({ events, tps }) => (
|
|
81
|
+
<Box flexDirection="row" width="100%">
|
|
82
|
+
<Branding />
|
|
83
|
+
<SystemMonitor events={events} />
|
|
84
|
+
<NetworkActivity tps={tps} />
|
|
85
|
+
</Box>
|
|
86
|
+
);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box } from 'ink';
|
|
3
|
+
import { Header } from './Header.js';
|
|
4
|
+
import { Sidebar } from './Sidebar.js';
|
|
5
|
+
import { ChatArea } from './ChatArea.js';
|
|
6
|
+
import { SystemEvent, ToolActivity } from './types.js';
|
|
7
|
+
|
|
8
|
+
interface LayoutProps {
|
|
9
|
+
// Header Data
|
|
10
|
+
systemEvents: SystemEvent[];
|
|
11
|
+
tps: number;
|
|
12
|
+
|
|
13
|
+
// Sidebar Data
|
|
14
|
+
model: string;
|
|
15
|
+
contextUsage: number;
|
|
16
|
+
totalTokens: number;
|
|
17
|
+
toolHistory: ToolActivity[];
|
|
18
|
+
|
|
19
|
+
// Chat Data
|
|
20
|
+
messages: any[];
|
|
21
|
+
streamingContent: string;
|
|
22
|
+
currentTool: string | null;
|
|
23
|
+
isProcessing: boolean;
|
|
24
|
+
input: string;
|
|
25
|
+
setInput: (v: string) => void;
|
|
26
|
+
onSubmit: (v: string) => void;
|
|
27
|
+
pendingApproval: any;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const Layout: React.FC<LayoutProps> = (props) => {
|
|
31
|
+
return (
|
|
32
|
+
<Box flexDirection="column" padding={1} height="100%">
|
|
33
|
+
{/* Top Zone: Header & Telemetry */}
|
|
34
|
+
<Header events={props.systemEvents} tps={props.tps} />
|
|
35
|
+
|
|
36
|
+
{/* Middle Zone: Chat & Sidebar */}
|
|
37
|
+
<Box flexDirection="row" flexGrow={1} marginTop={1}>
|
|
38
|
+
{/* Main Chat Area */}
|
|
39
|
+
<ChatArea
|
|
40
|
+
messages={props.messages}
|
|
41
|
+
streamingContent={props.streamingContent}
|
|
42
|
+
currentTool={props.currentTool}
|
|
43
|
+
isProcessing={props.isProcessing}
|
|
44
|
+
input={props.input}
|
|
45
|
+
setInput={props.setInput}
|
|
46
|
+
onSubmit={props.onSubmit}
|
|
47
|
+
pendingApproval={props.pendingApproval}
|
|
48
|
+
/>
|
|
49
|
+
|
|
50
|
+
{/* Right Sidebar */}
|
|
51
|
+
<Sidebar
|
|
52
|
+
model={props.model}
|
|
53
|
+
contextUsage={props.contextUsage}
|
|
54
|
+
totalTokens={props.totalTokens}
|
|
55
|
+
toolHistory={props.toolHistory}
|
|
56
|
+
/>
|
|
57
|
+
</Box>
|
|
58
|
+
</Box>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { ToolActivity } from './types.js';
|
|
4
|
+
|
|
5
|
+
const SessionInfo: React.FC<{ model: string; contextUsage: number; totalTokens: number }> = ({ model, contextUsage, totalTokens }) => {
|
|
6
|
+
const bars = Math.floor(contextUsage * 20);
|
|
7
|
+
const emptyBars = 20 - bars;
|
|
8
|
+
const barStr = '█'.repeat(bars) + '░'.repeat(emptyBars);
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<Box flexDirection="column" borderStyle="round" borderColor="yellow" paddingX={1} marginBottom={1}>
|
|
12
|
+
<Text color="yellow" bold>SESSION INTEL</Text>
|
|
13
|
+
<Box flexDirection="column" marginTop={1}>
|
|
14
|
+
<Text color="gray">Model:</Text>
|
|
15
|
+
<Text color="white">{model}</Text>
|
|
16
|
+
|
|
17
|
+
<Box marginTop={1} flexDirection="column">
|
|
18
|
+
<Text color="gray">Context Load:</Text>
|
|
19
|
+
<Text color={contextUsage > 0.8 ? 'red' : 'green'}>{barStr}</Text>
|
|
20
|
+
<Text color="gray">{(contextUsage * 100).toFixed(1)}%</Text>
|
|
21
|
+
</Box>
|
|
22
|
+
|
|
23
|
+
<Box marginTop={1} flexDirection="column">
|
|
24
|
+
<Text color="gray">Total Tokens:</Text>
|
|
25
|
+
<Text color="white">{totalTokens.toLocaleString()}</Text>
|
|
26
|
+
</Box>
|
|
27
|
+
</Box>
|
|
28
|
+
</Box>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const ToolMap: React.FC<{ history: ToolActivity[] }> = ({ history }) => {
|
|
33
|
+
// Create a fixed grid of 5x5 blocks (25 slots)
|
|
34
|
+
// Fill from end of history reversed
|
|
35
|
+
const slots = new Array(25).fill(null);
|
|
36
|
+
const recentHistory = history.slice(-25).reverse(); // Newest first
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Box flexDirection="column" borderStyle="round" borderColor="blue" paddingX={1} flexGrow={1}>
|
|
40
|
+
<Text color="blue" bold>TOOL ACTIVITY</Text>
|
|
41
|
+
<Box flexDirection="row" flexWrap="wrap" marginTop={1} width={15}>
|
|
42
|
+
{slots.map((_, i) => {
|
|
43
|
+
const activity = recentHistory[i];
|
|
44
|
+
if (!activity) {
|
|
45
|
+
return <Text key={i} color="gray">▪ </Text>;
|
|
46
|
+
}
|
|
47
|
+
const color = activity.status === 'success' ? 'green' : activity.status === 'failed' ? 'red' : 'yellow';
|
|
48
|
+
return <Text key={i} color={color}>■ </Text>;
|
|
49
|
+
})}
|
|
50
|
+
</Box>
|
|
51
|
+
<Box marginTop={1}>
|
|
52
|
+
<Text color="gray" dimColor>Latest: {recentHistory[0]?.toolName || 'None'}</Text>
|
|
53
|
+
</Box>
|
|
54
|
+
</Box>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const Sidebar: React.FC<{
|
|
59
|
+
model: string;
|
|
60
|
+
contextUsage: number;
|
|
61
|
+
totalTokens: number;
|
|
62
|
+
toolHistory: ToolActivity[];
|
|
63
|
+
}> = ({ model, contextUsage, totalTokens, toolHistory }) => (
|
|
64
|
+
<Box flexDirection="column" width={24} marginLeft={1}>
|
|
65
|
+
<SessionInfo model={model} contextUsage={contextUsage} totalTokens={totalTokens} />
|
|
66
|
+
<ToolMap history={toolHistory} />
|
|
67
|
+
</Box>
|
|
68
|
+
);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the Cyberpunk Dashboard UI
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface SystemEvent {
|
|
6
|
+
id: string;
|
|
7
|
+
timestamp: string;
|
|
8
|
+
message: string;
|
|
9
|
+
type: 'info' | 'success' | 'error' | 'warning';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ToolActivity {
|
|
13
|
+
id: string;
|
|
14
|
+
toolName: string;
|
|
15
|
+
status: 'running' | 'success' | 'failed';
|
|
16
|
+
timestamp: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface DragMetric {
|
|
20
|
+
label: string;
|
|
21
|
+
value: string | number;
|
|
22
|
+
unit?: string;
|
|
23
|
+
color?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DashboardState {
|
|
27
|
+
events: SystemEvent[];
|
|
28
|
+
toolHistory: ToolActivity[];
|
|
29
|
+
tokensPerSecond: number;
|
|
30
|
+
contextUsage: number; // 0 to 1
|
|
31
|
+
totalTokens: number;
|
|
32
|
+
}
|
package/src/config/constants.ts
CHANGED
|
@@ -10,7 +10,7 @@ export const DEFAULT_RERANK_MODEL = 'Qwen/Qwen3-Reranker-0.6B';
|
|
|
10
10
|
|
|
11
11
|
// CLI Metadata
|
|
12
12
|
export const CLI_NAME = 'bluehawks';
|
|
13
|
-
export const CLI_VERSION = '1.0.
|
|
13
|
+
export const CLI_VERSION = '1.0.39';
|
|
14
14
|
export const CLI_DESCRIPTION = 'A production-ready multi-agent AI CLI assistant';
|
|
15
15
|
|
|
16
16
|
// Configuration Paths
|