@hive-org/cli
Advanced tools
| 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; | ||
| } |
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
188872
7.09%52
4%4146
6.5%17
6.25%4
33.33%