Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@hive-org/cli

Package Overview
Dependencies
Maintainers
2
Versions
35
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@hive-org/cli - npm Package Compare versions

Comparing version
0.0.4
to
0.0.5
+46
dist/agents.js
import fs from 'fs-extra';
import path from 'path';
import os from 'os';
export async function scanAgents() {
const agentsDir = path.join(os.homedir(), '.hive', 'agents');
const exists = await fs.pathExists(agentsDir);
if (!exists) {
return [];
}
const entries = await fs.readdir(agentsDir, { withFileTypes: true });
const agents = [];
for (const entry of entries) {
if (!entry.isDirectory()) {
continue;
}
const soulPath = path.join(agentsDir, entry.name, 'SOUL.md');
const soulExists = await fs.pathExists(soulPath);
if (!soulExists) {
continue;
}
let provider = 'unknown';
const envPath = path.join(agentsDir, entry.name, '.env');
const envExists = await fs.pathExists(envPath);
if (envExists) {
const envContent = await fs.readFile(envPath, 'utf-8');
if (envContent.includes('OPENAI_API_KEY')) {
provider = 'OpenAI';
}
else if (envContent.includes('ANTHROPIC_API_KEY')) {
provider = 'Anthropic';
}
else if (envContent.includes('GOOGLE_GENERATIVE_AI_API_KEY')) {
provider = 'Google';
}
else if (envContent.includes('XAI_API_KEY')) {
provider = 'xAI';
}
else if (envContent.includes('OPENROUTER_API_KEY')) {
provider = 'OpenRouter';
}
}
const stat = await fs.stat(soulPath);
agents.push({ name: entry.name, provider, created: stat.birthtime });
}
return agents;
}
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
import { useState, useEffect, useCallback, useRef } from 'react';
import { Box, Text, useApp, useInput } from 'ink';
import { spawn } from 'child_process';
import path from 'path';
import os from 'os';
import { colors, symbols, border } from '../theme.js';
import { scanAgents } from '../agents.js';
const STATUS_COLORS = {
spawning: colors.honey,
running: colors.green,
exited: colors.grayDim,
errored: colors.red,
};
const STATUS_SYMBOLS = {
spawning: symbols.diamondOpen,
running: symbols.dot,
exited: '\u25CB', // ○
errored: symbols.cross,
};
const MAX_RECENT_LINES = 10;
const FORCE_KILL_TIMEOUT_MS = 10_000;
export function StartApp() {
const { exit } = useApp();
const [agents, setAgents] = useState([]);
const [recentLines, setRecentLines] = useState([]);
const [phase, setPhase] = useState('scanning');
const [error, setError] = useState(null);
const childrenRef = useRef(new Map());
const shuttingDownRef = useRef(false);
const lineIdRef = useRef(0);
const updateAgent = useCallback((name, update) => {
setAgents((prev) => prev.map((a) => (a.name === name ? { ...a, ...update } : a)));
}, []);
const pushRecentLine = useCallback((name, text) => {
const id = lineIdRef.current++;
setRecentLines((prev) => {
const next = [...prev, { id, name, text }];
if (next.length > MAX_RECENT_LINES) {
return next.slice(next.length - MAX_RECENT_LINES);
}
return next;
});
}, []);
const shutdown = useCallback(async () => {
if (shuttingDownRef.current) {
return;
}
shuttingDownRef.current = true;
setPhase('stopping');
const children = Array.from(childrenRef.current.values());
// Register exit listeners BEFORE sending signals to avoid race condition
const waitForAll = children.map((child) => new Promise((resolve) => {
if (child.exitCode !== null) {
resolve();
return;
}
child.on('exit', () => resolve());
}));
for (const child of children) {
child.kill('SIGTERM');
}
const forceKillTimer = setTimeout(() => {
for (const child of Array.from(childrenRef.current.values())) {
child.kill('SIGKILL');
}
}, FORCE_KILL_TIMEOUT_MS);
await Promise.all(waitForAll);
clearTimeout(forceKillTimer);
setPhase('done');
exit();
}, [exit]);
useInput((_input, key) => {
if (key.ctrl && _input === 'c') {
void shutdown();
}
});
const handleStreamData = useCallback((agentName, bufferRef) => {
return (chunk) => {
bufferRef.current += chunk.toString();
const lines = bufferRef.current.split('\n');
bufferRef.current = lines.pop() ?? '';
for (const line of lines) {
const trimmed = line.trim();
if (trimmed.length === 0) {
continue;
}
updateAgent(agentName, { status: 'running', lastLine: trimmed });
pushRecentLine(agentName, trimmed);
}
};
}, [updateAgent, pushRecentLine]);
useEffect(() => {
const run = async () => {
try {
const discovered = await scanAgents();
if (discovered.length === 0) {
setError('No agents found in ~/.hive/agents/');
setPhase('done');
exit();
return;
}
const agentsDir = path.join(os.homedir(), '.hive', 'agents');
const initial = discovered.map((a) => ({
name: a.name,
status: 'spawning',
exitCode: null,
lastLine: '',
}));
setAgents(initial);
setPhase('running');
for (const agent of discovered) {
const agentDir = path.join(agentsDir, agent.name);
const child = spawn('npm', ['start'], {
cwd: agentDir,
stdio: 'pipe',
});
childrenRef.current.set(agent.name, child);
const stdoutBuffer = { current: '' };
const stderrBuffer = { current: '' };
child.stdout?.on('data', handleStreamData(agent.name, stdoutBuffer));
child.stderr?.on('data', handleStreamData(agent.name, stderrBuffer));
child.on('error', (err) => {
updateAgent(agent.name, { status: 'errored', lastLine: err.message });
childrenRef.current.delete(agent.name);
});
child.on('exit', (code) => {
// Flush remaining buffered output
const remainingStdout = stdoutBuffer.current.trim();
if (remainingStdout.length > 0) {
pushRecentLine(agent.name, remainingStdout);
}
const remainingStderr = stderrBuffer.current.trim();
if (remainingStderr.length > 0) {
pushRecentLine(agent.name, remainingStderr);
}
const exitCode = code ?? 1;
const status = exitCode === 0 ? 'exited' : 'errored';
updateAgent(agent.name, { status, exitCode });
childrenRef.current.delete(agent.name);
if (childrenRef.current.size === 0 && !shuttingDownRef.current) {
setPhase('done');
exit();
}
});
}
}
catch (err) {
const message = err instanceof Error ? err.message : String(err);
setError(`Failed to scan agents: ${message}`);
setPhase('done');
exit();
}
};
void run();
return () => {
void shutdown();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (error) {
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: colors.honey, children: [symbols.hive, " "] }), _jsx(Text, { color: colors.red, children: error })] }), _jsxs(Text, { color: colors.gray, children: ["Create agents with: ", _jsx(Text, { color: colors.white, children: "npx @hive-org/cli@latest create" })] })] }));
}
if (phase === 'scanning') {
return (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: colors.gray, children: "Scanning agents..." }) }));
}
const runningCount = agents.filter((a) => a.status === 'running').length;
const spawningCount = agents.filter((a) => a.status === 'spawning').length;
const nameWidth = Math.max(16, ...agents.map((a) => a.name.length + 2));
const statusLabel = phase === 'stopping'
? 'shutting down...'
: `${runningCount} running${spawningCount > 0 ? `, ${spawningCount} starting` : ''}`;
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: colors.honey, children: [symbols.hive, " "] }), _jsx(Text, { color: colors.white, bold: true, children: "Hive Swarm" }), _jsxs(Text, { color: colors.gray, children: [" ", border.horizontal, " ", statusLabel] })] }), agents.map((agent) => {
const statusColor = STATUS_COLORS[agent.status];
const statusSymbol = STATUS_SYMBOLS[agent.status];
const statusText = agent.status === 'exited' || agent.status === 'errored'
? `${agent.status} (${agent.exitCode})`
: agent.status;
const truncatedLine = agent.lastLine.length > 60
? agent.lastLine.slice(0, 57) + '...'
: agent.lastLine;
return (_jsxs(Box, { children: [_jsx(Text, { color: statusColor, children: ` ${statusSymbol} ` }), _jsx(Text, { color: colors.white, children: agent.name.padEnd(nameWidth) }), _jsx(Text, { color: statusColor, children: statusText.padEnd(12) }), _jsx(Text, { color: colors.grayDim, children: truncatedLine })] }, agent.name));
}), recentLines.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Box, { children: _jsxs(Text, { color: colors.grayDim, children: [border.horizontal.repeat(3), " Recent Activity ", border.horizontal.repeat(3)] }) }), recentLines.map((line) => {
const truncatedText = line.text.length > 60
? line.text.slice(0, 57) + '...'
: line.text;
return (_jsxs(Box, { children: [_jsxs(Text, { color: colors.honey, children: [' ', line.name.padEnd(nameWidth)] }), _jsxs(Text, { color: colors.grayDim, children: [border.vertical, " "] }), _jsx(Text, { color: colors.gray, children: truncatedText })] }, line.id));
})] })), phase === 'stopping' && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.honey, children: "Sending SIGTERM to all agents..." }) }))] }));
}
+11
-0

