@zhive/cli 0.6.3 → 0.6.4
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/CLAUDE.md +7 -0
- package/dist/backtest/CLAUDE.md +7 -0
- package/dist/cli.js +20 -0
- package/dist/commands/agent/commands/profile.js +3 -2
- package/dist/commands/agent/commands/profile.test.js +10 -12
- package/dist/commands/doctor/commands/index.js +93 -0
- package/dist/commands/megathread/commands/create-comment.js +4 -9
- package/dist/commands/megathread/commands/create-comment.test.js +15 -173
- package/dist/commands/megathread/commands/list.js +5 -5
- package/dist/commands/megathread/commands/list.test.js +14 -14
- package/dist/commands/start/commands/prediction.js +3 -4
- package/dist/commands/start/hooks/useChat.js +40 -41
- package/dist/commands/start/services/command-registry.js +1 -1
- package/dist/index.js +2 -0
- package/dist/{agent → services/agent}/analysis.js +5 -5
- package/dist/{load-agent-env.js → services/agent/env.js} +1 -1
- package/dist/{agent → services/agent/helpers}/model.js +2 -2
- package/dist/{agent → services/agent/prompts}/memory-prompt.js +20 -22
- package/dist/{agent → services/agent/prompts}/prompt.js +80 -54
- package/dist/{agent → services/agent}/tools/market/client.js +1 -1
- package/dist/{agent → services/agent}/tools/mindshare/client.js +1 -1
- package/dist/{agents.js → services/config/agent.js} +2 -2
- package/dist/{config.js → services/config/config.js} +1 -7
- package/dist/services/config/constant.js +8 -0
- package/dist/shared/agent/config.js +75 -0
- package/dist/shared/agent/env.js +30 -0
- package/dist/shared/agent/helpers/model.js +92 -0
- package/dist/shared/ai-providers.js +66 -0
- package/dist/shared/config/agent.js +0 -11
- package/dist/shared/config/agent.test.js +4 -35
- package/package.json +2 -2
- package/dist/agent/app.js +0 -122
- package/dist/agent/commands/registry.js +0 -12
- package/dist/agent/components/AsciiTicker.js +0 -81
- package/dist/agent/components/CommandInput.js +0 -65
- package/dist/agent/components/HoneycombBoot.js +0 -291
- package/dist/agent/components/Spinner.js +0 -37
- package/dist/agent/hooks/useAgent.js +0 -480
- package/dist/agent/objects.js +0 -1
- package/dist/agent/process-lifecycle.js +0 -18
- package/dist/agent/run-headless.js +0 -189
- package/dist/agent/theme.js +0 -41
- package/dist/avatar.js +0 -34
- package/dist/backtest/default-backtest-data.js +0 -200
- package/dist/backtest/fetch.js +0 -41
- package/dist/backtest/import.js +0 -106
- package/dist/backtest/index.js +0 -10
- package/dist/backtest/results.js +0 -113
- package/dist/backtest/runner.js +0 -134
- package/dist/backtest/storage.js +0 -11
- package/dist/backtest/types.js +0 -1
- package/dist/commands/install.js +0 -50
- package/dist/commands/start/ui/PollText.js +0 -23
- package/dist/commands/start/ui/PredictionsPanel.js +0 -88
- package/dist/commands/start/ui/SpinnerContext.js +0 -20
- package/dist/components/InputGuard.js +0 -6
- package/dist/components/stdout-spinner.js +0 -48
- package/dist/create/CreateApp.js +0 -153
- package/dist/create/ai-generate.js +0 -147
- package/dist/create/generate.js +0 -73
- package/dist/create/steps/ApiKeyStep.js +0 -97
- package/dist/create/steps/AvatarStep.js +0 -16
- package/dist/create/steps/BioStep.js +0 -14
- package/dist/create/steps/DoneStep.js +0 -14
- package/dist/create/steps/IdentityStep.js +0 -163
- package/dist/create/steps/NameStep.js +0 -71
- package/dist/create/steps/ScaffoldStep.js +0 -58
- package/dist/create/steps/SoulStep.js +0 -58
- package/dist/create/steps/StrategyStep.js +0 -58
- package/dist/create/validate-api-key.js +0 -47
- package/dist/create/welcome.js +0 -304
- package/dist/list/ListApp.js +0 -79
- package/dist/migrate-templates/MigrateApp.js +0 -131
- package/dist/migrate-templates/migrate.js +0 -86
- package/dist/presets.js +0 -613
- package/dist/start/AgentProcessManager.js +0 -98
- package/dist/start/Dashboard.js +0 -92
- package/dist/start/SelectAgentApp.js +0 -81
- package/dist/start/StartApp.js +0 -189
- package/dist/start/patch-headless.js +0 -101
- package/dist/start/patch-managed-mode.js +0 -142
- package/dist/start/start-command.js +0 -24
- package/dist/theme.js +0 -54
- /package/dist/{agent → services/agent}/config.js +0 -0
- /package/dist/{agent → services/agent}/helpers.js +0 -0
- /package/dist/{agent → services/agent/prompts}/chat-prompt.js +0 -0
- /package/dist/{agent → services/agent}/skills/index.js +0 -0
- /package/dist/{agent → services/agent}/skills/skill-parser.js +0 -0
- /package/dist/{agent → services/agent}/skills/types.js +0 -0
- /package/dist/{agent → services/agent/tools}/edit-section.js +0 -0
- /package/dist/{agent → services/agent/tools}/fetch-rules.js +0 -0
- /package/dist/{agent → services/agent}/tools/index.js +0 -0
- /package/dist/{agent → services/agent}/tools/market/index.js +0 -0
- /package/dist/{agent → services/agent}/tools/market/tools.js +0 -0
- /package/dist/{agent → services/agent}/tools/mindshare/index.js +0 -0
- /package/dist/{agent → services/agent}/tools/mindshare/tools.js +0 -0
- /package/dist/{agent → services/agent}/tools/read-skill-tool.js +0 -0
- /package/dist/{agent → services/agent}/tools/ta/index.js +0 -0
- /package/dist/{agent → services/agent}/tools/ta/indicators.js +0 -0
- /package/dist/{agent → services/agent}/types.js +0 -0
- /package/dist/{ai-providers.js → services/ai-providers.js} +0 -0
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
const FORCE_KILL_TIMEOUT_MS = 5_000;
|
|
5
|
-
const ABSOLUTE_TIMEOUT_MS = FORCE_KILL_TIMEOUT_MS + 2_000;
|
|
6
|
-
export class AgentProcessManager {
|
|
7
|
-
constructor() {
|
|
8
|
-
this._agents = new Map();
|
|
9
|
-
this._agentsDir = path.join(os.homedir(), '.hive', 'agents');
|
|
10
|
-
}
|
|
11
|
-
spawnAll(discovered) {
|
|
12
|
-
for (const agent of discovered) {
|
|
13
|
-
this._agents.set(agent.name, {
|
|
14
|
-
name: agent.name,
|
|
15
|
-
status: 'spawning',
|
|
16
|
-
exitCode: null,
|
|
17
|
-
child: null,
|
|
18
|
-
});
|
|
19
|
-
this._spawnPiped(agent.name);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
getStates() {
|
|
23
|
-
const states = [];
|
|
24
|
-
for (const agent of this._agents.values()) {
|
|
25
|
-
states.push({
|
|
26
|
-
name: agent.name,
|
|
27
|
-
status: agent.status,
|
|
28
|
-
exitCode: agent.exitCode,
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
return states;
|
|
32
|
-
}
|
|
33
|
-
async stopAgent(name) {
|
|
34
|
-
const agent = this._agents.get(name);
|
|
35
|
-
if (!agent?.child) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
const child = agent.child;
|
|
39
|
-
const exitPromise = new Promise((resolve) => {
|
|
40
|
-
if (child.exitCode !== null) {
|
|
41
|
-
resolve();
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
child.on('exit', () => resolve());
|
|
45
|
-
// Absolute safety timeout in case SIGKILL is not enough
|
|
46
|
-
setTimeout(() => resolve(), ABSOLUTE_TIMEOUT_MS);
|
|
47
|
-
});
|
|
48
|
-
child.kill('SIGTERM');
|
|
49
|
-
const forceKillTimer = setTimeout(() => {
|
|
50
|
-
child.kill('SIGKILL');
|
|
51
|
-
}, FORCE_KILL_TIMEOUT_MS);
|
|
52
|
-
await exitPromise;
|
|
53
|
-
clearTimeout(forceKillTimer);
|
|
54
|
-
agent.child = null;
|
|
55
|
-
}
|
|
56
|
-
respawnPiped(name) {
|
|
57
|
-
const agent = this._agents.get(name);
|
|
58
|
-
if (!agent) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
agent.status = 'spawning';
|
|
62
|
-
agent.exitCode = null;
|
|
63
|
-
agent.child = null;
|
|
64
|
-
this._spawnPiped(name);
|
|
65
|
-
}
|
|
66
|
-
_spawnPiped(name) {
|
|
67
|
-
const agentDir = path.join(this._agentsDir, name);
|
|
68
|
-
const child = spawn('npx', ['@hive-org/cli@latest', 'run'], {
|
|
69
|
-
cwd: agentDir,
|
|
70
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
71
|
-
});
|
|
72
|
-
const agent = this._agents.get(name);
|
|
73
|
-
if (!agent) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
agent.child = child;
|
|
77
|
-
// Transition to 'running' once the OS has spawned the process
|
|
78
|
-
child.on('spawn', () => {
|
|
79
|
-
if (agent.status === 'spawning') {
|
|
80
|
-
agent.status = 'running';
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
// Drain stdout/stderr to prevent buffer blocking
|
|
84
|
-
child.stdout?.resume();
|
|
85
|
-
child.stderr?.resume();
|
|
86
|
-
child.on('error', () => {
|
|
87
|
-
agent.status = 'errored';
|
|
88
|
-
agent.exitCode = null;
|
|
89
|
-
agent.child = null;
|
|
90
|
-
});
|
|
91
|
-
child.on('exit', (code) => {
|
|
92
|
-
const exitCode = code ?? 1;
|
|
93
|
-
agent.status = exitCode === 0 ? 'exited' : 'errored';
|
|
94
|
-
agent.exitCode = exitCode;
|
|
95
|
-
agent.child = null;
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
}
|
package/dist/start/Dashboard.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect } from 'react';
|
|
3
|
-
import { Box, Text, useApp, useInput } from 'ink';
|
|
4
|
-
import { colors, symbols, border } from '../theme.js';
|
|
5
|
-
const STATUS_COLORS = {
|
|
6
|
-
spawning: colors.honey,
|
|
7
|
-
running: colors.green,
|
|
8
|
-
exited: colors.grayDim,
|
|
9
|
-
errored: colors.red,
|
|
10
|
-
};
|
|
11
|
-
const STATUS_SYMBOLS = {
|
|
12
|
-
spawning: symbols.diamondOpen,
|
|
13
|
-
running: symbols.dot,
|
|
14
|
-
exited: '\u25CB', // ○
|
|
15
|
-
errored: symbols.cross,
|
|
16
|
-
};
|
|
17
|
-
const POLL_INTERVAL_MS = 1_000;
|
|
18
|
-
const STOPPABLE = new Set(['running', 'spawning']);
|
|
19
|
-
const STARTABLE = new Set(['exited', 'errored']);
|
|
20
|
-
function ColoredStats({ stats }) {
|
|
21
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.honey, children: ["H:", Math.floor(stats.honey)] }), _jsx(Text, { color: colors.grayDim, children: " " }), _jsxs(Text, { color: colors.wax, children: ["W:", Math.floor(stats.wax)] }), _jsx(Text, { color: colors.grayDim, children: " " }), _jsxs(Text, { color: colors.green, children: ["WR:", (stats.win_rate * 100).toFixed(2), "%"] }), _jsx(Text, { color: colors.grayDim, children: " " }), _jsxs(Text, { color: colors.cyan, children: ["C:", stats.confidence.toFixed(2)] })] }));
|
|
22
|
-
}
|
|
23
|
-
export function Dashboard({ manager, statsMap }) {
|
|
24
|
-
const { exit } = useApp();
|
|
25
|
-
const [agents, setAgents] = useState(manager.getStates());
|
|
26
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
27
|
-
useEffect(() => {
|
|
28
|
-
const timer = setInterval(() => {
|
|
29
|
-
setAgents(manager.getStates());
|
|
30
|
-
}, POLL_INTERVAL_MS);
|
|
31
|
-
return () => clearInterval(timer);
|
|
32
|
-
}, [manager]);
|
|
33
|
-
// Clamp selectedIndex when agents list changes
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
const maxIndex = Math.max(agents.length - 1, 0);
|
|
36
|
-
setSelectedIndex((prev) => Math.min(prev, maxIndex));
|
|
37
|
-
}, [agents.length]);
|
|
38
|
-
useInput((_input, key) => {
|
|
39
|
-
if (key.upArrow) {
|
|
40
|
-
setSelectedIndex((prev) => {
|
|
41
|
-
const max = agents.length - 1;
|
|
42
|
-
return prev > 0 ? prev - 1 : max;
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
else if (key.downArrow) {
|
|
46
|
-
setSelectedIndex((prev) => {
|
|
47
|
-
const max = agents.length - 1;
|
|
48
|
-
return prev < max ? prev + 1 : 0;
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
else if (_input === ' ' || key.return) {
|
|
52
|
-
const agent = agents[selectedIndex];
|
|
53
|
-
if (!agent) {
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
if (STOPPABLE.has(agent.status)) {
|
|
57
|
-
void manager.stopAgent(agent.name);
|
|
58
|
-
}
|
|
59
|
-
else if (STARTABLE.has(agent.status)) {
|
|
60
|
-
manager.respawnPiped(agent.name);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
else if (key.ctrl && _input === 'c') {
|
|
64
|
-
exit();
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
const runningCount = agents.filter((a) => a.status === 'running').length;
|
|
68
|
-
const spawningCount = agents.filter((a) => a.status === 'spawning').length;
|
|
69
|
-
const stoppedCount = agents.filter((a) => a.status === 'exited' || a.status === 'errored').length;
|
|
70
|
-
const statusParts = [];
|
|
71
|
-
if (runningCount > 0) {
|
|
72
|
-
statusParts.push(`${runningCount} running`);
|
|
73
|
-
}
|
|
74
|
-
if (spawningCount > 0) {
|
|
75
|
-
statusParts.push(`${spawningCount} starting`);
|
|
76
|
-
}
|
|
77
|
-
if (stoppedCount > 0) {
|
|
78
|
-
statusParts.push(`${stoppedCount} stopped`);
|
|
79
|
-
}
|
|
80
|
-
const statusLabel = statusParts.length > 0 ? statusParts.join(', ') : 'no agents running';
|
|
81
|
-
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, " ", agents.length, " agent", agents.length !== 1 ? 's' : '', ' ', border.horizontal, " ", statusLabel] })] }), agents.map((agent, index) => {
|
|
82
|
-
const isSelected = index === selectedIndex;
|
|
83
|
-
const statusColor = STATUS_COLORS[agent.status];
|
|
84
|
-
const statusSymbol = STATUS_SYMBOLS[agent.status];
|
|
85
|
-
const isAlive = agent.status === 'running' || agent.status === 'spawning';
|
|
86
|
-
const statusText = agent.status === 'exited' || agent.status === 'errored'
|
|
87
|
-
? `${agent.status} (${agent.exitCode})`
|
|
88
|
-
: agent.status;
|
|
89
|
-
const agentStats = statsMap.get(agent.name);
|
|
90
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? colors.honey : undefined, children: isSelected ? `${symbols.diamond} ` : ' ' }), _jsxs(Text, { color: statusColor, children: [statusSymbol, " "] }), _jsx(Text, { color: isAlive ? colors.white : colors.grayDim, children: agent.name.padEnd(20) }), _jsx(Text, { color: statusColor, children: statusText.padEnd(16) }), agentStats && _jsx(ColoredStats, { stats: agentStats })] }, agent.name));
|
|
91
|
-
}), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.grayDim, children: [symbols.arrow, " ", '\u2191\u2193', " navigate ", ' ', " space/enter stop/start ", ' ', " ctrl+c quit"] }) })] }));
|
|
92
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect } from 'react';
|
|
3
|
-
import { Box, Text, useApp, useInput } from 'ink';
|
|
4
|
-
import { colors, symbols } from '../theme.js';
|
|
5
|
-
import { scanAgents, fetchBulkStats, sortByHoney } from '../agents.js';
|
|
6
|
-
function formatDate(date) {
|
|
7
|
-
const formatted = date.toLocaleDateString('en-US', {
|
|
8
|
-
year: 'numeric',
|
|
9
|
-
month: 'short',
|
|
10
|
-
day: 'numeric',
|
|
11
|
-
});
|
|
12
|
-
return formatted;
|
|
13
|
-
}
|
|
14
|
-
function StatsText({ stats }) {
|
|
15
|
-
if (stats === null) {
|
|
16
|
-
return _jsx(Text, { color: colors.grayDim, children: "-" });
|
|
17
|
-
}
|
|
18
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.honey, children: ["H:", Math.floor(stats.honey)] }), _jsx(Text, { color: colors.grayDim, children: " " }), _jsxs(Text, { color: colors.wax, children: ["W:", Math.floor(stats.wax)] }), _jsx(Text, { color: colors.grayDim, children: " " }), _jsxs(Text, { color: colors.green, children: ["WR:", (stats.win_rate * 100).toFixed(2), "%"] }), _jsx(Text, { color: colors.grayDim, children: " " }), _jsxs(Text, { color: colors.cyan, children: ["C:", stats.confidence.toFixed(2)] })] }));
|
|
19
|
-
}
|
|
20
|
-
export function SelectAgentApp({ onSelect }) {
|
|
21
|
-
const { exit } = useApp();
|
|
22
|
-
const [rows, setRows] = useState(null);
|
|
23
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
const load = async () => {
|
|
26
|
-
const agents = await scanAgents();
|
|
27
|
-
if (agents.length === 0) {
|
|
28
|
-
setRows([]);
|
|
29
|
-
exit();
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
const names = agents.map((a) => a.name);
|
|
33
|
-
const statsMap = await fetchBulkStats(names);
|
|
34
|
-
const agentRows = agents.map((info) => ({
|
|
35
|
-
info,
|
|
36
|
-
stats: statsMap.get(info.name) ?? null,
|
|
37
|
-
}));
|
|
38
|
-
const sortedRows = sortByHoney(agentRows);
|
|
39
|
-
setRows(sortedRows);
|
|
40
|
-
};
|
|
41
|
-
void load();
|
|
42
|
-
}, []);
|
|
43
|
-
useInput((_input, key) => {
|
|
44
|
-
if (rows === null || rows.length === 0) {
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
if (key.upArrow) {
|
|
48
|
-
setSelectedIndex((prev) => {
|
|
49
|
-
const max = rows.length - 1;
|
|
50
|
-
return prev > 0 ? prev - 1 : max;
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
else if (key.downArrow) {
|
|
54
|
-
setSelectedIndex((prev) => {
|
|
55
|
-
const max = rows.length - 1;
|
|
56
|
-
return prev < max ? prev + 1 : 0;
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
else if (key.return) {
|
|
60
|
-
const row = rows[selectedIndex];
|
|
61
|
-
if (row) {
|
|
62
|
-
onSelect(row.info);
|
|
63
|
-
exit();
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
else if (key.ctrl && _input === 'c') {
|
|
67
|
-
exit();
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
if (rows === null) {
|
|
71
|
-
return (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: colors.gray, children: "Scanning agents..." }) }));
|
|
72
|
-
}
|
|
73
|
-
if (rows.length === 0) {
|
|
74
|
-
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: "No agents found" })] }), _jsxs(Text, { color: colors.gray, children: ["Create one with: ", _jsx(Text, { color: colors.white, children: "npx @hive-org/cli@latest create" })] })] }));
|
|
75
|
-
}
|
|
76
|
-
const nameWidth = Math.max(6, ...rows.map((r) => r.info.name.length)) + 2;
|
|
77
|
-
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: "Select an agent to start" }), _jsxs(Text, { color: colors.grayDim, children: [" (", rows.length, ")"] })] }), rows.map((row, index) => {
|
|
78
|
-
const isSelected = index === selectedIndex;
|
|
79
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? colors.honey : colors.grayDim, children: isSelected ? `${symbols.diamond} ` : ' ' }), _jsx(Text, { color: isSelected ? colors.white : colors.gray, bold: isSelected, children: row.info.name.padEnd(nameWidth) }), _jsx(StatsText, { stats: row.stats }), _jsxs(Text, { color: colors.grayDim, children: [" ", formatDate(row.info.created)] })] }, row.info.name));
|
|
80
|
-
}), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.grayDim, children: [symbols.arrow, " ", '\u2191\u2193', " navigate ", ' ', " enter select ", ' ', " ctrl+c quit"] }) })] }));
|
|
81
|
-
}
|
package/dist/start/StartApp.js
DELETED
|
@@ -1,189 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
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
|
-
}
|