@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,88 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import { border, colors, symbols } from '../../shared/theme.js';
|
|
5
|
+
function formatCountdown(ms) {
|
|
6
|
+
if (ms < 0)
|
|
7
|
+
return 'expired';
|
|
8
|
+
const totalMinutes = Math.floor(ms / 60_000);
|
|
9
|
+
if (totalMinutes < 1)
|
|
10
|
+
return '<1m';
|
|
11
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
12
|
+
const minutes = totalMinutes % 60;
|
|
13
|
+
if (hours >= 1)
|
|
14
|
+
return `${hours}h ${minutes}m`;
|
|
15
|
+
return `${minutes}m`;
|
|
16
|
+
}
|
|
17
|
+
function isPredictionResolved(prediction, now) {
|
|
18
|
+
const resolvedAt = new Date(prediction.resolved_at);
|
|
19
|
+
return resolvedAt <= now;
|
|
20
|
+
}
|
|
21
|
+
function formatPredictionRow(prediction, now) {
|
|
22
|
+
const resolved = isPredictionResolved(prediction, now);
|
|
23
|
+
const sign = prediction.conviction >= 0 ? '+' : '';
|
|
24
|
+
const convictionStr = `${sign}${prediction.conviction.toFixed(2)}%`;
|
|
25
|
+
const project = `c/${prediction.project_id}`;
|
|
26
|
+
if (!resolved) {
|
|
27
|
+
const resolvedAt = new Date(prediction.resolved_at);
|
|
28
|
+
const remainingMs = resolvedAt.getTime() - now.getTime();
|
|
29
|
+
const countdown = formatCountdown(remainingMs);
|
|
30
|
+
return `${symbols.diamondOpen} ${project} ${convictionStr} pending ${countdown}`;
|
|
31
|
+
}
|
|
32
|
+
if (prediction.honey > 0) {
|
|
33
|
+
return `${symbols.check} ${project} ${convictionStr} +${prediction.honey.toFixed(2)} honey`;
|
|
34
|
+
}
|
|
35
|
+
if (prediction.wax > 0) {
|
|
36
|
+
return `${symbols.cross} ${project} ${convictionStr} ${prediction.wax.toFixed(2)} wax`;
|
|
37
|
+
}
|
|
38
|
+
return `${symbols.diamond} ${project} ${convictionStr} resolved`;
|
|
39
|
+
}
|
|
40
|
+
function getRowColor(prediction, now) {
|
|
41
|
+
const resolved = isPredictionResolved(prediction, now);
|
|
42
|
+
if (!resolved)
|
|
43
|
+
return colors.white;
|
|
44
|
+
if (prediction.honey > 0)
|
|
45
|
+
return colors.green;
|
|
46
|
+
if (prediction.wax > 0)
|
|
47
|
+
return colors.wax;
|
|
48
|
+
return colors.gray;
|
|
49
|
+
}
|
|
50
|
+
export function PredictionsPanel({ predictions, loading, hasMore, onExit, onLoadMore, termWidth, }) {
|
|
51
|
+
const [scrollOffset, setScrollOffset] = useState(0);
|
|
52
|
+
// Reserve lines: header(1) + padding(1) + footer separator(1) + footer text(1) + bottom border(1)
|
|
53
|
+
const chromeLines = 5;
|
|
54
|
+
const visibleRows = Math.max(1, (process.stdout.rows || 20) - 12 - chromeLines);
|
|
55
|
+
useInput((input, key) => {
|
|
56
|
+
if (key.escape) {
|
|
57
|
+
onExit();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (key.upArrow) {
|
|
61
|
+
setScrollOffset((prev) => Math.max(0, prev - 1));
|
|
62
|
+
}
|
|
63
|
+
if (key.downArrow) {
|
|
64
|
+
const maxOffset = Math.max(0, predictions.length - visibleRows);
|
|
65
|
+
setScrollOffset((prev) => Math.min(maxOffset, prev + 1));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
// Infinite scroll: trigger load when near bottom
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
const threshold = 3;
|
|
71
|
+
const nearBottom = scrollOffset + visibleRows >= predictions.length - threshold;
|
|
72
|
+
if (nearBottom && hasMore && !loading) {
|
|
73
|
+
void onLoadMore();
|
|
74
|
+
}
|
|
75
|
+
}, [scrollOffset, predictions.length, hasMore, loading, onLoadMore, visibleRows]);
|
|
76
|
+
const boxWidth = termWidth;
|
|
77
|
+
const innerWidth = boxWidth - 4;
|
|
78
|
+
const now = new Date();
|
|
79
|
+
const visiblePredictions = predictions.slice(scrollOffset, scrollOffset + visibleRows);
|
|
80
|
+
const countLabel = `${predictions.length} loaded`;
|
|
81
|
+
const titleText = ` ${symbols.hive} Predictions `;
|
|
82
|
+
const titleFill = Math.max(0, boxWidth - titleText.length - countLabel.length - 6);
|
|
83
|
+
return (_jsxs(Box, { flexDirection: "column", width: boxWidth, children: [_jsxs(Box, { children: [_jsxs(Text, { color: colors.honey, children: [border.topLeft, border.horizontal] }), _jsx(Text, { color: colors.honey, bold: true, children: titleText }), _jsxs(Text, { color: colors.gray, children: [border.horizontal.repeat(2), " ", countLabel, " ", border.horizontal.repeat(titleFill), border.topRight] })] }), _jsxs(Box, { flexDirection: "column", paddingLeft: 2, paddingRight: 2, minHeight: visibleRows, children: [predictions.length === 0 && !loading && (_jsx(Box, { children: _jsx(Text, { color: colors.gray, children: "No predictions yet." }) })), visiblePredictions.map((prediction) => {
|
|
84
|
+
const row = formatPredictionRow(prediction, now);
|
|
85
|
+
const rowColor = getRowColor(prediction, now);
|
|
86
|
+
return (_jsx(Box, { children: _jsx(Text, { color: rowColor, wrap: "truncate", children: row.length > innerWidth ? row.slice(0, innerWidth) : row }) }, prediction.id));
|
|
87
|
+
}), loading && (_jsx(Box, { children: _jsx(Text, { color: colors.honey, children: "Loading..." }) }))] }), _jsx(Box, { children: _jsxs(Text, { color: colors.gray, children: [border.teeLeft, border.horizontal.repeat(boxWidth - 2), border.teeRight] }) }), _jsx(Box, { paddingLeft: 2, children: _jsxs(Text, { color: colors.gray, children: ['\u2191\u2193', " scroll ", symbols.circle, " esc exit"] }) }), _jsx(Box, { children: _jsxs(Text, { color: colors.gray, children: [border.bottomLeft, border.horizontal.repeat(boxWidth - 2), border.bottomRight] }) })] }));
|
|
88
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
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 '../../shared/theme.js';
|
|
5
|
+
import { scanAgents, fetchBulkStats, sortByHoney, } from '../../../shared/config/agent.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 formatPnl(value) {
|
|
15
|
+
const abs = Math.abs(Math.round(value));
|
|
16
|
+
if (value > 0)
|
|
17
|
+
return `+$${abs}`;
|
|
18
|
+
if (value < 0)
|
|
19
|
+
return `-$${abs}`;
|
|
20
|
+
return '$0';
|
|
21
|
+
}
|
|
22
|
+
function StatsText({ stats }) {
|
|
23
|
+
if (stats === null) {
|
|
24
|
+
return _jsx(Text, { color: colors.grayDim, children: "-" });
|
|
25
|
+
}
|
|
26
|
+
const winRatePercent = Math.round(stats.win_rate * 100);
|
|
27
|
+
const confidencePercent = Math.round(stats.confidence * 100);
|
|
28
|
+
const pnl = formatPnl(stats.simulated_pnl);
|
|
29
|
+
const pnlColor = stats.simulated_pnl > 0 ? colors.green : stats.simulated_pnl < 0 ? colors.red : colors.grayDim;
|
|
30
|
+
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:", winRatePercent, "%"] }), _jsx(Text, { color: colors.grayDim, children: " " }), _jsxs(Text, { color: colors.cyan, children: ["C:", confidencePercent, "%"] }), _jsx(Text, { color: colors.grayDim, children: " " }), _jsxs(Text, { color: pnlColor, children: ["Sim PnL:", pnl] })] }));
|
|
31
|
+
}
|
|
32
|
+
export function SelectAgentApp({ onSelect }) {
|
|
33
|
+
const { exit } = useApp();
|
|
34
|
+
const [rows, setRows] = useState(null);
|
|
35
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const load = async () => {
|
|
38
|
+
const agents = await scanAgents();
|
|
39
|
+
if (agents.length === 0) {
|
|
40
|
+
setRows([]);
|
|
41
|
+
exit();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const names = agents.map((a) => a.name);
|
|
45
|
+
const statsMap = await fetchBulkStats(names);
|
|
46
|
+
const agentRows = agents.map((info) => ({
|
|
47
|
+
info,
|
|
48
|
+
stats: statsMap.get(info.name) ?? null,
|
|
49
|
+
}));
|
|
50
|
+
const sortedRows = sortByHoney(agentRows);
|
|
51
|
+
setRows(sortedRows);
|
|
52
|
+
};
|
|
53
|
+
void load();
|
|
54
|
+
}, []);
|
|
55
|
+
useInput((_input, key) => {
|
|
56
|
+
if (rows === null || rows.length === 0) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (key.upArrow) {
|
|
60
|
+
setSelectedIndex((prev) => {
|
|
61
|
+
const max = rows.length - 1;
|
|
62
|
+
return prev > 0 ? prev - 1 : max;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
else if (key.downArrow) {
|
|
66
|
+
setSelectedIndex((prev) => {
|
|
67
|
+
const max = rows.length - 1;
|
|
68
|
+
return prev < max ? prev + 1 : 0;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
else if (key.return) {
|
|
72
|
+
const row = rows[selectedIndex];
|
|
73
|
+
if (row) {
|
|
74
|
+
onSelect(row.info);
|
|
75
|
+
exit();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else if (key.ctrl && _input === 'c') {
|
|
79
|
+
exit();
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
if (rows === null) {
|
|
83
|
+
return (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: colors.gray, children: "Scanning agents..." }) }));
|
|
84
|
+
}
|
|
85
|
+
if (rows.length === 0) {
|
|
86
|
+
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 @zhive/cli@latest create" })] })] }));
|
|
87
|
+
}
|
|
88
|
+
const nameWidth = Math.max(6, ...rows.map((r) => r.info.name.length)) + 2;
|
|
89
|
+
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) => {
|
|
90
|
+
const isSelected = index === selectedIndex;
|
|
91
|
+
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));
|
|
92
|
+
}), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.grayDim, children: [symbols.arrow, " ", '\u2191\u2193', " navigate ", ' ', " enter select ", ' ', " ctrl+c quit"] }) })] }));
|
|
93
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { Text } from 'ink';
|
|
4
|
+
import { colors } from '../../shared/theme.js';
|
|
5
|
+
import { SPINNER_FRAMES, useSpinnerFrame } from './SpinnerContext.js';
|
|
6
|
+
export function Spinner({ label }) {
|
|
7
|
+
const frame = useSpinnerFrame();
|
|
8
|
+
return (_jsxs(Text, { children: [_jsx(Text, { color: colors.honey, children: SPINNER_FRAMES[frame] }), _jsxs(Text, { color: colors.gray, children: [" ", label] })] }));
|
|
9
|
+
}
|
|
10
|
+
export function TypewriterText({ text, color, speed = 25, }) {
|
|
11
|
+
const [visible, setVisible] = useState(0);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (visible >= text.length)
|
|
14
|
+
return;
|
|
15
|
+
const timer = setTimeout(() => {
|
|
16
|
+
setVisible((prev) => Math.min(prev + 2, text.length));
|
|
17
|
+
}, speed);
|
|
18
|
+
return () => {
|
|
19
|
+
clearTimeout(timer);
|
|
20
|
+
};
|
|
21
|
+
}, [visible, text, speed]);
|
|
22
|
+
return _jsx(Text, { color: color, children: text.slice(0, visible) });
|
|
23
|
+
}
|
|
24
|
+
export function PollText({ text, color, animate, }) {
|
|
25
|
+
if (animate) {
|
|
26
|
+
return _jsx(TypewriterText, { text: text, color: color });
|
|
27
|
+
}
|
|
28
|
+
return _jsx(Text, { color: color, children: text });
|
|
29
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useEffect, useState } from 'react';
|
|
3
|
+
export const SPINNER_FRAMES = ['\u25D0', '\u25D3', '\u25D1', '\u25D2'];
|
|
4
|
+
const SPINNER_INTERVAL_MS = 200;
|
|
5
|
+
const SpinnerContext = createContext(0);
|
|
6
|
+
export function SpinnerProvider({ children }) {
|
|
7
|
+
const [frame, setFrame] = useState(0);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const timer = setInterval(() => {
|
|
10
|
+
setFrame((prev) => (prev + 1) % SPINNER_FRAMES.length);
|
|
11
|
+
}, SPINNER_INTERVAL_MS);
|
|
12
|
+
return () => {
|
|
13
|
+
clearInterval(timer);
|
|
14
|
+
};
|
|
15
|
+
}, []);
|
|
16
|
+
return _jsx(SpinnerContext.Provider, { value: frame, children: children });
|
|
17
|
+
}
|
|
18
|
+
export function useSpinnerFrame() {
|
|
19
|
+
return useContext(SpinnerContext);
|
|
20
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Static, Text } from 'ink';
|
|
3
|
+
import { useAgent } from '../hooks/useAgent.js';
|
|
4
|
+
import { PollText, Spinner } from './Spinner.js';
|
|
5
|
+
import { SpinnerProvider } from './SpinnerContext.js';
|
|
6
|
+
import { CommandInput } from './CommandInput.js';
|
|
7
|
+
import { border, colors, symbols } from '../../shared/theme.js';
|
|
8
|
+
import { HIVE_FRONTEND_URL } from '../../../shared/config/constant.js';
|
|
9
|
+
import { formatTime } from '../../../shared/agent/utils.js';
|
|
10
|
+
import { useChat } from '../hooks/useChat.js';
|
|
11
|
+
import { activityFormatter } from '../hooks/utils.js';
|
|
12
|
+
import { ColoredStats } from '../../../components/ColoredStats.js';
|
|
13
|
+
// ─── Main TUI App ────────────────────────────────────
|
|
14
|
+
export function App() {
|
|
15
|
+
const { connected, agentName, modelInfo, sectorsDisplay, timeframesDisplay, activePollActivities, settledPollActivities, predictionCount, termWidth, stats, statsUpdatedAt, } = useAgent();
|
|
16
|
+
const { input, chatActivity, chatBuffer, chatStreaming, handleChatSubmit, setInput } = useChat(agentName);
|
|
17
|
+
// When stdin is not a TTY (piped by hive-cli start), skip interactive input
|
|
18
|
+
const isInteractive = process.stdin.isTTY === true;
|
|
19
|
+
const boxWidth = termWidth;
|
|
20
|
+
const visibleChatActivity = chatActivity.slice(-5);
|
|
21
|
+
const statsText = predictionCount > 0 ? ` ${border.horizontal.repeat(3)} ${predictionCount} predicted` : '';
|
|
22
|
+
const connectedDisplay = connected ? 'Connected to the Hive' : 'connecting...';
|
|
23
|
+
const nameDisplay = `${agentName} agent`;
|
|
24
|
+
const headerFill = Math.max(0, boxWidth - nameDisplay.length - connectedDisplay.length - 12 - statsText.length);
|
|
25
|
+
return (_jsxs(SpinnerProvider, { children: [_jsx(Static, { items: settledPollActivities, children: (item, i) => {
|
|
26
|
+
const formatted = activityFormatter.format(item);
|
|
27
|
+
if (formatted.length === 0)
|
|
28
|
+
return _jsx(Box, {}, `settled-${item.id ?? i}`);
|
|
29
|
+
return _jsx(Text, { children: formatted.join('\n') }, `settled-${item.id ?? i}`);
|
|
30
|
+
} }), _jsxs(Box, { flexDirection: "column", width: boxWidth, children: [_jsxs(Box, { children: [_jsx(Text, { color: colors.honey, children: `${border.topLeft}${border.horizontal} ${symbols.hive} ` }), _jsx(Text, { color: colors.white, bold: true, children: nameDisplay }), _jsxs(Text, { color: colors.gray, children: [" ", `${border.horizontal.repeat(3)} `] }), _jsx(Text, { color: connected ? colors.green : colors.honey, children: connectedDisplay }), statsText && _jsxs(Text, { color: colors.gray, children: [" ", `${border.horizontal.repeat(3)} `] }), statsText && _jsxs(Text, { color: colors.honey, children: [predictionCount, " predicted"] }), _jsxs(Text, { color: colors.gray, children: [' ', border.horizontal.repeat(Math.max(0, headerFill)), border.topRight] })] }), modelInfo && (_jsxs(Box, { paddingLeft: 1, children: [_jsxs(Text, { color: colors.gray, children: [symbols.hive, " "] }), _jsx(Text, { color: colors.cyan, children: modelInfo.modelId }), _jsxs(Text, { color: colors.gray, children: [" ", '\u00d7', " "] }), _jsx(Text, { color: colors.white, children: "zData" })] })), sectorsDisplay && (_jsxs(Box, { paddingLeft: 1, children: [_jsxs(Text, { color: colors.gray, children: [symbols.hive, " "] }), _jsx(Text, { color: colors.gray, children: "sectors: " }), _jsx(Text, { color: colors.white, children: sectorsDisplay })] })), timeframesDisplay && (_jsxs(Box, { paddingLeft: 1, children: [_jsxs(Text, { color: colors.gray, children: [symbols.hive, " "] }), _jsx(Text, { color: colors.gray, children: "timeframes: " }), _jsx(Text, { color: colors.white, children: timeframesDisplay })] })), stats && (_jsxs(Box, { paddingLeft: 1, children: [_jsxs(Text, { color: colors.gray, children: [symbols.hive, " "] }), _jsx(ColoredStats, { stats: stats }), statsUpdatedAt && (_jsxs(Text, { color: colors.grayDim, children: [" ", '\u00b7', " updated ", statsUpdatedAt.toLocaleDateString(), " ", formatTime(statsUpdatedAt)] }))] })), connected && (_jsxs(Box, { paddingLeft: 1, children: [_jsxs(Text, { color: colors.gray, children: [symbols.hive, " View all ", agentName, "'s activity at "] }), _jsxs(Text, { color: colors.cyan, children: [HIVE_FRONTEND_URL, "/agent/", agentName] })] })), _jsxs(Box, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, minHeight: 2, children: [!connected && _jsx(Spinner, { label: "Initiating neural link..." }), activePollActivities.map((item, i) => {
|
|
31
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Text, { color: colors.gray, dimColor: true, children: [formatTime(item.timestamp), ' '] }), _jsxs(Text, { color: colors.controversial, children: [symbols.hive, " "] }), _jsx(PollText, { color: colors.controversial, text: activityFormatter.getText(item), animate: false }), _jsx(Text, { children: " " }), _jsx(Spinner, { label: "analyzing..." })] }), activityFormatter.getDetail(item) && (_jsx(Box, { marginLeft: 13, children: _jsx(PollText, { color: colors.gray, text: `"${activityFormatter.getDetail(item)}"`, animate: false }) }))] }, `active-${item.id ?? i}`));
|
|
32
|
+
})] }), (chatActivity.length > 0 || chatStreaming) && (_jsxs(_Fragment, { children: [_jsx(Box, { children: _jsxs(Text, { color: colors.gray, children: [border.teeLeft, `${border.horizontal.repeat(2)} chat with ${agentName} agent `, border.horizontal.repeat(Math.max(0, boxWidth - agentName.length - 22)), border.teeRight] }) }), _jsxs(Box, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, minHeight: 2, maxHeight: 8, children: [visibleChatActivity.map((item, i) => (_jsxs(Box, { children: [item.type === 'chat-user' && (_jsxs(Box, { children: [_jsxs(Text, { color: colors.white, bold: true, children: ["you:", ' '] }), _jsx(Text, { color: colors.white, children: item.text })] })), item.type === 'chat-agent' && (_jsxs(Box, { children: [_jsxs(Text, { color: colors.honey, bold: true, children: [agentName, " agent:", ' '] }), _jsx(Text, { color: colors.white, wrap: "wrap", children: item.text })] })), item.type === 'chat-error' && (_jsx(Box, { children: _jsxs(Text, { color: colors.red, children: [symbols.cross, " ", item.text] }) }))] }, i))), chatStreaming && chatBuffer && (_jsxs(Box, { children: [_jsxs(Text, { color: colors.honey, bold: true, children: [agentName, " agent:", ' '] }), _jsx(Text, { color: colors.white, wrap: "wrap", children: chatBuffer })] }))] })] })), _jsx(Box, { children: _jsxs(Text, { color: colors.gray, children: [isInteractive ? border.teeLeft : border.bottomLeft, border.horizontal.repeat(boxWidth - 2), isInteractive ? border.teeRight : border.bottomRight] }) }), isInteractive && (_jsxs(_Fragment, { children: [_jsx(Box, { paddingLeft: 1, children: _jsx(CommandInput, { value: input, onChange: setInput, onSubmit: (val) => {
|
|
33
|
+
setInput('');
|
|
34
|
+
void handleChatSubmit(val);
|
|
35
|
+
}, placeholder: chatStreaming ? 'thinking...' : `chat with ${agentName} agent...` }) }), _jsx(Box, { children: _jsxs(Text, { color: colors.gray, children: [border.bottomLeft, border.horizontal.repeat(boxWidth - 2), border.bottomRight] }) })] }))] })] }));
|
|
36
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
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', ['@zhive/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
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from 'ink';
|
|
3
|
+
import { showWelcome } from '../../shared/welcome.js';
|
|
4
|
+
import { styled, symbols } from '../../shared/theme.js';
|
|
5
|
+
import { AgentProcessManager } from '../AgentProcessManager.js';
|
|
6
|
+
import { Dashboard } from '../ui/Dashboard.js';
|
|
7
|
+
import { fetchBulkStats, scanAgents, sortAgentsByHoney } from '../../../shared/config/agent.js';
|
|
8
|
+
export async function startAllCommand() {
|
|
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 @zhive/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
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { Box, Text, useApp, useInput } from 'ink';
|
|
4
|
+
import { colors, symbols, border } from '../../shared/theme.js';
|
|
5
|
+
import { HIVE_FRONTEND_URL } from '../../../shared/config/constant.js';
|
|
6
|
+
import { ColoredStats } from '../../../components/ColoredStats.js';
|
|
7
|
+
const STATUS_COLORS = {
|
|
8
|
+
spawning: colors.honey,
|
|
9
|
+
running: colors.green,
|
|
10
|
+
exited: colors.grayDim,
|
|
11
|
+
errored: colors.red,
|
|
12
|
+
};
|
|
13
|
+
const STATUS_SYMBOLS = {
|
|
14
|
+
spawning: symbols.diamondOpen,
|
|
15
|
+
running: symbols.dot,
|
|
16
|
+
exited: '\u25CB', // ○
|
|
17
|
+
errored: symbols.cross,
|
|
18
|
+
};
|
|
19
|
+
const POLL_INTERVAL_MS = 1_000;
|
|
20
|
+
const STOPPABLE = new Set(['running', 'spawning']);
|
|
21
|
+
const STARTABLE = new Set(['exited', 'errored']);
|
|
22
|
+
export function Dashboard({ manager, statsMap }) {
|
|
23
|
+
const { exit } = useApp();
|
|
24
|
+
const [agents, setAgents] = useState(manager.getStates());
|
|
25
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const timer = setInterval(() => {
|
|
28
|
+
setAgents(manager.getStates());
|
|
29
|
+
}, POLL_INTERVAL_MS);
|
|
30
|
+
return () => clearInterval(timer);
|
|
31
|
+
}, [manager]);
|
|
32
|
+
// Clamp selectedIndex when agents list changes
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const maxIndex = Math.max(agents.length - 1, 0);
|
|
35
|
+
setSelectedIndex((prev) => Math.min(prev, maxIndex));
|
|
36
|
+
}, [agents.length]);
|
|
37
|
+
useInput((_input, key) => {
|
|
38
|
+
if (key.upArrow) {
|
|
39
|
+
setSelectedIndex((prev) => {
|
|
40
|
+
const max = agents.length - 1;
|
|
41
|
+
return prev > 0 ? prev - 1 : max;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
else if (key.downArrow) {
|
|
45
|
+
setSelectedIndex((prev) => {
|
|
46
|
+
const max = agents.length - 1;
|
|
47
|
+
return prev < max ? prev + 1 : 0;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
else if (_input === ' ' || key.return) {
|
|
51
|
+
const agent = agents[selectedIndex];
|
|
52
|
+
if (!agent) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (STOPPABLE.has(agent.status)) {
|
|
56
|
+
void manager.stopAgent(agent.name);
|
|
57
|
+
}
|
|
58
|
+
else if (STARTABLE.has(agent.status)) {
|
|
59
|
+
manager.respawnPiped(agent.name);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else if (key.ctrl && _input === 'c') {
|
|
63
|
+
exit();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
const runningCount = agents.filter((a) => a.status === 'running').length;
|
|
67
|
+
const spawningCount = agents.filter((a) => a.status === 'spawning').length;
|
|
68
|
+
const stoppedCount = agents.filter((a) => a.status === 'exited' || a.status === 'errored').length;
|
|
69
|
+
const statusParts = [];
|
|
70
|
+
if (runningCount > 0) {
|
|
71
|
+
statusParts.push(`${runningCount} running`);
|
|
72
|
+
}
|
|
73
|
+
if (spawningCount > 0) {
|
|
74
|
+
statusParts.push(`${spawningCount} starting`);
|
|
75
|
+
}
|
|
76
|
+
if (stoppedCount > 0) {
|
|
77
|
+
statusParts.push(`${stoppedCount} stopped`);
|
|
78
|
+
}
|
|
79
|
+
const statusLabel = statusParts.length > 0 ? statusParts.join(', ') : 'no agents running';
|
|
80
|
+
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) => {
|
|
81
|
+
const isSelected = index === selectedIndex;
|
|
82
|
+
const statusColor = STATUS_COLORS[agent.status];
|
|
83
|
+
const statusSymbol = STATUS_SYMBOLS[agent.status];
|
|
84
|
+
const isAlive = agent.status === 'running' || agent.status === 'spawning';
|
|
85
|
+
const statusText = agent.status === 'exited' || agent.status === 'errored'
|
|
86
|
+
? `${agent.status} (${agent.exitCode})`
|
|
87
|
+
: agent.status;
|
|
88
|
+
const agentStats = statsMap.get(agent.name);
|
|
89
|
+
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));
|
|
90
|
+
}), agents[selectedIndex] && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.gray, children: "View your agent's activity at " }), _jsxs(Text, { color: colors.cyan, children: [HIVE_FRONTEND_URL, "/agent/", agents[selectedIndex].name] })] })), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.grayDim, children: [symbols.arrow, " ", '\u2191\u2193', " navigate ", ' ', " space/enter stop/start ", ' ', " ctrl+c quit"] }) })] }));
|
|
91
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import { colors, animation } from '../commands/shared/theme.js';
|
|
5
|
+
function buildTickerChars(step) {
|
|
6
|
+
const stepStr = String(step).padStart(2, '0');
|
|
7
|
+
const digits = stepStr.split('');
|
|
8
|
+
return animation.HEX_CHARS + digits.join('') + '▪▫░▒';
|
|
9
|
+
}
|
|
10
|
+
function buildRow(cols, frame, rowIndex, tickerChars) {
|
|
11
|
+
const segments = [];
|
|
12
|
+
const isSecondRow = rowIndex === 1;
|
|
13
|
+
const scrollSpeed = isSecondRow ? 3 : 2;
|
|
14
|
+
const direction = isSecondRow ? -1 : 1;
|
|
15
|
+
const sinFreq = isSecondRow ? 0.4 : 0.3;
|
|
16
|
+
const sinPhase = isSecondRow ? -0.4 : 0.6;
|
|
17
|
+
const wrapLen = cols * 2;
|
|
18
|
+
for (let c = 0; c < cols; c++) {
|
|
19
|
+
const scrolledC = direction === 1
|
|
20
|
+
? (c + frame * scrollSpeed) % wrapLen
|
|
21
|
+
: (cols - c + frame * scrollSpeed) % wrapLen;
|
|
22
|
+
const charIdx = scrolledC % tickerChars.length;
|
|
23
|
+
const char = tickerChars[charIdx];
|
|
24
|
+
const isHex = char === '⬡' || char === '⬢';
|
|
25
|
+
const pulseHit = Math.sin((c + frame * sinPhase) * sinFreq) > 0.5;
|
|
26
|
+
// Edge fade: dim the outermost 4 columns
|
|
27
|
+
const edgeDist = Math.min(c, cols - 1 - c);
|
|
28
|
+
if (edgeDist < 2) {
|
|
29
|
+
segments.push({ char: '·', color: colors.grayDim });
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (edgeDist < 4) {
|
|
33
|
+
segments.push({ char, color: colors.grayDim });
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (pulseHit && isHex) {
|
|
37
|
+
segments.push({ char, color: colors.honey });
|
|
38
|
+
}
|
|
39
|
+
else if (pulseHit) {
|
|
40
|
+
segments.push({ char, color: colors.green });
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
segments.push({ char, color: colors.grayDim });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return segments;
|
|
47
|
+
}
|
|
48
|
+
function renderSegments(segments) {
|
|
49
|
+
const elements = [];
|
|
50
|
+
let runColor = segments[0]?.color ?? colors.grayDim;
|
|
51
|
+
let runChars = '';
|
|
52
|
+
for (let i = 0; i < segments.length; i++) {
|
|
53
|
+
const seg = segments[i];
|
|
54
|
+
if (seg.color === runColor) {
|
|
55
|
+
runChars += seg.char;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
elements.push(_jsx(Text, { color: runColor, children: runChars }, `${elements.length}`));
|
|
59
|
+
runColor = seg.color;
|
|
60
|
+
runChars = seg.char;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (runChars.length > 0) {
|
|
64
|
+
elements.push(_jsx(Text, { color: runColor, children: runChars }, `${elements.length}`));
|
|
65
|
+
}
|
|
66
|
+
return elements;
|
|
67
|
+
}
|
|
68
|
+
export function AsciiTicker({ rows = 1, step = 1 }) {
|
|
69
|
+
const [frame, setFrame] = useState(0);
|
|
70
|
+
const cols = process.stdout.columns || 60;
|
|
71
|
+
const tickerChars = buildTickerChars(step);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const timer = setInterval(() => {
|
|
74
|
+
setFrame((prev) => prev + 1);
|
|
75
|
+
}, animation.TICK_MS);
|
|
76
|
+
return () => {
|
|
77
|
+
clearInterval(timer);
|
|
78
|
+
};
|
|
79
|
+
}, []);
|
|
80
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: renderSegments(buildRow(cols, frame, 0, tickerChars)) }), rows === 2 && _jsx(Text, { children: renderSegments(buildRow(cols, frame, 1, tickerChars)) })] }));
|
|
81
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { colors, border } from '../commands/shared/theme.js';
|
|
4
|
+
export function CharacterSummaryCard({ name, personality, voice, tradingStyle, sectors, sentiment, timeframe, bio, }) {
|
|
5
|
+
if (!personality && !voice && !tradingStyle && !sectors && !sentiment && !timeframe && !bio) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const termWidth = process.stdout.columns || 60;
|
|
9
|
+
const boxWidth = Math.min(termWidth - 4, 52);
|
|
10
|
+
const title = `AGENT IDENTITY — ${name}`;
|
|
11
|
+
const topBar = `${border.topLeft}${border.horizontal} ${title} ${border.horizontal.repeat(Math.max(0, boxWidth - title.length - 5))}${border.topRight}`;
|
|
12
|
+
const bottomBar = `${border.bottomLeft}${border.horizontal.repeat(Math.max(0, boxWidth - 2))}${border.bottomRight}`;
|
|
13
|
+
const rows = [];
|
|
14
|
+
const personalityDisplay = personality ?? '???';
|
|
15
|
+
rows.push({ label: 'Personality', value: personalityDisplay });
|
|
16
|
+
const voiceDisplay = voice ?? '???';
|
|
17
|
+
rows.push({ label: 'Voice', value: voiceDisplay });
|
|
18
|
+
const tradingStyleDisplay = tradingStyle ?? '???';
|
|
19
|
+
rows.push({ label: 'Trading', value: tradingStyleDisplay });
|
|
20
|
+
const sectorsDisplay = sectors ?? '???';
|
|
21
|
+
rows.push({ label: 'Sectors', value: sectorsDisplay });
|
|
22
|
+
const sentimentDisplay = sentiment ?? '???';
|
|
23
|
+
rows.push({ label: 'Sentiment', value: sentimentDisplay });
|
|
24
|
+
const timeframeDisplay = timeframe ?? '???';
|
|
25
|
+
rows.push({ label: 'Timeframe', value: timeframeDisplay });
|
|
26
|
+
const bioDisplay = bio ? (bio.length > 30 ? bio.slice(0, 30) + '...' : bio) : '???';
|
|
27
|
+
rows.push({ label: 'Bio', value: bioDisplay });
|
|
28
|
+
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [_jsx(Box, { children: _jsx(Text, { color: colors.honey, children: topBar }) }), rows.map((row) => {
|
|
29
|
+
const content = ` ${row.label}: ${row.value}`;
|
|
30
|
+
const padding = Math.max(0, boxWidth - content.length - 3);
|
|
31
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: colors.honey, children: border.vertical }), _jsxs(Text, { color: colors.grayDim, children: [" ", row.label, ": "] }), _jsx(Text, { color: colors.white, children: row.value }), _jsx(Text, { children: ' '.repeat(padding) }), _jsx(Text, { color: colors.honey, children: border.vertical })] }, row.label));
|
|
32
|
+
}), _jsx(Box, { children: _jsx(Text, { color: colors.honey, children: bottomBar }) })] }));
|
|
33
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { colors, border } from '../commands/shared/theme.js';
|
|
4
|
+
export function CodeBlock({ title, children }) {
|
|
5
|
+
const termWidth = process.stdout.columns || 60;
|
|
6
|
+
const boxWidth = Math.min(termWidth - 4, 76);
|
|
7
|
+
const lines = children.split('\n');
|
|
8
|
+
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsx(Box, { children: _jsxs(Text, { color: colors.grayDim, children: [border.topLeft, title
|
|
9
|
+
? `${border.horizontal} ${title} ${border.horizontal.repeat(Math.max(0, boxWidth - title.length - 5))}`
|
|
10
|
+
: border.horizontal.repeat(Math.max(0, boxWidth - 2)), border.topRight] }) }), lines.map((line, i) => (_jsxs(Box, { children: [_jsx(Text, { color: colors.grayDim, children: border.vertical }), _jsxs(Text, { color: colors.white, children: [" ", line] }), _jsx(Text, { children: ' '.repeat(Math.max(0, boxWidth - line.length - 3)) }), _jsx(Text, { color: colors.grayDim, children: border.vertical })] }, i))), _jsx(Box, { children: _jsxs(Text, { color: colors.grayDim, children: [border.bottomLeft, border.horizontal.repeat(Math.max(0, boxWidth - 2)), border.bottomRight] }) })] }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Text } from 'ink';
|
|
3
|
+
import { colors } from '../commands/shared/theme.js';
|
|
4
|
+
function formatPnl(value) {
|
|
5
|
+
const abs = Math.abs(Math.round(value));
|
|
6
|
+
if (value > 0)
|
|
7
|
+
return `+$${abs}`;
|
|
8
|
+
if (value < 0)
|
|
9
|
+
return `-$${abs}`;
|
|
10
|
+
return '$0';
|
|
11
|
+
}
|
|
12
|
+
export function ColoredStats({ stats }) {
|
|
13
|
+
const winRatePercent = Math.round(stats.win_rate * 100);
|
|
14
|
+
const confidencePercent = Math.round(stats.confidence * 100);
|
|
15
|
+
const pnl = formatPnl(stats.simulated_pnl);
|
|
16
|
+
const pnlColor = stats.simulated_pnl > 0 ? colors.green : stats.simulated_pnl < 0 ? colors.red : colors.grayDim;
|
|
17
|
+
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:", winRatePercent, "%"] }), _jsx(Text, { color: colors.grayDim, children: " " }), _jsxs(Text, { color: colors.cyan, children: ["C:", confidencePercent, "%"] }), _jsx(Text, { color: colors.grayDim, children: " " }), _jsxs(Text, { color: pnlColor, children: ["Sim PnL:", pnl] })] }));
|
|
18
|
+
}
|