@bluehawks/cli 1.0.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/.eslintrc.json +36 -0
- package/.prettierrc +8 -0
- package/README.md +288 -0
- package/dist/cli/app.d.ts +12 -0
- package/dist/cli/app.d.ts.map +1 -0
- package/dist/cli/app.js +201 -0
- package/dist/cli/app.js.map +1 -0
- package/dist/cli/commands/index.d.ts +56 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/index.js +201 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/config/constants.d.ts +32 -0
- package/dist/config/constants.d.ts.map +1 -0
- package/dist/config/constants.js +39 -0
- package/dist/config/constants.js.map +1 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +4 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/schema.d.ts +56 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +28 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/settings.d.ts +20 -0
- package/dist/config/settings.d.ts.map +1 -0
- package/dist/config/settings.js +102 -0
- package/dist/config/settings.js.map +1 -0
- package/dist/core/agents/agent.d.ts +33 -0
- package/dist/core/agents/agent.d.ts.map +1 -0
- package/dist/core/agents/agent.js +156 -0
- package/dist/core/agents/agent.js.map +1 -0
- package/dist/core/agents/index.d.ts +3 -0
- package/dist/core/agents/index.d.ts.map +1 -0
- package/dist/core/agents/index.js +3 -0
- package/dist/core/agents/index.js.map +1 -0
- package/dist/core/agents/orchestrator.d.ts +56 -0
- package/dist/core/agents/orchestrator.d.ts.map +1 -0
- package/dist/core/agents/orchestrator.js +151 -0
- package/dist/core/agents/orchestrator.js.map +1 -0
- package/dist/core/api/client.d.ts +46 -0
- package/dist/core/api/client.d.ts.map +1 -0
- package/dist/core/api/client.js +223 -0
- package/dist/core/api/client.js.map +1 -0
- package/dist/core/api/index.d.ts +3 -0
- package/dist/core/api/index.d.ts.map +1 -0
- package/dist/core/api/index.js +3 -0
- package/dist/core/api/index.js.map +1 -0
- package/dist/core/api/types.d.ts +126 -0
- package/dist/core/api/types.d.ts.map +1 -0
- package/dist/core/api/types.js +16 -0
- package/dist/core/api/types.js.map +1 -0
- package/dist/core/hooks/index.d.ts +3 -0
- package/dist/core/hooks/index.d.ts.map +1 -0
- package/dist/core/hooks/index.js +3 -0
- package/dist/core/hooks/index.js.map +1 -0
- package/dist/core/hooks/manager.d.ts +43 -0
- package/dist/core/hooks/manager.d.ts.map +1 -0
- package/dist/core/hooks/manager.js +178 -0
- package/dist/core/hooks/manager.js.map +1 -0
- package/dist/core/hooks/types.d.ts +68 -0
- package/dist/core/hooks/types.d.ts.map +1 -0
- package/dist/core/hooks/types.js +6 -0
- package/dist/core/hooks/types.js.map +1 -0
- package/dist/core/mcp/client.d.ts +48 -0
- package/dist/core/mcp/client.d.ts.map +1 -0
- package/dist/core/mcp/client.js +139 -0
- package/dist/core/mcp/client.js.map +1 -0
- package/dist/core/mcp/index.d.ts +3 -0
- package/dist/core/mcp/index.d.ts.map +1 -0
- package/dist/core/mcp/index.js +3 -0
- package/dist/core/mcp/index.js.map +1 -0
- package/dist/core/mcp/manager.d.ts +46 -0
- package/dist/core/mcp/manager.d.ts.map +1 -0
- package/dist/core/mcp/manager.js +133 -0
- package/dist/core/mcp/manager.js.map +1 -0
- package/dist/core/plugins/index.d.ts +3 -0
- package/dist/core/plugins/index.d.ts.map +1 -0
- package/dist/core/plugins/index.js +3 -0
- package/dist/core/plugins/index.js.map +1 -0
- package/dist/core/plugins/loader.d.ts +63 -0
- package/dist/core/plugins/loader.d.ts.map +1 -0
- package/dist/core/plugins/loader.js +258 -0
- package/dist/core/plugins/loader.js.map +1 -0
- package/dist/core/plugins/types.d.ts +95 -0
- package/dist/core/plugins/types.d.ts.map +1 -0
- package/dist/core/plugins/types.js +6 -0
- package/dist/core/plugins/types.js.map +1 -0
- package/dist/core/session/index.d.ts +3 -0
- package/dist/core/session/index.d.ts.map +1 -0
- package/dist/core/session/index.js +3 -0
- package/dist/core/session/index.js.map +1 -0
- package/dist/core/session/manager.d.ts +57 -0
- package/dist/core/session/manager.d.ts.map +1 -0
- package/dist/core/session/manager.js +182 -0
- package/dist/core/session/manager.js.map +1 -0
- package/dist/core/session/storage.d.ts +42 -0
- package/dist/core/session/storage.d.ts.map +1 -0
- package/dist/core/session/storage.js +138 -0
- package/dist/core/session/storage.js.map +1 -0
- package/dist/core/tools/definitions/file.d.ts +6 -0
- package/dist/core/tools/definitions/file.d.ts.map +1 -0
- package/dist/core/tools/definitions/file.js +276 -0
- package/dist/core/tools/definitions/file.js.map +1 -0
- package/dist/core/tools/definitions/git.d.ts +6 -0
- package/dist/core/tools/definitions/git.d.ts.map +1 -0
- package/dist/core/tools/definitions/git.js +294 -0
- package/dist/core/tools/definitions/git.js.map +1 -0
- package/dist/core/tools/definitions/index.d.ts +11 -0
- package/dist/core/tools/definitions/index.d.ts.map +1 -0
- package/dist/core/tools/definitions/index.js +22 -0
- package/dist/core/tools/definitions/index.js.map +1 -0
- package/dist/core/tools/definitions/search.d.ts +6 -0
- package/dist/core/tools/definitions/search.d.ts.map +1 -0
- package/dist/core/tools/definitions/search.js +223 -0
- package/dist/core/tools/definitions/search.js.map +1 -0
- package/dist/core/tools/definitions/shell.d.ts +6 -0
- package/dist/core/tools/definitions/shell.d.ts.map +1 -0
- package/dist/core/tools/definitions/shell.js +190 -0
- package/dist/core/tools/definitions/shell.js.map +1 -0
- package/dist/core/tools/definitions/web.d.ts +6 -0
- package/dist/core/tools/definitions/web.d.ts.map +1 -0
- package/dist/core/tools/definitions/web.js +104 -0
- package/dist/core/tools/definitions/web.js.map +1 -0
- package/dist/core/tools/executor.d.ts +24 -0
- package/dist/core/tools/executor.d.ts.map +1 -0
- package/dist/core/tools/executor.js +111 -0
- package/dist/core/tools/executor.js.map +1 -0
- package/dist/core/tools/index.d.ts +4 -0
- package/dist/core/tools/index.d.ts.map +1 -0
- package/dist/core/tools/index.js +4 -0
- package/dist/core/tools/index.js.map +1 -0
- package/dist/core/tools/registry.d.ts +23 -0
- package/dist/core/tools/registry.d.ts.map +1 -0
- package/dist/core/tools/registry.js +28 -0
- package/dist/core/tools/registry.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +352 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -0
- package/src/cli/app.tsx +319 -0
- package/src/cli/commands/index.ts +261 -0
- package/src/config/constants.ts +45 -0
- package/src/config/index.ts +3 -0
- package/src/config/schema.ts +36 -0
- package/src/config/settings.ts +121 -0
- package/src/core/agents/agent.ts +205 -0
- package/src/core/agents/index.ts +2 -0
- package/src/core/agents/orchestrator.ts +223 -0
- package/src/core/api/client.ts +300 -0
- package/src/core/api/index.ts +2 -0
- package/src/core/api/types.ts +149 -0
- package/src/core/hooks/index.ts +2 -0
- package/src/core/hooks/manager.ts +212 -0
- package/src/core/hooks/types.ts +116 -0
- package/src/core/mcp/client.ts +198 -0
- package/src/core/mcp/index.ts +2 -0
- package/src/core/mcp/manager.ts +153 -0
- package/src/core/plugins/index.ts +2 -0
- package/src/core/plugins/loader.ts +312 -0
- package/src/core/plugins/types.ts +111 -0
- package/src/core/session/index.ts +2 -0
- package/src/core/session/manager.ts +246 -0
- package/src/core/session/storage.ts +184 -0
- package/src/core/tools/definitions/file.ts +312 -0
- package/src/core/tools/definitions/git.ts +326 -0
- package/src/core/tools/definitions/index.ts +24 -0
- package/src/core/tools/definitions/search.ts +266 -0
- package/src/core/tools/definitions/shell.ts +228 -0
- package/src/core/tools/definitions/web.ts +113 -0
- package/src/core/tools/executor.ts +145 -0
- package/src/core/tools/index.ts +3 -0
- package/src/core/tools/registry.ts +44 -0
- package/src/index.ts +407 -0
- package/tsconfig.json +40 -0
- package/vitest.config.ts +13 -0
package/src/cli/app.tsx
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bluehawks CLI - Main Application Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React, { useState, useCallback, useEffect } from 'react';
|
|
6
|
+
import { Text, Box, useInput, useApp } from 'ink';
|
|
7
|
+
import Spinner from 'ink-spinner';
|
|
8
|
+
import TextInput from 'ink-text-input';
|
|
9
|
+
import { APIClient } from '../core/api/client.js';
|
|
10
|
+
import { Orchestrator } from '../core/agents/orchestrator.js';
|
|
11
|
+
import { ToolExecutor, toolRegistry, registerAllTools } from '../core/tools/index.js';
|
|
12
|
+
import { SessionManager } from '../core/session/manager.js';
|
|
13
|
+
import { commandRegistry, type CommandContext } from './commands/index.js';
|
|
14
|
+
import { CLI_NAME, CLI_VERSION, COLORS } from '../config/constants.js';
|
|
15
|
+
import { hooksManager } from '../core/hooks/index.js';
|
|
16
|
+
import type { SessionStartInput, StopInput } from '../core/hooks/types.js';
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
interface AppProps {
|
|
20
|
+
initialPrompt?: string;
|
|
21
|
+
apiKey?: string;
|
|
22
|
+
yoloMode?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface MessageDisplay {
|
|
26
|
+
role: 'user' | 'assistant' | 'tool' | 'system' | 'error';
|
|
27
|
+
content: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const App: React.FC<AppProps> = ({ initialPrompt, apiKey, yoloMode = false }) => {
|
|
31
|
+
const { exit } = useApp();
|
|
32
|
+
const [input, setInput] = useState('');
|
|
33
|
+
const [messages, setMessages] = useState<MessageDisplay[]>([]);
|
|
34
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
35
|
+
const [currentTool, setCurrentTool] = useState<string | null>(null);
|
|
36
|
+
const [streamingContent, setStreamingContent] = useState('');
|
|
37
|
+
const [pendingApproval, setPendingApproval] = useState<{
|
|
38
|
+
toolName: string;
|
|
39
|
+
args: Record<string, unknown>;
|
|
40
|
+
resolve: (approved: boolean) => void;
|
|
41
|
+
} | null>(null);
|
|
42
|
+
const [isYoloMode, setIsYoloMode] = useState(yoloMode);
|
|
43
|
+
|
|
44
|
+
// Initialize components
|
|
45
|
+
const [apiClient] = useState(() => new APIClient({ apiKey }));
|
|
46
|
+
const [toolExecutor] = useState(() => {
|
|
47
|
+
const executor = new ToolExecutor({
|
|
48
|
+
approvalMode: yoloMode ? 'never' : 'unsafe-only',
|
|
49
|
+
});
|
|
50
|
+
return executor;
|
|
51
|
+
});
|
|
52
|
+
const [orchestrator] = useState(() => {
|
|
53
|
+
registerAllTools();
|
|
54
|
+
return new Orchestrator({
|
|
55
|
+
projectPath: process.cwd(),
|
|
56
|
+
apiClient,
|
|
57
|
+
toolExecutor,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
const [sessionManager] = useState(
|
|
61
|
+
() => new SessionManager(process.cwd(), apiClient.currentModel)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// Set up approval handler and lifecycle hooks
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
toolExecutor.setApprovalHandler(async (toolName, args) => {
|
|
67
|
+
if (isYoloMode) return true;
|
|
68
|
+
|
|
69
|
+
return new Promise<boolean>((resolve) => {
|
|
70
|
+
setPendingApproval({ toolName, args, resolve });
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Initialize orchestrator and trigger SessionStart hook
|
|
75
|
+
orchestrator.initialize().then(async () => {
|
|
76
|
+
const hookContext: SessionStartInput = {
|
|
77
|
+
sessionId: sessionManager.getSessionId(),
|
|
78
|
+
projectPath: process.cwd(),
|
|
79
|
+
model: apiClient.currentModel,
|
|
80
|
+
timestamp: new Date().toISOString(),
|
|
81
|
+
cwd: process.cwd(),
|
|
82
|
+
};
|
|
83
|
+
await hooksManager.execute('SessionStart', hookContext);
|
|
84
|
+
}).catch((err) => {
|
|
85
|
+
setMessages((prev) => [...prev, { role: 'error', content: `Init error: ${err}` }]);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Cleanup: trigger Stop hook and auto-save session on exit
|
|
89
|
+
return () => {
|
|
90
|
+
const cleanup = async () => {
|
|
91
|
+
const stopContext: StopInput = {
|
|
92
|
+
sessionId: sessionManager.getSessionId(),
|
|
93
|
+
projectPath: process.cwd(),
|
|
94
|
+
model: apiClient.currentModel,
|
|
95
|
+
timestamp: new Date().toISOString(),
|
|
96
|
+
reason: 'completed',
|
|
97
|
+
messageCount: sessionManager.getStats().messageCount,
|
|
98
|
+
};
|
|
99
|
+
await hooksManager.execute('Stop', stopContext);
|
|
100
|
+
await sessionManager.save();
|
|
101
|
+
};
|
|
102
|
+
cleanup().catch(console.error);
|
|
103
|
+
};
|
|
104
|
+
}, [toolExecutor, isYoloMode, orchestrator, sessionManager, apiClient]);
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
// Handle initial prompt
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (initialPrompt) {
|
|
110
|
+
handleSubmit(initialPrompt);
|
|
111
|
+
}
|
|
112
|
+
}, []);
|
|
113
|
+
|
|
114
|
+
const handleSubmit = useCallback(
|
|
115
|
+
async (value: string) => {
|
|
116
|
+
const trimmed = value.trim();
|
|
117
|
+
if (!trimmed || isProcessing) return;
|
|
118
|
+
|
|
119
|
+
// Check for slash commands
|
|
120
|
+
if (commandRegistry.isCommand(trimmed)) {
|
|
121
|
+
const context: CommandContext = {
|
|
122
|
+
sessionManager,
|
|
123
|
+
orchestrator,
|
|
124
|
+
toolRegistry,
|
|
125
|
+
onExit: () => exit(),
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const result = await commandRegistry.execute(trimmed, context);
|
|
129
|
+
if (result) {
|
|
130
|
+
setMessages((prev) => [...prev, { role: 'system', content: result }]);
|
|
131
|
+
}
|
|
132
|
+
setInput('');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Check for YOLO toggle
|
|
137
|
+
if (trimmed.toLowerCase() === '/yolo') {
|
|
138
|
+
setIsYoloMode((prev) => {
|
|
139
|
+
const newValue = !prev;
|
|
140
|
+
toolExecutor.setApprovalMode(newValue ? 'never' : 'unsafe-only');
|
|
141
|
+
setMessages((prev) => [
|
|
142
|
+
...prev,
|
|
143
|
+
{
|
|
144
|
+
role: 'system',
|
|
145
|
+
content: newValue
|
|
146
|
+
? '⚡ YOLO mode enabled! All tools will auto-execute.'
|
|
147
|
+
: '🛡️ YOLO mode disabled. Dangerous tools will require approval.',
|
|
148
|
+
},
|
|
149
|
+
]);
|
|
150
|
+
return newValue;
|
|
151
|
+
});
|
|
152
|
+
setInput('');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Add user message
|
|
157
|
+
setMessages((prev) => [...prev, { role: 'user', content: trimmed }]);
|
|
158
|
+
setInput('');
|
|
159
|
+
setIsProcessing(true);
|
|
160
|
+
setStreamingContent('');
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const response = await orchestrator.chat(trimmed, [], {
|
|
164
|
+
onChunk: (chunk) => {
|
|
165
|
+
setStreamingContent((prev) => prev + chunk);
|
|
166
|
+
},
|
|
167
|
+
onToolStart: (name) => {
|
|
168
|
+
setCurrentTool(name);
|
|
169
|
+
setMessages((prev) => [
|
|
170
|
+
...prev,
|
|
171
|
+
{ role: 'tool', content: `🔧 Running: ${name}...` },
|
|
172
|
+
]);
|
|
173
|
+
},
|
|
174
|
+
onToolEnd: (name, result) => {
|
|
175
|
+
setCurrentTool(null);
|
|
176
|
+
const truncated =
|
|
177
|
+
result.length > 500 ? result.substring(0, 500) + '...' : result;
|
|
178
|
+
setMessages((prev) => [
|
|
179
|
+
...prev,
|
|
180
|
+
{ role: 'tool', content: `✓ ${name}:\n${truncated}` },
|
|
181
|
+
]);
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Add final response
|
|
186
|
+
if (response.content) {
|
|
187
|
+
setMessages((prev) => [...prev, { role: 'assistant', content: response.content }]);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Update session
|
|
191
|
+
sessionManager.addMessage({ role: 'user', content: trimmed });
|
|
192
|
+
sessionManager.addMessage({ role: 'assistant', content: response.content });
|
|
193
|
+
response.toolsUsed.forEach((tool) => sessionManager.addToolUsed(tool));
|
|
194
|
+
} catch (error) {
|
|
195
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
196
|
+
setMessages((prev) => [...prev, { role: 'error', content: `Error: ${errorMessage}` }]);
|
|
197
|
+
} finally {
|
|
198
|
+
setIsProcessing(false);
|
|
199
|
+
setStreamingContent('');
|
|
200
|
+
setCurrentTool(null);
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
[isProcessing, orchestrator, sessionManager, exit, toolExecutor, isYoloMode]
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Handle approval input
|
|
207
|
+
useInput(
|
|
208
|
+
(input, key) => {
|
|
209
|
+
if (pendingApproval) {
|
|
210
|
+
if (input.toLowerCase() === 'y' || key.return) {
|
|
211
|
+
pendingApproval.resolve(true);
|
|
212
|
+
setPendingApproval(null);
|
|
213
|
+
} else if (input.toLowerCase() === 'n' || key.escape) {
|
|
214
|
+
pendingApproval.resolve(false);
|
|
215
|
+
setPendingApproval(null);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
{ isActive: pendingApproval !== null }
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const getRoleColor = (role: MessageDisplay['role']): string => {
|
|
223
|
+
switch (role) {
|
|
224
|
+
case 'user':
|
|
225
|
+
return COLORS.primary;
|
|
226
|
+
case 'assistant':
|
|
227
|
+
return COLORS.success;
|
|
228
|
+
case 'tool':
|
|
229
|
+
return COLORS.info;
|
|
230
|
+
case 'system':
|
|
231
|
+
return COLORS.warning;
|
|
232
|
+
case 'error':
|
|
233
|
+
return COLORS.error;
|
|
234
|
+
default:
|
|
235
|
+
return COLORS.muted;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
return (
|
|
240
|
+
<Box flexDirection="column" padding={1}>
|
|
241
|
+
{/* Header */}
|
|
242
|
+
<Box marginBottom={1}>
|
|
243
|
+
<Text bold color={COLORS.primary}>
|
|
244
|
+
🦅 {CLI_NAME} v{CLI_VERSION}
|
|
245
|
+
</Text>
|
|
246
|
+
<Text color={COLORS.muted}> | </Text>
|
|
247
|
+
<Text color={COLORS.muted}>Type /help for commands</Text>
|
|
248
|
+
{isYoloMode && (
|
|
249
|
+
<>
|
|
250
|
+
<Text color={COLORS.muted}> | </Text>
|
|
251
|
+
<Text color={COLORS.warning}>⚡ YOLO</Text>
|
|
252
|
+
</>
|
|
253
|
+
)}
|
|
254
|
+
</Box>
|
|
255
|
+
|
|
256
|
+
{/* Messages */}
|
|
257
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
258
|
+
{messages.slice(-20).map((msg, i) => (
|
|
259
|
+
<Box key={i} marginBottom={1}>
|
|
260
|
+
<Text color={getRoleColor(msg.role)}>
|
|
261
|
+
{msg.role === 'user' ? '› ' : msg.role === 'assistant' ? '🦅 ' : ''}
|
|
262
|
+
{msg.content}
|
|
263
|
+
</Text>
|
|
264
|
+
</Box>
|
|
265
|
+
))}
|
|
266
|
+
|
|
267
|
+
{/* Streaming content */}
|
|
268
|
+
{streamingContent && (
|
|
269
|
+
<Box marginBottom={1}>
|
|
270
|
+
<Text color={COLORS.success}>🦅 {streamingContent}</Text>
|
|
271
|
+
</Box>
|
|
272
|
+
)}
|
|
273
|
+
|
|
274
|
+
{/* Current tool */}
|
|
275
|
+
{currentTool && (
|
|
276
|
+
<Box>
|
|
277
|
+
<Spinner type="dots" />
|
|
278
|
+
<Text color={COLORS.info}> Running {currentTool}...</Text>
|
|
279
|
+
</Box>
|
|
280
|
+
)}
|
|
281
|
+
|
|
282
|
+
{/* Approval prompt */}
|
|
283
|
+
{pendingApproval && (
|
|
284
|
+
<Box flexDirection="column" borderStyle="round" borderColor="yellow" padding={1}>
|
|
285
|
+
<Text color={COLORS.warning}>
|
|
286
|
+
⚠️ Tool requires approval: {pendingApproval.toolName}
|
|
287
|
+
</Text>
|
|
288
|
+
<Text color={COLORS.muted}>
|
|
289
|
+
Args: {JSON.stringify(pendingApproval.args, null, 2).substring(0, 200)}
|
|
290
|
+
</Text>
|
|
291
|
+
<Text>
|
|
292
|
+
Press <Text color="green">Y</Text> to approve, <Text color="red">N</Text> to deny
|
|
293
|
+
</Text>
|
|
294
|
+
</Box>
|
|
295
|
+
)}
|
|
296
|
+
</Box>
|
|
297
|
+
|
|
298
|
+
{/* Input */}
|
|
299
|
+
{!pendingApproval && (
|
|
300
|
+
<Box>
|
|
301
|
+
<Text color={COLORS.primary}>› </Text>
|
|
302
|
+
{isProcessing ? (
|
|
303
|
+
<Box>
|
|
304
|
+
<Spinner type="dots" />
|
|
305
|
+
<Text color={COLORS.muted}> Thinking...</Text>
|
|
306
|
+
</Box>
|
|
307
|
+
) : (
|
|
308
|
+
<TextInput
|
|
309
|
+
value={input}
|
|
310
|
+
onChange={setInput}
|
|
311
|
+
onSubmit={handleSubmit}
|
|
312
|
+
placeholder="Ask me anything..."
|
|
313
|
+
/>
|
|
314
|
+
)}
|
|
315
|
+
</Box>
|
|
316
|
+
)}
|
|
317
|
+
</Box>
|
|
318
|
+
);
|
|
319
|
+
};
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bluehawks CLI - Slash Commands
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface Command {
|
|
6
|
+
name: string;
|
|
7
|
+
aliases?: string[];
|
|
8
|
+
description: string;
|
|
9
|
+
execute: (args: string[], context: CommandContext) => Promise<string> | string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface CommandContext {
|
|
13
|
+
sessionManager: {
|
|
14
|
+
getStats: () => {
|
|
15
|
+
messageCount: number;
|
|
16
|
+
userMessages: number;
|
|
17
|
+
assistantMessages: number;
|
|
18
|
+
toolMessages: number;
|
|
19
|
+
tokensUsed: number;
|
|
20
|
+
toolsUsed: string[];
|
|
21
|
+
duration: number;
|
|
22
|
+
};
|
|
23
|
+
clear: () => void;
|
|
24
|
+
compressHistory: () => void;
|
|
25
|
+
save: () => Promise<string>;
|
|
26
|
+
};
|
|
27
|
+
orchestrator: {
|
|
28
|
+
setPlanMode: (enabled: boolean) => void;
|
|
29
|
+
isPlanMode: () => boolean;
|
|
30
|
+
getSubAgents: () => Array<{ name: string; description: string }>;
|
|
31
|
+
};
|
|
32
|
+
toolRegistry: {
|
|
33
|
+
getAll: () => Array<{ name: string; definition: { function: { description: string } } }>;
|
|
34
|
+
};
|
|
35
|
+
onExit: () => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Help command
|
|
39
|
+
const helpCommand: Command = {
|
|
40
|
+
name: 'help',
|
|
41
|
+
aliases: ['h', '?'],
|
|
42
|
+
description: 'Show available commands',
|
|
43
|
+
execute: (_args, _context) => {
|
|
44
|
+
const lines: string[] = [
|
|
45
|
+
'📘 Available Commands:',
|
|
46
|
+
'',
|
|
47
|
+
' /help, /h, /? Show this help message',
|
|
48
|
+
' /clear, /c Clear conversation history',
|
|
49
|
+
' /stats, /s Show session statistics',
|
|
50
|
+
' /tools List available tools',
|
|
51
|
+
' /agents List available sub-agents',
|
|
52
|
+
' /plan Toggle plan mode (think before acting)',
|
|
53
|
+
' /compress Compress conversation history',
|
|
54
|
+
' /save Save current session',
|
|
55
|
+
' /yolo Toggle YOLO mode (auto-approve all tools)',
|
|
56
|
+
' /exit, /quit, /q Exit Bluehawks',
|
|
57
|
+
'',
|
|
58
|
+
'💡 Tips:',
|
|
59
|
+
' - Use @ to reference files: @src/index.ts',
|
|
60
|
+
' - Use Ctrl+C to cancel current operation',
|
|
61
|
+
' - Use Up/Down arrows for command history',
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
return lines.join('\n');
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Clear command
|
|
69
|
+
const clearCommand: Command = {
|
|
70
|
+
name: 'clear',
|
|
71
|
+
aliases: ['c'],
|
|
72
|
+
description: 'Clear conversation history',
|
|
73
|
+
execute: (_args, context) => {
|
|
74
|
+
context.sessionManager.clear();
|
|
75
|
+
return '🗑️ Conversation history cleared.';
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Stats command
|
|
80
|
+
const statsCommand: Command = {
|
|
81
|
+
name: 'stats',
|
|
82
|
+
aliases: ['s'],
|
|
83
|
+
description: 'Show session statistics',
|
|
84
|
+
execute: (_args, context) => {
|
|
85
|
+
const stats = context.sessionManager.getStats();
|
|
86
|
+
const duration = Math.floor(stats.duration / 1000);
|
|
87
|
+
const minutes = Math.floor(duration / 60);
|
|
88
|
+
const seconds = duration % 60;
|
|
89
|
+
|
|
90
|
+
const lines: string[] = [
|
|
91
|
+
'📊 Session Statistics:',
|
|
92
|
+
'',
|
|
93
|
+
` Messages: ${stats.messageCount}`,
|
|
94
|
+
` User: ${stats.userMessages}`,
|
|
95
|
+
` Assistant: ${stats.assistantMessages}`,
|
|
96
|
+
` Tool calls: ${stats.toolMessages}`,
|
|
97
|
+
` Tokens used: ${stats.tokensUsed}`,
|
|
98
|
+
` Duration: ${minutes}m ${seconds}s`,
|
|
99
|
+
'',
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
if (stats.toolsUsed.length > 0) {
|
|
103
|
+
lines.push(` Tools used: ${stats.toolsUsed.join(', ')}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return lines.join('\n');
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Tools command
|
|
111
|
+
const toolsCommand: Command = {
|
|
112
|
+
name: 'tools',
|
|
113
|
+
description: 'List available tools',
|
|
114
|
+
execute: (_args, context) => {
|
|
115
|
+
const tools = context.toolRegistry.getAll();
|
|
116
|
+
const lines: string[] = ['🔧 Available Tools:', ''];
|
|
117
|
+
|
|
118
|
+
for (const tool of tools) {
|
|
119
|
+
const desc = tool.definition.function.description.substring(0, 60);
|
|
120
|
+
lines.push(` ${tool.name.padEnd(20)} ${desc}...`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return lines.join('\n');
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Agents command
|
|
128
|
+
const agentsCommand: Command = {
|
|
129
|
+
name: 'agents',
|
|
130
|
+
description: 'List available sub-agents',
|
|
131
|
+
execute: (_args, context) => {
|
|
132
|
+
const agents = context.orchestrator.getSubAgents();
|
|
133
|
+
const lines: string[] = ['🤖 Available Sub-Agents:', ''];
|
|
134
|
+
|
|
135
|
+
for (const agent of agents) {
|
|
136
|
+
lines.push(` ${agent.name.padEnd(15)} ${agent.description}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return lines.join('\n');
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Plan command
|
|
144
|
+
const planCommand: Command = {
|
|
145
|
+
name: 'plan',
|
|
146
|
+
description: 'Toggle plan mode',
|
|
147
|
+
execute: (_args, context) => {
|
|
148
|
+
const current = context.orchestrator.isPlanMode();
|
|
149
|
+
context.orchestrator.setPlanMode(!current);
|
|
150
|
+
|
|
151
|
+
if (!current) {
|
|
152
|
+
return '📋 Plan mode enabled. I will create a plan before making changes.';
|
|
153
|
+
} else {
|
|
154
|
+
return '⚡ Plan mode disabled. I will execute tasks directly.';
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// Compress command
|
|
160
|
+
const compressCommand: Command = {
|
|
161
|
+
name: 'compress',
|
|
162
|
+
description: 'Compress conversation history',
|
|
163
|
+
execute: (_args, context) => {
|
|
164
|
+
context.sessionManager.compressHistory();
|
|
165
|
+
return '📦 Conversation history compressed to save tokens.';
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Save command
|
|
170
|
+
const saveCommand: Command = {
|
|
171
|
+
name: 'save',
|
|
172
|
+
description: 'Save current session',
|
|
173
|
+
execute: async (_args, context) => {
|
|
174
|
+
const path = await context.sessionManager.save();
|
|
175
|
+
return `💾 Session saved to: ${path}`;
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// Exit command
|
|
180
|
+
const exitCommand: Command = {
|
|
181
|
+
name: 'exit',
|
|
182
|
+
aliases: ['quit', 'q'],
|
|
183
|
+
description: 'Exit Bluehawks',
|
|
184
|
+
execute: (_args, context) => {
|
|
185
|
+
context.onExit();
|
|
186
|
+
return '👋 Goodbye!';
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Bug command
|
|
191
|
+
const bugCommand: Command = {
|
|
192
|
+
name: 'bug',
|
|
193
|
+
description: 'Report a bug',
|
|
194
|
+
execute: () => {
|
|
195
|
+
return `🐛 To report a bug:
|
|
196
|
+
1. Go to: https://github.com/bluehawks/cli/issues
|
|
197
|
+
2. Click "New Issue"
|
|
198
|
+
3. Describe the bug with steps to reproduce
|
|
199
|
+
|
|
200
|
+
Thank you for helping improve Bluehawks!`;
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// All commands
|
|
205
|
+
export const commands: Command[] = [
|
|
206
|
+
helpCommand,
|
|
207
|
+
clearCommand,
|
|
208
|
+
statsCommand,
|
|
209
|
+
toolsCommand,
|
|
210
|
+
agentsCommand,
|
|
211
|
+
planCommand,
|
|
212
|
+
compressCommand,
|
|
213
|
+
saveCommand,
|
|
214
|
+
exitCommand,
|
|
215
|
+
bugCommand,
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
// Command registry
|
|
219
|
+
class CommandRegistry {
|
|
220
|
+
private commands: Map<string, Command> = new Map();
|
|
221
|
+
|
|
222
|
+
constructor() {
|
|
223
|
+
for (const cmd of commands) {
|
|
224
|
+
this.register(cmd);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
register(command: Command): void {
|
|
229
|
+
this.commands.set(command.name, command);
|
|
230
|
+
if (command.aliases) {
|
|
231
|
+
for (const alias of command.aliases) {
|
|
232
|
+
this.commands.set(alias, command);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
get(name: string): Command | undefined {
|
|
238
|
+
return this.commands.get(name);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async execute(input: string, context: CommandContext): Promise<string | null> {
|
|
242
|
+
if (!input.startsWith('/')) return null;
|
|
243
|
+
|
|
244
|
+
const parts = input.slice(1).split(/\s+/);
|
|
245
|
+
const commandName = parts[0].toLowerCase();
|
|
246
|
+
const args = parts.slice(1);
|
|
247
|
+
|
|
248
|
+
const command = this.get(commandName);
|
|
249
|
+
if (!command) {
|
|
250
|
+
return `Unknown command: /${commandName}. Type /help for available commands.`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return command.execute(args, context);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
isCommand(input: string): boolean {
|
|
257
|
+
return input.startsWith('/');
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export const commandRegistry = new CommandRegistry();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bluehawks CLI - Core Constants
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// API Configuration
|
|
6
|
+
export const API_BASE_URL = 'https://api.bluehawks.ai/v1';
|
|
7
|
+
export const DEFAULT_MODEL = 'Qwen/Qwen3-0.6B';
|
|
8
|
+
|
|
9
|
+
// CLI Metadata
|
|
10
|
+
export const CLI_NAME = 'bluehawks';
|
|
11
|
+
export const CLI_VERSION = '1.0.0';
|
|
12
|
+
export const CLI_DESCRIPTION = 'A production-ready multi-agent AI CLI assistant';
|
|
13
|
+
|
|
14
|
+
// Configuration Paths
|
|
15
|
+
export const CONFIG_DIR_NAME = '.bluehawks';
|
|
16
|
+
export const SETTINGS_FILE = 'settings.json';
|
|
17
|
+
export const CONTEXT_FILE = 'BLUEHAWKS.md';
|
|
18
|
+
export const HISTORY_FILE = 'history.json';
|
|
19
|
+
export const ENV_FILE = '.env';
|
|
20
|
+
|
|
21
|
+
// API Defaults
|
|
22
|
+
export const DEFAULT_MAX_TOKENS = 4096;
|
|
23
|
+
export const DEFAULT_TEMPERATURE = 0.7;
|
|
24
|
+
export const DEFAULT_TIMEOUT_MS = 120000;
|
|
25
|
+
export const MAX_RETRIES = 3;
|
|
26
|
+
export const RETRY_DELAY_MS = 1000;
|
|
27
|
+
|
|
28
|
+
// Tool Execution
|
|
29
|
+
export const COMMAND_TIMEOUT_MS = 60000;
|
|
30
|
+
export const MAX_OUTPUT_LENGTH = 50000;
|
|
31
|
+
export const MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024; // 10MB
|
|
32
|
+
|
|
33
|
+
// Session
|
|
34
|
+
export const MAX_HISTORY_MESSAGES = 100;
|
|
35
|
+
export const COMPRESS_THRESHOLD = 50;
|
|
36
|
+
|
|
37
|
+
// Colors (for terminal output)
|
|
38
|
+
export const COLORS = {
|
|
39
|
+
primary: '#3B82F6',
|
|
40
|
+
success: '#10B981',
|
|
41
|
+
warning: '#F59E0B',
|
|
42
|
+
error: '#EF4444',
|
|
43
|
+
info: '#6366F1',
|
|
44
|
+
muted: '#6B7280',
|
|
45
|
+
} as const;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bluehawks CLI - Settings Schema
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
export const settingsSchema = z.object({
|
|
8
|
+
// API Settings
|
|
9
|
+
apiUrl: z.string().url().optional(),
|
|
10
|
+
apiKey: z.string().optional(),
|
|
11
|
+
model: z.string().optional(),
|
|
12
|
+
|
|
13
|
+
// Execution Settings
|
|
14
|
+
approvalMode: z.enum(['always', 'never', 'unsafe-only']).default('unsafe-only'),
|
|
15
|
+
maxTokens: z.number().min(1).max(32768).default(4096),
|
|
16
|
+
temperature: z.number().min(0).max(2).default(0.7),
|
|
17
|
+
timeout: z.number().min(1000).max(600000).default(120000),
|
|
18
|
+
|
|
19
|
+
// UI Settings
|
|
20
|
+
theme: z.enum(['dark', 'light', 'auto']).default('dark'),
|
|
21
|
+
showTimestamps: z.boolean().default(false),
|
|
22
|
+
compactMode: z.boolean().default(false),
|
|
23
|
+
|
|
24
|
+
// Feature Flags
|
|
25
|
+
planMode: z.boolean().default(false),
|
|
26
|
+
mcpEnabled: z.boolean().default(false),
|
|
27
|
+
|
|
28
|
+
// Tool Settings
|
|
29
|
+
commandTimeout: z.number().min(1000).max(300000).default(60000),
|
|
30
|
+
maxOutputLength: z.number().min(1000).max(100000).default(50000),
|
|
31
|
+
excludePatterns: z.array(z.string()).default(['node_modules', '.git', 'dist', 'build']),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export type Settings = z.infer<typeof settingsSchema>;
|
|
35
|
+
|
|
36
|
+
export const defaultSettings: Settings = settingsSchema.parse({});
|