@hive-org/cli 0.0.7 → 0.0.9

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 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 { phase, connected, agentName, pollActivity, chatActivity, input, chatStreaming, chatBuffer, predictionCount, termWidth, setInput, handleChatSubmit, handleBootComplete, } = useAgent();
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 { jsx as _jsx } from "react/jsx-runtime";
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
- function compressRow(row) {
31
- if (row.length === 0)
32
- return [];
33
- const segments = [];
34
- let current = { text: row[0].char, color: row[0].color };
35
- for (let i = 1; i < row.length; i++) {
36
- if (row[i].color === current.color) {
37
- current.text += row[i].char;
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
- else {
40
- segments.push(current);
41
- current = { text: row[i].char, color: row[i].color };
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
- segments.push(current);
45
- return segments;
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 scrambleProgress = Math.min(1, (frame - 22) / 8);
151
- // Clear space around the name
152
- for (let c = nameStart - 2; c < nameStart + nameText.length + 2 && c < cols; c++) {
153
- if (c >= 0) {
154
- grid[centerR][c] = { char: ' ', color: colors.grayDim };
155
- if (centerR - 1 >= 0)
156
- grid[centerR - 1][c] = { char: ' ', color: colors.grayDim };
157
- if (centerR + 1 < rows)
158
- grid[centerR + 1][c] = { char: ' ', color: colors.grayDim };
159
- }
160
- }
161
- // Top/bottom border lines around name
162
- for (let c = nameStart; c < nameStart + nameText.length && c < cols; c++) {
163
- if (centerR - 1 >= 0)
164
- grid[centerR - 1][c] = { char: '\u2500', color: colors.honey };
165
- if (centerR + 1 < rows)
166
- grid[centerR + 1][c] = { char: '\u2500', color: colors.honey };
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 (beesRef.current.length > 0 && f % 2 === 0) {
219
- for (const bee of beesRef.current) {
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 >= h - 1) {
80
+ if (bee.r <= 0 || bee.r >= gridRows - 1) {
223
81
  bee.vr *= -1;
224
- bee.r = Math.max(0, Math.min(h - 1, bee.r));
82
+ bee.r = Math.max(0, Math.min(gridRows - 1, bee.r));
225
83
  }
226
- if (bee.c <= 0 || bee.c >= width - 1) {
84
+ if (bee.c <= 0 || bee.c >= cols - 1) {
227
85
  bee.vc *= -1;
228
- bee.c = Math.max(0, Math.min(width - 1, bee.c));
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 every 4 frames
236
- if (f % 4 === 0) {
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() * h);
240
- const pc = Math.floor(Math.random() * width);
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
- currentPulses.push({ r: pr, c: pc, ttl: 8, color });
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
- frameRef.current = f + 1;
252
- setFrame(frameRef.current);
253
- if (frameRef.current >= BOOT_TOTAL_FRAMES && !completedRef.current) {
254
- completedRef.current = true;
255
- clearInterval(tick);
256
- setTimeout(onComplete, 300);
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
- }, BOOT_FRAME_MS);
259
- return () => {
260
- clearInterval(tick);
261
- };
262
- }, [onComplete, width]);
263
- const height = heightRef.current;
264
- const clampedFrame = Math.min(frame, BOOT_TOTAL_FRAMES);
265
- const grid = buildHoneycombGrid(width, height, clampedFrame, agentName, beesRef.current, streamColsRef.current, pulsesRef.current);
266
- return (_jsx(Box, { flexDirection: "column", height: height, children: grid.map((row, r) => {
267
- const segments = compressRow(row);
268
- return (_jsx(Box, { width: width, children: _jsx(Text, { children: segments.map((seg, i) => (_jsx(Text, { color: seg.color, children: seg.text }, i))) }) }, r));
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
  }
@@ -10,7 +10,6 @@ import { processSignalAndSummarize, extractAndSaveMemory } from '../analysis.js'
10
10
  import { registerShutdownAgent } from '../process-lifecycle.js';
11
11
  import { getModel } from '../model.js';
12
12
  export function useAgent() {
13
- const [phase, setPhase] = useState('booting');
14
13
  const [connected, setConnected] = useState(false);
15
14
  const [agentName, setAgentName] = useState('agent');
16
15
  const [agentBio, setAgentBio] = useState('');
@@ -93,7 +92,8 @@ export function useAgent() {
93
92
  // ─── Agent lifecycle ────────────────────────────────
94
93
  useEffect(() => {
95
94
  const start = async () => {
96
- const baseUrl = process.env.HIVE_API_URL ?? 'http://localhost:6969';
95
+ const { HIVE_API_URL } = await import('../../config.js');
96
+ const baseUrl = HIVE_API_URL;
97
97
  const config = await loadAgentConfig();
98
98
  setAgentName(config.name);
99
99
  setAgentBio(config.bio ?? '');
@@ -246,12 +246,7 @@ export function useAgent() {
246
246
  setChatStreaming(false);
247
247
  }
248
248
  }, [chatStreaming, addChatActivity]);
249
- // ─── Boot transition ────────────────────────────────
250
- const handleBootComplete = useCallback(() => {
251
- setPhase('running');
252
- }, []);
253
249
  return {
254
- phase,
255
250
  connected,
256
251
  agentName,
257
252
  agentBio,
@@ -264,6 +259,5 @@ export function useAgent() {
264
259
  termWidth,
265
260
  setInput,
266
261
  handleChatSubmit,
267
- handleBootComplete,
268
262
  };
269
263
  }
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
- agents.push({ name: entry.name, dir: agentDir, provider, created: stat.birthtime });
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
+ }
package/dist/config.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs-extra';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
+ export const HIVE_API_URL = 'https://api.zhive.ai';
4
5
  export function getHiveDir() {
5
6
  const hiveDir = path.join(os.homedir(), '.hive');
6
7
  return hiveDir;
@@ -44,8 +44,7 @@ export async function scaffoldProject(projectName, provider, apiKey, soulContent
44
44
  await fs.writeFile(path.join(projectDir, 'MEMORY.md'), seedMemory, 'utf-8');
45
45
  // 3. Write .env
46
46
  callbacks.onStep('Writing environment file');
47
- const envContent = `HIVE_API_URL="https://hive-backend.z3n.dev"
48
- ${provider.envVar}="${apiKey}"
47
+ const envContent = `${provider.envVar}="${apiKey}"
49
48
  `;
50
49
  await fs.writeFile(path.join(projectDir, '.env'), envContent, { encoding: 'utf-8', mode: 0o600 });
51
50
  // 4. Write minimal package.json — no deps needed, npx fetches @hive-org/agent@latest on every run
@@ -55,7 +54,7 @@ ${provider.envVar}="${apiKey}"
55
54
  private: true,
56
55
  type: 'module',
57
56
  scripts: {
58
- start: 'npx @hive-org/cli@latest run',
57
+ start: 'npx @hive-org/cli@latest start',
59
58
  },
60
59
  };
61
60
  await fs.writeJson(path.join(projectDir, 'package.json'), packageJson, { spaces: 2 });
@@ -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:" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { color: colors.gray, children: [" 1. ", _jsxs(Text, { color: colors.white, children: ["cd ", projectDir] })] }), _jsxs(Text, { color: colors.gray, children: [" 2. ", _jsx(Text, { color: colors.white, children: "npm start" })] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.grayDim, children: " Edit SOUL.md and STRATEGY.md to fine-tune your agent." }) })] })] }));
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" })] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.grayDim, children: " Edit SOUL.md and STRATEGY.md to fine-tune your agent." }) })] })] }));
14
14
  }
@@ -5,6 +5,7 @@ import axios from 'axios';
5
5
  import { TextPrompt } from '../../components/TextPrompt.js';
6
6
  import { Spinner } from '../../components/Spinner.js';
7
7
  import { colors, symbols } from '../../theme.js';
8
+ import { HIVE_API_URL } from '../../config.js';
8
9
  const ADJECTIVES = [
9
10
  'royal', 'golden', 'buzzy', 'honey', 'sweet', 'stung', 'waxed', 'bold',
10
11
  'swift', 'wild', 'keen', 'warm', 'hazy', 'calm', 'busy', 'amber',
@@ -38,7 +39,7 @@ export function NameStep({ onComplete }) {
38
39
  setChecking(true);
39
40
  setError('');
40
41
  try {
41
- const apiUrl = process.env['HIVE_API_URL'] ?? 'https://hive-backend.z3n.dev';
42
+ const apiUrl = HIVE_API_URL;
42
43
  const response = await axios.get(`${apiUrl}/agent/check-name`, {
43
44
  params: { name },
44
45
  timeout: 3000,
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import { render } from 'ink';
4
4
  import React from 'react';
5
5
  import { CreateApp } from './create/CreateApp.js';
6
6
  import { ListApp } from './list/ListApp.js';
7
+ import { SelectAgentApp } from './start/SelectAgentApp.js';
7
8
  import { startCommand } from './start/start-command.js';
8
9
  import { showWelcome } from './create/welcome.js';
9
10
  const require = createRequire(import.meta.url);
@@ -13,8 +14,8 @@ const HELP_TEXT = `@hive-org/cli v${pkg.version}
13
14
  Usage:
14
15
  npx @hive-org/cli@latest create [agent-name] Scaffold a new Hive agent
15
16
  npx @hive-org/cli@latest list List existing agents
16
- npx @hive-org/cli@latest start Start all agents
17
- npx @hive-org/cli@latest run Run agent in current directory
17
+ npx @hive-org/cli@latest start Select and start an agent
18
+ npx @hive-org/cli@latest start-all Start all agents
18
19
  npx @hive-org/cli@latest migrate-templates Migrate old-style agents
19
20
  npx @hive-org/cli@latest --help Show this help message
20
21
  npx @hive-org/cli@latest --version Print version
@@ -23,8 +24,8 @@ Examples:
23
24
  npx @hive-org/cli@latest create alpha Creates ~/.hive/agents/alpha/
24
25
  npx @hive-org/cli@latest create Interactive setup
25
26
  npx @hive-org/cli@latest list Show all agents
26
- npx @hive-org/cli@latest start Launch all agents as child processes
27
- npx @hive-org/cli@latest run Run agent from cwd (reads SOUL.md, .env)`;
27
+ npx @hive-org/cli@latest start Pick an agent and run it
28
+ npx @hive-org/cli@latest start-all Launch all agents as child processes`;
28
29
  const command = process.argv[2];
29
30
  const arg = process.argv[3];
30
31
  if (command === '--version' || command === '-v') {
@@ -39,7 +40,7 @@ if (command === 'list') {
39
40
  const { waitUntilExit } = render(React.createElement(ListApp));
40
41
  await waitUntilExit();
41
42
  }
42
- else if (command === 'start') {
43
+ else if (command === 'start-all') {
43
44
  await startCommand();
44
45
  }
45
46
  else if (command === 'create') {
@@ -54,20 +55,27 @@ else if (command === 'migrate-templates') {
54
55
  const { waitUntilExit } = render(React.createElement(MigrateApp));
55
56
  await waitUntilExit();
56
57
  }
57
- else if (command === 'run') {
58
- const fs = await import('fs');
59
- const path = await import('path');
60
- const soulPath = path.join(process.cwd(), 'SOUL.md');
61
- if (!fs.existsSync(soulPath)) {
62
- console.error('No SOUL.md found in current directory. Run this command from an agent directory.');
63
- process.exit(1);
58
+ else if (command === 'start') {
59
+ await showWelcome();
60
+ let selectedAgent = null;
61
+ const { waitUntilExit: waitForSelect } = render(React.createElement(SelectAgentApp, {
62
+ onSelect: (agent) => {
63
+ selectedAgent = agent;
64
+ },
65
+ }));
66
+ await waitForSelect();
67
+ if (selectedAgent) {
68
+ const picked = selectedAgent;
69
+ const { showHoneycombBoot } = await import('./agent/components/HoneycombBoot.js');
70
+ await showHoneycombBoot(picked.name);
71
+ process.chdir(picked.dir);
72
+ await import('dotenv/config');
73
+ const { setupProcessLifecycle } = await import('./agent/process-lifecycle.js');
74
+ const { App } = await import('./agent/app.js');
75
+ setupProcessLifecycle();
76
+ const { waitUntilExit } = render(React.createElement(App));
77
+ await waitUntilExit();
64
78
  }
65
- await import('dotenv/config');
66
- const { setupProcessLifecycle } = await import('./agent/process-lifecycle.js');
67
- const { App } = await import('./agent/app.js');
68
- setupProcessLifecycle();
69
- const { waitUntilExit } = render(React.createElement(App));
70
- await waitUntilExit();
71
79
  }
72
80
  else {
73
81
  console.error(`Unknown command: ${command}\n`);
@@ -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 [agents, setAgents] = useState(null);
28
+ const [rows, setRows] = useState(null);
17
29
  useEffect(() => {
18
30
  const load = async () => {
19
- const results = await scanAgents();
20
- setAgents(results);
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 (agents !== null) {
48
+ if (rows !== null) {
26
49
  exit();
27
50
  }
28
- }, [agents]);
29
- if (agents === null) {
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 (agents.length === 0) {
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 nameWidth = Math.max(6, ...agents.map((a) => a.name.length)) + 2;
36
- const providerWidth = Math.max(10, ...agents.map((a) => a.provider.length)) + 2;
37
- 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: [" (", agents.length, ")"] })] }), _jsx(Box, { children: _jsxs(Text, { color: colors.honey, children: [border.topLeft, border.horizontal.repeat(nameWidth), border.horizontal, border.horizontal.repeat(providerWidth), border.horizontal, border.horizontal.repeat(14), border.topRight] }) }), _jsxs(Box, { children: [_jsx(Text, { color: colors.honey, children: border.vertical }), _jsx(Text, { color: colors.white, bold: true, children: ' Name'.padEnd(nameWidth) }), _jsx(Text, { color: colors.honey, children: border.vertical }), _jsx(Text, { color: colors.white, bold: true, children: ' Provider'.padEnd(providerWidth) }), _jsx(Text, { color: colors.honey, children: border.vertical }), _jsx(Text, { color: colors.white, bold: true, children: ' Created'.padEnd(14) }), _jsx(Text, { color: colors.honey, children: border.vertical })] }), _jsx(Box, { children: _jsxs(Text, { color: colors.honey, children: [border.teeLeft, border.horizontal.repeat(nameWidth), border.horizontal, border.horizontal.repeat(providerWidth), border.horizontal, border.horizontal.repeat(14), border.teeRight] }) }), agents.map((agent) => (_jsxs(Box, { children: [_jsx(Text, { color: colors.honey, children: border.vertical }), _jsx(Text, { color: colors.white, children: ` ${agent.name}`.padEnd(nameWidth) }), _jsx(Text, { color: colors.honey, children: border.vertical }), _jsx(Text, { color: colors.gray, children: ` ${agent.provider}`.padEnd(providerWidth) }), _jsx(Text, { color: colors.honey, children: border.vertical }), _jsx(Text, { color: colors.grayDim, children: ` ${formatDate(agent.created)}`.padEnd(14) }), _jsx(Text, { color: colors.honey, children: border.vertical })] }, agent.name))), _jsx(Box, { children: _jsxs(Text, { color: colors.honey, children: [border.bottomLeft, border.horizontal.repeat(nameWidth), border.horizontal, border.horizontal.repeat(providerWidth), border.horizontal, border.horizontal.repeat(14), border.bottomRight] }) })] }));
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
  }
@@ -127,5 +127,5 @@ export function MigrateApp() {
127
127
  const successCount = results.filter((r) => r.success && !r.error).length;
128
128
  const alreadyNew = results.filter((r) => r.error === 'Already migrated').length;
129
129
  const failCount = results.filter((r) => !r.success).length;
130
- return (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, children: [_jsxs(Text, { color: colors.honey, bold: true, children: [symbols.hive, " Migration complete"] }), _jsx(Text, { color: "gray", children: border.horizontal.repeat(termWidth - 4) }), results.map((r) => (_jsxs(Box, { children: [r.success && !r.error && (_jsxs(Text, { color: colors.green, children: [symbols.check, " ", r.name, " \u2014 migrated"] })), r.error === 'Already migrated' && (_jsxs(Text, { color: "gray", children: [symbols.check, " ", r.name, " \u2014 already migrated"] })), !r.success && r.error !== 'Already migrated' && (_jsxs(Text, { color: colors.red, children: [symbols.cross, " ", r.name, " \u2014 ", r.error] }))] }, r.name))), agents.length === 0 && results.length === 0 && (_jsx(Text, { color: "gray", children: "No agents found in ~/.hive/agents/" })), _jsx(Text, { children: " " }), successCount > 0 && (_jsxs(Text, { color: "gray", children: ["Agents now run via @hive-org/cli. ", styled.white('npx @hive-org/cli@latest run'), " always uses the latest version."] }))] }));
130
+ return (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, children: [_jsxs(Text, { color: colors.honey, bold: true, children: [symbols.hive, " Migration complete"] }), _jsx(Text, { color: "gray", children: border.horizontal.repeat(termWidth - 4) }), results.map((r) => (_jsxs(Box, { children: [r.success && !r.error && (_jsxs(Text, { color: colors.green, children: [symbols.check, " ", r.name, " \u2014 migrated"] })), r.error === 'Already migrated' && (_jsxs(Text, { color: "gray", children: [symbols.check, " ", r.name, " \u2014 already migrated"] })), !r.success && r.error !== 'Already migrated' && (_jsxs(Text, { color: colors.red, children: [symbols.cross, " ", r.name, " \u2014 ", r.error] }))] }, r.name))), agents.length === 0 && results.length === 0 && (_jsx(Text, { color: "gray", children: "No agents found in ~/.hive/agents/" })), _jsx(Text, { children: " " }), successCount > 0 && (_jsxs(Text, { color: "gray", children: ["Agents now run via @hive-org/cli. ", styled.white('npx @hive-org/cli@latest start'), " always uses the latest version."] }))] }));
131
131
  }
@@ -61,7 +61,7 @@ export async function migrateAgent(agentDir, name, onStep) {
61
61
  private: true,
62
62
  type: 'module',
63
63
  scripts: {
64
- start: 'npx @hive-org/cli@latest run',
64
+ start: 'npx @hive-org/cli@latest start',
65
65
  },
66
66
  };
67
67
  await fs.writeJson(pkgPath, newPkg, { spaces: 2 });
@@ -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
- export function Dashboard({ manager }) {
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
- 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 })] }, agent.name));
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
  }
@@ -0,0 +1,81 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { Box, Text, useApp, useInput } from 'ink';
4
+ import { colors, symbols } from '../theme.js';
5
+ import { scanAgents, fetchBulkStats, sortByHoney } from '../agents.js';
6
+ function formatDate(date) {
7
+ const formatted = date.toLocaleDateString('en-US', {
8
+ year: 'numeric',
9
+ month: 'short',
10
+ day: 'numeric',
11
+ });
12
+ return formatted;
13
+ }
14
+ function StatsText({ stats }) {
15
+ if (stats === null) {
16
+ return _jsx(Text, { color: colors.grayDim, children: "-" });
17
+ }
18
+ return (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.honey, children: ["H:", Math.floor(stats.honey)] }), _jsx(Text, { color: colors.grayDim, children: " " }), _jsxs(Text, { color: colors.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
+ }
20
+ export function SelectAgentApp({ onSelect }) {
21
+ const { exit } = useApp();
22
+ const [rows, setRows] = useState(null);
23
+ const [selectedIndex, setSelectedIndex] = useState(0);
24
+ useEffect(() => {
25
+ const load = async () => {
26
+ const agents = await scanAgents();
27
+ if (agents.length === 0) {
28
+ setRows([]);
29
+ exit();
30
+ return;
31
+ }
32
+ const names = agents.map((a) => a.name);
33
+ const statsMap = await fetchBulkStats(names);
34
+ const agentRows = agents.map((info) => ({
35
+ info,
36
+ stats: statsMap.get(info.name) ?? null,
37
+ }));
38
+ const sortedRows = sortByHoney(agentRows);
39
+ setRows(sortedRows);
40
+ };
41
+ void load();
42
+ }, []);
43
+ useInput((_input, key) => {
44
+ if (rows === null || rows.length === 0) {
45
+ return;
46
+ }
47
+ if (key.upArrow) {
48
+ setSelectedIndex((prev) => {
49
+ const max = rows.length - 1;
50
+ return prev > 0 ? prev - 1 : max;
51
+ });
52
+ }
53
+ else if (key.downArrow) {
54
+ setSelectedIndex((prev) => {
55
+ const max = rows.length - 1;
56
+ return prev < max ? prev + 1 : 0;
57
+ });
58
+ }
59
+ else if (key.return) {
60
+ const row = rows[selectedIndex];
61
+ if (row) {
62
+ onSelect(row.info);
63
+ exit();
64
+ }
65
+ }
66
+ else if (key.ctrl && _input === 'c') {
67
+ exit();
68
+ }
69
+ });
70
+ if (rows === null) {
71
+ return (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: colors.gray, children: "Scanning agents..." }) }));
72
+ }
73
+ if (rows.length === 0) {
74
+ return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: colors.honey, children: [symbols.hive, " "] }), _jsx(Text, { color: colors.white, bold: true, children: "No agents found" })] }), _jsxs(Text, { color: colors.gray, children: ["Create one with: ", _jsx(Text, { color: colors.white, children: "npx @hive-org/cli@latest create" })] })] }));
75
+ }
76
+ const nameWidth = Math.max(6, ...rows.map((r) => r.info.name.length)) + 2;
77
+ return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: colors.honey, children: [symbols.hive, " "] }), _jsx(Text, { color: colors.white, bold: true, children: "Select an agent to start" }), _jsxs(Text, { color: colors.grayDim, children: [" (", rows.length, ")"] })] }), rows.map((row, index) => {
78
+ const isSelected = index === selectedIndex;
79
+ return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? colors.honey : colors.grayDim, children: isSelected ? `${symbols.diamond} ` : ' ' }), _jsx(Text, { color: isSelected ? colors.white : colors.gray, bold: isSelected, children: row.info.name.padEnd(nameWidth) }), _jsx(StatsText, { stats: row.stats }), _jsxs(Text, { color: colors.grayDim, children: [" ", formatDate(row.info.created)] })] }, row.info.name));
80
+ }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.grayDim, children: [symbols.arrow, " ", '\u2191\u2193', " navigate ", ' ', " enter select ", ' ', " ctrl+c quit"] }) })] }));
81
+ }
@@ -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,12 @@ 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(discovered);
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
24
  await manager.shutdownAll();
22
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hive-org/cli",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "CLI for bootstrapping Hive AI Agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",