@hive-org/cli
Advanced tools
| import { HiveAgent } from '@hive-org/sdk'; | ||
| import { loadMemory } from '@hive-org/sdk'; | ||
| import { loadAgentConfig } from './config.js'; | ||
| import { processSignalAndSummarize } from './analysis.js'; | ||
| function timestamp() { | ||
| const now = new Date(); | ||
| const hours = String(now.getHours()).padStart(2, '0'); | ||
| const minutes = String(now.getMinutes()).padStart(2, '0'); | ||
| const seconds = String(now.getSeconds()).padStart(2, '0'); | ||
| const ts = `${hours}:${minutes}:${seconds}`; | ||
| return ts; | ||
| } | ||
| export async function runHeadless() { | ||
| const { HIVE_API_URL } = await import('../config.js'); | ||
| const config = await loadAgentConfig(); | ||
| const initialMemory = await loadMemory(); | ||
| let memoryRef = initialMemory; | ||
| const soulContent = config.soulContent; | ||
| const strategyContent = config.strategyContent; | ||
| let predictionCount = 0; | ||
| const agent = new HiveAgent(HIVE_API_URL, { | ||
| name: config.name, | ||
| avatarUrl: config.avatarUrl, | ||
| bio: config.bio ?? undefined, | ||
| predictionProfile: config.predictionProfile, | ||
| pollIntervalMs: 5000, | ||
| pollLimit: 20, | ||
| onPollEmpty: () => { | ||
| console.log(`[${timestamp()}] idle — no new threads`); | ||
| }, | ||
| onStop: async () => { }, | ||
| onNewThread: async (thread) => { | ||
| try { | ||
| const threadPreview = thread.text.length > 80 ? thread.text.slice(0, 80) + '\u2026' : thread.text; | ||
| console.log(`[${timestamp()}] signal c/${thread.project_id} · ${thread.id.slice(0, 8)}`); | ||
| console.log(` "${threadPreview}"`); | ||
| console.log(`[${timestamp()}] analyzing...`); | ||
| const result = await processSignalAndSummarize(thread, agent.recentComments, memoryRef, soulContent, strategyContent); | ||
| if (result.skip) { | ||
| console.log(`[${timestamp()}] skipped — outside expertise`); | ||
| return; | ||
| } | ||
| await agent.postComment(thread.id, { | ||
| thread_id: thread.id, | ||
| text: result.summary, | ||
| conviction: result.conviction, | ||
| }, thread.text); | ||
| predictionCount += 1; | ||
| const sign = result.conviction >= 0 ? '+' : ''; | ||
| console.log(`[${timestamp()}] predicted [${sign}${result.conviction}%] "${result.summary}" (${predictionCount} total)`); | ||
| } | ||
| catch (err) { | ||
| const raw = err instanceof Error ? err.message : String(err); | ||
| const message = raw.length > 120 ? raw.slice(0, 120) + '\u2026' : raw; | ||
| console.error(`[${timestamp()}] error: ${message}`); | ||
| } | ||
| }, | ||
| }); | ||
| const shutdown = async () => { | ||
| console.log(`[${config.name}] shutting down...`); | ||
| await agent.stop(); | ||
| process.exit(0); | ||
| }; | ||
| process.on('SIGINT', () => void shutdown()); | ||
| process.on('SIGTERM', () => void shutdown()); | ||
| await agent.start(); | ||
| console.log(`[${config.name}] agent online — "${config.bio ?? ''}"`); | ||
| } |
@@ -10,3 +10,2 @@ import { streamText, stepCountIs } from 'ai'; | ||
| import { processSignalAndSummarize, extractAndSaveMemory } from '../analysis.js'; | ||
| import { registerShutdownAgent } from '../process-lifecycle.js'; | ||
| import { getModel } from '../model.js'; | ||
@@ -157,5 +156,2 @@ export function useAgent() { | ||
| agentRef.current = agent; | ||
| registerShutdownAgent(async () => { | ||
| await agent.stop(); | ||
| }); | ||
| await agent.start(); | ||
@@ -162,0 +158,0 @@ setConnected(true); |
| import chalk from 'chalk'; | ||
| import { symbols } from './theme.js'; | ||
| let _shutdownAgent = null; | ||
| let _shuttingDown = false; | ||
| export function registerShutdownAgent(fn) { | ||
| _shutdownAgent = fn; | ||
| } | ||
| export function clearShutdownAgent() { | ||
| _shutdownAgent = null; | ||
| } | ||
| const restoreScreen = () => { | ||
| process.stdout.write('\x1b[?1049l'); | ||
| }; | ||
| export const gracefulShutdown = async (exitCode = 0) => { | ||
| if (_shuttingDown) { | ||
| return; | ||
| } | ||
| _shuttingDown = true; | ||
| if (_shutdownAgent) { | ||
| try { | ||
| await _shutdownAgent(); | ||
| } | ||
| catch { | ||
| // Best-effort memory save on shutdown | ||
| } | ||
| _shutdownAgent = null; | ||
| } | ||
| const exitImmediately = (exitCode = 0) => { | ||
| restoreScreen(); | ||
@@ -44,14 +23,4 @@ process.exit(exitCode); | ||
| process.on('exit', restoreScreen); | ||
| process.on('SIGINT', () => { | ||
| gracefulShutdown().catch(() => { | ||
| restoreScreen(); | ||
| process.exit(1); | ||
| }); | ||
| }); | ||
| process.on('SIGTERM', () => { | ||
| gracefulShutdown().catch(() => { | ||
| restoreScreen(); | ||
| process.exit(1); | ||
| }); | ||
| }); | ||
| process.on('SIGINT', () => exitImmediately(0)); | ||
| process.on('SIGTERM', () => exitImmediately(0)); | ||
| } |
@@ -13,3 +13,3 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime"; | ||
| const line = border.horizontal.repeat(boxWidth - 2); | ||
| 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." }) })] })] })); | ||
| 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 })] })] })] })] })); | ||
| } |
+50
-14
@@ -17,4 +17,5 @@ #!/usr/bin/env node | ||
| npx @hive-org/cli@latest list List existing agents | ||
| npx @hive-org/cli@latest start Select and start an agent | ||
| npx @hive-org/cli@latest start Start an agent (auto-detects agent dir) | ||
| npx @hive-org/cli@latest start-all Start all agents | ||
| npx @hive-org/cli@latest run Run agent headless (no TUI, used by start-all) | ||
| npx @hive-org/cli@latest migrate-templates Migrate old-style agents | ||
@@ -59,16 +60,28 @@ npx @hive-org/cli@latest --help Show this help message | ||
| } | ||
| else if (command === 'run') { | ||
| // Headless agent run — no TUI, just console output. | ||
| // Used by start-all to spawn agents as child processes. | ||
| const { access } = await import('fs/promises'); | ||
| const { join } = await import('path'); | ||
| const isAgentDir = await access(join(process.cwd(), 'SOUL.md')) | ||
| .then(() => true) | ||
| .catch(() => false); | ||
| if (!isAgentDir) { | ||
| console.error('Error: "run" must be called from an agent directory (with SOUL.md)'); | ||
| process.exit(1); | ||
| } | ||
| await import('dotenv/config'); | ||
| const { runHeadless } = await import('./agent/run-headless.js'); | ||
| await runHeadless(); | ||
| } | ||
| else if (command === 'start') { | ||
| await showWelcome(); | ||
| let selectedAgent = null; | ||
| const { waitUntilExit: waitForSelect } = render(React.createElement(SelectAgentApp, { | ||
| onSelect: (agent) => { | ||
| selectedAgent = agent; | ||
| }, | ||
| })); | ||
| await waitForSelect(); | ||
| if (selectedAgent) { | ||
| const picked = selectedAgent; | ||
| const { showHoneycombBoot } = await import('./agent/components/HoneycombBoot.js'); | ||
| await showHoneycombBoot(picked.name); | ||
| process.chdir(picked.dir); | ||
| // Detect if cwd is an agent directory (has SOUL.md). | ||
| // When called via agent's "npm start", cwd is the agent dir. | ||
| const { access } = await import('fs/promises'); | ||
| const { join } = await import('path'); | ||
| const isAgentDir = await access(join(process.cwd(), 'SOUL.md')) | ||
| .then(() => true) | ||
| .catch(() => false); | ||
| if (isAgentDir) { | ||
| // Direct agent run — cwd is already the agent directory. | ||
| await import('dotenv/config'); | ||
@@ -81,2 +94,25 @@ const { setupProcessLifecycle } = await import('./agent/process-lifecycle.js'); | ||
| } | ||
| else { | ||
| // Interactive agent selection | ||
| await showWelcome(); | ||
| let selectedAgent = null; | ||
| const { waitUntilExit: waitForSelect } = render(React.createElement(SelectAgentApp, { | ||
| onSelect: (agent) => { | ||
| selectedAgent = agent; | ||
| }, | ||
| })); | ||
| await waitForSelect(); | ||
| if (selectedAgent) { | ||
| const picked = selectedAgent; | ||
| const { showHoneycombBoot } = await import('./agent/components/HoneycombBoot.js'); | ||
| await showHoneycombBoot(picked.name); | ||
| process.chdir(picked.dir); | ||
| await import('dotenv/config'); | ||
| const { setupProcessLifecycle } = await import('./agent/process-lifecycle.js'); | ||
| const { App } = await import('./agent/app.js'); | ||
| setupProcessLifecycle(); | ||
| const { waitUntilExit } = render(React.createElement(App)); | ||
| await waitUntilExit(); | ||
| } | ||
| } | ||
| } | ||
@@ -83,0 +119,0 @@ else { |
@@ -5,3 +5,2 @@ import { spawn } from 'child_process'; | ||
| const FORCE_KILL_TIMEOUT_MS = 5_000; | ||
| const SHUTDOWN_TIMEOUT_MS = 10_000; | ||
| const ABSOLUTE_TIMEOUT_MS = FORCE_KILL_TIMEOUT_MS + 2_000; | ||
@@ -68,37 +67,5 @@ export class AgentProcessManager { | ||
| } | ||
| async shutdownAll() { | ||
| const children = []; | ||
| for (const agent of this._agents.values()) { | ||
| if (agent.child) { | ||
| children.push(agent.child); | ||
| } | ||
| } | ||
| if (children.length === 0) { | ||
| return; | ||
| } | ||
| const waitForAll = children.map((child) => new Promise((resolve) => { | ||
| if (child.exitCode !== null) { | ||
| resolve(); | ||
| return; | ||
| } | ||
| child.on('exit', () => resolve()); | ||
| // Absolute safety timeout | ||
| setTimeout(() => resolve(), SHUTDOWN_TIMEOUT_MS + 2_000); | ||
| })); | ||
| for (const child of children) { | ||
| child.kill('SIGTERM'); | ||
| } | ||
| const forceKillTimer = setTimeout(() => { | ||
| for (const agent of this._agents.values()) { | ||
| if (agent.child && agent.child.exitCode === null) { | ||
| agent.child.kill('SIGKILL'); | ||
| } | ||
| } | ||
| }, SHUTDOWN_TIMEOUT_MS); | ||
| await Promise.all(waitForAll); | ||
| clearTimeout(forceKillTimer); | ||
| } | ||
| _spawnPiped(name) { | ||
| const agentDir = path.join(this._agentsDir, name); | ||
| const child = spawn('npm', ['start'], { | ||
| const child = spawn('npx', ['@hive-org/cli@latest', 'run'], { | ||
| cwd: agentDir, | ||
@@ -105,0 +72,0 @@ stdio: ['ignore', 'pipe', 'pipe'], |
@@ -24,3 +24,2 @@ import React from 'react'; | ||
| await waitUntilExit(); | ||
| await manager.shutdownAll(); | ||
| } |
+1
-1
| { | ||
| "name": "@hive-org/cli", | ||
| "version": "0.0.9", | ||
| "version": "0.0.10", | ||
| "description": "CLI for bootstrapping Hive AI Agents", | ||
@@ -5,0 +5,0 @@ "type": "module", |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
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
226526
1.33%61
1.67%4639
0.76%19
5.56%