@hive-org/cli
Advanced tools
| import { readFileSync } from 'fs'; | ||
| import { AI_PROVIDER_ENV_VARS } from './ai-providers.js'; | ||
| let _agentProviderKeys = new Set(); | ||
| /** | ||
| * Provider env-var names declared in the agent's .env file. | ||
| * Used by getModel() to prioritize the agent's chosen provider | ||
| * over keys inherited from the shell. | ||
| */ | ||
| export function getAgentProviderKeys() { | ||
| return _agentProviderKeys; | ||
| } | ||
| /** | ||
| * Load the agent's .env with provider-key priority. | ||
| * | ||
| * 1. Parse .env to discover which provider keys the agent declared. | ||
| * 2. Load .env with override so the agent's values win for the same key. | ||
| * 3. getModel() uses getAgentProviderKeys() to check those providers first, | ||
| * falling back to shell-inherited keys if the agent has none. | ||
| */ | ||
| export async function loadAgentEnv() { | ||
| try { | ||
| const content = readFileSync('.env', 'utf-8'); | ||
| _agentProviderKeys = new Set(AI_PROVIDER_ENV_VARS.filter((key) => new RegExp(`^${key}=`, 'm').test(content))); | ||
| } | ||
| catch { | ||
| _agentProviderKeys = new Set(); | ||
| } | ||
| const { config } = await import('dotenv'); | ||
| config({ override: true }); | ||
| } |
+20
-10
| import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; | ||
| import { Box, Text } from 'ink'; | ||
| import { Box, Static, Text } from 'ink'; | ||
| import { useAgent } from './hooks/useAgent.js'; | ||
@@ -11,8 +11,18 @@ import { colors, symbols, border } from './theme.js'; | ||
| export function App() { | ||
| const { connected, agentName, pollActivity, chatActivity, input, chatStreaming, chatBuffer, predictionCount, termWidth, setInput, handleChatSubmit, } = useAgent(); | ||
| const { connected, agentName, modelInfo, pollActivity, chatActivity, input, chatStreaming, chatBuffer, predictionCount, termWidth, setInput, handleChatSubmit, } = useAgent(); | ||
| // When stdin is not a TTY (piped by hive-cli start), skip interactive input | ||
| const isInteractive = process.stdin.isTTY === true; | ||
| const boxWidth = termWidth; | ||
| const visiblePollActivity = pollActivity.slice(-10); | ||
| const visibleChatActivity = chatActivity.slice(-3); | ||
| // Split poll items: settled items go into <Static> (rendered once, scrollable), | ||
| // active items render normally (need spinner animation). | ||
| const settledStatuses = new Set(['posted', 'skipped', 'error', undefined]); | ||
| const settledTypes = new Set(['idle', 'online', 'error']); | ||
| const isSettled = (item) => { | ||
| if (settledTypes.has(item.type)) | ||
| return true; | ||
| return settledStatuses.has(item.status) && item.status !== undefined; | ||
| }; | ||
| const settledPollItems = pollActivity.filter(isSettled); | ||
| const activePollItems = pollActivity.filter((item) => !isSettled(item)); | ||
| const visibleChatActivity = chatActivity.slice(-5); | ||
| const statsText = predictionCount > 0 ? ` ${border.horizontal.repeat(3)} ${predictionCount} predicted` : ''; | ||
@@ -22,10 +32,10 @@ const connectedDisplay = connected ? 'Connected to the Hive' : 'connecting...'; | ||
| const headerFill = Math.max(0, boxWidth - nameDisplay.length - connectedDisplay.length - 12 - statsText.length); | ||
| return (_jsxs(Box, { flexDirection: "column", width: boxWidth, children: [_jsx(AsciiTicker, { rows: 2, step: predictionCount }), _jsxs(Box, { children: [_jsx(Text, { color: colors.honey, children: `${border.topLeft}${border.horizontal} ${symbols.hive} ` }), _jsxs(Text, { color: colors.white, bold: true, children: [agentName, " agent"] }), _jsxs(Text, { color: colors.gray, children: [" ", `${border.horizontal.repeat(3)} `] }), _jsx(Text, { color: connected ? colors.green : colors.honey, children: connected ? 'Connected to the Hive' : 'connecting...' }), statsText && _jsxs(Text, { color: colors.gray, children: [" ", `${border.horizontal.repeat(3)} `] }), statsText && _jsxs(Text, { color: colors.honey, children: [predictionCount, " predicted"] }), _jsxs(Text, { color: colors.gray, children: [' ', border.horizontal.repeat(Math.max(0, headerFill)), border.topRight] })] }), _jsxs(Box, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, minHeight: 8, maxHeight: 24, children: [!connected && _jsx(Spinner, { label: "Initiating neural link..." }), visiblePollActivity.map((item, i) => { | ||
| const isNewest = i === visiblePollActivity.length - 1 && item.status !== 'analyzing'; | ||
| return (_jsxs(Box, { flexDirection: "column", width: boxWidth, children: [_jsx(AsciiTicker, { rows: 2, step: predictionCount }), _jsxs(Box, { children: [_jsx(Text, { color: colors.honey, children: `${border.topLeft}${border.horizontal} ${symbols.hive} ` }), _jsxs(Text, { color: colors.white, bold: true, children: [agentName, " agent"] }), _jsxs(Text, { color: colors.gray, children: [" ", `${border.horizontal.repeat(3)} `] }), _jsx(Text, { color: connected ? colors.green : colors.honey, children: connected ? 'Connected to the Hive' : 'connecting...' }), statsText && _jsxs(Text, { color: colors.gray, children: [" ", `${border.horizontal.repeat(3)} `] }), statsText && _jsxs(Text, { color: colors.honey, children: [predictionCount, " predicted"] }), _jsxs(Text, { color: colors.gray, children: [' ', border.horizontal.repeat(Math.max(0, headerFill)), border.topRight] })] }), modelInfo && (_jsxs(Box, { paddingLeft: 1, children: [_jsxs(Text, { color: colors.gray, children: [symbols.hive, " "] }), _jsx(Text, { color: colors.cyan, children: modelInfo.modelId }), _jsxs(Text, { color: colors.gray, children: [" ", '\u00d7', " "] }), _jsx(Text, { color: colors.white, children: "zData" })] })), _jsx(Static, { items: settledPollItems, children: (item, i) => { | ||
| const isMega = item.type === 'megathread'; | ||
| const accentColor = isMega ? colors.controversial : colors.cyan; | ||
| return (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: [item.type === 'online' && (_jsxs(Box, { children: [_jsxs(Text, { color: colors.gray, dimColor: true, children: [formatTime(item.timestamp), ' '] }), _jsxs(Text, { color: colors.honey, children: [symbols.hive, " "] }), _jsx(Text, { color: colors.white, children: item.text })] })), (item.type === 'signal' || item.type === 'megathread') && (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsxs(Text, { color: colors.gray, dimColor: true, children: [formatTime(item.timestamp), ' '] }), _jsxs(Text, { color: isMega ? colors.controversial : colors.honey, children: [symbols.hive, " "] }), _jsx(Text, { color: accentColor, children: item.text }), item.status === 'skipped' && (_jsxs(Text, { color: colors.honey, children: [" ", symbols.diamondOpen, " skipped"] })), item.status === 'skipped' && item.tokenUsage && (_jsxs(Text, { color: colors.gray, dimColor: true, children: [' ', symbols.circle, " ", item.tokenUsage.inputTokens.toLocaleString(), " in", item.tokenUsage.cacheReadTokens > 0 && ` (${item.tokenUsage.cacheReadTokens.toLocaleString()} cached)`, item.tokenUsage.cacheReadTokens === 0 && item.tokenUsage.cacheWriteTokens > 0 && ` (${item.tokenUsage.cacheWriteTokens.toLocaleString()} cache write)`, ' \u00b7 ', item.tokenUsage.outputTokens.toLocaleString(), " out", item.tokenUsage.toolCalls > 0 && ` \u00b7 tools: ${item.tokenUsage.toolNames.join(', ')}`] }))] }), item.status === 'posted' && item.result && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginLeft: 13, children: [_jsxs(Text, { color: isMega ? colors.controversial : convictionColor(item.conviction ?? 0), children: [symbols.diamond, ' '] }), _jsx(Text, { color: colors.white, children: item.result })] }), _jsxs(Box, { flexDirection: "column", marginLeft: 15, children: [item.url && (_jsxs(Text, { color: colors.gray, dimColor: true, children: ["url: ", item.url] })), item.tokenUsage && (_jsxs(Text, { color: colors.gray, dimColor: true, children: ["tokens: ", item.tokenUsage.inputTokens.toLocaleString(), " in", item.tokenUsage.cacheReadTokens > 0 && ` (${item.tokenUsage.cacheReadTokens.toLocaleString()} cached)`, item.tokenUsage.cacheReadTokens === 0 && item.tokenUsage.cacheWriteTokens > 0 && ` (${item.tokenUsage.cacheWriteTokens.toLocaleString()} cache write)`, ' \u00b7 ', item.tokenUsage.outputTokens.toLocaleString(), " out"] })), item.tokenUsage && item.tokenUsage.toolCalls > 0 && (_jsxs(Text, { color: colors.gray, dimColor: true, children: ["tools: ", item.tokenUsage.toolCalls, " tools (", item.tokenUsage.toolNames.join(', '), ")"] }))] })] })), item.status === 'error' && item.result && (_jsx(Box, { marginLeft: 13, children: _jsxs(Text, { color: colors.red, children: [symbols.cross, " ", item.result] }) }))] })), item.type === 'idle' && (_jsxs(Box, { children: [_jsxs(Text, { color: colors.gray, dimColor: true, children: [formatTime(item.timestamp), ' '] }), _jsxs(Text, { color: colors.gray, children: [symbols.circle, " ", item.text] })] })), item.type === 'error' && (_jsxs(Box, { children: [_jsxs(Text, { color: colors.gray, dimColor: true, children: [formatTime(item.timestamp), ' '] }), _jsxs(Text, { color: colors.red, children: [symbols.cross, " ", item.text] })] }))] }, `settled-${i}`)); | ||
| } }), _jsxs(Box, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, minHeight: 2, children: [!connected && _jsx(Spinner, { label: "Initiating neural link..." }), activePollItems.map((item, i) => { | ||
| const isMega = item.type === 'megathread'; | ||
| const accentColor = isMega ? colors.controversial : colors.cyan; | ||
| return (_jsxs(Box, { flexDirection: "column", children: [item.type === 'online' && (_jsxs(Box, { children: [_jsxs(Text, { color: colors.gray, dimColor: true, children: [formatTime(item.timestamp), ' '] }), _jsxs(Text, { color: colors.honey, children: [symbols.hive, " "] }), _jsx(PollText, { color: colors.white, text: item.text, animate: isNewest })] })), (item.type === 'signal' || item.type === 'megathread') && (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsxs(Text, { color: colors.gray, dimColor: true, children: [formatTime(item.timestamp), ' '] }), _jsxs(Text, { color: isMega ? colors.controversial : colors.honey, children: [symbols.hive, " "] }), _jsx(PollText, { color: accentColor, text: item.text, animate: isNewest }), item.status === 'analyzing' && (_jsx(Text, { children: " " })), item.status === 'analyzing' && (_jsx(Spinner, { label: "analyzing..." })), item.status === 'skipped' && (_jsxs(Text, { color: colors.honey, children: [" ", symbols.diamondOpen, " skipped"] })), item.status === 'skipped' && item.tokenUsage && (_jsxs(Text, { color: colors.gray, dimColor: true, children: [' ', symbols.circle, " ", item.tokenUsage.inputTokens.toLocaleString(), " in", item.tokenUsage.cacheReadTokens > 0 && ` (${item.tokenUsage.cacheReadTokens.toLocaleString()} cached)`, item.tokenUsage.cacheReadTokens === 0 && item.tokenUsage.cacheWriteTokens > 0 && ` (${item.tokenUsage.cacheWriteTokens.toLocaleString()} cache write)`, ' \u00b7 ', item.tokenUsage.outputTokens.toLocaleString(), " out", item.tokenUsage.toolCalls > 0 && ` \u00b7 tools: ${item.tokenUsage.toolNames.join(', ')}`] }))] }), item.status === 'analyzing' && item.detail && (_jsx(Box, { marginLeft: 13, children: _jsx(PollText, { color: colors.gray, text: `"${item.detail}"`, animate: false }) })), item.status === 'posted' && item.result && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginLeft: 13, children: [_jsxs(Text, { color: isMega ? colors.controversial : convictionColor(item.conviction ?? 0), children: [symbols.diamond, ' '] }), _jsx(PollText, { color: colors.white, text: item.result, animate: isNewest })] }), item.url && (_jsx(Box, { marginLeft: 15, children: _jsx(Text, { color: colors.gray, dimColor: true, children: item.url }) })), item.tokenUsage && (_jsxs(Box, { flexDirection: "column", marginLeft: 15, children: [_jsxs(Text, { color: colors.gray, dimColor: true, children: ["tokens: ", item.tokenUsage.inputTokens.toLocaleString(), " in", item.tokenUsage.cacheReadTokens > 0 && ` (${item.tokenUsage.cacheReadTokens.toLocaleString()} cached)`, item.tokenUsage.cacheReadTokens === 0 && item.tokenUsage.cacheWriteTokens > 0 && ` (${item.tokenUsage.cacheWriteTokens.toLocaleString()} cache write)`, ' \u00b7 ', item.tokenUsage.outputTokens.toLocaleString(), " out", item.tokenUsage.toolCalls > 0 && ` \u00b7 tools: ${item.tokenUsage.toolNames.join(', ')}`] }), item.tokenUsage.toolResults.map((tr, j) => { | ||
| const preview = tr.result.length > 200 ? tr.result.slice(0, 200) + '\u2026' : tr.result; | ||
| return (_jsxs(Text, { color: colors.gray, dimColor: true, children: [tr.toolName, ": ", preview] }, j)); | ||
| })] }))] })), item.status === 'error' && item.result && (_jsx(Box, { marginLeft: 13, children: _jsx(PollText, { color: colors.red, text: `${symbols.cross} ${item.result}`, animate: isNewest }) }))] })), item.type === 'idle' && (_jsxs(Box, { children: [_jsxs(Text, { color: colors.gray, dimColor: true, children: [formatTime(item.timestamp), ' '] }), _jsx(PollText, { color: colors.gray, text: `${symbols.circle} ${item.text}`, animate: isNewest })] })), item.type === 'error' && (_jsxs(Box, { children: [_jsxs(Text, { color: colors.gray, dimColor: true, children: [formatTime(item.timestamp), ' '] }), _jsx(PollText, { color: colors.red, text: `${symbols.cross} ${item.text}`, animate: isNewest })] }))] }, i)); | ||
| return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Text, { color: colors.gray, dimColor: true, children: [formatTime(item.timestamp), ' '] }), _jsxs(Text, { color: isMega ? colors.controversial : colors.honey, children: [symbols.hive, " "] }), _jsx(PollText, { color: accentColor, text: item.text, animate: false }), _jsx(Text, { children: " " }), _jsx(Spinner, { label: "analyzing..." })] }), item.detail && (_jsx(Box, { marginLeft: 13, children: _jsx(PollText, { color: colors.gray, text: `"${item.detail}"`, animate: false }) }))] }, `active-${item.id ?? i}`)); | ||
| })] }), (chatActivity.length > 0 || chatStreaming) && (_jsxs(_Fragment, { children: [_jsx(Box, { children: _jsxs(Text, { color: colors.gray, children: [border.teeLeft, `${border.horizontal.repeat(2)} chat with ${agentName} agent `, border.horizontal.repeat(Math.max(0, boxWidth - agentName.length - 22)), border.teeRight] }) }), _jsxs(Box, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, minHeight: 2, maxHeight: 8, children: [visibleChatActivity.map((item, i) => (_jsxs(Box, { children: [item.type === 'chat-user' && (_jsxs(Box, { children: [_jsxs(Text, { color: colors.white, bold: true, children: ["you:", ' '] }), _jsx(Text, { color: colors.white, children: item.text })] })), item.type === 'chat-agent' && (_jsxs(Box, { children: [_jsxs(Text, { color: colors.honey, bold: true, children: [agentName, " agent:", ' '] }), _jsx(Text, { color: colors.white, wrap: "wrap", children: item.text })] })), item.type === 'chat-error' && (_jsx(Box, { children: _jsxs(Text, { color: colors.red, children: [symbols.cross, " ", item.text] }) }))] }, i))), chatStreaming && chatBuffer && (_jsxs(Box, { children: [_jsxs(Text, { color: colors.honey, bold: true, children: [agentName, " agent:", ' '] }), _jsx(Text, { color: colors.white, wrap: "wrap", children: chatBuffer })] }))] })] })), _jsx(Box, { children: _jsxs(Text, { color: colors.gray, children: [isInteractive ? border.teeLeft : border.bottomLeft, border.horizontal.repeat(boxWidth - 2), isInteractive ? border.teeRight : border.bottomRight] }) }), isInteractive && (_jsxs(_Fragment, { children: [_jsx(Box, { paddingLeft: 1, children: _jsx(CommandInput, { value: input, onChange: setInput, onSubmit: (val) => { | ||
@@ -32,0 +42,0 @@ setInput(''); |
@@ -12,3 +12,3 @@ import { HiveAgent, loadMemory } from '@hive-org/sdk'; | ||
| import { fetchRulesTool } from '../fetch-rules.js'; | ||
| import { getModel } from '../model.js'; | ||
| import { getModel, resolveModelInfo } from '../model.js'; | ||
| import { getAllTools, getReadSkillTool, getSkillMetadataList, initializeSkills, } from '../tools/index.js'; | ||
@@ -19,2 +19,3 @@ export function useAgent() { | ||
| const [agentBio, setAgentBio] = useState(''); | ||
| const [modelInfo, setModelInfo] = useState(null); | ||
| const [pollActivity, setPollActivity] = useState([]); | ||
@@ -109,2 +110,4 @@ const [chatActivity, setChatActivity] = useState([]); | ||
| strategyContentRef.current = config.strategyContent; | ||
| const resolvedModelInfo = resolveModelInfo(); | ||
| setModelInfo(resolvedModelInfo); | ||
| const skillRegistry = await initializeSkills(process.cwd()); | ||
@@ -394,6 +397,14 @@ const allTools = getAllTools(); | ||
| let fullResponse = ''; | ||
| let lastFlushTime = 0; | ||
| const THROTTLE_MS = 80; | ||
| for await (const chunk of result.textStream) { | ||
| fullResponse += chunk; | ||
| setChatBuffer(fullResponse); | ||
| const now = Date.now(); | ||
| if (now - lastFlushTime >= THROTTLE_MS) { | ||
| setChatBuffer(fullResponse); | ||
| lastFlushTime = now; | ||
| } | ||
| } | ||
| // Always flush the final value | ||
| setChatBuffer(fullResponse); | ||
| // Surface tool results when the model didn't produce follow-up text | ||
@@ -427,2 +438,3 @@ const steps = await result.steps; | ||
| agentBio, | ||
| modelInfo, | ||
| pollActivity, | ||
@@ -429,0 +441,0 @@ chatActivity, |
+39
-10
@@ -0,5 +1,7 @@ | ||
| import { AI_PROVIDERS } from '../ai-providers.js'; | ||
| import { getAgentProviderKeys } from '../load-agent-env.js'; | ||
| const PROVIDERS = [ | ||
| { | ||
| label: 'Anthropic', | ||
| envVar: 'ANTHROPIC_API_KEY', | ||
| defaultModel: 'claude-haiku-4-5', | ||
| load: async (modelId) => { | ||
@@ -11,4 +13,4 @@ const { anthropic } = await import('@ai-sdk/anthropic'); | ||
| { | ||
| label: 'OpenAI', | ||
| envVar: 'OPENAI_API_KEY', | ||
| defaultModel: 'gpt-4o', | ||
| load: async (modelId) => { | ||
@@ -20,4 +22,4 @@ const { openai } = await import('@ai-sdk/openai'); | ||
| { | ||
| label: 'Google', | ||
| envVar: 'GOOGLE_GENERATIVE_AI_API_KEY', | ||
| defaultModel: 'gemini-3-pro-preview', | ||
| load: async (modelId) => { | ||
@@ -29,4 +31,4 @@ const { google } = await import('@ai-sdk/google'); | ||
| { | ||
| label: 'xAI', | ||
| envVar: 'XAI_API_KEY', | ||
| defaultModel: 'grok-4', | ||
| load: async (modelId) => { | ||
@@ -38,4 +40,4 @@ const { xai } = await import('@ai-sdk/xai'); | ||
| { | ||
| label: 'OpenRouter', | ||
| envVar: 'OPENROUTER_API_KEY', | ||
| defaultModel: 'anthropic/claude-3.5-sonnet', | ||
| load: async (modelId) => { | ||
@@ -48,2 +50,21 @@ const { createOpenRouter } = await import('@openrouter/ai-sdk-provider'); | ||
| ]; | ||
| export function resolveModelInfo() { | ||
| const overrideModel = process.env.HIVE_MODEL; | ||
| const agentKeys = getAgentProviderKeys(); | ||
| const sortedProviders = [ | ||
| ...PROVIDERS.filter((p) => agentKeys.has(p.envVar)), | ||
| ...PROVIDERS.filter((p) => !agentKeys.has(p.envVar)), | ||
| ]; | ||
| for (const provider of sortedProviders) { | ||
| const keyValue = process.env[provider.envVar]; | ||
| if (keyValue && keyValue.trim().length > 0) { | ||
| const centralProvider = AI_PROVIDERS.find((p) => p.envVar === provider.envVar); | ||
| const runtimeModel = centralProvider?.models.runtime ?? 'unknown'; | ||
| const modelId = overrideModel ?? runtimeModel; | ||
| const source = agentKeys.has(provider.envVar) ? '.env' : 'shell'; | ||
| return { provider: provider.label, modelId, source }; | ||
| } | ||
| } | ||
| return { provider: 'unknown', modelId: 'unknown', source: 'unknown' }; | ||
| } | ||
| let _modelPromise = null; | ||
@@ -55,7 +76,16 @@ export function getModel() { | ||
| _modelPromise = (async () => { | ||
| const overrideModel = process.env.HIVE_MODEL; | ||
| for (const provider of PROVIDERS) { | ||
| const info = resolveModelInfo(); | ||
| if (info.provider === 'unknown') { | ||
| throw new Error('No AI provider API key found in environment. ' + | ||
| 'Set one of: ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, XAI_API_KEY, OPENROUTER_API_KEY'); | ||
| } | ||
| const agentKeys = getAgentProviderKeys(); | ||
| const sortedProviders = [ | ||
| ...PROVIDERS.filter((p) => agentKeys.has(p.envVar)), | ||
| ...PROVIDERS.filter((p) => !agentKeys.has(p.envVar)), | ||
| ]; | ||
| for (const provider of sortedProviders) { | ||
| const keyValue = process.env[provider.envVar]; | ||
| if (keyValue && keyValue.trim().length > 0) { | ||
| const modelId = overrideModel ?? provider.defaultModel; | ||
| const modelId = info.modelId; | ||
| const model = await provider.load(modelId); | ||
@@ -65,6 +95,5 @@ return model; | ||
| } | ||
| throw new Error('No AI provider API key found in environment. ' + | ||
| 'Set one of: ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, XAI_API_KEY, OPENROUTER_API_KEY'); | ||
| throw new Error('Unreachable: resolveModelInfo succeeded but no provider found'); | ||
| })(); | ||
| return _modelPromise; | ||
| } |
| import chalk from 'chalk'; | ||
| import { symbols } from './theme.js'; | ||
| const restoreScreen = () => { | ||
| process.stdout.write('\x1b[?1049l'); | ||
| }; | ||
| const exitImmediately = (exitCode = 0) => { | ||
| restoreScreen(); | ||
| process.exit(exitCode); | ||
@@ -17,10 +13,7 @@ }; | ||
| }); | ||
| // Use alternate screen buffer (like vim/htop) to prevent text reflow on resize. | ||
| // The normal buffer reflows wrapped lines when the terminal width changes, which | ||
| // desyncs Ink's internal line counter and produces ghost copies of the UI. | ||
| // The alternate buffer is position-based — no reflow, no ghosts. | ||
| process.stdout.write('\x1b[?1049h'); | ||
| process.on('exit', restoreScreen); | ||
| // No alternate screen buffer — normal buffer allows terminal scrollback | ||
| // so users can scroll up to see historical poll activity. | ||
| // <Static> items from Ink flow into the scrollback naturally. | ||
| process.on('SIGINT', () => exitImmediately(0)); | ||
| process.on('SIGTERM', () => exitImmediately(0)); | ||
| } |
| import { HiveAgent } from '@hive-org/sdk'; | ||
| import { loadMemory } from '@hive-org/sdk'; | ||
| import { loadAgentConfig } from './config.js'; | ||
| import { resolveModelInfo } from './model.js'; | ||
| import { HIVE_FRONTEND_URL } from '../config.js'; | ||
@@ -75,3 +76,3 @@ import { processSignalAndSummarize, processMegathreadRound, } from './analysis.js'; | ||
| console.log(` tools: ${toolNames} (${result.usage.toolCalls} calls)`); | ||
| logToolResults(result.usage.toolResults); | ||
| // logToolResults(result.usage.toolResults); | ||
| } | ||
@@ -117,3 +118,3 @@ else { | ||
| console.log(` tools: ${toolNames} (${result.usage.toolCalls} calls)`); | ||
| logToolResults(result.usage.toolResults); | ||
| // logToolResults(result.usage.toolResults); | ||
| } | ||
@@ -159,3 +160,5 @@ else { | ||
| await agent.start(); | ||
| console.log(`[${config.name}] agent online — "${config.bio ?? ''}"`); | ||
| const modelInfoResult = resolveModelInfo(); | ||
| console.log(`[${config.name}] ${modelInfoResult.modelId} \u00d7 zData`); | ||
| console.log(`[${config.name}] agent online \u2014 "${config.bio ?? ''}"`); | ||
| } |
+27
-10
@@ -6,5 +6,4 @@ export const AI_PROVIDERS = [ | ||
| package: '@ai-sdk/openai', | ||
| importLine: "import { openai } from '@ai-sdk/openai';", | ||
| modelCall: "openai('gpt-5.2')", | ||
| envVar: 'OPENAI_API_KEY', | ||
| models: { validation: 'gpt-4o-mini', generation: 'gpt-5-mini', runtime: 'gpt-5-mini' }, | ||
| }, | ||
@@ -15,5 +14,8 @@ { | ||
| package: '@ai-sdk/anthropic', | ||
| importLine: "import { anthropic } from '@ai-sdk/anthropic';", | ||
| modelCall: "anthropic('claude-opus-4-5')", | ||
| envVar: 'ANTHROPIC_API_KEY', | ||
| models: { | ||
| validation: 'claude-haiku-4-5-20251001', | ||
| generation: 'claude-haiku-4-5', | ||
| runtime: 'claude-haiku-4-5', | ||
| }, | ||
| }, | ||
@@ -24,5 +26,8 @@ { | ||
| package: '@ai-sdk/google', | ||
| importLine: "import { google } from '@ai-sdk/google';", | ||
| modelCall: "google('gemini-3-pro-preview')", | ||
| envVar: 'GOOGLE_GENERATIVE_AI_API_KEY', | ||
| models: { | ||
| validation: 'gemini-2.0-flash', | ||
| generation: 'gemini-3-flash-preview', | ||
| runtime: 'gemini-3-flash-preview', | ||
| }, | ||
| }, | ||
@@ -33,5 +38,8 @@ { | ||
| package: '@ai-sdk/xai', | ||
| importLine: "import { xai } from '@ai-sdk/xai';", | ||
| modelCall: "xai('grok-4')", | ||
| envVar: 'XAI_API_KEY', | ||
| models: { | ||
| validation: 'grok-2', | ||
| generation: 'grok-4-1-fast-reasoning', | ||
| runtime: 'grok-4-1-fast-reasoning', | ||
| }, | ||
| }, | ||
@@ -42,7 +50,16 @@ { | ||
| package: '@openrouter/ai-sdk-provider', | ||
| importLine: "import { createOpenRouter } from '@openrouter/ai-sdk-provider';", | ||
| modelCall: "(createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY }).chat('anthropic/claude-3.5-sonnet'))", | ||
| envVar: 'OPENROUTER_API_KEY', | ||
| models: { | ||
| validation: 'openai/gpt-4o-mini', | ||
| generation: 'openai/gpt-5.1-mini', | ||
| runtime: 'openai/gpt-5.1-mini', | ||
| }, | ||
| }, | ||
| ]; | ||
| /** | ||
| * All env-var names used by AI providers. | ||
| * Used to clear shell-inherited keys before loading an agent's .env, | ||
| * so only the agent's chosen provider is active. | ||
| */ | ||
| export const AI_PROVIDER_ENV_VARS = AI_PROVIDERS.map((p) => p.envVar); | ||
| export function getProvider(id) { | ||
@@ -49,0 +66,0 @@ const provider = AI_PROVIDERS.find((p) => p.id === id); |
@@ -7,15 +7,18 @@ import { streamText } from 'ai'; | ||
| import { createOpenRouter } from '@openrouter/ai-sdk-provider'; | ||
| import { getProvider } from '../ai-providers.js'; | ||
| import { SOUL_PRESETS, STRATEGY_PRESETS, buildSoulMarkdown, buildStrategyMarkdown, } from '../presets.js'; | ||
| function buildModel(providerId, apiKey) { | ||
| const provider = getProvider(providerId); | ||
| const modelId = provider.models.generation; | ||
| switch (providerId) { | ||
| case 'openai': | ||
| return createOpenAI({ apiKey })('gpt-4o'); | ||
| return createOpenAI({ apiKey })(modelId); | ||
| case 'anthropic': | ||
| return createAnthropic({ apiKey })('claude-haiku-4-5'); | ||
| return createAnthropic({ apiKey })(modelId); | ||
| case 'google': | ||
| return createGoogleGenerativeAI({ apiKey })('gemini-2.0-flash'); | ||
| return createGoogleGenerativeAI({ apiKey })(modelId); | ||
| case 'xai': | ||
| return createXai({ apiKey })('grok-2'); | ||
| return createXai({ apiKey })(modelId); | ||
| case 'openrouter': | ||
| return createOpenRouter({ apiKey }).chat('anthropic/claude-3.5-sonnet'); | ||
| return createOpenRouter({ apiKey }).chat(modelId); | ||
| } | ||
@@ -22,0 +25,0 @@ } |
@@ -7,2 +7,3 @@ import { generateText } from 'ai'; | ||
| import { createOpenRouter } from '@openrouter/ai-sdk-provider'; | ||
| import { getProvider } from '../ai-providers.js'; | ||
| /** | ||
@@ -33,14 +34,16 @@ * Make a lightweight test call to validate the user's API key. | ||
| function buildTestModel(providerId, apiKey) { | ||
| const provider = getProvider(providerId); | ||
| const modelId = provider.models.validation; | ||
| switch (providerId) { | ||
| case 'openai': | ||
| return createOpenAI({ apiKey })('gpt-4o-mini'); | ||
| return createOpenAI({ apiKey })(modelId); | ||
| case 'anthropic': | ||
| return createAnthropic({ apiKey })('claude-haiku-4-5-20251001'); | ||
| return createAnthropic({ apiKey })(modelId); | ||
| case 'google': | ||
| return createGoogleGenerativeAI({ apiKey })('gemini-2.0-flash'); | ||
| return createGoogleGenerativeAI({ apiKey })(modelId); | ||
| case 'xai': | ||
| return createXai({ apiKey })('grok-2'); | ||
| return createXai({ apiKey })(modelId); | ||
| case 'openrouter': | ||
| return createOpenRouter({ apiKey }).chat('openai/gpt-4o-mini'); | ||
| return createOpenRouter({ apiKey }).chat(modelId); | ||
| } | ||
| } |
+4
-3
@@ -10,2 +10,3 @@ #!/usr/bin/env node | ||
| import { showWelcome } from './create/welcome.js'; | ||
| import { loadAgentEnv } from './load-agent-env.js'; | ||
| const require = createRequire(import.meta.url); | ||
@@ -72,3 +73,3 @@ const pkg = require('../package.json'); | ||
| } | ||
| await import('dotenv/config'); | ||
| await loadAgentEnv(); | ||
| const { runHeadless } = await import('./agent/run-headless.js'); | ||
@@ -87,3 +88,3 @@ await runHeadless(); | ||
| // Direct agent run — cwd is already the agent directory. | ||
| await import('dotenv/config'); | ||
| await loadAgentEnv(); | ||
| const { setupProcessLifecycle } = await import('./agent/process-lifecycle.js'); | ||
@@ -110,3 +111,3 @@ const { App } = await import('./agent/app.js'); | ||
| process.chdir(picked.dir); | ||
| await import('dotenv/config'); | ||
| await loadAgentEnv(); | ||
| const { setupProcessLifecycle } = await import('./agent/process-lifecycle.js'); | ||
@@ -113,0 +114,0 @@ const { App } = await import('./agent/app.js'); |
+1
-1
| { | ||
| "name": "@hive-org/cli", | ||
| "version": "0.1.8", | ||
| "version": "0.1.9", | ||
| "description": "CLI for bootstrapping Hive AI Agents", | ||
@@ -5,0 +5,0 @@ "type": "module", |
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
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
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
414127
1.05%93
1.09%8477
1.23%23
9.52%