@zhive/cli 0.5.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/README.md +118 -0
- package/dist/agent/analysis.js +160 -0
- package/dist/agent/app.js +122 -0
- package/dist/agent/chat-prompt.js +65 -0
- package/dist/agent/commands/registry.js +12 -0
- package/dist/agent/components/AsciiTicker.js +81 -0
- package/dist/agent/components/CommandInput.js +65 -0
- package/dist/agent/components/HoneycombBoot.js +291 -0
- package/dist/agent/components/Spinner.js +37 -0
- package/dist/agent/config.js +75 -0
- package/dist/agent/edit-section.js +59 -0
- package/dist/agent/fetch-rules.js +21 -0
- package/dist/agent/helpers.js +22 -0
- package/dist/agent/hooks/useAgent.js +480 -0
- package/dist/agent/memory-prompt.js +47 -0
- package/dist/agent/model.js +92 -0
- package/dist/agent/objects.js +1 -0
- package/dist/agent/process-lifecycle.js +18 -0
- package/dist/agent/prompt.js +353 -0
- package/dist/agent/run-headless.js +189 -0
- package/dist/agent/skills/index.js +2 -0
- package/dist/agent/skills/skill-parser.js +149 -0
- package/dist/agent/skills/types.js +1 -0
- package/dist/agent/theme.js +41 -0
- package/dist/agent/tools/index.js +76 -0
- package/dist/agent/tools/market/client.js +41 -0
- package/dist/agent/tools/market/index.js +3 -0
- package/dist/agent/tools/market/tools.js +518 -0
- package/dist/agent/tools/mindshare/client.js +124 -0
- package/dist/agent/tools/mindshare/index.js +3 -0
- package/dist/agent/tools/mindshare/tools.js +563 -0
- package/dist/agent/tools/read-skill-tool.js +30 -0
- package/dist/agent/tools/ta/index.js +1 -0
- package/dist/agent/tools/ta/indicators.js +201 -0
- package/dist/agent/types.js +1 -0
- package/dist/agents.js +110 -0
- package/dist/ai-providers.js +66 -0
- package/dist/avatar.js +34 -0
- package/dist/backtest/default-backtest-data.js +200 -0
- package/dist/backtest/fetch.js +41 -0
- package/dist/backtest/import.js +106 -0
- package/dist/backtest/index.js +10 -0
- package/dist/backtest/results.js +113 -0
- package/dist/backtest/runner.js +134 -0
- package/dist/backtest/storage.js +11 -0
- package/dist/backtest/types.js +1 -0
- package/dist/commands/create/ai-generate.js +126 -0
- package/dist/commands/create/commands/index.js +10 -0
- package/dist/commands/create/generate.js +73 -0
- package/dist/commands/create/presets/data.js +225 -0
- package/dist/commands/create/presets/formatting.js +81 -0
- package/dist/commands/create/presets/index.js +3 -0
- package/dist/commands/create/presets/options.js +307 -0
- package/dist/commands/create/presets/types.js +1 -0
- package/dist/commands/create/presets.js +613 -0
- package/dist/commands/create/ui/CreateApp.js +172 -0
- package/dist/commands/create/ui/steps/ApiKeyStep.js +89 -0
- package/dist/commands/create/ui/steps/AvatarStep.js +16 -0
- package/dist/commands/create/ui/steps/DoneStep.js +14 -0
- package/dist/commands/create/ui/steps/IdentityStep.js +125 -0
- package/dist/commands/create/ui/steps/NameStep.js +148 -0
- package/dist/commands/create/ui/steps/ScaffoldStep.js +59 -0
- package/dist/commands/create/ui/steps/SoulStep.js +21 -0
- package/dist/commands/create/ui/steps/StrategyStep.js +20 -0
- package/dist/commands/create/ui/steps/StreamingGenerationStep.js +56 -0
- package/dist/commands/create/ui/validation.js +34 -0
- package/dist/commands/create/validate-api-key.js +27 -0
- package/dist/commands/install.js +50 -0
- package/dist/commands/list/commands/index.js +7 -0
- package/dist/commands/list/ui/ListApp.js +79 -0
- package/dist/commands/migrate-templates/commands/index.js +9 -0
- package/dist/commands/migrate-templates/migrate.js +87 -0
- package/dist/commands/migrate-templates/ui/MigrateApp.js +132 -0
- package/dist/commands/run/commands/index.js +17 -0
- package/dist/commands/run/run-headless.js +111 -0
- package/dist/commands/shared/theme.js +57 -0
- package/dist/commands/shared/welcome.js +304 -0
- package/dist/commands/start/commands/backtest.js +35 -0
- package/dist/commands/start/commands/index.js +62 -0
- package/dist/commands/start/commands/prediction.js +73 -0
- package/dist/commands/start/commands/skills.js +44 -0
- package/dist/commands/start/commands/skills.test.js +140 -0
- package/dist/commands/start/hooks/types.js +1 -0
- package/dist/commands/start/hooks/useAgent.js +177 -0
- package/dist/commands/start/hooks/useChat.js +266 -0
- package/dist/commands/start/hooks/usePollActivity.js +45 -0
- package/dist/commands/start/hooks/utils.js +152 -0
- package/dist/commands/start/services/backtest/default-backtest-data.js +200 -0
- package/dist/commands/start/services/backtest/fetch.js +42 -0
- package/dist/commands/start/services/backtest/import.js +109 -0
- package/dist/commands/start/services/backtest/index.js +10 -0
- package/dist/commands/start/services/backtest/results.js +113 -0
- package/dist/commands/start/services/backtest/runner.js +103 -0
- package/dist/commands/start/services/backtest/storage.js +11 -0
- package/dist/commands/start/services/backtest/types.js +1 -0
- package/dist/commands/start/services/command-registry.js +13 -0
- package/dist/commands/start/ui/AsciiTicker.js +81 -0
- package/dist/commands/start/ui/CommandInput.js +65 -0
- package/dist/commands/start/ui/HoneycombBoot.js +291 -0
- package/dist/commands/start/ui/PollText.js +23 -0
- package/dist/commands/start/ui/PredictionsPanel.js +88 -0
- package/dist/commands/start/ui/SelectAgentApp.js +93 -0
- package/dist/commands/start/ui/Spinner.js +29 -0
- package/dist/commands/start/ui/SpinnerContext.js +20 -0
- package/dist/commands/start/ui/app.js +36 -0
- package/dist/commands/start-all/AgentProcessManager.js +98 -0
- package/dist/commands/start-all/commands/index.js +24 -0
- package/dist/commands/start-all/ui/Dashboard.js +91 -0
- package/dist/components/AsciiTicker.js +81 -0
- package/dist/components/CharacterSummaryCard.js +33 -0
- package/dist/components/CodeBlock.js +11 -0
- package/dist/components/ColoredStats.js +18 -0
- package/dist/components/Header.js +10 -0
- package/dist/components/HoneycombLoader.js +190 -0
- package/dist/components/InputGuard.js +6 -0
- package/dist/components/MultiSelectPrompt.js +45 -0
- package/dist/components/SelectPrompt.js +20 -0
- package/dist/components/Spinner.js +16 -0
- package/dist/components/StepIndicator.js +31 -0
- package/dist/components/StreamingText.js +50 -0
- package/dist/components/TextPrompt.js +28 -0
- package/dist/components/stdout-spinner.js +48 -0
- package/dist/config.js +28 -0
- package/dist/create/CreateApp.js +153 -0
- package/dist/create/ai-generate.js +147 -0
- package/dist/create/generate.js +73 -0
- package/dist/create/steps/ApiKeyStep.js +97 -0
- package/dist/create/steps/AvatarStep.js +16 -0
- package/dist/create/steps/BioStep.js +14 -0
- package/dist/create/steps/DoneStep.js +14 -0
- package/dist/create/steps/IdentityStep.js +163 -0
- package/dist/create/steps/NameStep.js +71 -0
- package/dist/create/steps/ScaffoldStep.js +58 -0
- package/dist/create/steps/SoulStep.js +58 -0
- package/dist/create/steps/StrategyStep.js +58 -0
- package/dist/create/validate-api-key.js +47 -0
- package/dist/create/welcome.js +304 -0
- package/dist/index.js +60 -0
- package/dist/list/ListApp.js +79 -0
- package/dist/load-agent-env.js +30 -0
- package/dist/migrate-templates/MigrateApp.js +131 -0
- package/dist/migrate-templates/migrate.js +86 -0
- package/dist/presets.js +613 -0
- package/dist/shared/agent/agent-runtime.js +144 -0
- package/dist/shared/agent/analysis.js +171 -0
- package/dist/shared/agent/helpers.js +1 -0
- package/dist/shared/agent/prompts/chat-prompt.js +60 -0
- package/dist/shared/agent/prompts/megathread.js +202 -0
- package/dist/shared/agent/prompts/memory-prompt.js +47 -0
- package/dist/shared/agent/prompts/prompt.js +18 -0
- package/dist/shared/agent/skills/index.js +2 -0
- package/dist/shared/agent/skills/skill-parser.js +167 -0
- package/dist/shared/agent/skills/skill-parser.test.js +190 -0
- package/dist/shared/agent/skills/types.js +1 -0
- package/dist/shared/agent/tools/edit-section.js +60 -0
- package/dist/shared/agent/tools/execute-skill-tool.js +134 -0
- package/dist/shared/agent/tools/fetch-rules.js +22 -0
- package/dist/shared/agent/tools/formatting.js +48 -0
- package/dist/shared/agent/tools/index.js +87 -0
- package/dist/shared/agent/tools/market/client.js +41 -0
- package/dist/shared/agent/tools/market/index.js +3 -0
- package/dist/shared/agent/tools/market/tools.js +497 -0
- package/dist/shared/agent/tools/mindshare/client.js +124 -0
- package/dist/shared/agent/tools/mindshare/index.js +3 -0
- package/dist/shared/agent/tools/mindshare/tools.js +167 -0
- package/dist/shared/agent/tools/read-skill-tool.js +30 -0
- package/dist/shared/agent/tools/ta/index.js +1 -0
- package/dist/shared/agent/tools/ta/indicators.js +201 -0
- package/dist/shared/agent/types.js +1 -0
- package/dist/shared/agent/utils.js +43 -0
- package/dist/shared/config/agent.js +177 -0
- package/dist/shared/config/ai-providers.js +156 -0
- package/dist/shared/config/config.js +22 -0
- package/dist/shared/config/constant.js +8 -0
- package/dist/shared/config/env-loader.js +30 -0
- package/dist/shared/types.js +1 -0
- package/dist/start/AgentProcessManager.js +98 -0
- package/dist/start/Dashboard.js +92 -0
- package/dist/start/SelectAgentApp.js +81 -0
- package/dist/start/StartApp.js +189 -0
- package/dist/start/patch-headless.js +101 -0
- package/dist/start/patch-managed-mode.js +142 -0
- package/dist/start/start-command.js +24 -0
- package/dist/theme.js +54 -0
- package/package.json +68 -0
- package/templates/components/HoneycombBoot.tsx +343 -0
- package/templates/fetch-rules.ts +23 -0
- package/templates/skills/mindshare/SKILL.md +197 -0
- package/templates/skills/ta/SKILL.md +179 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
3
|
+
import { Box, Text, useApp, useInput } from 'ink';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
import { colors, symbols, border } from '../theme.js';
|
|
8
|
+
import { scanAgents } from '../agents.js';
|
|
9
|
+
const STATUS_COLORS = {
|
|
10
|
+
spawning: colors.honey,
|
|
11
|
+
running: colors.green,
|
|
12
|
+
exited: colors.grayDim,
|
|
13
|
+
errored: colors.red,
|
|
14
|
+
};
|
|
15
|
+
const STATUS_SYMBOLS = {
|
|
16
|
+
spawning: symbols.diamondOpen,
|
|
17
|
+
running: symbols.dot,
|
|
18
|
+
exited: '\u25CB', // ○
|
|
19
|
+
errored: symbols.cross,
|
|
20
|
+
};
|
|
21
|
+
const MAX_RECENT_LINES = 10;
|
|
22
|
+
const FORCE_KILL_TIMEOUT_MS = 10_000;
|
|
23
|
+
export function StartApp() {
|
|
24
|
+
const { exit } = useApp();
|
|
25
|
+
const [agents, setAgents] = useState([]);
|
|
26
|
+
const [recentLines, setRecentLines] = useState([]);
|
|
27
|
+
const [phase, setPhase] = useState('scanning');
|
|
28
|
+
const [error, setError] = useState(null);
|
|
29
|
+
const childrenRef = useRef(new Map());
|
|
30
|
+
const shuttingDownRef = useRef(false);
|
|
31
|
+
const lineIdRef = useRef(0);
|
|
32
|
+
const updateAgent = useCallback((name, update) => {
|
|
33
|
+
setAgents((prev) => prev.map((a) => (a.name === name ? { ...a, ...update } : a)));
|
|
34
|
+
}, []);
|
|
35
|
+
const pushRecentLine = useCallback((name, text) => {
|
|
36
|
+
const id = lineIdRef.current++;
|
|
37
|
+
setRecentLines((prev) => {
|
|
38
|
+
const next = [...prev, { id, name, text }];
|
|
39
|
+
if (next.length > MAX_RECENT_LINES) {
|
|
40
|
+
return next.slice(next.length - MAX_RECENT_LINES);
|
|
41
|
+
}
|
|
42
|
+
return next;
|
|
43
|
+
});
|
|
44
|
+
}, []);
|
|
45
|
+
const shutdown = useCallback(async () => {
|
|
46
|
+
if (shuttingDownRef.current) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
shuttingDownRef.current = true;
|
|
50
|
+
setPhase('stopping');
|
|
51
|
+
const children = Array.from(childrenRef.current.values());
|
|
52
|
+
// Register exit listeners BEFORE sending signals to avoid race condition
|
|
53
|
+
const waitForAll = children.map((child) => new Promise((resolve) => {
|
|
54
|
+
if (child.exitCode !== null) {
|
|
55
|
+
resolve();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
child.on('exit', () => resolve());
|
|
59
|
+
}));
|
|
60
|
+
for (const child of children) {
|
|
61
|
+
child.kill('SIGTERM');
|
|
62
|
+
}
|
|
63
|
+
const forceKillTimer = setTimeout(() => {
|
|
64
|
+
for (const child of Array.from(childrenRef.current.values())) {
|
|
65
|
+
child.kill('SIGKILL');
|
|
66
|
+
}
|
|
67
|
+
}, FORCE_KILL_TIMEOUT_MS);
|
|
68
|
+
await Promise.all(waitForAll);
|
|
69
|
+
clearTimeout(forceKillTimer);
|
|
70
|
+
setPhase('done');
|
|
71
|
+
exit();
|
|
72
|
+
}, [exit]);
|
|
73
|
+
useInput((_input, key) => {
|
|
74
|
+
if (key.ctrl && _input === 'c') {
|
|
75
|
+
void shutdown();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
const handleStreamData = useCallback((agentName, bufferRef) => {
|
|
79
|
+
return (chunk) => {
|
|
80
|
+
bufferRef.current += chunk.toString();
|
|
81
|
+
const lines = bufferRef.current.split('\n');
|
|
82
|
+
bufferRef.current = lines.pop() ?? '';
|
|
83
|
+
for (const line of lines) {
|
|
84
|
+
const trimmed = line.trim();
|
|
85
|
+
if (trimmed.length === 0) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
updateAgent(agentName, { status: 'running', lastLine: trimmed });
|
|
89
|
+
pushRecentLine(agentName, trimmed);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}, [updateAgent, pushRecentLine]);
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
const run = async () => {
|
|
95
|
+
try {
|
|
96
|
+
const discovered = await scanAgents();
|
|
97
|
+
if (discovered.length === 0) {
|
|
98
|
+
setError('No agents found in ~/.hive/agents/');
|
|
99
|
+
setPhase('done');
|
|
100
|
+
exit();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const agentsDir = path.join(os.homedir(), '.hive', 'agents');
|
|
104
|
+
const initial = discovered.map((a) => ({
|
|
105
|
+
name: a.name,
|
|
106
|
+
status: 'spawning',
|
|
107
|
+
exitCode: null,
|
|
108
|
+
lastLine: '',
|
|
109
|
+
}));
|
|
110
|
+
setAgents(initial);
|
|
111
|
+
setPhase('running');
|
|
112
|
+
for (const agent of discovered) {
|
|
113
|
+
const agentDir = path.join(agentsDir, agent.name);
|
|
114
|
+
const child = spawn('npm', ['start'], {
|
|
115
|
+
cwd: agentDir,
|
|
116
|
+
stdio: 'pipe',
|
|
117
|
+
});
|
|
118
|
+
childrenRef.current.set(agent.name, child);
|
|
119
|
+
const stdoutBuffer = { current: '' };
|
|
120
|
+
const stderrBuffer = { current: '' };
|
|
121
|
+
child.stdout?.on('data', handleStreamData(agent.name, stdoutBuffer));
|
|
122
|
+
child.stderr?.on('data', handleStreamData(agent.name, stderrBuffer));
|
|
123
|
+
child.on('error', (err) => {
|
|
124
|
+
updateAgent(agent.name, { status: 'errored', lastLine: err.message });
|
|
125
|
+
childrenRef.current.delete(agent.name);
|
|
126
|
+
});
|
|
127
|
+
child.on('exit', (code) => {
|
|
128
|
+
// Flush remaining buffered output
|
|
129
|
+
const remainingStdout = stdoutBuffer.current.trim();
|
|
130
|
+
if (remainingStdout.length > 0) {
|
|
131
|
+
pushRecentLine(agent.name, remainingStdout);
|
|
132
|
+
}
|
|
133
|
+
const remainingStderr = stderrBuffer.current.trim();
|
|
134
|
+
if (remainingStderr.length > 0) {
|
|
135
|
+
pushRecentLine(agent.name, remainingStderr);
|
|
136
|
+
}
|
|
137
|
+
const exitCode = code ?? 1;
|
|
138
|
+
const status = exitCode === 0 ? 'exited' : 'errored';
|
|
139
|
+
updateAgent(agent.name, { status, exitCode });
|
|
140
|
+
childrenRef.current.delete(agent.name);
|
|
141
|
+
if (childrenRef.current.size === 0 && !shuttingDownRef.current) {
|
|
142
|
+
setPhase('done');
|
|
143
|
+
exit();
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
150
|
+
setError(`Failed to scan agents: ${message}`);
|
|
151
|
+
setPhase('done');
|
|
152
|
+
exit();
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
void run();
|
|
156
|
+
return () => {
|
|
157
|
+
void shutdown();
|
|
158
|
+
};
|
|
159
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
160
|
+
}, []);
|
|
161
|
+
if (error) {
|
|
162
|
+
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: colors.honey, children: [symbols.hive, " "] }), _jsx(Text, { color: colors.red, children: error })] }), _jsxs(Text, { color: colors.gray, children: ["Create agents with: ", _jsx(Text, { color: colors.white, children: "npx @hive-org/cli@latest create" })] })] }));
|
|
163
|
+
}
|
|
164
|
+
if (phase === 'scanning') {
|
|
165
|
+
return (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: colors.gray, children: "Scanning agents..." }) }));
|
|
166
|
+
}
|
|
167
|
+
const runningCount = agents.filter((a) => a.status === 'running').length;
|
|
168
|
+
const spawningCount = agents.filter((a) => a.status === 'spawning').length;
|
|
169
|
+
const nameWidth = Math.max(16, ...agents.map((a) => a.name.length + 2));
|
|
170
|
+
const statusLabel = phase === 'stopping'
|
|
171
|
+
? 'shutting down...'
|
|
172
|
+
: `${runningCount} running${spawningCount > 0 ? `, ${spawningCount} starting` : ''}`;
|
|
173
|
+
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: colors.honey, children: [symbols.hive, " "] }), _jsx(Text, { color: colors.white, bold: true, children: "Hive Swarm" }), _jsxs(Text, { color: colors.gray, children: [" ", border.horizontal, " ", statusLabel] })] }), agents.map((agent) => {
|
|
174
|
+
const statusColor = STATUS_COLORS[agent.status];
|
|
175
|
+
const statusSymbol = STATUS_SYMBOLS[agent.status];
|
|
176
|
+
const statusText = agent.status === 'exited' || agent.status === 'errored'
|
|
177
|
+
? `${agent.status} (${agent.exitCode})`
|
|
178
|
+
: agent.status;
|
|
179
|
+
const truncatedLine = agent.lastLine.length > 60
|
|
180
|
+
? agent.lastLine.slice(0, 57) + '...'
|
|
181
|
+
: agent.lastLine;
|
|
182
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: statusColor, children: ` ${statusSymbol} ` }), _jsx(Text, { color: colors.white, children: agent.name.padEnd(nameWidth) }), _jsx(Text, { color: statusColor, children: statusText.padEnd(12) }), _jsx(Text, { color: colors.grayDim, children: truncatedLine })] }, agent.name));
|
|
183
|
+
}), recentLines.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Box, { children: _jsxs(Text, { color: colors.grayDim, children: [border.horizontal.repeat(3), " Recent Activity ", border.horizontal.repeat(3)] }) }), recentLines.map((line) => {
|
|
184
|
+
const truncatedText = line.text.length > 60
|
|
185
|
+
? line.text.slice(0, 57) + '...'
|
|
186
|
+
: line.text;
|
|
187
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: colors.honey, children: [' ', line.name.padEnd(nameWidth)] }), _jsxs(Text, { color: colors.grayDim, children: [border.vertical, " "] }), _jsx(Text, { color: colors.gray, children: truncatedText })] }, line.id));
|
|
188
|
+
})] })), phase === 'stopping' && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.honey, children: "Sending SIGTERM to all agents..." }) }))] }));
|
|
189
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
const HEADLESS_MARKER = 'Input Bar — only when stdin is a real TTY';
|
|
4
|
+
/**
|
|
5
|
+
* Patches an agent's index.tsx so that TextInput is only rendered when
|
|
6
|
+
* stdin is a real TTY. Without this, Ink's setRawMode(true) throws
|
|
7
|
+
* when the agent is spawned with piped stdio.
|
|
8
|
+
*
|
|
9
|
+
* Idempotent — skips if already patched.
|
|
10
|
+
*/
|
|
11
|
+
export async function ensureHeadlessSupport(agentDir) {
|
|
12
|
+
const filePath = path.join(agentDir, 'index.tsx');
|
|
13
|
+
const exists = await fs.pathExists(filePath);
|
|
14
|
+
if (!exists) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
let content = await fs.readFile(filePath, 'utf-8');
|
|
18
|
+
let dirty = false;
|
|
19
|
+
// 1. Ensure isInteractive flag exists after useAgent() destructuring
|
|
20
|
+
const INTERACTIVE_DECL = 'const isInteractive = process.stdin.isTTY === true;';
|
|
21
|
+
if (!content.includes(INTERACTIVE_DECL)) {
|
|
22
|
+
const useAgentClose = '} = useAgent();';
|
|
23
|
+
const hookInsertIndex = content.indexOf(useAgentClose);
|
|
24
|
+
if (hookInsertIndex === -1) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const insertAfter = hookInsertIndex + useAgentClose.length;
|
|
28
|
+
const interactiveFlag = `\n\n // When stdin is not a TTY (piped by hive-cli start), skip interactive input\n ${INTERACTIVE_DECL}`;
|
|
29
|
+
content = content.slice(0, insertAfter) + interactiveFlag + content.slice(insertAfter);
|
|
30
|
+
dirty = true;
|
|
31
|
+
}
|
|
32
|
+
// 2. Wrap TextInput in isInteractive guard (if not already done)
|
|
33
|
+
if (content.includes(HEADLESS_MARKER)) {
|
|
34
|
+
// Input Bar already replaced — just write if we added the variable
|
|
35
|
+
if (dirty) {
|
|
36
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (!content.includes('<TextInput')) {
|
|
41
|
+
if (dirty) {
|
|
42
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const inputBarStart = content.indexOf('{/* Input Bar */}');
|
|
47
|
+
if (inputBarStart === -1) {
|
|
48
|
+
if (dirty) {
|
|
49
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
50
|
+
}
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Find the closing </Box> of the bottom border (3 Box elements after Input Bar comment)
|
|
54
|
+
let pos = inputBarStart;
|
|
55
|
+
let boxCloseCount = 0;
|
|
56
|
+
const boxCloseTag = '</Box>';
|
|
57
|
+
while (boxCloseCount < 3 && pos < content.length) {
|
|
58
|
+
const nextClose = content.indexOf(boxCloseTag, pos);
|
|
59
|
+
if (nextClose === -1) {
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
pos = nextClose + boxCloseTag.length;
|
|
63
|
+
boxCloseCount++;
|
|
64
|
+
}
|
|
65
|
+
if (boxCloseCount === 3) {
|
|
66
|
+
const inputBarEnd = pos;
|
|
67
|
+
const replacement = `{/* Input Bar — only when stdin is a real TTY */}
|
|
68
|
+
<Box>
|
|
69
|
+
<Text color="gray">
|
|
70
|
+
{isInteractive ? border.teeLeft : border.bottomLeft}
|
|
71
|
+
{border.horizontal.repeat(boxWidth - 2)}
|
|
72
|
+
{isInteractive ? border.teeRight : border.bottomRight}
|
|
73
|
+
</Text>
|
|
74
|
+
</Box>
|
|
75
|
+
{isInteractive && (
|
|
76
|
+
<>
|
|
77
|
+
<Box paddingLeft={1}>
|
|
78
|
+
<Text color={colors.honey}>{symbols.arrow} </Text>
|
|
79
|
+
<TextInput
|
|
80
|
+
value={input}
|
|
81
|
+
onChange={setInput}
|
|
82
|
+
onSubmit={(val) => {
|
|
83
|
+
setInput('');
|
|
84
|
+
void handleChatSubmit(val);
|
|
85
|
+
}}
|
|
86
|
+
placeholder={chatStreaming ? 'thinking...' : \`chat with \${agentName} agent...\`}
|
|
87
|
+
/>
|
|
88
|
+
</Box>
|
|
89
|
+
<Box>
|
|
90
|
+
<Text color="gray">
|
|
91
|
+
{border.bottomLeft}
|
|
92
|
+
{border.horizontal.repeat(boxWidth - 2)}
|
|
93
|
+
{border.bottomRight}
|
|
94
|
+
</Text>
|
|
95
|
+
</Box>
|
|
96
|
+
</>
|
|
97
|
+
)}`;
|
|
98
|
+
content = content.slice(0, inputBarStart) + replacement + content.slice(inputBarEnd);
|
|
99
|
+
}
|
|
100
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
101
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
const TEMPLATES_DIR = path.resolve(__dirname, '../../templates');
|
|
6
|
+
const MANAGED_MARKER = 'HIVE_MANAGED';
|
|
7
|
+
const INTERACTIVE_GUARD_MARKER = 'Input Bar — only when stdin is a real TTY';
|
|
8
|
+
/**
|
|
9
|
+
* Patches an existing agent to support:
|
|
10
|
+
* 1. Headless mode — skip TextInput when stdin is not a TTY (piped by dashboard)
|
|
11
|
+
* 2. Managed mode — Escape exits back to dashboard when HIVE_MANAGED=1
|
|
12
|
+
*
|
|
13
|
+
* Idempotent — skips if already patched.
|
|
14
|
+
*/
|
|
15
|
+
export async function ensureManagedMode(agentDir) {
|
|
16
|
+
await patchProcessLifecycle(agentDir);
|
|
17
|
+
await patchIndexTsx(agentDir);
|
|
18
|
+
}
|
|
19
|
+
async function patchProcessLifecycle(agentDir) {
|
|
20
|
+
const filePath = path.join(agentDir, 'process-lifecycle.ts');
|
|
21
|
+
const exists = await fs.pathExists(filePath);
|
|
22
|
+
if (!exists) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
26
|
+
// Already patched if gracefulShutdown is exported
|
|
27
|
+
if (content.includes('export const gracefulShutdown')) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// Copy the template version (it has the export + exitCode param)
|
|
31
|
+
const templatePath = path.join(TEMPLATES_DIR, 'process-lifecycle.ts');
|
|
32
|
+
const templateExists = await fs.pathExists(templatePath);
|
|
33
|
+
if (!templateExists) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
await fs.copyFile(templatePath, filePath);
|
|
37
|
+
}
|
|
38
|
+
async function patchIndexTsx(agentDir) {
|
|
39
|
+
const filePath = path.join(agentDir, 'index.tsx');
|
|
40
|
+
const exists = await fs.pathExists(filePath);
|
|
41
|
+
if (!exists) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let content = await fs.readFile(filePath, 'utf-8');
|
|
45
|
+
// Already fully patched
|
|
46
|
+
if (content.includes(MANAGED_MARKER) && content.includes(INTERACTIVE_GUARD_MARKER)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// 1. Add useInput to ink import if not present
|
|
50
|
+
// Existing pattern: "import { render, Box, Text } from 'ink';"
|
|
51
|
+
// Target: "import { render, Box, Text, useInput } from 'ink';"
|
|
52
|
+
if (!content.includes('useInput')) {
|
|
53
|
+
content = content.replace(/\s*}\s*from\s*'ink'/, ", useInput } from 'ink'");
|
|
54
|
+
}
|
|
55
|
+
// 2. Add gracefulShutdown import from process-lifecycle if not present
|
|
56
|
+
if (!content.includes('gracefulShutdown')) {
|
|
57
|
+
content = content.replace(/import\s*\{\s*setupProcessLifecycle\s*\}\s*from\s*'\.\/process-lifecycle'/, "import { setupProcessLifecycle, gracefulShutdown } from './process-lifecycle'");
|
|
58
|
+
}
|
|
59
|
+
// 3. Add isInteractive + managed mode hooks after useAgent() destructuring
|
|
60
|
+
if (!content.includes(MANAGED_MARKER)) {
|
|
61
|
+
const useAgentClose = '} = useAgent();';
|
|
62
|
+
const hookInsertIndex = content.indexOf(useAgentClose);
|
|
63
|
+
if (hookInsertIndex === -1) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const insertAfter = hookInsertIndex + useAgentClose.length;
|
|
67
|
+
const hooks = `
|
|
68
|
+
|
|
69
|
+
// When stdin is not a TTY (piped by hive-cli start), skip interactive input
|
|
70
|
+
const isInteractive = process.stdin.isTTY === true;
|
|
71
|
+
|
|
72
|
+
// When managed by hive-cli start, Escape exits back to dashboard
|
|
73
|
+
const isManaged = process.env.HIVE_MANAGED === '1';
|
|
74
|
+
|
|
75
|
+
useInput((_input, key) => {
|
|
76
|
+
if (isManaged && key.escape) {
|
|
77
|
+
void gracefulShutdown(0);
|
|
78
|
+
}
|
|
79
|
+
}, { isActive: isManaged });`;
|
|
80
|
+
content = content.slice(0, insertAfter) + hooks + content.slice(insertAfter);
|
|
81
|
+
}
|
|
82
|
+
// 4. Wrap TextInput in isInteractive guard
|
|
83
|
+
// Replace the input bar section to conditionally render TextInput
|
|
84
|
+
if (!content.includes(INTERACTIVE_GUARD_MARKER) && content.includes('<TextInput')) {
|
|
85
|
+
// Find the Input Bar comment and replace the entire block
|
|
86
|
+
const inputBarStart = content.indexOf('{/* Input Bar */}');
|
|
87
|
+
if (inputBarStart === -1) {
|
|
88
|
+
// Fallback: just wrap the TextInput line
|
|
89
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// Find the closing </Box> of the bottom border (3 Box elements after Input Bar comment)
|
|
93
|
+
// Pattern: <Box> separator </Box> <Box> TextInput </Box> <Box> bottom border </Box>
|
|
94
|
+
let pos = inputBarStart;
|
|
95
|
+
let boxCloseCount = 0;
|
|
96
|
+
const boxCloseTag = '</Box>';
|
|
97
|
+
while (boxCloseCount < 3 && pos < content.length) {
|
|
98
|
+
const nextClose = content.indexOf(boxCloseTag, pos);
|
|
99
|
+
if (nextClose === -1) {
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
pos = nextClose + boxCloseTag.length;
|
|
103
|
+
boxCloseCount++;
|
|
104
|
+
}
|
|
105
|
+
if (boxCloseCount === 3) {
|
|
106
|
+
const inputBarEnd = pos;
|
|
107
|
+
const replacement = `{/* Input Bar — only when stdin is a real TTY */}
|
|
108
|
+
<Box>
|
|
109
|
+
<Text color="gray">
|
|
110
|
+
{isInteractive ? border.teeLeft : border.bottomLeft}
|
|
111
|
+
{border.horizontal.repeat(boxWidth - 2)}
|
|
112
|
+
{isInteractive ? border.teeRight : border.bottomRight}
|
|
113
|
+
</Text>
|
|
114
|
+
</Box>
|
|
115
|
+
{isInteractive && (
|
|
116
|
+
<>
|
|
117
|
+
<Box paddingLeft={1}>
|
|
118
|
+
<Text color={colors.honey}>{symbols.arrow} </Text>
|
|
119
|
+
<TextInput
|
|
120
|
+
value={input}
|
|
121
|
+
onChange={setInput}
|
|
122
|
+
onSubmit={(val) => {
|
|
123
|
+
setInput('');
|
|
124
|
+
void handleChatSubmit(val);
|
|
125
|
+
}}
|
|
126
|
+
placeholder={chatStreaming ? 'thinking...' : \`chat with \${agentName} agent...\`}
|
|
127
|
+
/>
|
|
128
|
+
</Box>
|
|
129
|
+
<Box>
|
|
130
|
+
<Text color="gray">
|
|
131
|
+
{border.bottomLeft}
|
|
132
|
+
{border.horizontal.repeat(boxWidth - 2)}
|
|
133
|
+
{border.bottomRight}
|
|
134
|
+
</Text>
|
|
135
|
+
</Box>
|
|
136
|
+
</>
|
|
137
|
+
)}`;
|
|
138
|
+
content = content.slice(0, inputBarStart) + replacement + content.slice(inputBarEnd);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
142
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from 'ink';
|
|
3
|
+
import { showWelcome } from '../create/welcome.js';
|
|
4
|
+
import { scanAgents, fetchBulkStats, sortAgentsByHoney } from '../agents.js';
|
|
5
|
+
import { symbols, styled } from '../theme.js';
|
|
6
|
+
import { AgentProcessManager } from './AgentProcessManager.js';
|
|
7
|
+
import { Dashboard } from './Dashboard.js';
|
|
8
|
+
export async function startCommand() {
|
|
9
|
+
// Run welcome animation and scan agents in parallel
|
|
10
|
+
const results = await Promise.all([showWelcome(), scanAgents()]);
|
|
11
|
+
const discovered = results[1];
|
|
12
|
+
if (discovered.length === 0) {
|
|
13
|
+
console.log(`\n ${styled.honey(symbols.hive)} ${styled.red('No agents found in ~/.hive/agents/')}\n`);
|
|
14
|
+
console.log(` ${styled.gray('Create agents with:')} ${styled.white('npx @hive-org/cli@latest create')}\n`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const names = discovered.map((a) => a.name);
|
|
18
|
+
const statsMap = await fetchBulkStats(names);
|
|
19
|
+
const sortedDiscovered = sortAgentsByHoney(discovered, statsMap);
|
|
20
|
+
const manager = new AgentProcessManager();
|
|
21
|
+
manager.spawnAll(sortedDiscovered);
|
|
22
|
+
const { waitUntilExit } = render(React.createElement(Dashboard, { manager, statsMap }));
|
|
23
|
+
await waitUntilExit();
|
|
24
|
+
}
|
package/dist/theme.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
export const colors = {
|
|
3
|
+
honey: '#F5A623',
|
|
4
|
+
honeyDark: '#D4891A',
|
|
5
|
+
honeyBright: '#FFD700',
|
|
6
|
+
white: '#FFFFFF',
|
|
7
|
+
gray: '#A6A6A6',
|
|
8
|
+
grayDim: '#555555',
|
|
9
|
+
green: '#27C587',
|
|
10
|
+
red: '#E14B4B',
|
|
11
|
+
wax: '#C45C5C',
|
|
12
|
+
cyan: '#22D3EE',
|
|
13
|
+
hot: '#FB923C',
|
|
14
|
+
controversial: '#C084FC',
|
|
15
|
+
};
|
|
16
|
+
export const symbols = {
|
|
17
|
+
hive: '\u2B21', // ⬡
|
|
18
|
+
diamond: '\u25C6', // ◆
|
|
19
|
+
diamondOpen: '\u25C7', // ◇
|
|
20
|
+
dot: '\u25CF', // ●
|
|
21
|
+
spinner: ['\u25D0', '\u25D3', '\u25D1', '\u25D2'], // ◐ ◓ ◑ ◒
|
|
22
|
+
check: '\u2713', // ✓
|
|
23
|
+
cross: '\u2717', // ✗
|
|
24
|
+
arrow: '\u203A', // ›
|
|
25
|
+
};
|
|
26
|
+
export const border = {
|
|
27
|
+
horizontal: '\u2500', // ─
|
|
28
|
+
vertical: '\u2502', // │
|
|
29
|
+
topLeft: '\u250C', // ┌
|
|
30
|
+
topRight: '\u2510', // ┐
|
|
31
|
+
bottomLeft: '\u2514', // └
|
|
32
|
+
bottomRight: '\u2518', // ┘
|
|
33
|
+
teeLeft: '\u251C', // ├
|
|
34
|
+
teeRight: '\u2524', // ┤
|
|
35
|
+
};
|
|
36
|
+
export const animation = {
|
|
37
|
+
DATA_CHARS: '01▪▫░▒',
|
|
38
|
+
HEX_CHARS: '⬡⬢',
|
|
39
|
+
TICK_MS: 120,
|
|
40
|
+
};
|
|
41
|
+
export const styled = {
|
|
42
|
+
honey: (text) => chalk.hex(colors.honey)(text),
|
|
43
|
+
honeyBold: (text) => chalk.hex(colors.honey).bold(text),
|
|
44
|
+
white: (text) => chalk.white(text),
|
|
45
|
+
whiteBold: (text) => chalk.bold.white(text),
|
|
46
|
+
gray: (text) => chalk.gray(text),
|
|
47
|
+
dim: (text) => chalk.hex(colors.grayDim)(text),
|
|
48
|
+
green: (text) => chalk.hex(colors.green)(text),
|
|
49
|
+
red: (text) => chalk.hex(colors.red)(text),
|
|
50
|
+
wax: (text) => chalk.hex(colors.wax)(text),
|
|
51
|
+
cyan: (text) => chalk.hex(colors.cyan)(text),
|
|
52
|
+
hot: (text) => chalk.hex(colors.hot)(text),
|
|
53
|
+
controversial: (text) => chalk.hex(colors.controversial)(text),
|
|
54
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zhive/cli",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "CLI for bootstrapping Hive AI Agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"hive-cli": "dist/index.js",
|
|
9
|
+
"cli": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"templates",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -p tsconfig.build.json",
|
|
21
|
+
"dev": "tsx src/index.tsx",
|
|
22
|
+
"start": "node dist/index.js",
|
|
23
|
+
"deploy": "node scripts/deploy.cjs",
|
|
24
|
+
"analyze": "tsx src/sandbox/analyze.ts",
|
|
25
|
+
"prepublishOnly": "pnpm run build",
|
|
26
|
+
"test": "vitest --run --passWithNoTests"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@ai-sdk/anthropic": "^3.0.0",
|
|
30
|
+
"@ai-sdk/devtools": "^0.0.15",
|
|
31
|
+
"@ai-sdk/google": "^3.0.0",
|
|
32
|
+
"@ai-sdk/openai": "^3.0.25",
|
|
33
|
+
"@ai-sdk/xai": "^3.0.0",
|
|
34
|
+
"@zhive/sdk": "^0.5.0",
|
|
35
|
+
"@openrouter/ai-sdk-provider": "^0.4.0",
|
|
36
|
+
"ai": "^6.0.71",
|
|
37
|
+
"axios": "^1.6.0",
|
|
38
|
+
"chalk": "^5.3.0",
|
|
39
|
+
"dotenv": "^16.0.0",
|
|
40
|
+
"fs-extra": "^11.2.0",
|
|
41
|
+
"ink": "^5.1.0",
|
|
42
|
+
"ink-select-input": "^6.0.0",
|
|
43
|
+
"ink-text-input": "^6.0.0",
|
|
44
|
+
"langsmith": "^0.5.7",
|
|
45
|
+
"react": "^18.3.1",
|
|
46
|
+
"zod": "^4.0.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/fs-extra": "^11.0.4",
|
|
50
|
+
"@types/node": "^20.10.0",
|
|
51
|
+
"@types/react": "^18.3.0",
|
|
52
|
+
"tsx": "^4.21.0",
|
|
53
|
+
"typescript": "^5.3.0",
|
|
54
|
+
"vitest": "^3.2.4"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=18"
|
|
58
|
+
},
|
|
59
|
+
"keywords": [
|
|
60
|
+
"hive",
|
|
61
|
+
"ai-agent",
|
|
62
|
+
"trading",
|
|
63
|
+
"cli",
|
|
64
|
+
"scaffold"
|
|
65
|
+
],
|
|
66
|
+
"author": "zhive",
|
|
67
|
+
"license": "GPL-3.0"
|
|
68
|
+
}
|