@@ -342,2 +342,13 @@ export const SOUL_PRESETS = [

## Conviction Magnitude
Your conviction number is a predicted % price change. Use the full range for your style:
- conservative: ±0.5% to ±3% (only exceed on extreme signals)
- moderate: ±1% to ±6%
- bold: ±3% to ±12%
- degen: ±5% to ±25% (go big or skip)
You are "${preset.predictionProfile.conviction_style}" — stay within that range but VARY your numbers. Never default to the same value repeatedly. A 0.8% call is just as valid as a 5.3% call if the signal warrants it.
## Sector Focus

@@ -344,0 +355,0 @@

+1
-1
{
"name": "@hive-org/cli",
"version": "0.0.4",
"version": "0.0.5",
"description": "CLI for bootstrapping Hive AI Agents",

@@ -5,0 +5,0 @@ "type": "module",

@@ -32,3 +32,3 @@ import { openai } from '@ai-sdk/openai';

.describe(
'Predicted percent price change over the next 3 hours, up to one decimal place (e.g. 2.6 for +2.6%, -3.5 for -3.5%). Use 0 if neutral. null if skipping.',
'Predicted percent price change over the next 3 hours, up to one decimal place. Use the FULL range based on signal strength: tiny signals ±0.1-1.0, moderate ±1.5-5.0, strong ±5.0-12.0, extreme ±12.0-25.0. Negative for bearish. 0 if neutral. null if skipping. VARY your predictions — do NOT default to the same number repeatedly.',
),

@@ -35,0 +35,0 @@ });

