@hive-org/cli 0.0.8 → 0.0.10
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/agent/app.js +1 -5
- package/dist/agent/components/HoneycombBoot.js +240 -219
- package/dist/agent/hooks/useAgent.js +0 -11
- package/dist/agent/process-lifecycle.js +3 -34
- package/dist/agent/run-headless.js +68 -0
- package/dist/agents.js +56 -1
- package/dist/avatar.js +34 -0
- package/dist/create/steps/DoneStep.js +1 -1
- package/dist/index.js +50 -11
- package/dist/list/ListApp.js +52 -11
- package/dist/start/AgentProcessManager.js +1 -34
- package/dist/start/Dashboard.js +7 -3
- package/dist/start/SelectAgentApp.js +40 -17
- package/dist/start/start-command.js +6 -4
- package/package.json +1 -1
package/dist/agent/app.js
CHANGED
|
@@ -6,15 +6,11 @@ import { colors, symbols, border } from './theme.js';
|
|
|
6
6
|
import { formatTime, convictionColor } from './helpers.js';
|
|
7
7
|
import { Spinner, PollText } from './components/Spinner.js';
|
|
8
8
|
import { AsciiTicker } from './components/AsciiTicker.js';
|
|
9
|
-
import { HoneycombBoot } from './components/HoneycombBoot.js';
|
|
10
9
|
// ─── Main TUI App ────────────────────────────────────
|
|
11
10
|
export function App() {
|
|
12
|
-
const {
|
|
11
|
+
const { connected, agentName, pollActivity, chatActivity, input, chatStreaming, chatBuffer, predictionCount, termWidth, setInput, handleChatSubmit, } = useAgent();
|
|
13
12
|
// When stdin is not a TTY (piped by hive-cli start), skip interactive input
|
|
14
13
|
const isInteractive = process.stdin.isTTY === true;
|
|
15
|
-
if (phase === 'booting') {
|
|
16
|
-
return _jsx(HoneycombBoot, { agentName: agentName, width: termWidth, onComplete: handleBootComplete });
|
|
17
|
-
}
|
|
18
14
|
const boxWidth = termWidth;
|
|
19
15
|
const visiblePollActivity = pollActivity.slice(-10);
|
|
20
16
|
const visibleChatActivity = chatActivity.slice(-3);
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { useState, useEffect, useRef } from 'react';
|
|
3
|
-
import { Box, Text } from 'ink';
|
|
1
|
+
import chalk from 'chalk';
|
|
4
2
|
import { colors, animation } from '../theme.js';
|
|
5
3
|
const BOOT_TOTAL_FRAMES = 58;
|
|
6
4
|
const BOOT_FRAME_MS = 80;
|
|
5
|
+
const DURATION_MS = BOOT_TOTAL_FRAMES * BOOT_FRAME_MS;
|
|
7
6
|
const NUM_BEES = 4;
|
|
8
7
|
const NUM_STREAMS = 5;
|
|
9
8
|
const SCRAMBLE_CHARS = '\u2B21\u2B22\u25C6\u25C7\u2591\u2592!@#$%01';
|
|
@@ -27,244 +26,266 @@ function isHexEdge(r, c) {
|
|
|
27
26
|
}
|
|
28
27
|
return false;
|
|
29
28
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
// ─── Raw ANSI boot animation ────────────────────────
|
|
30
|
+
export function showHoneycombBoot(agentName) {
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
const cols = process.stdout.columns || 60;
|
|
33
|
+
const gridRows = process.stdout.rows || 24;
|
|
34
|
+
let frame = 0;
|
|
35
|
+
// Init bees
|
|
36
|
+
const bees = [];
|
|
37
|
+
for (let i = 0; i < NUM_BEES; i++) {
|
|
38
|
+
bees.push({
|
|
39
|
+
r: Math.floor(Math.random() * gridRows),
|
|
40
|
+
c: Math.floor(Math.random() * cols),
|
|
41
|
+
vr: Math.random() > 0.5 ? 1 : -1,
|
|
42
|
+
vc: Math.random() > 0.5 ? 1 : -1,
|
|
43
|
+
});
|
|
38
44
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
// Init stream columns
|
|
46
|
+
const streamCols = [];
|
|
47
|
+
const spacing = Math.floor(cols / (NUM_STREAMS + 1));
|
|
48
|
+
for (let i = 1; i <= NUM_STREAMS; i++) {
|
|
49
|
+
streamCols.push(spacing * i);
|
|
42
50
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
function initBees(rows, cols) {
|
|
48
|
-
const bees = [];
|
|
49
|
-
for (let i = 0; i < NUM_BEES; i++) {
|
|
50
|
-
bees.push({
|
|
51
|
-
r: Math.floor(Math.random() * rows),
|
|
52
|
-
c: Math.floor(Math.random() * cols),
|
|
53
|
-
vr: Math.random() > 0.5 ? 1 : -1,
|
|
54
|
-
vc: Math.random() > 0.5 ? 1 : -1,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
return bees;
|
|
58
|
-
}
|
|
59
|
-
function initStreamCols(cols) {
|
|
60
|
-
const streamCols = [];
|
|
61
|
-
const spacing = Math.floor(cols / (NUM_STREAMS + 1));
|
|
62
|
-
for (let i = 1; i <= NUM_STREAMS; i++) {
|
|
63
|
-
streamCols.push(spacing * i);
|
|
64
|
-
}
|
|
65
|
-
return streamCols;
|
|
66
|
-
}
|
|
67
|
-
// ─── Grid builder ────────────────────────────────────
|
|
68
|
-
function buildHoneycombGrid(cols, rows, frame, agentName, bees, streamCols, pulses) {
|
|
69
|
-
const centerR = Math.floor(rows / 2) - 2;
|
|
70
|
-
const centerC = Math.floor(cols / 2);
|
|
71
|
-
// Initialize empty grid
|
|
72
|
-
const grid = [];
|
|
73
|
-
for (let r = 0; r < rows; r++) {
|
|
74
|
-
const row = [];
|
|
75
|
-
for (let c = 0; c < cols; c++) {
|
|
76
|
-
row.push({ char: ' ', color: colors.grayDim });
|
|
77
|
-
}
|
|
78
|
-
grid.push(row);
|
|
79
|
-
}
|
|
80
|
-
// ── Layer 1: Hex skeleton base ──
|
|
81
|
-
for (let r = 0; r < rows; r++) {
|
|
82
|
-
for (let c = 0; c < cols; c++) {
|
|
83
|
-
if (isHexEdge(r, c)) {
|
|
84
|
-
grid[r][c] = { char: '\u00B7', color: colors.grayDim };
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
// ── Layer 2: Scanning wave ──
|
|
89
|
-
const scanRow = frame % (rows + 6);
|
|
90
|
-
for (let r = 0; r < rows; r++) {
|
|
91
|
-
for (let c = 0; c < cols; c++) {
|
|
92
|
-
if (!isHexEdge(r, c))
|
|
93
|
-
continue;
|
|
94
|
-
const dist = Math.abs(r - scanRow);
|
|
95
|
-
if (dist === 0) {
|
|
96
|
-
grid[r][c] = { char: '\u2B22', color: colors.honey };
|
|
97
|
-
}
|
|
98
|
-
else if (dist <= 1) {
|
|
99
|
-
grid[r][c] = { char: '\u2B21', color: colors.honey };
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
// ── Layer 3: Vertical data streams ──
|
|
104
|
-
if (frame >= 8) {
|
|
105
|
-
const streamPhase = frame - 8;
|
|
106
|
-
for (const sc of streamCols) {
|
|
107
|
-
if (sc >= cols)
|
|
108
|
-
continue;
|
|
109
|
-
for (let r = 0; r < rows; r++) {
|
|
110
|
-
const streamOffset = (streamPhase * 2 + sc) % (rows * 3);
|
|
111
|
-
const streamDist = (((r - streamOffset) % rows) + rows) % rows;
|
|
112
|
-
if (streamDist < 6) {
|
|
113
|
-
const charIdx = (frame + r) % animation.DATA_CHARS.length;
|
|
114
|
-
const streamChar = animation.DATA_CHARS[charIdx];
|
|
115
|
-
let streamColor = colors.grayDim;
|
|
116
|
-
if (streamDist === 0) {
|
|
117
|
-
streamColor = colors.white;
|
|
118
|
-
}
|
|
119
|
-
else if (streamDist < 3) {
|
|
120
|
-
streamColor = colors.green;
|
|
121
|
-
}
|
|
122
|
-
grid[r][sc] = { char: streamChar, color: streamColor };
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
// ── Layer 4: Pulse overlay ──
|
|
128
|
-
for (const pulse of pulses) {
|
|
129
|
-
if (pulse.r >= 0 && pulse.r < rows && pulse.c >= 0 && pulse.c < cols) {
|
|
130
|
-
const brightness = pulse.ttl / 8;
|
|
131
|
-
const cell = grid[pulse.r][pulse.c];
|
|
132
|
-
if (cell.char === '\u00B7' || cell.char === ' ') {
|
|
133
|
-
grid[pulse.r][pulse.c] = {
|
|
134
|
-
char: brightness > 0.5 ? '\u2B21' : '\u00B7',
|
|
135
|
-
color: pulse.color,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
// ── Layer 5: Bee overlay ──
|
|
141
|
-
for (const bee of bees) {
|
|
142
|
-
const br = Math.max(0, Math.min(rows - 1, Math.round(bee.r)));
|
|
143
|
-
const bc = Math.max(0, Math.min(cols - 1, Math.round(bee.c)));
|
|
144
|
-
grid[br][bc] = { char: '\u25C6', color: colors.honey };
|
|
145
|
-
}
|
|
146
|
-
// ── Layer 6: Agent name with scramble→reveal ──
|
|
147
|
-
if (frame >= 22) {
|
|
51
|
+
let pulses = [];
|
|
52
|
+
// Text positioning
|
|
53
|
+
const centerR = Math.floor(gridRows / 2) - 2;
|
|
54
|
+
const centerC = Math.floor(cols / 2);
|
|
148
55
|
const nameText = `\u2B21 ${agentName} agent \u2B21`;
|
|
149
56
|
const nameStart = Math.max(0, centerC - Math.floor(nameText.length / 2));
|
|
150
|
-
const
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
// Name text with scramble effect
|
|
169
|
-
for (let i = 0; i < nameText.length; i++) {
|
|
170
|
-
const c = nameStart + i;
|
|
171
|
-
if (c >= cols)
|
|
172
|
-
break;
|
|
173
|
-
const charThreshold = i / nameText.length;
|
|
174
|
-
if (charThreshold <= scrambleProgress) {
|
|
175
|
-
grid[centerR][c] = { char: nameText[i], color: colors.honey };
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
const scrambleIdx = Math.floor(Math.random() * SCRAMBLE_CHARS.length);
|
|
179
|
-
grid[centerR][c] = { char: SCRAMBLE_CHARS[scrambleIdx], color: colors.gray };
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
// ── Layer 7: Boot messages with typewriter ──
|
|
184
|
-
const msgStartRow = centerR + 4;
|
|
185
|
-
for (let idx = 0; idx < BOOT_MESSAGES.length; idx++) {
|
|
186
|
-
const msg = BOOT_MESSAGES[idx];
|
|
187
|
-
if (frame < msg.frame)
|
|
188
|
-
continue;
|
|
189
|
-
const r = msgStartRow + idx;
|
|
190
|
-
if (r >= rows)
|
|
191
|
-
continue;
|
|
192
|
-
const fullText = `${msg.prefix} ${msg.text.replace('{name}', agentName)}`;
|
|
193
|
-
const msgStart = Math.max(0, centerC - Math.floor(fullText.length / 2));
|
|
194
|
-
const visibleChars = Math.min(fullText.length, (frame - msg.frame) * 3);
|
|
195
|
-
for (let i = 0; i < visibleChars; i++) {
|
|
196
|
-
const c = msgStart + i;
|
|
197
|
-
if (c >= cols)
|
|
198
|
-
break;
|
|
199
|
-
const isCheckmark = msg.prefix === '\u2713';
|
|
200
|
-
grid[r][c] = { char: fullText[i], color: isCheckmark ? colors.green : colors.honey };
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return grid;
|
|
204
|
-
}
|
|
205
|
-
export function HoneycombBoot({ agentName, width, onComplete }) {
|
|
206
|
-
const [frame, setFrame] = useState(0);
|
|
207
|
-
const heightRef = useRef(process.stdout.rows || 24);
|
|
208
|
-
const completedRef = useRef(false);
|
|
209
|
-
const frameRef = useRef(0);
|
|
210
|
-
const beesRef = useRef(initBees(heightRef.current, width));
|
|
211
|
-
const streamColsRef = useRef(initStreamCols(width));
|
|
212
|
-
const pulsesRef = useRef([]);
|
|
213
|
-
useEffect(() => {
|
|
214
|
-
const tick = setInterval(() => {
|
|
215
|
-
const h = heightRef.current;
|
|
216
|
-
const f = frameRef.current;
|
|
57
|
+
const msgStartRow = centerR + 4;
|
|
58
|
+
// Quiet zone around text: no animation renders here
|
|
59
|
+
const PADDING_H = 3;
|
|
60
|
+
const PADDING_V = 1;
|
|
61
|
+
const longestMsg = BOOT_MESSAGES.reduce((max, m) => Math.max(max, m.prefix.length + 1 + m.text.replace('{name}', agentName).length), 0);
|
|
62
|
+
const msgLeftEdge = Math.floor((cols - longestMsg) / 2);
|
|
63
|
+
const msgRightEdge = msgLeftEdge + longestMsg;
|
|
64
|
+
const quietLeft = Math.min(nameStart, msgLeftEdge) - PADDING_H;
|
|
65
|
+
const quietRight = Math.max(nameStart + nameText.length, msgRightEdge) + PADDING_H;
|
|
66
|
+
const quietTop = (centerR - 1) - PADDING_V;
|
|
67
|
+
const quietBottom = msgStartRow + BOOT_MESSAGES.length + PADDING_V;
|
|
68
|
+
// Hide cursor
|
|
69
|
+
process.stdout.write('\x1b[?25l');
|
|
70
|
+
// Clear screen
|
|
71
|
+
process.stdout.write('\x1b[2J');
|
|
72
|
+
function renderFrame() {
|
|
73
|
+
// Move cursor to top-left
|
|
74
|
+
process.stdout.write('\x1b[H');
|
|
217
75
|
// Advance bees every other frame
|
|
218
|
-
if (
|
|
219
|
-
for (const bee of
|
|
76
|
+
if (frame > 0 && frame % 2 === 0) {
|
|
77
|
+
for (const bee of bees) {
|
|
220
78
|
bee.r += bee.vr;
|
|
221
79
|
bee.c += bee.vc;
|
|
222
|
-
if (bee.r <= 0 || bee.r >=
|
|
80
|
+
if (bee.r <= 0 || bee.r >= gridRows - 1) {
|
|
223
81
|
bee.vr *= -1;
|
|
224
|
-
bee.r = Math.max(0, Math.min(
|
|
82
|
+
bee.r = Math.max(0, Math.min(gridRows - 1, bee.r));
|
|
225
83
|
}
|
|
226
|
-
if (bee.c <= 0 || bee.c >=
|
|
84
|
+
if (bee.c <= 0 || bee.c >= cols - 1) {
|
|
227
85
|
bee.vc *= -1;
|
|
228
|
-
bee.c = Math.max(0, Math.min(
|
|
86
|
+
bee.c = Math.max(0, Math.min(cols - 1, bee.c));
|
|
229
87
|
}
|
|
230
88
|
if (Math.random() > 0.3) {
|
|
231
89
|
bee.vc = Math.random() > 0.5 ? 1 : -1;
|
|
232
90
|
}
|
|
233
91
|
}
|
|
234
92
|
}
|
|
235
|
-
// Spawn pulses
|
|
236
|
-
if (
|
|
237
|
-
const currentPulses = pulsesRef.current;
|
|
93
|
+
// Spawn pulses
|
|
94
|
+
if (frame % 4 === 0) {
|
|
238
95
|
for (let i = 0; i < 3; i++) {
|
|
239
|
-
const pr = Math.floor(Math.random() *
|
|
240
|
-
const pc = Math.floor(Math.random() *
|
|
96
|
+
const pr = Math.floor(Math.random() * gridRows);
|
|
97
|
+
const pc = Math.floor(Math.random() * cols);
|
|
241
98
|
if (isHexEdge(pr, pc)) {
|
|
242
99
|
const pulseColors = [colors.green, colors.red, colors.honey];
|
|
243
100
|
const color = pulseColors[Math.floor(Math.random() * pulseColors.length)];
|
|
244
|
-
|
|
101
|
+
pulses.push({ r: pr, c: pc, ttl: 8, color });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
pulses = pulses.filter((p) => p.ttl > 0).map((p) => ({ ...p, ttl: p.ttl - 1 }));
|
|
105
|
+
}
|
|
106
|
+
// Build grid: char + color pairs
|
|
107
|
+
const charGrid = [];
|
|
108
|
+
const colorGrid = [];
|
|
109
|
+
for (let r = 0; r < gridRows; r++) {
|
|
110
|
+
const chars = [];
|
|
111
|
+
const clrs = [];
|
|
112
|
+
for (let c = 0; c < cols; c++) {
|
|
113
|
+
// Skip animation in quiet zone around text
|
|
114
|
+
const inQuietZone = r >= quietTop && r <= quietBottom && c >= quietLeft && c < quietRight;
|
|
115
|
+
if (inQuietZone) {
|
|
116
|
+
chars.push(' ');
|
|
117
|
+
clrs.push(colors.grayDim);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const hexEdge = isHexEdge(r, c);
|
|
121
|
+
// Scanning wave
|
|
122
|
+
const scanRow = frame % (gridRows + 6);
|
|
123
|
+
const dist = Math.abs(r - scanRow);
|
|
124
|
+
if (hexEdge && dist === 0) {
|
|
125
|
+
chars.push('\u2B22');
|
|
126
|
+
clrs.push(colors.honey);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (hexEdge && dist <= 1) {
|
|
130
|
+
chars.push('\u2B21');
|
|
131
|
+
clrs.push(colors.honey);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
// Data streams
|
|
135
|
+
let isStream = false;
|
|
136
|
+
if (frame >= 8) {
|
|
137
|
+
for (const sc of streamCols) {
|
|
138
|
+
if (c === sc) {
|
|
139
|
+
const streamOffset = ((frame - 8) * 2 + sc) % (gridRows * 3);
|
|
140
|
+
const streamDist = (((r - streamOffset) % gridRows) + gridRows) % gridRows;
|
|
141
|
+
if (streamDist < 6) {
|
|
142
|
+
const charIdx = (frame + r) % animation.DATA_CHARS.length;
|
|
143
|
+
const streamChar = animation.DATA_CHARS[charIdx];
|
|
144
|
+
chars.push(streamChar);
|
|
145
|
+
if (streamDist === 0) {
|
|
146
|
+
clrs.push(colors.white);
|
|
147
|
+
}
|
|
148
|
+
else if (streamDist < 3) {
|
|
149
|
+
clrs.push(colors.green);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
clrs.push(colors.grayDim);
|
|
153
|
+
}
|
|
154
|
+
isStream = true;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (isStream)
|
|
161
|
+
continue;
|
|
162
|
+
// Default
|
|
163
|
+
if (hexEdge) {
|
|
164
|
+
chars.push('\u00B7');
|
|
165
|
+
clrs.push(colors.grayDim);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
chars.push(' ');
|
|
169
|
+
clrs.push(colors.grayDim);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
charGrid.push(chars);
|
|
173
|
+
colorGrid.push(clrs);
|
|
174
|
+
}
|
|
175
|
+
// Overlay pulses (skip quiet zone)
|
|
176
|
+
for (const pulse of pulses) {
|
|
177
|
+
if (pulse.r >= 0 && pulse.r < gridRows && pulse.c >= 0 && pulse.c < cols) {
|
|
178
|
+
const inQuietZone = pulse.r >= quietTop &&
|
|
179
|
+
pulse.r <= quietBottom &&
|
|
180
|
+
pulse.c >= quietLeft &&
|
|
181
|
+
pulse.c < quietRight;
|
|
182
|
+
if (inQuietZone)
|
|
183
|
+
continue;
|
|
184
|
+
const brightness = pulse.ttl / 8;
|
|
185
|
+
const cell = charGrid[pulse.r][pulse.c];
|
|
186
|
+
if (cell === '\u00B7' || cell === ' ') {
|
|
187
|
+
charGrid[pulse.r][pulse.c] = brightness > 0.5 ? '\u2B21' : '\u00B7';
|
|
188
|
+
colorGrid[pulse.r][pulse.c] = pulse.color;
|
|
245
189
|
}
|
|
246
190
|
}
|
|
247
|
-
pulsesRef.current = currentPulses
|
|
248
|
-
.filter((p) => p.ttl > 0)
|
|
249
|
-
.map((p) => ({ ...p, ttl: p.ttl - 1 }));
|
|
250
191
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
192
|
+
// Overlay bees (skip quiet zone)
|
|
193
|
+
for (const bee of bees) {
|
|
194
|
+
const br = Math.max(0, Math.min(gridRows - 1, Math.round(bee.r)));
|
|
195
|
+
const bc = Math.max(0, Math.min(cols - 1, Math.round(bee.c)));
|
|
196
|
+
const inQuietZone = br >= quietTop && br <= quietBottom && bc >= quietLeft && bc < quietRight;
|
|
197
|
+
if (!inQuietZone) {
|
|
198
|
+
charGrid[br][bc] = '\u25C6';
|
|
199
|
+
colorGrid[br][bc] = colors.honey;
|
|
200
|
+
}
|
|
257
201
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
202
|
+
// Overlay agent name with scramble→reveal effect
|
|
203
|
+
if (frame >= 22) {
|
|
204
|
+
const scrambleProgress = Math.min(1, (frame - 22) / 8);
|
|
205
|
+
// Top/bottom border lines around name
|
|
206
|
+
for (let c = nameStart; c < nameStart + nameText.length && c < cols; c++) {
|
|
207
|
+
if (centerR - 1 >= 0) {
|
|
208
|
+
charGrid[centerR - 1][c] = '\u2500';
|
|
209
|
+
colorGrid[centerR - 1][c] = colors.honey;
|
|
210
|
+
}
|
|
211
|
+
if (centerR + 1 < gridRows) {
|
|
212
|
+
charGrid[centerR + 1][c] = '\u2500';
|
|
213
|
+
colorGrid[centerR + 1][c] = colors.honey;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Name text with scramble effect
|
|
217
|
+
for (let i = 0; i < nameText.length; i++) {
|
|
218
|
+
const c = nameStart + i;
|
|
219
|
+
if (c >= cols)
|
|
220
|
+
break;
|
|
221
|
+
const charThreshold = i / nameText.length;
|
|
222
|
+
if (charThreshold <= scrambleProgress) {
|
|
223
|
+
charGrid[centerR][c] = nameText[i];
|
|
224
|
+
colorGrid[centerR][c] = colors.honey;
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
const scrambleIdx = Math.floor(Math.random() * SCRAMBLE_CHARS.length);
|
|
228
|
+
charGrid[centerR][c] = SCRAMBLE_CHARS[scrambleIdx];
|
|
229
|
+
colorGrid[centerR][c] = colors.gray;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Overlay typewriter boot messages
|
|
234
|
+
for (let idx = 0; idx < BOOT_MESSAGES.length; idx++) {
|
|
235
|
+
const msg = BOOT_MESSAGES[idx];
|
|
236
|
+
if (frame < msg.frame)
|
|
237
|
+
continue;
|
|
238
|
+
const r = msgStartRow + idx;
|
|
239
|
+
if (r < 0 || r >= gridRows)
|
|
240
|
+
continue;
|
|
241
|
+
const fullText = `${msg.prefix} ${msg.text.replace('{name}', agentName)}`;
|
|
242
|
+
const msgCol = Math.floor((cols - fullText.length) / 2);
|
|
243
|
+
const visibleChars = Math.min(fullText.length, (frame - msg.frame) * 3);
|
|
244
|
+
const isCheckmark = msg.prefix === '\u2713';
|
|
245
|
+
const msgColor = isCheckmark ? colors.green : colors.honey;
|
|
246
|
+
for (let i = 0; i < visibleChars; i++) {
|
|
247
|
+
const c = msgCol + i;
|
|
248
|
+
if (c < 0 || c >= cols)
|
|
249
|
+
continue;
|
|
250
|
+
charGrid[r][c] = fullText[i];
|
|
251
|
+
colorGrid[r][c] = msgColor;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Render to stdout
|
|
255
|
+
let output = '';
|
|
256
|
+
for (let r = 0; r < gridRows; r++) {
|
|
257
|
+
let line = '';
|
|
258
|
+
let runColor = colorGrid[r][0];
|
|
259
|
+
let runChars = '';
|
|
260
|
+
for (let c = 0; c < cols; c++) {
|
|
261
|
+
const curColor = colorGrid[r][c];
|
|
262
|
+
const curChar = charGrid[r][c];
|
|
263
|
+
if (curColor === runColor) {
|
|
264
|
+
runChars += curChar;
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
line += chalk.hex(runColor)(runChars);
|
|
268
|
+
runColor = curColor;
|
|
269
|
+
runChars = curChar;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (runChars.length > 0) {
|
|
273
|
+
line += chalk.hex(runColor)(runChars);
|
|
274
|
+
}
|
|
275
|
+
output += line;
|
|
276
|
+
if (r < gridRows - 1) {
|
|
277
|
+
output += '\n';
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
process.stdout.write(output);
|
|
281
|
+
frame++;
|
|
282
|
+
}
|
|
283
|
+
const timer = setInterval(renderFrame, BOOT_FRAME_MS);
|
|
284
|
+
setTimeout(() => {
|
|
285
|
+
clearInterval(timer);
|
|
286
|
+
// Clear screen, show cursor, move to top
|
|
287
|
+
process.stdout.write('\x1b[2J\x1b[H\x1b[?25h');
|
|
288
|
+
resolve();
|
|
289
|
+
}, DURATION_MS);
|
|
290
|
+
});
|
|
270
291
|
}
|
|
@@ -7,10 +7,8 @@ import { editSectionTool } from '../edit-section.js';
|
|
|
7
7
|
import { fetchRulesTool } from '../fetch-rules.js';
|
|
8
8
|
import { loadMemory } from '@hive-org/sdk';
|
|
9
9
|
import { processSignalAndSummarize, extractAndSaveMemory } from '../analysis.js';
|
|
10
|
-
import { registerShutdownAgent } from '../process-lifecycle.js';
|
|
11
10
|
import { getModel } from '../model.js';
|
|
12
11
|
export function useAgent() {
|
|
13
|
-
const [phase, setPhase] = useState('booting');
|
|
14
12
|
const [connected, setConnected] = useState(false);
|
|
15
13
|
const [agentName, setAgentName] = useState('agent');
|
|
16
14
|
const [agentBio, setAgentBio] = useState('');
|
|
@@ -154,9 +152,6 @@ export function useAgent() {
|
|
|
154
152
|
},
|
|
155
153
|
});
|
|
156
154
|
agentRef.current = agent;
|
|
157
|
-
registerShutdownAgent(async () => {
|
|
158
|
-
await agent.stop();
|
|
159
|
-
});
|
|
160
155
|
await agent.start();
|
|
161
156
|
setConnected(true);
|
|
162
157
|
const bio = config.bio ?? '';
|
|
@@ -247,12 +242,7 @@ export function useAgent() {
|
|
|
247
242
|
setChatStreaming(false);
|
|
248
243
|
}
|
|
249
244
|
}, [chatStreaming, addChatActivity]);
|
|
250
|
-
// ─── Boot transition ────────────────────────────────
|
|
251
|
-
const handleBootComplete = useCallback(() => {
|
|
252
|
-
setPhase('running');
|
|
253
|
-
}, []);
|
|
254
245
|
return {
|
|
255
|
-
phase,
|
|
256
246
|
connected,
|
|
257
247
|
agentName,
|
|
258
248
|
agentBio,
|
|
@@ -265,6 +255,5 @@ export function useAgent() {
|
|
|
265
255
|
termWidth,
|
|
266
256
|
setInput,
|
|
267
257
|
handleChatSubmit,
|
|
268
|
-
handleBootComplete,
|
|
269
258
|
};
|
|
270
259
|
}
|
|
@@ -1,30 +1,9 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { symbols } from './theme.js';
|
|
3
|
-
let _shutdownAgent = null;
|
|
4
|
-
let _shuttingDown = false;
|
|
5
|
-
export function registerShutdownAgent(fn) {
|
|
6
|
-
_shutdownAgent = fn;
|
|
7
|
-
}
|
|
8
|
-
export function clearShutdownAgent() {
|
|
9
|
-
_shutdownAgent = null;
|
|
10
|
-
}
|
|
11
3
|
const restoreScreen = () => {
|
|
12
4
|
process.stdout.write('\x1b[?1049l');
|
|
13
5
|
};
|
|
14
|
-
|
|
15
|
-
if (_shuttingDown) {
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
_shuttingDown = true;
|
|
19
|
-
if (_shutdownAgent) {
|
|
20
|
-
try {
|
|
21
|
-
await _shutdownAgent();
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
// Best-effort memory save on shutdown
|
|
25
|
-
}
|
|
26
|
-
_shutdownAgent = null;
|
|
27
|
-
}
|
|
6
|
+
const exitImmediately = (exitCode = 0) => {
|
|
28
7
|
restoreScreen();
|
|
29
8
|
process.exit(exitCode);
|
|
30
9
|
};
|
|
@@ -41,16 +20,6 @@ export function setupProcessLifecycle() {
|
|
|
41
20
|
// The alternate buffer is position-based — no reflow, no ghosts.
|
|
42
21
|
process.stdout.write('\x1b[?1049h');
|
|
43
22
|
process.on('exit', restoreScreen);
|
|
44
|
-
process.on('SIGINT', () =>
|
|
45
|
-
|
|
46
|
-
restoreScreen();
|
|
47
|
-
process.exit(1);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
process.on('SIGTERM', () => {
|
|
51
|
-
gracefulShutdown().catch(() => {
|
|
52
|
-
restoreScreen();
|
|
53
|
-
process.exit(1);
|
|
54
|
-
});
|
|
55
|
-
});
|
|
23
|
+
process.on('SIGINT', () => exitImmediately(0));
|
|
24
|
+
process.on('SIGTERM', () => exitImmediately(0));
|
|
56
25
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { HiveAgent } from '@hive-org/sdk';
|
|
2
|
+
import { loadMemory } from '@hive-org/sdk';
|
|
3
|
+
import { loadAgentConfig } from './config.js';
|
|
4
|
+
import { processSignalAndSummarize } from './analysis.js';
|
|
5
|
+
function timestamp() {
|
|
6
|
+
const now = new Date();
|
|
7
|
+
const hours = String(now.getHours()).padStart(2, '0');
|
|
8
|
+
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
9
|
+
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
10
|
+
const ts = `${hours}:${minutes}:${seconds}`;
|
|
11
|
+
return ts;
|
|
12
|
+
}
|
|
13
|
+
export async function runHeadless() {
|
|
14
|
+
const { HIVE_API_URL } = await import('../config.js');
|
|
15
|
+
const config = await loadAgentConfig();
|
|
16
|
+
const initialMemory = await loadMemory();
|
|
17
|
+
let memoryRef = initialMemory;
|
|
18
|
+
const soulContent = config.soulContent;
|
|
19
|
+
const strategyContent = config.strategyContent;
|
|
20
|
+
let predictionCount = 0;
|
|
21
|
+
const agent = new HiveAgent(HIVE_API_URL, {
|
|
22
|
+
name: config.name,
|
|
23
|
+
avatarUrl: config.avatarUrl,
|
|
24
|
+
bio: config.bio ?? undefined,
|
|
25
|
+
predictionProfile: config.predictionProfile,
|
|
26
|
+
pollIntervalMs: 5000,
|
|
27
|
+
pollLimit: 20,
|
|
28
|
+
onPollEmpty: () => {
|
|
29
|
+
console.log(`[${timestamp()}] idle — no new threads`);
|
|
30
|
+
},
|
|
31
|
+
onStop: async () => { },
|
|
32
|
+
onNewThread: async (thread) => {
|
|
33
|
+
try {
|
|
34
|
+
const threadPreview = thread.text.length > 80 ? thread.text.slice(0, 80) + '\u2026' : thread.text;
|
|
35
|
+
console.log(`[${timestamp()}] signal c/${thread.project_id} · ${thread.id.slice(0, 8)}`);
|
|
36
|
+
console.log(` "${threadPreview}"`);
|
|
37
|
+
console.log(`[${timestamp()}] analyzing...`);
|
|
38
|
+
const result = await processSignalAndSummarize(thread, agent.recentComments, memoryRef, soulContent, strategyContent);
|
|
39
|
+
if (result.skip) {
|
|
40
|
+
console.log(`[${timestamp()}] skipped — outside expertise`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
await agent.postComment(thread.id, {
|
|
44
|
+
thread_id: thread.id,
|
|
45
|
+
text: result.summary,
|
|
46
|
+
conviction: result.conviction,
|
|
47
|
+
}, thread.text);
|
|
48
|
+
predictionCount += 1;
|
|
49
|
+
const sign = result.conviction >= 0 ? '+' : '';
|
|
50
|
+
console.log(`[${timestamp()}] predicted [${sign}${result.conviction}%] "${result.summary}" (${predictionCount} total)`);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
54
|
+
const message = raw.length > 120 ? raw.slice(0, 120) + '\u2026' : raw;
|
|
55
|
+
console.error(`[${timestamp()}] error: ${message}`);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
const shutdown = async () => {
|
|
60
|
+
console.log(`[${config.name}] shutting down...`);
|
|
61
|
+
await agent.stop();
|
|
62
|
+
process.exit(0);
|
|
63
|
+
};
|
|
64
|
+
process.on('SIGINT', () => void shutdown());
|
|
65
|
+
process.on('SIGTERM', () => void shutdown());
|
|
66
|
+
await agent.start();
|
|
67
|
+
console.log(`[${config.name}] agent online — "${config.bio ?? ''}"`);
|
|
68
|
+
}
|
package/dist/agents.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
|
+
import axios from 'axios';
|
|
4
5
|
import { AI_PROVIDERS } from './ai-providers.js';
|
|
6
|
+
import { HIVE_API_URL } from './config.js';
|
|
7
|
+
function extractField(content, pattern) {
|
|
8
|
+
const match = content.match(pattern);
|
|
9
|
+
if (match === null) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const value = match[1].trim();
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
5
15
|
export async function scanAgents() {
|
|
6
16
|
const agentsDir = path.join(os.homedir(), '.hive', 'agents');
|
|
7
17
|
const exists = await fs.pathExists(agentsDir);
|
|
@@ -22,10 +32,55 @@ export async function scanAgents() {
|
|
|
22
32
|
const agentDir = path.join(agentsDir, entry.name);
|
|
23
33
|
const provider = await detectProvider(agentDir);
|
|
24
34
|
const stat = await fs.stat(soulPath);
|
|
25
|
-
|
|
35
|
+
const soulContent = await fs.readFile(soulPath, 'utf-8');
|
|
36
|
+
const parsedName = extractField(soulContent, /^#\s+Agent:\s+(.+)$/m);
|
|
37
|
+
const avatarUrl = extractField(soulContent, /^## Avatar\s*\n+(https?:\/\/.+)$/m);
|
|
38
|
+
const bio = extractField(soulContent, /^## Bio\s*\n+(.+)$/m);
|
|
39
|
+
agents.push({
|
|
40
|
+
name: parsedName ?? entry.name,
|
|
41
|
+
dir: agentDir,
|
|
42
|
+
provider,
|
|
43
|
+
created: stat.birthtime,
|
|
44
|
+
avatar_url: avatarUrl ?? undefined,
|
|
45
|
+
bio: bio ?? undefined,
|
|
46
|
+
});
|
|
26
47
|
}
|
|
27
48
|
return agents;
|
|
28
49
|
}
|
|
50
|
+
export function sortByHoney(rows) {
|
|
51
|
+
const sorted = [...rows].sort((a, b) => (b.stats?.honey ?? 0) - (a.stats?.honey ?? 0));
|
|
52
|
+
return sorted;
|
|
53
|
+
}
|
|
54
|
+
export function sortAgentsByHoney(agents, statsMap) {
|
|
55
|
+
const sorted = [...agents].sort((a, b) => {
|
|
56
|
+
const honeyA = statsMap.get(a.name)?.honey ?? 0;
|
|
57
|
+
const honeyB = statsMap.get(b.name)?.honey ?? 0;
|
|
58
|
+
return honeyB - honeyA;
|
|
59
|
+
});
|
|
60
|
+
return sorted;
|
|
61
|
+
}
|
|
62
|
+
export async function fetchBulkStats(names) {
|
|
63
|
+
const statsMap = new Map();
|
|
64
|
+
if (names.length === 0) {
|
|
65
|
+
return statsMap;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const response = await axios.post(`${HIVE_API_URL}/agent/by-names`, { names });
|
|
69
|
+
for (const agent of response.data) {
|
|
70
|
+
statsMap.set(agent.name, {
|
|
71
|
+
honey: agent.honey ?? 0,
|
|
72
|
+
wax: agent.wax ?? 0,
|
|
73
|
+
win_rate: agent.win_rate ?? 0,
|
|
74
|
+
sting: agent.sting ?? 0,
|
|
75
|
+
total_comments: agent.total_comments ?? 0,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// API unreachable — return empty map, CLI will show dashes
|
|
81
|
+
}
|
|
82
|
+
return statsMap;
|
|
83
|
+
}
|
|
29
84
|
async function detectProvider(agentDir) {
|
|
30
85
|
// Try old-style detection: check package.json dependencies
|
|
31
86
|
const pkgPath = path.join(agentDir, 'package.json');
|
package/dist/avatar.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import terminalImage from 'terminal-image';
|
|
3
|
+
const AVATAR_WIDTH = 6;
|
|
4
|
+
const AVATAR_HEIGHT = 3;
|
|
5
|
+
export async function renderAvatar(url) {
|
|
6
|
+
try {
|
|
7
|
+
const response = await axios.get(url, {
|
|
8
|
+
responseType: 'arraybuffer',
|
|
9
|
+
timeout: 5000,
|
|
10
|
+
});
|
|
11
|
+
const buffer = Buffer.from(response.data);
|
|
12
|
+
const rendered = await terminalImage.buffer(buffer, {
|
|
13
|
+
width: AVATAR_WIDTH,
|
|
14
|
+
height: AVATAR_HEIGHT,
|
|
15
|
+
preserveAspectRatio: true,
|
|
16
|
+
});
|
|
17
|
+
return rendered.trimEnd();
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function renderAvatars(agents) {
|
|
24
|
+
const avatarMap = new Map();
|
|
25
|
+
const entries = agents.filter((a) => a.avatar_url);
|
|
26
|
+
const results = await Promise.all(entries.map((a) => renderAvatar(a.avatar_url)));
|
|
27
|
+
for (let i = 0; i < entries.length; i++) {
|
|
28
|
+
const rendered = results[i];
|
|
29
|
+
if (rendered !== null) {
|
|
30
|
+
avatarMap.set(entries[i].name, rendered);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return avatarMap;
|
|
34
|
+
}
|
|
@@ -10,5 +10,5 @@ export function DoneStep({ projectDir }) {
|
|
|
10
10
|
const termWidth = process.stdout.columns || 60;
|
|
11
11
|
const boxWidth = Math.min(termWidth - 4, 60);
|
|
12
12
|
const line = border.horizontal.repeat(boxWidth - 2);
|
|
13
|
-
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsx(Box, { children: _jsxs(Text, { color: colors.honey, children: [border.topLeft, line, border.topRight] }) }), _jsxs(Box, { children: [_jsx(Text, { color: colors.honey, children: border.vertical }), _jsx(Text, { children: " " }), _jsxs(Text, { color: colors.honey, bold: true, children: [symbols.hive, " Agent created successfully!"] }), _jsx(Text, { children: ' '.repeat(Math.max(0, boxWidth - 32)) }), _jsx(Text, { color: colors.honey, children: border.vertical })] }), _jsx(Box, { children: _jsxs(Text, { color: colors.honey, children: [border.bottomLeft, line, border.bottomRight] }) }), _jsxs(Box, { flexDirection: "column", marginTop: 1, marginLeft: 1, children: [_jsx(Text, { color: colors.white, bold: true, children: "Next steps:" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsxs(Text, { color: colors.gray, children: [" 1. ", _jsx(Text, { color: colors.white, children: "npx @hive-org/cli@latest start" })] }) }),
|
|
13
|
+
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsx(Box, { children: _jsxs(Text, { color: colors.honey, children: [border.topLeft, line, border.topRight] }) }), _jsxs(Box, { children: [_jsx(Text, { color: colors.honey, children: border.vertical }), _jsx(Text, { children: " " }), _jsxs(Text, { color: colors.honey, bold: true, children: [symbols.hive, " Agent created successfully!"] }), _jsx(Text, { children: ' '.repeat(Math.max(0, boxWidth - 32)) }), _jsx(Text, { color: colors.honey, children: border.vertical })] }), _jsx(Box, { children: _jsxs(Text, { color: colors.honey, children: [border.bottomLeft, line, border.bottomRight] }) }), _jsxs(Box, { flexDirection: "column", marginTop: 1, marginLeft: 1, children: [_jsx(Text, { color: colors.white, bold: true, children: "Next steps:" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsxs(Text, { color: colors.gray, children: [" 1. ", _jsx(Text, { color: colors.white, children: "npx @hive-org/cli@latest start" })] }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.grayDim, children: " Fine-tune SOUL.md and STRATEGY.md by chatting with your agent during" }), _jsx(Text, { color: colors.grayDim, children: " a run, or edit them directly at:" }), _jsxs(Text, { color: colors.grayDim, children: [" ", _jsx(Text, { color: colors.white, children: projectDir })] })] })] })] }));
|
|
14
14
|
}
|
package/dist/index.js
CHANGED
|
@@ -14,8 +14,9 @@ const HELP_TEXT = `@hive-org/cli v${pkg.version}
|
|
|
14
14
|
Usage:
|
|
15
15
|
npx @hive-org/cli@latest create [agent-name] Scaffold a new Hive agent
|
|
16
16
|
npx @hive-org/cli@latest list List existing agents
|
|
17
|
-
npx @hive-org/cli@latest start
|
|
17
|
+
npx @hive-org/cli@latest start Start an agent (auto-detects agent dir)
|
|
18
18
|
npx @hive-org/cli@latest start-all Start all agents
|
|
19
|
+
npx @hive-org/cli@latest run Run agent headless (no TUI, used by start-all)
|
|
19
20
|
npx @hive-org/cli@latest migrate-templates Migrate old-style agents
|
|
20
21
|
npx @hive-org/cli@latest --help Show this help message
|
|
21
22
|
npx @hive-org/cli@latest --version Print version
|
|
@@ -55,17 +56,32 @@ else if (command === 'migrate-templates') {
|
|
|
55
56
|
const { waitUntilExit } = render(React.createElement(MigrateApp));
|
|
56
57
|
await waitUntilExit();
|
|
57
58
|
}
|
|
59
|
+
else if (command === 'run') {
|
|
60
|
+
// Headless agent run — no TUI, just console output.
|
|
61
|
+
// Used by start-all to spawn agents as child processes.
|
|
62
|
+
const { access } = await import('fs/promises');
|
|
63
|
+
const { join } = await import('path');
|
|
64
|
+
const isAgentDir = await access(join(process.cwd(), 'SOUL.md'))
|
|
65
|
+
.then(() => true)
|
|
66
|
+
.catch(() => false);
|
|
67
|
+
if (!isAgentDir) {
|
|
68
|
+
console.error('Error: "run" must be called from an agent directory (with SOUL.md)');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
await import('dotenv/config');
|
|
72
|
+
const { runHeadless } = await import('./agent/run-headless.js');
|
|
73
|
+
await runHeadless();
|
|
74
|
+
}
|
|
58
75
|
else if (command === 'start') {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
process.chdir(picked.dir);
|
|
76
|
+
// Detect if cwd is an agent directory (has SOUL.md).
|
|
77
|
+
// When called via agent's "npm start", cwd is the agent dir.
|
|
78
|
+
const { access } = await import('fs/promises');
|
|
79
|
+
const { join } = await import('path');
|
|
80
|
+
const isAgentDir = await access(join(process.cwd(), 'SOUL.md'))
|
|
81
|
+
.then(() => true)
|
|
82
|
+
.catch(() => false);
|
|
83
|
+
if (isAgentDir) {
|
|
84
|
+
// Direct agent run — cwd is already the agent directory.
|
|
69
85
|
await import('dotenv/config');
|
|
70
86
|
const { setupProcessLifecycle } = await import('./agent/process-lifecycle.js');
|
|
71
87
|
const { App } = await import('./agent/app.js');
|
|
@@ -73,6 +89,29 @@ else if (command === 'start') {
|
|
|
73
89
|
const { waitUntilExit } = render(React.createElement(App));
|
|
74
90
|
await waitUntilExit();
|
|
75
91
|
}
|
|
92
|
+
else {
|
|
93
|
+
// Interactive agent selection
|
|
94
|
+
await showWelcome();
|
|
95
|
+
let selectedAgent = null;
|
|
96
|
+
const { waitUntilExit: waitForSelect } = render(React.createElement(SelectAgentApp, {
|
|
97
|
+
onSelect: (agent) => {
|
|
98
|
+
selectedAgent = agent;
|
|
99
|
+
},
|
|
100
|
+
}));
|
|
101
|
+
await waitForSelect();
|
|
102
|
+
if (selectedAgent) {
|
|
103
|
+
const picked = selectedAgent;
|
|
104
|
+
const { showHoneycombBoot } = await import('./agent/components/HoneycombBoot.js');
|
|
105
|
+
await showHoneycombBoot(picked.name);
|
|
106
|
+
process.chdir(picked.dir);
|
|
107
|
+
await import('dotenv/config');
|
|
108
|
+
const { setupProcessLifecycle } = await import('./agent/process-lifecycle.js');
|
|
109
|
+
const { App } = await import('./agent/app.js');
|
|
110
|
+
setupProcessLifecycle();
|
|
111
|
+
const { waitUntilExit } = render(React.createElement(App));
|
|
112
|
+
await waitUntilExit();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
76
115
|
}
|
|
77
116
|
else {
|
|
78
117
|
console.error(`Unknown command: ${command}\n`);
|
package/dist/list/ListApp.js
CHANGED
|
@@ -2,7 +2,19 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useState, useEffect } from 'react';
|
|
3
3
|
import { Box, Text, useApp } from 'ink';
|
|
4
4
|
import { colors, symbols, border } from '../theme.js';
|
|
5
|
-
import { scanAgents } from '../agents.js';
|
|
5
|
+
import { scanAgents, fetchBulkStats, sortByHoney } from '../agents.js';
|
|
6
|
+
const COL = {
|
|
7
|
+
name: 0,
|
|
8
|
+
honey: 8,
|
|
9
|
+
wax: 8,
|
|
10
|
+
winRate: 10,
|
|
11
|
+
sting: 8,
|
|
12
|
+
provider: 0,
|
|
13
|
+
created: 14,
|
|
14
|
+
};
|
|
15
|
+
function cell(text, width) {
|
|
16
|
+
return ` ${text}`.padEnd(width);
|
|
17
|
+
}
|
|
6
18
|
function formatDate(date) {
|
|
7
19
|
const formatted = date.toLocaleDateString('en-US', {
|
|
8
20
|
year: 'numeric',
|
|
@@ -13,26 +25,55 @@ function formatDate(date) {
|
|
|
13
25
|
}
|
|
14
26
|
export function ListApp() {
|
|
15
27
|
const { exit } = useApp();
|
|
16
|
-
const [
|
|
28
|
+
const [rows, setRows] = useState(null);
|
|
17
29
|
useEffect(() => {
|
|
18
30
|
const load = async () => {
|
|
19
|
-
const
|
|
20
|
-
|
|
31
|
+
const agents = await scanAgents();
|
|
32
|
+
if (agents.length === 0) {
|
|
33
|
+
setRows([]);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const names = agents.map((a) => a.name);
|
|
37
|
+
const statsMap = await fetchBulkStats(names);
|
|
38
|
+
const agentRows = agents.map((info) => ({
|
|
39
|
+
info,
|
|
40
|
+
stats: statsMap.get(info.name) ?? null,
|
|
41
|
+
}));
|
|
42
|
+
const sortedRows = sortByHoney(agentRows);
|
|
43
|
+
setRows(sortedRows);
|
|
21
44
|
};
|
|
22
45
|
void load();
|
|
23
46
|
}, []);
|
|
24
47
|
useEffect(() => {
|
|
25
|
-
if (
|
|
48
|
+
if (rows !== null) {
|
|
26
49
|
exit();
|
|
27
50
|
}
|
|
28
|
-
}, [
|
|
29
|
-
if (
|
|
51
|
+
}, [rows]);
|
|
52
|
+
if (rows === null) {
|
|
30
53
|
return (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: colors.gray, children: "Scanning agents..." }) }));
|
|
31
54
|
}
|
|
32
|
-
if (
|
|
55
|
+
if (rows.length === 0) {
|
|
33
56
|
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" })] })] }));
|
|
34
57
|
}
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
58
|
+
const nameW = Math.max(COL.name, ...rows.map((r) => r.info.name.length)) + 2;
|
|
59
|
+
const providerW = Math.max(COL.provider, ...rows.map((r) => r.info.provider.length)) + 2;
|
|
60
|
+
const honeyW = COL.honey;
|
|
61
|
+
const waxW = COL.wax;
|
|
62
|
+
const winRateW = COL.winRate;
|
|
63
|
+
const stingW = COL.sting;
|
|
64
|
+
const createdW = COL.created;
|
|
65
|
+
const sep = border.horizontal;
|
|
66
|
+
const totalWidth = nameW + 1 + honeyW + 1 + waxW + 1 + winRateW + 1 + stingW + 1 + providerW + 1 + createdW;
|
|
67
|
+
const topBorder = `${border.topLeft}${sep.repeat(totalWidth)}${border.topRight}`;
|
|
68
|
+
const midBorder = `${border.teeLeft}${sep.repeat(totalWidth)}${border.teeRight}`;
|
|
69
|
+
const botBorder = `${border.bottomLeft}${sep.repeat(totalWidth)}${border.bottomRight}`;
|
|
70
|
+
const v = border.vertical;
|
|
71
|
+
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: "Your Hive Agents" }), _jsxs(Text, { color: colors.grayDim, children: [" (", rows.length, ")"] })] }), _jsx(Box, { children: _jsx(Text, { color: colors.honey, children: topBorder }) }), _jsxs(Box, { children: [_jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.white, bold: true, children: cell('Name', nameW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.white, bold: true, children: cell('Honey', honeyW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.white, bold: true, children: cell('Wax', waxW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.white, bold: true, children: cell('Win Rate', winRateW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.white, bold: true, children: cell('Sting', stingW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.white, bold: true, children: cell('Provider', providerW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.white, bold: true, children: cell('Created', createdW) }), _jsx(Text, { color: colors.honey, children: v })] }), _jsx(Box, { children: _jsx(Text, { color: colors.honey, children: midBorder }) }), rows.map((row) => {
|
|
72
|
+
const s = row.stats;
|
|
73
|
+
const honeyText = s !== null ? String(Math.floor(s.honey)) : '-';
|
|
74
|
+
const waxText = s !== null ? String(Math.floor(s.wax)) : '-';
|
|
75
|
+
const winRateText = s !== null ? `${(s.win_rate * 100).toFixed(2)}%` : '-';
|
|
76
|
+
const stingText = s !== null ? s.sting.toFixed(2) : '-';
|
|
77
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.white, children: cell(row.info.name, nameW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.honey, children: cell(honeyText, honeyW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.red, children: cell(waxText, waxW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.green, children: cell(winRateText, winRateW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.cyan, children: cell(stingText, stingW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.gray, children: cell(row.info.provider, providerW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.grayDim, children: cell(formatDate(row.info.created), createdW) }), _jsx(Text, { color: colors.honey, children: v })] }, row.info.name));
|
|
78
|
+
}), _jsx(Box, { children: _jsx(Text, { color: colors.honey, children: botBorder }) })] }));
|
|
38
79
|
}
|
|
@@ -2,7 +2,6 @@ import { spawn } from 'child_process';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
4
|
const FORCE_KILL_TIMEOUT_MS = 5_000;
|
|
5
|
-
const SHUTDOWN_TIMEOUT_MS = 10_000;
|
|
6
5
|
const ABSOLUTE_TIMEOUT_MS = FORCE_KILL_TIMEOUT_MS + 2_000;
|
|
7
6
|
export class AgentProcessManager {
|
|
8
7
|
constructor() {
|
|
@@ -64,41 +63,9 @@ export class AgentProcessManager {
|
|
|
64
63
|
agent.child = null;
|
|
65
64
|
this._spawnPiped(name);
|
|
66
65
|
}
|
|
67
|
-
async shutdownAll() {
|
|
68
|
-
const children = [];
|
|
69
|
-
for (const agent of this._agents.values()) {
|
|
70
|
-
if (agent.child) {
|
|
71
|
-
children.push(agent.child);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (children.length === 0) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const waitForAll = children.map((child) => new Promise((resolve) => {
|
|
78
|
-
if (child.exitCode !== null) {
|
|
79
|
-
resolve();
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
child.on('exit', () => resolve());
|
|
83
|
-
// Absolute safety timeout
|
|
84
|
-
setTimeout(() => resolve(), SHUTDOWN_TIMEOUT_MS + 2_000);
|
|
85
|
-
}));
|
|
86
|
-
for (const child of children) {
|
|
87
|
-
child.kill('SIGTERM');
|
|
88
|
-
}
|
|
89
|
-
const forceKillTimer = setTimeout(() => {
|
|
90
|
-
for (const agent of this._agents.values()) {
|
|
91
|
-
if (agent.child && agent.child.exitCode === null) {
|
|
92
|
-
agent.child.kill('SIGKILL');
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}, SHUTDOWN_TIMEOUT_MS);
|
|
96
|
-
await Promise.all(waitForAll);
|
|
97
|
-
clearTimeout(forceKillTimer);
|
|
98
|
-
}
|
|
99
66
|
_spawnPiped(name) {
|
|
100
67
|
const agentDir = path.join(this._agentsDir, name);
|
|
101
|
-
const child = spawn('
|
|
68
|
+
const child = spawn('npx', ['@hive-org/cli@latest', 'run'], {
|
|
102
69
|
cwd: agentDir,
|
|
103
70
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
104
71
|
});
|
package/dist/start/Dashboard.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect } from 'react';
|
|
3
3
|
import { Box, Text, useApp, useInput } from 'ink';
|
|
4
4
|
import { colors, symbols, border } from '../theme.js';
|
|
@@ -17,7 +17,10 @@ const STATUS_SYMBOLS = {
|
|
|
17
17
|
const POLL_INTERVAL_MS = 1_000;
|
|
18
18
|
const STOPPABLE = new Set(['running', 'spawning']);
|
|
19
19
|
const STARTABLE = new Set(['exited', 'errored']);
|
|
20
|
-
|
|
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.red, 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: ["S:", stats.sting.toFixed(2)] })] }));
|
|
22
|
+
}
|
|
23
|
+
export function Dashboard({ manager, statsMap }) {
|
|
21
24
|
const { exit } = useApp();
|
|
22
25
|
const [agents, setAgents] = useState(manager.getStates());
|
|
23
26
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
@@ -83,6 +86,7 @@ export function Dashboard({ manager }) {
|
|
|
83
86
|
const statusText = agent.status === 'exited' || agent.status === 'errored'
|
|
84
87
|
? `${agent.status} (${agent.exitCode})`
|
|
85
88
|
: agent.status;
|
|
86
|
-
|
|
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));
|
|
87
91
|
}), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.grayDim, children: [symbols.arrow, " ", '\u2191\u2193', " navigate ", ' ', " space/enter stop/start ", ' ', " ctrl+c quit"] }) })] }));
|
|
88
92
|
}
|
|
@@ -1,42 +1,65 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect } from 'react';
|
|
3
3
|
import { Box, Text, useApp, useInput } from 'ink';
|
|
4
4
|
import { colors, symbols } from '../theme.js';
|
|
5
|
-
import { scanAgents } from '../agents.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.red, 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: ["S:", stats.sting.toFixed(2)] })] }));
|
|
19
|
+
}
|
|
6
20
|
export function SelectAgentApp({ onSelect }) {
|
|
7
21
|
const { exit } = useApp();
|
|
8
|
-
const [
|
|
22
|
+
const [rows, setRows] = useState(null);
|
|
9
23
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
10
24
|
useEffect(() => {
|
|
11
25
|
const load = async () => {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
26
|
+
const agents = await scanAgents();
|
|
27
|
+
if (agents.length === 0) {
|
|
28
|
+
setRows([]);
|
|
15
29
|
exit();
|
|
30
|
+
return;
|
|
16
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);
|
|
17
40
|
};
|
|
18
41
|
void load();
|
|
19
42
|
}, []);
|
|
20
43
|
useInput((_input, key) => {
|
|
21
|
-
if (
|
|
44
|
+
if (rows === null || rows.length === 0) {
|
|
22
45
|
return;
|
|
23
46
|
}
|
|
24
47
|
if (key.upArrow) {
|
|
25
48
|
setSelectedIndex((prev) => {
|
|
26
|
-
const max =
|
|
49
|
+
const max = rows.length - 1;
|
|
27
50
|
return prev > 0 ? prev - 1 : max;
|
|
28
51
|
});
|
|
29
52
|
}
|
|
30
53
|
else if (key.downArrow) {
|
|
31
54
|
setSelectedIndex((prev) => {
|
|
32
|
-
const max =
|
|
55
|
+
const max = rows.length - 1;
|
|
33
56
|
return prev < max ? prev + 1 : 0;
|
|
34
57
|
});
|
|
35
58
|
}
|
|
36
59
|
else if (key.return) {
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
39
|
-
onSelect(
|
|
60
|
+
const row = rows[selectedIndex];
|
|
61
|
+
if (row) {
|
|
62
|
+
onSelect(row.info);
|
|
40
63
|
exit();
|
|
41
64
|
}
|
|
42
65
|
}
|
|
@@ -44,15 +67,15 @@ export function SelectAgentApp({ onSelect }) {
|
|
|
44
67
|
exit();
|
|
45
68
|
}
|
|
46
69
|
});
|
|
47
|
-
if (
|
|
70
|
+
if (rows === null) {
|
|
48
71
|
return (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: colors.gray, children: "Scanning agents..." }) }));
|
|
49
72
|
}
|
|
50
|
-
if (
|
|
73
|
+
if (rows.length === 0) {
|
|
51
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" })] })] }));
|
|
52
75
|
}
|
|
53
|
-
const nameWidth = Math.max(6, ...
|
|
54
|
-
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: [" (",
|
|
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) => {
|
|
55
78
|
const isSelected = index === selectedIndex;
|
|
56
|
-
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:
|
|
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));
|
|
57
80
|
}), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.grayDim, children: [symbols.arrow, " ", '\u2191\u2193', " navigate ", ' ', " enter select ", ' ', " ctrl+c quit"] }) })] }));
|
|
58
81
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render } from 'ink';
|
|
3
3
|
import { showWelcome } from '../create/welcome.js';
|
|
4
|
-
import { scanAgents } from '../agents.js';
|
|
4
|
+
import { scanAgents, fetchBulkStats, sortAgentsByHoney } from '../agents.js';
|
|
5
5
|
import { symbols, styled } from '../theme.js';
|
|
6
6
|
import { AgentProcessManager } from './AgentProcessManager.js';
|
|
7
7
|
import { Dashboard } from './Dashboard.js';
|
|
@@ -14,9 +14,11 @@ export async function startCommand() {
|
|
|
14
14
|
console.log(` ${styled.gray('Create agents with:')} ${styled.white('npx @hive-org/cli@latest create')}\n`);
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
|
+
const names = discovered.map((a) => a.name);
|
|
18
|
+
const statsMap = await fetchBulkStats(names);
|
|
19
|
+
const sortedDiscovered = sortAgentsByHoney(discovered, statsMap);
|
|
17
20
|
const manager = new AgentProcessManager();
|
|
18
|
-
manager.spawnAll(
|
|
19
|
-
const { waitUntilExit } = render(React.createElement(Dashboard, { manager }));
|
|
21
|
+
manager.spawnAll(sortedDiscovered);
|
|
22
|
+
const { waitUntilExit } = render(React.createElement(Dashboard, { manager, statsMap }));
|
|
20
23
|
await waitUntilExit();
|
|
21
|
-
await manager.shutdownAll();
|
|
22
24
|
}
|