@zhive/cli 0.5.0
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/README.md +118 -0
- package/dist/agent/analysis.js +160 -0
- package/dist/agent/app.js +122 -0
- package/dist/agent/chat-prompt.js +65 -0
- package/dist/agent/commands/registry.js +12 -0
- package/dist/agent/components/AsciiTicker.js +81 -0
- package/dist/agent/components/CommandInput.js +65 -0
- package/dist/agent/components/HoneycombBoot.js +291 -0
- package/dist/agent/components/Spinner.js +37 -0
- package/dist/agent/config.js +75 -0
- package/dist/agent/edit-section.js +59 -0
- package/dist/agent/fetch-rules.js +21 -0
- package/dist/agent/helpers.js +22 -0
- package/dist/agent/hooks/useAgent.js +480 -0
- package/dist/agent/memory-prompt.js +47 -0
- package/dist/agent/model.js +92 -0
- package/dist/agent/objects.js +1 -0
- package/dist/agent/process-lifecycle.js +18 -0
- package/dist/agent/prompt.js +353 -0
- package/dist/agent/run-headless.js +189 -0
- package/dist/agent/skills/index.js +2 -0
- package/dist/agent/skills/skill-parser.js +149 -0
- package/dist/agent/skills/types.js +1 -0
- package/dist/agent/theme.js +41 -0
- package/dist/agent/tools/index.js +76 -0
- package/dist/agent/tools/market/client.js +41 -0
- package/dist/agent/tools/market/index.js +3 -0
- package/dist/agent/tools/market/tools.js +518 -0
- package/dist/agent/tools/mindshare/client.js +124 -0
- package/dist/agent/tools/mindshare/index.js +3 -0
- package/dist/agent/tools/mindshare/tools.js +563 -0
- package/dist/agent/tools/read-skill-tool.js +30 -0
- package/dist/agent/tools/ta/index.js +1 -0
- package/dist/agent/tools/ta/indicators.js +201 -0
- package/dist/agent/types.js +1 -0
- package/dist/agents.js +110 -0
- package/dist/ai-providers.js +66 -0
- package/dist/avatar.js +34 -0
- package/dist/backtest/default-backtest-data.js +200 -0
- package/dist/backtest/fetch.js +41 -0
- package/dist/backtest/import.js +106 -0
- package/dist/backtest/index.js +10 -0
- package/dist/backtest/results.js +113 -0
- package/dist/backtest/runner.js +134 -0
- package/dist/backtest/storage.js +11 -0
- package/dist/backtest/types.js +1 -0
- package/dist/commands/create/ai-generate.js +126 -0
- package/dist/commands/create/commands/index.js +10 -0
- package/dist/commands/create/generate.js +73 -0
- package/dist/commands/create/presets/data.js +225 -0
- package/dist/commands/create/presets/formatting.js +81 -0
- package/dist/commands/create/presets/index.js +3 -0
- package/dist/commands/create/presets/options.js +307 -0
- package/dist/commands/create/presets/types.js +1 -0
- package/dist/commands/create/presets.js +613 -0
- package/dist/commands/create/ui/CreateApp.js +172 -0
- package/dist/commands/create/ui/steps/ApiKeyStep.js +89 -0
- package/dist/commands/create/ui/steps/AvatarStep.js +16 -0
- package/dist/commands/create/ui/steps/DoneStep.js +14 -0
- package/dist/commands/create/ui/steps/IdentityStep.js +125 -0
- package/dist/commands/create/ui/steps/NameStep.js +148 -0
- package/dist/commands/create/ui/steps/ScaffoldStep.js +59 -0
- package/dist/commands/create/ui/steps/SoulStep.js +21 -0
- package/dist/commands/create/ui/steps/StrategyStep.js +20 -0
- package/dist/commands/create/ui/steps/StreamingGenerationStep.js +56 -0
- package/dist/commands/create/ui/validation.js +34 -0
- package/dist/commands/create/validate-api-key.js +27 -0
- package/dist/commands/install.js +50 -0
- package/dist/commands/list/commands/index.js +7 -0
- package/dist/commands/list/ui/ListApp.js +79 -0
- package/dist/commands/migrate-templates/commands/index.js +9 -0
- package/dist/commands/migrate-templates/migrate.js +87 -0
- package/dist/commands/migrate-templates/ui/MigrateApp.js +132 -0
- package/dist/commands/run/commands/index.js +17 -0
- package/dist/commands/run/run-headless.js +111 -0
- package/dist/commands/shared/theme.js +57 -0
- package/dist/commands/shared/welcome.js +304 -0
- package/dist/commands/start/commands/backtest.js +35 -0
- package/dist/commands/start/commands/index.js +62 -0
- package/dist/commands/start/commands/prediction.js +73 -0
- package/dist/commands/start/commands/skills.js +44 -0
- package/dist/commands/start/commands/skills.test.js +140 -0
- package/dist/commands/start/hooks/types.js +1 -0
- package/dist/commands/start/hooks/useAgent.js +177 -0
- package/dist/commands/start/hooks/useChat.js +266 -0
- package/dist/commands/start/hooks/usePollActivity.js +45 -0
- package/dist/commands/start/hooks/utils.js +152 -0
- package/dist/commands/start/services/backtest/default-backtest-data.js +200 -0
- package/dist/commands/start/services/backtest/fetch.js +42 -0
- package/dist/commands/start/services/backtest/import.js +109 -0
- package/dist/commands/start/services/backtest/index.js +10 -0
- package/dist/commands/start/services/backtest/results.js +113 -0
- package/dist/commands/start/services/backtest/runner.js +103 -0
- package/dist/commands/start/services/backtest/storage.js +11 -0
- package/dist/commands/start/services/backtest/types.js +1 -0
- package/dist/commands/start/services/command-registry.js +13 -0
- package/dist/commands/start/ui/AsciiTicker.js +81 -0
- package/dist/commands/start/ui/CommandInput.js +65 -0
- package/dist/commands/start/ui/HoneycombBoot.js +291 -0
- package/dist/commands/start/ui/PollText.js +23 -0
- package/dist/commands/start/ui/PredictionsPanel.js +88 -0
- package/dist/commands/start/ui/SelectAgentApp.js +93 -0
- package/dist/commands/start/ui/Spinner.js +29 -0
- package/dist/commands/start/ui/SpinnerContext.js +20 -0
- package/dist/commands/start/ui/app.js +36 -0
- package/dist/commands/start-all/AgentProcessManager.js +98 -0
- package/dist/commands/start-all/commands/index.js +24 -0
- package/dist/commands/start-all/ui/Dashboard.js +91 -0
- package/dist/components/AsciiTicker.js +81 -0
- package/dist/components/CharacterSummaryCard.js +33 -0
- package/dist/components/CodeBlock.js +11 -0
- package/dist/components/ColoredStats.js +18 -0
- package/dist/components/Header.js +10 -0
- package/dist/components/HoneycombLoader.js +190 -0
- package/dist/components/InputGuard.js +6 -0
- package/dist/components/MultiSelectPrompt.js +45 -0
- package/dist/components/SelectPrompt.js +20 -0
- package/dist/components/Spinner.js +16 -0
- package/dist/components/StepIndicator.js +31 -0
- package/dist/components/StreamingText.js +50 -0
- package/dist/components/TextPrompt.js +28 -0
- package/dist/components/stdout-spinner.js +48 -0
- package/dist/config.js +28 -0
- package/dist/create/CreateApp.js +153 -0
- package/dist/create/ai-generate.js +147 -0
- package/dist/create/generate.js +73 -0
- package/dist/create/steps/ApiKeyStep.js +97 -0
- package/dist/create/steps/AvatarStep.js +16 -0
- package/dist/create/steps/BioStep.js +14 -0
- package/dist/create/steps/DoneStep.js +14 -0
- package/dist/create/steps/IdentityStep.js +163 -0
- package/dist/create/steps/NameStep.js +71 -0
- package/dist/create/steps/ScaffoldStep.js +58 -0
- package/dist/create/steps/SoulStep.js +58 -0
- package/dist/create/steps/StrategyStep.js +58 -0
- package/dist/create/validate-api-key.js +47 -0
- package/dist/create/welcome.js +304 -0
- package/dist/index.js +60 -0
- package/dist/list/ListApp.js +79 -0
- package/dist/load-agent-env.js +30 -0
- package/dist/migrate-templates/MigrateApp.js +131 -0
- package/dist/migrate-templates/migrate.js +86 -0
- package/dist/presets.js +613 -0
- package/dist/shared/agent/agent-runtime.js +144 -0
- package/dist/shared/agent/analysis.js +171 -0
- package/dist/shared/agent/helpers.js +1 -0
- package/dist/shared/agent/prompts/chat-prompt.js +60 -0
- package/dist/shared/agent/prompts/megathread.js +202 -0
- package/dist/shared/agent/prompts/memory-prompt.js +47 -0
- package/dist/shared/agent/prompts/prompt.js +18 -0
- package/dist/shared/agent/skills/index.js +2 -0
- package/dist/shared/agent/skills/skill-parser.js +167 -0
- package/dist/shared/agent/skills/skill-parser.test.js +190 -0
- package/dist/shared/agent/skills/types.js +1 -0
- package/dist/shared/agent/tools/edit-section.js +60 -0
- package/dist/shared/agent/tools/execute-skill-tool.js +134 -0
- package/dist/shared/agent/tools/fetch-rules.js +22 -0
- package/dist/shared/agent/tools/formatting.js +48 -0
- package/dist/shared/agent/tools/index.js +87 -0
- package/dist/shared/agent/tools/market/client.js +41 -0
- package/dist/shared/agent/tools/market/index.js +3 -0
- package/dist/shared/agent/tools/market/tools.js +497 -0
- package/dist/shared/agent/tools/mindshare/client.js +124 -0
- package/dist/shared/agent/tools/mindshare/index.js +3 -0
- package/dist/shared/agent/tools/mindshare/tools.js +167 -0
- package/dist/shared/agent/tools/read-skill-tool.js +30 -0
- package/dist/shared/agent/tools/ta/index.js +1 -0
- package/dist/shared/agent/tools/ta/indicators.js +201 -0
- package/dist/shared/agent/types.js +1 -0
- package/dist/shared/agent/utils.js +43 -0
- package/dist/shared/config/agent.js +177 -0
- package/dist/shared/config/ai-providers.js +156 -0
- package/dist/shared/config/config.js +22 -0
- package/dist/shared/config/constant.js +8 -0
- package/dist/shared/config/env-loader.js +30 -0
- package/dist/shared/types.js +1 -0
- package/dist/start/AgentProcessManager.js +98 -0
- package/dist/start/Dashboard.js +92 -0
- package/dist/start/SelectAgentApp.js +81 -0
- package/dist/start/StartApp.js +189 -0
- package/dist/start/patch-headless.js +101 -0
- package/dist/start/patch-managed-mode.js +142 -0
- package/dist/start/start-command.js +24 -0
- package/dist/theme.js +54 -0
- package/package.json +68 -0
- package/templates/components/HoneycombBoot.tsx +343 -0
- package/templates/fetch-rules.ts +23 -0
- package/templates/skills/mindshare/SKILL.md +197 -0
- package/templates/skills/ta/SKILL.md +179 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import fsExtra from 'fs-extra';
|
|
3
|
+
import * as fs from 'fs/promises';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import path, { join } from 'path';
|
|
6
|
+
import { HIVE_API_URL } from './constant.js';
|
|
7
|
+
import { AI_PROVIDERS } from './ai-providers.js';
|
|
8
|
+
const VALID_SENTIMENTS = [
|
|
9
|
+
'very-bullish',
|
|
10
|
+
'bullish',
|
|
11
|
+
'neutral',
|
|
12
|
+
'bearish',
|
|
13
|
+
'very-bearish',
|
|
14
|
+
];
|
|
15
|
+
const VALID_TIMEFRAMES = ['1h', '4h', '24h'];
|
|
16
|
+
function extractField(content, pattern) {
|
|
17
|
+
const match = content.match(pattern);
|
|
18
|
+
if (match === null) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const value = match[1].trim();
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
async function detectProvider(agentDir) {
|
|
25
|
+
// Try old-style detection: check package.json dependencies
|
|
26
|
+
const pkgPath = path.join(agentDir, 'package.json');
|
|
27
|
+
const pkgExists = await fsExtra.pathExists(pkgPath);
|
|
28
|
+
if (pkgExists) {
|
|
29
|
+
const pkg = await fsExtra.readJson(pkgPath);
|
|
30
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
31
|
+
for (const provider of AI_PROVIDERS) {
|
|
32
|
+
if (deps[provider.package]) {
|
|
33
|
+
return provider.label;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// New-style detection: check .env for provider API keys
|
|
38
|
+
const envPath = path.join(agentDir, '.env');
|
|
39
|
+
const envExists = await fsExtra.pathExists(envPath);
|
|
40
|
+
if (envExists) {
|
|
41
|
+
const envContent = await fs.readFile(envPath, 'utf-8');
|
|
42
|
+
for (const provider of AI_PROVIDERS) {
|
|
43
|
+
const pattern = new RegExp(`^${provider.envVar}=.+`, 'm');
|
|
44
|
+
if (pattern.test(envContent)) {
|
|
45
|
+
return provider.label;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return 'unknown';
|
|
50
|
+
}
|
|
51
|
+
export async function loadAgentConfig(_agentDir) {
|
|
52
|
+
const agentDir = _agentDir ?? process.cwd();
|
|
53
|
+
const soulPath = join(agentDir, 'SOUL.md');
|
|
54
|
+
const strategyPath = join(agentDir, 'STRATEGY.md');
|
|
55
|
+
const soulContent = await loadMarkdownFile(soulPath);
|
|
56
|
+
const strategyContent = await loadMarkdownFile(strategyPath);
|
|
57
|
+
const name = extractField(soulContent, /^#\s+Agent:\s+(.+)$/m);
|
|
58
|
+
if (name === null) {
|
|
59
|
+
throw new Error('Could not parse agent name from SOUL.md. Expected "# Agent: <name>" as the first heading.');
|
|
60
|
+
}
|
|
61
|
+
const avatarUrl = extractField(soulContent, /^## Avatar\s*\n+(https?:\/\/.+)$/m);
|
|
62
|
+
if (avatarUrl === null) {
|
|
63
|
+
throw new Error('Could not parse avatar URL from SOUL.md. Expected a valid URL under "## Avatar".');
|
|
64
|
+
}
|
|
65
|
+
const bioRaw = extractField(soulContent, /^## Bio\s*\n+(.+)$/m);
|
|
66
|
+
const bio = bioRaw ?? null;
|
|
67
|
+
const sentimentRaw = extractField(strategyContent, /^-\s+Bias:\s+(.+)$/m);
|
|
68
|
+
const sectorsRaw = extractField(strategyContent, /^-\s+Sectors:\s+(.+)$/m);
|
|
69
|
+
const timeframesRaw = extractField(strategyContent, /^-\s+Active timeframes:\s+(.+)$/m);
|
|
70
|
+
const stat = await fs.stat(soulPath);
|
|
71
|
+
const provider = await detectProvider(agentDir);
|
|
72
|
+
const agentProfile = {
|
|
73
|
+
sentiment: parseSentiment(sentimentRaw),
|
|
74
|
+
sectors: parseSectors(sectorsRaw),
|
|
75
|
+
timeframes: parseTimeframes(timeframesRaw),
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
name,
|
|
79
|
+
bio,
|
|
80
|
+
dir: agentDir,
|
|
81
|
+
provider,
|
|
82
|
+
avatarUrl,
|
|
83
|
+
soulContent,
|
|
84
|
+
strategyContent,
|
|
85
|
+
agentProfile,
|
|
86
|
+
created: stat.birthtime,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export async function scanAgents() {
|
|
90
|
+
const agentsDir = path.join(os.homedir(), '.hive', 'agents');
|
|
91
|
+
const exists = await fsExtra.pathExists(agentsDir);
|
|
92
|
+
if (!exists) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
const entries = await fsExtra.readdir(agentsDir, { withFileTypes: true });
|
|
96
|
+
const agents = [];
|
|
97
|
+
for (const entry of entries) {
|
|
98
|
+
if (!entry.isDirectory()) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const dir = path.join(agentsDir, entry.name);
|
|
102
|
+
const config = await loadAgentConfig(dir).catch((err) => null);
|
|
103
|
+
if (!config) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
agents.push(config);
|
|
107
|
+
}
|
|
108
|
+
return agents;
|
|
109
|
+
}
|
|
110
|
+
export function sortByHoney(rows) {
|
|
111
|
+
const sorted = [...rows].sort((a, b) => (b.stats?.honey ?? 0) - (a.stats?.honey ?? 0));
|
|
112
|
+
return sorted;
|
|
113
|
+
}
|
|
114
|
+
export function sortAgentsByHoney(agents, statsMap) {
|
|
115
|
+
const sorted = [...agents].sort((a, b) => {
|
|
116
|
+
const honeyA = statsMap.get(a.name)?.honey ?? 0;
|
|
117
|
+
const honeyB = statsMap.get(b.name)?.honey ?? 0;
|
|
118
|
+
return honeyB - honeyA;
|
|
119
|
+
});
|
|
120
|
+
return sorted;
|
|
121
|
+
}
|
|
122
|
+
export async function fetchBulkStats(names) {
|
|
123
|
+
const statsMap = new Map();
|
|
124
|
+
if (names.length === 0) {
|
|
125
|
+
return statsMap;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const response = await axios.post(`${HIVE_API_URL}/agent/by-names`, { names });
|
|
129
|
+
for (const agent of response.data) {
|
|
130
|
+
statsMap.set(agent.name, {
|
|
131
|
+
honey: agent.honey ?? 0,
|
|
132
|
+
wax: agent.wax ?? 0,
|
|
133
|
+
win_rate: agent.win_rate ?? 0,
|
|
134
|
+
confidence: agent.confidence ?? 0,
|
|
135
|
+
simulated_pnl: agent.simulated_pnl ?? 0,
|
|
136
|
+
total_comments: agent.total_comments ?? 0,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// API unreachable — return empty map, CLI will show dashes
|
|
142
|
+
}
|
|
143
|
+
return statsMap;
|
|
144
|
+
}
|
|
145
|
+
async function loadMarkdownFile(filePath) {
|
|
146
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
147
|
+
return content;
|
|
148
|
+
}
|
|
149
|
+
function parseSentiment(raw) {
|
|
150
|
+
if (raw !== null && VALID_SENTIMENTS.includes(raw)) {
|
|
151
|
+
return raw;
|
|
152
|
+
}
|
|
153
|
+
return 'neutral';
|
|
154
|
+
}
|
|
155
|
+
function parseSectors(raw) {
|
|
156
|
+
if (raw === null || raw.trim() === '') {
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
const sectors = raw
|
|
160
|
+
.split(',')
|
|
161
|
+
.map((s) => s.trim())
|
|
162
|
+
.filter((s) => s.length > 0);
|
|
163
|
+
return sectors;
|
|
164
|
+
}
|
|
165
|
+
function parseTimeframes(raw) {
|
|
166
|
+
if (raw === null || raw.trim() === '') {
|
|
167
|
+
return ['1h', '4h', '24h'];
|
|
168
|
+
}
|
|
169
|
+
const parsed = raw
|
|
170
|
+
.split(',')
|
|
171
|
+
.map((t) => t.trim())
|
|
172
|
+
.filter((t) => VALID_TIMEFRAMES.includes(t));
|
|
173
|
+
if (parsed.length === 0) {
|
|
174
|
+
return ['1h', '4h', '24h'];
|
|
175
|
+
}
|
|
176
|
+
return parsed;
|
|
177
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { createOpenAI } from '@ai-sdk/openai';
|
|
2
|
+
import { createAnthropic } from '@ai-sdk/anthropic';
|
|
3
|
+
import { createGoogleGenerativeAI } from '@ai-sdk/google';
|
|
4
|
+
import { createXai } from '@ai-sdk/xai';
|
|
5
|
+
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
|
6
|
+
import { getAgentProviderKeys } from './env-loader.js';
|
|
7
|
+
let _modelPromise = null;
|
|
8
|
+
export const AI_PROVIDERS = [
|
|
9
|
+
{
|
|
10
|
+
id: 'openai',
|
|
11
|
+
label: 'OpenAI',
|
|
12
|
+
package: '@ai-sdk/openai',
|
|
13
|
+
envVar: 'OPENAI_API_KEY',
|
|
14
|
+
models: { validation: 'gpt-4o-mini', generation: 'gpt-5-mini', runtime: 'gpt-5-mini' },
|
|
15
|
+
load: async (modelId) => {
|
|
16
|
+
const { openai } = await import('@ai-sdk/openai');
|
|
17
|
+
return openai(modelId);
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'anthropic',
|
|
22
|
+
label: 'Anthropic',
|
|
23
|
+
package: '@ai-sdk/anthropic',
|
|
24
|
+
envVar: 'ANTHROPIC_API_KEY',
|
|
25
|
+
models: {
|
|
26
|
+
validation: 'claude-haiku-4-5-20251001',
|
|
27
|
+
generation: 'claude-haiku-4-5',
|
|
28
|
+
runtime: 'claude-haiku-4-5',
|
|
29
|
+
},
|
|
30
|
+
load: async (modelId) => {
|
|
31
|
+
const { anthropic } = await import('@ai-sdk/anthropic');
|
|
32
|
+
return anthropic(modelId);
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'google',
|
|
37
|
+
label: 'Google',
|
|
38
|
+
package: '@ai-sdk/google',
|
|
39
|
+
envVar: 'GOOGLE_GENERATIVE_AI_API_KEY',
|
|
40
|
+
models: {
|
|
41
|
+
validation: 'gemini-2.0-flash',
|
|
42
|
+
generation: 'gemini-3-flash-preview',
|
|
43
|
+
runtime: 'gemini-3-flash-preview',
|
|
44
|
+
},
|
|
45
|
+
load: async (modelId) => {
|
|
46
|
+
const { google } = await import('@ai-sdk/google');
|
|
47
|
+
return google(modelId);
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'xai',
|
|
52
|
+
label: 'xAI',
|
|
53
|
+
package: '@ai-sdk/xai',
|
|
54
|
+
envVar: 'XAI_API_KEY',
|
|
55
|
+
models: {
|
|
56
|
+
validation: 'grok-2',
|
|
57
|
+
generation: 'grok-4-1-fast-reasoning',
|
|
58
|
+
runtime: 'grok-4-1-fast-reasoning',
|
|
59
|
+
},
|
|
60
|
+
load: async (modelId) => {
|
|
61
|
+
const { xai } = await import('@ai-sdk/xai');
|
|
62
|
+
return xai(modelId);
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'openrouter',
|
|
67
|
+
label: 'OpenRouter',
|
|
68
|
+
package: '@openrouter/ai-sdk-provider',
|
|
69
|
+
envVar: 'OPENROUTER_API_KEY',
|
|
70
|
+
models: {
|
|
71
|
+
validation: 'openai/gpt-4o-mini',
|
|
72
|
+
generation: 'openai/gpt-5.1-mini',
|
|
73
|
+
runtime: 'openai/gpt-5.1-mini',
|
|
74
|
+
},
|
|
75
|
+
load: async (modelId) => {
|
|
76
|
+
const { createOpenRouter } = await import('@openrouter/ai-sdk-provider');
|
|
77
|
+
const openrouter = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });
|
|
78
|
+
return openrouter.chat(modelId);
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
/**
|
|
83
|
+
* All env-var names used by AI providers.
|
|
84
|
+
* Used to clear shell-inherited keys before loading an agent's .env,
|
|
85
|
+
* so only the agent's chosen provider is active.
|
|
86
|
+
*/
|
|
87
|
+
export const AI_PROVIDER_ENV_VARS = AI_PROVIDERS.map((p) => p.envVar);
|
|
88
|
+
export function buildLanguageModel(providerId, apiKey, modelType) {
|
|
89
|
+
const provider = getProvider(providerId);
|
|
90
|
+
const modelId = provider.models[modelType];
|
|
91
|
+
switch (providerId) {
|
|
92
|
+
case 'openai':
|
|
93
|
+
return createOpenAI({ apiKey })(modelId);
|
|
94
|
+
case 'anthropic':
|
|
95
|
+
return createAnthropic({ apiKey })(modelId);
|
|
96
|
+
case 'google':
|
|
97
|
+
return createGoogleGenerativeAI({ apiKey })(modelId);
|
|
98
|
+
case 'xai':
|
|
99
|
+
return createXai({ apiKey })(modelId);
|
|
100
|
+
case 'openrouter':
|
|
101
|
+
return createOpenRouter({ apiKey }).chat(modelId);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export function getProvider(id) {
|
|
105
|
+
const provider = AI_PROVIDERS.find((p) => p.id === id);
|
|
106
|
+
if (!provider) {
|
|
107
|
+
throw new Error(`Unknown AI provider: ${id}`);
|
|
108
|
+
}
|
|
109
|
+
return provider;
|
|
110
|
+
}
|
|
111
|
+
export function resolveModelInfo() {
|
|
112
|
+
const overrideModel = process.env.HIVE_MODEL;
|
|
113
|
+
const agentKeys = getAgentProviderKeys();
|
|
114
|
+
const sortedProviders = [
|
|
115
|
+
...AI_PROVIDERS.filter((p) => agentKeys.has(p.envVar)),
|
|
116
|
+
...AI_PROVIDERS.filter((p) => !agentKeys.has(p.envVar)),
|
|
117
|
+
];
|
|
118
|
+
for (const provider of sortedProviders) {
|
|
119
|
+
const keyValue = process.env[provider.envVar];
|
|
120
|
+
if (keyValue && keyValue.trim().length > 0) {
|
|
121
|
+
const centralProvider = AI_PROVIDERS.find((p) => p.envVar === provider.envVar);
|
|
122
|
+
const runtimeModel = centralProvider?.models.runtime ?? 'unknown';
|
|
123
|
+
const modelId = overrideModel ?? runtimeModel;
|
|
124
|
+
const source = agentKeys.has(provider.envVar) ? '.env' : 'shell';
|
|
125
|
+
return { provider: provider.label, modelId, source };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return { provider: 'unknown', modelId: 'unknown', source: 'unknown' };
|
|
129
|
+
}
|
|
130
|
+
export function getModel() {
|
|
131
|
+
if (_modelPromise) {
|
|
132
|
+
return _modelPromise;
|
|
133
|
+
}
|
|
134
|
+
_modelPromise = (async () => {
|
|
135
|
+
const info = resolveModelInfo();
|
|
136
|
+
if (info.provider === 'unknown') {
|
|
137
|
+
throw new Error('No AI provider API key found in environment. ' +
|
|
138
|
+
'Set one of: ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, XAI_API_KEY, OPENROUTER_API_KEY');
|
|
139
|
+
}
|
|
140
|
+
const agentKeys = getAgentProviderKeys();
|
|
141
|
+
const sortedProviders = [
|
|
142
|
+
...AI_PROVIDERS.filter((p) => agentKeys.has(p.envVar)),
|
|
143
|
+
...AI_PROVIDERS.filter((p) => !agentKeys.has(p.envVar)),
|
|
144
|
+
];
|
|
145
|
+
for (const provider of sortedProviders) {
|
|
146
|
+
const keyValue = process.env[provider.envVar];
|
|
147
|
+
if (keyValue && keyValue.trim().length > 0) {
|
|
148
|
+
const modelId = info.modelId;
|
|
149
|
+
const model = await provider.load(modelId);
|
|
150
|
+
return model;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
throw new Error('Unreachable: resolveModelInfo succeeded but no provider found');
|
|
154
|
+
})();
|
|
155
|
+
return _modelPromise;
|
|
156
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getHiveDir } from './constant.js';
|
|
4
|
+
export async function readConfig() {
|
|
5
|
+
try {
|
|
6
|
+
const configPath = path.join(getHiveDir(), 'config.json');
|
|
7
|
+
const config = (await fs.readJson(configPath));
|
|
8
|
+
if (!config.providerId || !config.apiKey) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
return config;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function writeConfig(config) {
|
|
18
|
+
const hiveDir = getHiveDir();
|
|
19
|
+
await fs.ensureDir(hiveDir);
|
|
20
|
+
const configPath = path.join(hiveDir, 'config.json');
|
|
21
|
+
await fs.writeJson(configPath, config, { spaces: 2, mode: 0o600 });
|
|
22
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
export const HIVE_API_URL = 'https://api.zhive.ai';
|
|
4
|
+
export const HIVE_FRONTEND_URL = 'https://www.zhive.ai';
|
|
5
|
+
export function getHiveDir() {
|
|
6
|
+
const hiveDir = path.join(os.homedir(), '.hive');
|
|
7
|
+
return hiveDir;
|
|
8
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { AI_PROVIDER_ENV_VARS } from './ai-providers.js';
|
|
3
|
+
let _agentProviderKeys = new Set();
|
|
4
|
+
/**
|
|
5
|
+
* Provider env-var names declared in the agent's .env file.
|
|
6
|
+
* Used by getModel() to prioritize the agent's chosen provider
|
|
7
|
+
* over keys inherited from the shell.
|
|
8
|
+
*/
|
|
9
|
+
export function getAgentProviderKeys() {
|
|
10
|
+
return _agentProviderKeys;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Load the agent's .env with provider-key priority.
|
|
14
|
+
*
|
|
15
|
+
* 1. Parse .env to discover which provider keys the agent declared.
|
|
16
|
+
* 2. Load .env with override so the agent's values win for the same key.
|
|
17
|
+
* 3. getModel() uses getAgentProviderKeys() to check those providers first,
|
|
18
|
+
* falling back to shell-inherited keys if the agent has none.
|
|
19
|
+
*/
|
|
20
|
+
export async function loadAgentEnv() {
|
|
21
|
+
try {
|
|
22
|
+
const content = readFileSync('.env', 'utf-8');
|
|
23
|
+
_agentProviderKeys = new Set(AI_PROVIDER_ENV_VARS.filter((key) => new RegExp(`^${key}=`, 'm').test(content)));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
_agentProviderKeys = new Set();
|
|
27
|
+
}
|
|
28
|
+
const { config } = await import('dotenv');
|
|
29
|
+
config({ override: true });
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
const FORCE_KILL_TIMEOUT_MS = 5_000;
|
|
5
|
+
const ABSOLUTE_TIMEOUT_MS = FORCE_KILL_TIMEOUT_MS + 2_000;
|
|
6
|
+
export class AgentProcessManager {
|
|
7
|
+
constructor() {
|
|
8
|
+
this._agents = new Map();
|
|
9
|
+
this._agentsDir = path.join(os.homedir(), '.hive', 'agents');
|
|
10
|
+
}
|
|
11
|
+
spawnAll(discovered) {
|
|
12
|
+
for (const agent of discovered) {
|
|
13
|
+
this._agents.set(agent.name, {
|
|
14
|
+
name: agent.name,
|
|
15
|
+
status: 'spawning',
|
|
16
|
+
exitCode: null,
|
|
17
|
+
child: null,
|
|
18
|
+
});
|
|
19
|
+
this._spawnPiped(agent.name);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
getStates() {
|
|
23
|
+
const states = [];
|
|
24
|
+
for (const agent of this._agents.values()) {
|
|
25
|
+
states.push({
|
|
26
|
+
name: agent.name,
|
|
27
|
+
status: agent.status,
|
|
28
|
+
exitCode: agent.exitCode,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return states;
|
|
32
|
+
}
|
|
33
|
+
async stopAgent(name) {
|
|
34
|
+
const agent = this._agents.get(name);
|
|
35
|
+
if (!agent?.child) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const child = agent.child;
|
|
39
|
+
const exitPromise = new Promise((resolve) => {
|
|
40
|
+
if (child.exitCode !== null) {
|
|
41
|
+
resolve();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
child.on('exit', () => resolve());
|
|
45
|
+
// Absolute safety timeout in case SIGKILL is not enough
|
|
46
|
+
setTimeout(() => resolve(), ABSOLUTE_TIMEOUT_MS);
|
|
47
|
+
});
|
|
48
|
+
child.kill('SIGTERM');
|
|
49
|
+
const forceKillTimer = setTimeout(() => {
|
|
50
|
+
child.kill('SIGKILL');
|
|
51
|
+
}, FORCE_KILL_TIMEOUT_MS);
|
|
52
|
+
await exitPromise;
|
|
53
|
+
clearTimeout(forceKillTimer);
|
|
54
|
+
agent.child = null;
|
|
55
|
+
}
|
|
56
|
+
respawnPiped(name) {
|
|
57
|
+
const agent = this._agents.get(name);
|
|
58
|
+
if (!agent) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
agent.status = 'spawning';
|
|
62
|
+
agent.exitCode = null;
|
|
63
|
+
agent.child = null;
|
|
64
|
+
this._spawnPiped(name);
|
|
65
|
+
}
|
|
66
|
+
_spawnPiped(name) {
|
|
67
|
+
const agentDir = path.join(this._agentsDir, name);
|
|
68
|
+
const child = spawn('npx', ['@hive-org/cli@latest', 'run'], {
|
|
69
|
+
cwd: agentDir,
|
|
70
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
71
|
+
});
|
|
72
|
+
const agent = this._agents.get(name);
|
|
73
|
+
if (!agent) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
agent.child = child;
|
|
77
|
+
// Transition to 'running' once the OS has spawned the process
|
|
78
|
+
child.on('spawn', () => {
|
|
79
|
+
if (agent.status === 'spawning') {
|
|
80
|
+
agent.status = 'running';
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
// Drain stdout/stderr to prevent buffer blocking
|
|
84
|
+
child.stdout?.resume();
|
|
85
|
+
child.stderr?.resume();
|
|
86
|
+
child.on('error', () => {
|
|
87
|
+
agent.status = 'errored';
|
|
88
|
+
agent.exitCode = null;
|
|
89
|
+
agent.child = null;
|
|
90
|
+
});
|
|
91
|
+
child.on('exit', (code) => {
|
|
92
|
+
const exitCode = code ?? 1;
|
|
93
|
+
agent.status = exitCode === 0 ? 'exited' : 'errored';
|
|
94
|
+
agent.exitCode = exitCode;
|
|
95
|
+
agent.child = null;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx, 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, border } from '../theme.js';
|
|
5
|
+
const STATUS_COLORS = {
|
|
6
|
+
spawning: colors.honey,
|
|
7
|
+
running: colors.green,
|
|
8
|
+
exited: colors.grayDim,
|
|
9
|
+
errored: colors.red,
|
|
10
|
+
};
|
|
11
|
+
const STATUS_SYMBOLS = {
|
|
12
|
+
spawning: symbols.diamondOpen,
|
|
13
|
+
running: symbols.dot,
|
|
14
|
+
exited: '\u25CB', // ○
|
|
15
|
+
errored: symbols.cross,
|
|
16
|
+
};
|
|
17
|
+
const POLL_INTERVAL_MS = 1_000;
|
|
18
|
+
const STOPPABLE = new Set(['running', 'spawning']);
|
|
19
|
+
const STARTABLE = new Set(['exited', 'errored']);
|
|
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.wax, 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: ["C:", stats.confidence.toFixed(2)] })] }));
|
|
22
|
+
}
|
|
23
|
+
export function Dashboard({ manager, statsMap }) {
|
|
24
|
+
const { exit } = useApp();
|
|
25
|
+
const [agents, setAgents] = useState(manager.getStates());
|
|
26
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const timer = setInterval(() => {
|
|
29
|
+
setAgents(manager.getStates());
|
|
30
|
+
}, POLL_INTERVAL_MS);
|
|
31
|
+
return () => clearInterval(timer);
|
|
32
|
+
}, [manager]);
|
|
33
|
+
// Clamp selectedIndex when agents list changes
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const maxIndex = Math.max(agents.length - 1, 0);
|
|
36
|
+
setSelectedIndex((prev) => Math.min(prev, maxIndex));
|
|
37
|
+
}, [agents.length]);
|
|
38
|
+
useInput((_input, key) => {
|
|
39
|
+
if (key.upArrow) {
|
|
40
|
+
setSelectedIndex((prev) => {
|
|
41
|
+
const max = agents.length - 1;
|
|
42
|
+
return prev > 0 ? prev - 1 : max;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
else if (key.downArrow) {
|
|
46
|
+
setSelectedIndex((prev) => {
|
|
47
|
+
const max = agents.length - 1;
|
|
48
|
+
return prev < max ? prev + 1 : 0;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
else if (_input === ' ' || key.return) {
|
|
52
|
+
const agent = agents[selectedIndex];
|
|
53
|
+
if (!agent) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (STOPPABLE.has(agent.status)) {
|
|
57
|
+
void manager.stopAgent(agent.name);
|
|
58
|
+
}
|
|
59
|
+
else if (STARTABLE.has(agent.status)) {
|
|
60
|
+
manager.respawnPiped(agent.name);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else if (key.ctrl && _input === 'c') {
|
|
64
|
+
exit();
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
const runningCount = agents.filter((a) => a.status === 'running').length;
|
|
68
|
+
const spawningCount = agents.filter((a) => a.status === 'spawning').length;
|
|
69
|
+
const stoppedCount = agents.filter((a) => a.status === 'exited' || a.status === 'errored').length;
|
|
70
|
+
const statusParts = [];
|
|
71
|
+
if (runningCount > 0) {
|
|
72
|
+
statusParts.push(`${runningCount} running`);
|
|
73
|
+
}
|
|
74
|
+
if (spawningCount > 0) {
|
|
75
|
+
statusParts.push(`${spawningCount} starting`);
|
|
76
|
+
}
|
|
77
|
+
if (stoppedCount > 0) {
|
|
78
|
+
statusParts.push(`${stoppedCount} stopped`);
|
|
79
|
+
}
|
|
80
|
+
const statusLabel = statusParts.length > 0 ? statusParts.join(', ') : 'no agents running';
|
|
81
|
+
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, " ", agents.length, " agent", agents.length !== 1 ? 's' : '', ' ', border.horizontal, " ", statusLabel] })] }), agents.map((agent, index) => {
|
|
82
|
+
const isSelected = index === selectedIndex;
|
|
83
|
+
const statusColor = STATUS_COLORS[agent.status];
|
|
84
|
+
const statusSymbol = STATUS_SYMBOLS[agent.status];
|
|
85
|
+
const isAlive = agent.status === 'running' || agent.status === 'spawning';
|
|
86
|
+
const statusText = agent.status === 'exited' || agent.status === 'errored'
|
|
87
|
+
? `${agent.status} (${agent.exitCode})`
|
|
88
|
+
: agent.status;
|
|
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));
|
|
91
|
+
}), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.grayDim, children: [symbols.arrow, " ", '\u2191\u2193', " navigate ", ' ', " space/enter stop/start ", ' ', " ctrl+c quit"] }) })] }));
|
|
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.wax, 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: ["C:", stats.confidence.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
|
+
}
|