@@ -138,2 +138,3 @@ import { openai } from '@ai-sdk/openai';

avatarUrl: config.avatarUrl,
bio: config.bio ?? undefined,
predictionProfile: config.predictionProfile,

@@ -140,0 +141,0 @@ pollIntervalMs: 5000,

@@ -157,5 +157,13 @@ import * as fs from 'fs/promises';

Give your take in character and a conviction number.
Conviction: predicted % price change from the snapshot price ($${priceOnFetch}) over the 3 hours following signal time (${timestamp}), up to one decimal. Positive = up, negative = down. 0 = neutral.`;
Conviction: predicted % price change from the snapshot price ($${priceOnFetch}) over the 3 hours following signal time (${timestamp}), up to one decimal. Positive = up, negative = down. 0 = neutral.
Conviction calibration — match signal strength to magnitude:
- Routine ecosystem update, minor partnership → ±0.5 to ±2.0
- Notable catalyst, solid metrics, growing momentum → ±2.0 to ±6.0
- Major protocol upgrade, big institutional entry, trend reversal → ±6.0 to ±12.0
- Black swan, regulatory bombshell, massive exploit → ±12.0 to ±25.0
IMPORTANT: Vary your conviction numbers. Do NOT reuse the same number across signals. Each signal has different strength — your conviction should reflect that.`;
return prompt;
}