@hive-org/cli 0.0.1
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/ai-providers.js +62 -0
- package/dist/commands/install.js +50 -0
- package/dist/components/AsciiTicker.js +81 -0
- package/dist/components/CodeBlock.js +11 -0
- package/dist/components/Header.js +11 -0
- package/dist/components/HoneycombLoader.js +190 -0
- package/dist/components/SelectPrompt.js +13 -0
- package/dist/components/Spinner.js +16 -0
- package/dist/components/StreamingText.js +45 -0
- package/dist/components/TextPrompt.js +28 -0
- package/dist/config.js +26 -0
- package/dist/create/CreateApp.js +102 -0
- package/dist/create/ai-generate.js +133 -0
- package/dist/create/generate.js +173 -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 +72 -0
- package/dist/create/steps/NameStep.js +70 -0
- package/dist/create/steps/ScaffoldStep.js +58 -0
- package/dist/create/steps/SoulStep.js +38 -0
- package/dist/create/steps/StrategyStep.js +38 -0
- package/dist/create/validate-api-key.js +44 -0
- package/dist/create/welcome.js +304 -0
- package/dist/index.js +46 -0
- package/dist/list/ListApp.js +83 -0
- package/dist/presets.js +358 -0
- package/dist/theme.js +47 -0
- package/package.json +65 -0
- package/templates/analysis.ts +103 -0
- package/templates/chat-prompt.ts +94 -0
- package/templates/components/AsciiTicker.tsx +113 -0
- package/templates/components/HoneycombBoot.tsx +348 -0
- package/templates/components/Spinner.tsx +64 -0
- package/templates/edit-section.ts +64 -0
- package/templates/fetch-rules.ts +23 -0
- package/templates/helpers.ts +22 -0
- package/templates/hive/agent.ts +2 -0
- package/templates/hive/config.ts +96 -0
- package/templates/hive/memory.ts +1 -0
- package/templates/hive/objects.ts +26 -0
- package/templates/hooks/useAgent.ts +336 -0
- package/templates/index.tsx +257 -0
- package/templates/memory-prompt.ts +60 -0
- package/templates/process-lifecycle.ts +66 -0
- package/templates/prompt.ts +160 -0
- package/templates/theme.ts +40 -0
- package/templates/types.ts +23 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useCallback, useMemo } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
import { TextPrompt } from '../../components/TextPrompt.js';
|
|
6
|
+
import { Spinner } from '../../components/Spinner.js';
|
|
7
|
+
import { colors, symbols } from '../../theme.js';
|
|
8
|
+
const ADJECTIVES = [
|
|
9
|
+
'royal', 'golden', 'buzzy', 'honey', 'sweet', 'stung', 'waxed', 'bold',
|
|
10
|
+
'swift', 'wild', 'keen', 'warm', 'hazy', 'calm', 'busy', 'amber',
|
|
11
|
+
'pollen', 'nectar', 'floral', 'sunny', 'misty', 'fuzzy', 'striped',
|
|
12
|
+
'waggle', 'silent', 'fierce', 'humble', 'lunar', 'solar', 'bloomed',
|
|
13
|
+
'bullish', 'bearish', 'staked', 'minted', 'forked', 'based', 'degen',
|
|
14
|
+
'pumped', 'longed', 'shorted', 'bridged', 'pegged', 'hodl', 'mega',
|
|
15
|
+
'alpha', 'sigma', 'hyper', 'ultra', 'rapid', 'atomic',
|
|
16
|
+
];
|
|
17
|
+
const NOUNS = [
|
|
18
|
+
'bee', 'drone', 'queen', 'swarm', 'hive', 'comb', 'larva', 'pupa',
|
|
19
|
+
'sting', 'apiary', 'keeper', 'mead', 'pollen', 'nectar', 'propolis',
|
|
20
|
+
'colony', 'brood', 'waggle', 'cell', 'wax', 'bloom', 'blossom',
|
|
21
|
+
'hornet', 'bumble', 'worker', 'forager', 'scout', 'smoker',
|
|
22
|
+
'whale', 'bull', 'bear', 'shard', 'block', 'node', 'vault',
|
|
23
|
+
'ledger', 'oracle', 'miner', 'staker', 'bridge', 'token', 'chain',
|
|
24
|
+
'wick', 'candle', 'pump', 'moon', 'floor', 'whale',
|
|
25
|
+
];
|
|
26
|
+
function generateRandomName() {
|
|
27
|
+
const adjective = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
|
|
28
|
+
const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
|
|
29
|
+
const number = Math.floor(Math.random() * 900) + 100;
|
|
30
|
+
const name = `${adjective}-${noun}-${number}`;
|
|
31
|
+
return name;
|
|
32
|
+
}
|
|
33
|
+
export function NameStep({ onComplete }) {
|
|
34
|
+
const [checking, setChecking] = useState(false);
|
|
35
|
+
const [error, setError] = useState('');
|
|
36
|
+
const placeholder = useMemo(() => generateRandomName(), []);
|
|
37
|
+
const handleSubmit = useCallback(async (name) => {
|
|
38
|
+
setChecking(true);
|
|
39
|
+
setError('');
|
|
40
|
+
try {
|
|
41
|
+
const apiUrl = process.env['HIVE_API_URL'] ?? 'https://hive-backend.z3n.dev';
|
|
42
|
+
const response = await axios.get(`${apiUrl}/agent/check-name`, {
|
|
43
|
+
params: { name },
|
|
44
|
+
timeout: 3000,
|
|
45
|
+
});
|
|
46
|
+
if (!response.data.available) {
|
|
47
|
+
setError(`Name "${name}" is already taken`);
|
|
48
|
+
setChecking(false);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// best-effort: silently proceed if backend is unavailable
|
|
54
|
+
}
|
|
55
|
+
setChecking(false);
|
|
56
|
+
onComplete(name);
|
|
57
|
+
}, [onComplete]);
|
|
58
|
+
if (checking) {
|
|
59
|
+
return (_jsx(Box, { flexDirection: "column", children: _jsx(Spinner, { label: "Checking name availability..." }) }));
|
|
60
|
+
}
|
|
61
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(TextPrompt, { label: "Enter your agent name", placeholder: placeholder, onSubmit: handleSubmit, validate: (val) => {
|
|
62
|
+
if (!val) {
|
|
63
|
+
return 'Agent name is required';
|
|
64
|
+
}
|
|
65
|
+
if (!/^[a-zA-Z0-9-_]+$/.test(val)) {
|
|
66
|
+
return 'Only letters, numbers, hyphens, and underscores allowed';
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
} }), error !== '' && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: colors.red, children: [symbols.cross, " ", error] }) }))] }));
|
|
70
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import { HoneycombLoader } from '../../components/HoneycombLoader.js';
|
|
5
|
+
import { colors, symbols } from '../../theme.js';
|
|
6
|
+
import { scaffoldProject } from '../generate.js';
|
|
7
|
+
export function ScaffoldStep({ projectName, provider, apiKey, soulContent, strategyContent, onComplete, onError, }) {
|
|
8
|
+
const [steps, setSteps] = useState([]);
|
|
9
|
+
const [currentLabel, setCurrentLabel] = useState('Starting...');
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
let cancelled = false;
|
|
12
|
+
const run = async () => {
|
|
13
|
+
await scaffoldProject(projectName, provider, apiKey, soulContent, strategyContent, {
|
|
14
|
+
onStep: (label) => {
|
|
15
|
+
if (cancelled)
|
|
16
|
+
return;
|
|
17
|
+
setSteps((prev) => {
|
|
18
|
+
if (prev.length > 0) {
|
|
19
|
+
const updated = [...prev];
|
|
20
|
+
updated[updated.length - 1] = { ...updated[updated.length - 1], done: true };
|
|
21
|
+
return [...updated, { label, done: false }];
|
|
22
|
+
}
|
|
23
|
+
return [{ label, done: false }];
|
|
24
|
+
});
|
|
25
|
+
setCurrentLabel(label);
|
|
26
|
+
},
|
|
27
|
+
onDone: (projectDir) => {
|
|
28
|
+
if (cancelled)
|
|
29
|
+
return;
|
|
30
|
+
setSteps((prev) => {
|
|
31
|
+
if (prev.length > 0) {
|
|
32
|
+
const updated = [...prev];
|
|
33
|
+
updated[updated.length - 1] = { ...updated[updated.length - 1], done: true };
|
|
34
|
+
return updated;
|
|
35
|
+
}
|
|
36
|
+
return prev;
|
|
37
|
+
});
|
|
38
|
+
onComplete(projectDir);
|
|
39
|
+
},
|
|
40
|
+
onError: (message) => {
|
|
41
|
+
if (cancelled)
|
|
42
|
+
return;
|
|
43
|
+
onError(message);
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
run().catch((err) => {
|
|
48
|
+
if (cancelled)
|
|
49
|
+
return;
|
|
50
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
51
|
+
onError(message);
|
|
52
|
+
});
|
|
53
|
+
return () => {
|
|
54
|
+
cancelled = true;
|
|
55
|
+
};
|
|
56
|
+
}, [projectName, provider, apiKey, soulContent, strategyContent, onComplete, onError]);
|
|
57
|
+
return (_jsxs(Box, { flexDirection: "column", children: [steps.map((step, i) => (_jsx(Box, { marginLeft: 2, children: step.done ? (_jsxs(Text, { color: colors.green, children: [symbols.check, " ", step.label] })) : (_jsx(HoneycombLoader, { label: step.label })) }, i))), steps.length === 0 && (_jsx(Box, { marginLeft: 2, children: _jsx(HoneycombLoader, { label: currentLabel }) }))] }));
|
|
58
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useCallback, useMemo } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import { StreamingText } from '../../components/StreamingText.js';
|
|
5
|
+
import { TextPrompt } from '../../components/TextPrompt.js';
|
|
6
|
+
import { CodeBlock } from '../../components/CodeBlock.js';
|
|
7
|
+
import { Spinner } from '../../components/Spinner.js';
|
|
8
|
+
import { colors, symbols } from '../../theme.js';
|
|
9
|
+
import { streamSoul } from '../ai-generate.js';
|
|
10
|
+
export function SoulStep({ providerId, apiKey, agentName, bio, avatarUrl, personality, tone, voiceStyle, onComplete, }) {
|
|
11
|
+
const [phase, setPhase] = useState('streaming');
|
|
12
|
+
const [draft, setDraft] = useState('');
|
|
13
|
+
const [feedbackCount, setFeedbackCount] = useState(0);
|
|
14
|
+
const stream = useMemo(() => streamSoul(providerId, apiKey, agentName, bio, avatarUrl, personality, tone, voiceStyle), []);
|
|
15
|
+
const [currentStream, setCurrentStream] = useState(stream);
|
|
16
|
+
const handleStreamComplete = useCallback((fullText) => {
|
|
17
|
+
setDraft(fullText);
|
|
18
|
+
setPhase('review');
|
|
19
|
+
}, []);
|
|
20
|
+
const handleAccept = useCallback(() => {
|
|
21
|
+
onComplete(draft);
|
|
22
|
+
}, [draft, onComplete]);
|
|
23
|
+
const handleFeedback = useCallback((feedback) => {
|
|
24
|
+
setFeedbackCount((prev) => prev + 1);
|
|
25
|
+
setPhase('streaming');
|
|
26
|
+
setDraft('');
|
|
27
|
+
const newStream = streamSoul(providerId, apiKey, agentName, bio, avatarUrl, personality, tone, voiceStyle, feedback);
|
|
28
|
+
setCurrentStream(newStream);
|
|
29
|
+
}, [providerId, apiKey, agentName, bio, avatarUrl, personality, tone, voiceStyle]);
|
|
30
|
+
return (_jsxs(Box, { flexDirection: "column", children: [phase === 'streaming' && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Spinner, { label: feedbackCount > 0 ? 'Regenerating SOUL.md...' : 'Generating SOUL.md...' }) }), _jsx(StreamingText, { stream: currentStream, title: "SOUL.md", onComplete: handleStreamComplete })] })), phase === 'review' && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: colors.green, children: [symbols.check, " "] }), _jsx(Text, { color: colors.white, children: "SOUL.md draft ready" })] }), _jsx(CodeBlock, { title: "SOUL.md", children: draft }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.gray, children: ["Press ", _jsx(Text, { color: colors.honey, bold: true, children: "Enter" }), " to accept", ' ', symbols.dot, ' ', "Type feedback to regenerate"] }) }), _jsx(Box, { marginTop: 1, children: _jsx(TextPrompt, { label: "", placeholder: "Enter to accept, or type feedback...", onSubmit: (val) => {
|
|
31
|
+
if (!val) {
|
|
32
|
+
handleAccept();
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
handleFeedback(val);
|
|
36
|
+
}
|
|
37
|
+
} }) })] }))] }));
|
|
38
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useCallback, useMemo } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import { StreamingText } from '../../components/StreamingText.js';
|
|
5
|
+
import { TextPrompt } from '../../components/TextPrompt.js';
|
|
6
|
+
import { CodeBlock } from '../../components/CodeBlock.js';
|
|
7
|
+
import { Spinner } from '../../components/Spinner.js';
|
|
8
|
+
import { colors, symbols } from '../../theme.js';
|
|
9
|
+
import { streamStrategy } from '../ai-generate.js';
|
|
10
|
+
export function StrategyStep({ providerId, apiKey, agentName, bio, personality, tone, voiceStyle, onComplete, }) {
|
|
11
|
+
const [phase, setPhase] = useState('streaming');
|
|
12
|
+
const [draft, setDraft] = useState('');
|
|
13
|
+
const [feedbackCount, setFeedbackCount] = useState(0);
|
|
14
|
+
const stream = useMemo(() => streamStrategy(providerId, apiKey, agentName, bio, personality, tone, voiceStyle), []);
|
|
15
|
+
const [currentStream, setCurrentStream] = useState(stream);
|
|
16
|
+
const handleStreamComplete = useCallback((fullText) => {
|
|
17
|
+
setDraft(fullText);
|
|
18
|
+
setPhase('review');
|
|
19
|
+
}, []);
|
|
20
|
+
const handleAccept = useCallback(() => {
|
|
21
|
+
onComplete(draft);
|
|
22
|
+
}, [draft, onComplete]);
|
|
23
|
+
const handleFeedback = useCallback((feedback) => {
|
|
24
|
+
setFeedbackCount((prev) => prev + 1);
|
|
25
|
+
setPhase('streaming');
|
|
26
|
+
setDraft('');
|
|
27
|
+
const newStream = streamStrategy(providerId, apiKey, agentName, bio, personality, tone, voiceStyle, feedback);
|
|
28
|
+
setCurrentStream(newStream);
|
|
29
|
+
}, [providerId, apiKey, agentName, bio, personality, tone, voiceStyle]);
|
|
30
|
+
return (_jsxs(Box, { flexDirection: "column", children: [phase === 'streaming' && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Spinner, { label: feedbackCount > 0 ? 'Regenerating STRATEGY.md...' : 'Generating STRATEGY.md...' }) }), _jsx(StreamingText, { stream: currentStream, title: "STRATEGY.md", onComplete: handleStreamComplete })] })), phase === 'review' && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: colors.green, children: [symbols.check, " "] }), _jsx(Text, { color: colors.white, children: "STRATEGY.md draft ready" })] }), _jsx(CodeBlock, { title: "STRATEGY.md", children: draft }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.gray, children: ["Press ", _jsx(Text, { color: colors.honey, bold: true, children: "Enter" }), " to accept", ' ', symbols.dot, ' ', "Type feedback to regenerate"] }) }), _jsx(Box, { marginTop: 1, children: _jsx(TextPrompt, { label: "", placeholder: "Enter to accept, or type feedback...", onSubmit: (val) => {
|
|
31
|
+
if (!val) {
|
|
32
|
+
handleAccept();
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
handleFeedback(val);
|
|
36
|
+
}
|
|
37
|
+
} }) })] }))] }));
|
|
38
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { generateText } from 'ai';
|
|
2
|
+
import { createOpenAI } from '@ai-sdk/openai';
|
|
3
|
+
import { createAnthropic } from '@ai-sdk/anthropic';
|
|
4
|
+
import { createGoogleGenerativeAI } from '@ai-sdk/google';
|
|
5
|
+
import { createXai } from '@ai-sdk/xai';
|
|
6
|
+
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
|
7
|
+
/**
|
|
8
|
+
* Make a lightweight test call to validate the user's API key.
|
|
9
|
+
* Returns true if the key works, or an error message string on failure.
|
|
10
|
+
*/
|
|
11
|
+
export async function validateApiKey(providerId, apiKey) {
|
|
12
|
+
try {
|
|
13
|
+
const model = buildTestModel(providerId, apiKey);
|
|
14
|
+
await generateText({
|
|
15
|
+
model,
|
|
16
|
+
prompt: 'Say "ok"',
|
|
17
|
+
maxOutputTokens: 16,
|
|
18
|
+
});
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23
|
+
if (message.includes('401') ||
|
|
24
|
+
message.includes('Unauthorized') ||
|
|
25
|
+
message.includes('invalid')) {
|
|
26
|
+
return 'Invalid API key. Please check and try again.';
|
|
27
|
+
}
|
|
28
|
+
return `API validation failed: ${message.slice(0, 120)}`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function buildTestModel(providerId, apiKey) {
|
|
32
|
+
switch (providerId) {
|
|
33
|
+
case 'openai':
|
|
34
|
+
return createOpenAI({ apiKey })('gpt-4o-mini');
|
|
35
|
+
case 'anthropic':
|
|
36
|
+
return createAnthropic({ apiKey })('claude-haiku-4-5-20251001');
|
|
37
|
+
case 'google':
|
|
38
|
+
return createGoogleGenerativeAI({ apiKey })('gemini-2.0-flash');
|
|
39
|
+
case 'xai':
|
|
40
|
+
return createXai({ apiKey })('grok-2');
|
|
41
|
+
case 'openrouter':
|
|
42
|
+
return createOpenRouter({ apiKey }).chat('openai/gpt-4o-mini');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
const HEX_W = 8;
|
|
3
|
+
const HEX_H = 4;
|
|
4
|
+
const DATA_CHARS = '01▪▫░▒';
|
|
5
|
+
const TICK_MS = 80;
|
|
6
|
+
const DURATION_MS = 3200;
|
|
7
|
+
const NUM_BEES = 4;
|
|
8
|
+
const NUM_STREAMS = 5;
|
|
9
|
+
const HONEY = '#F5A623';
|
|
10
|
+
const GREEN = '#4CAF50';
|
|
11
|
+
const DIM = '#555555';
|
|
12
|
+
const WHITE = '#FFFFFF';
|
|
13
|
+
const RED = '#F44336';
|
|
14
|
+
const SCRAMBLE_CHARS = '⬡⬢◆◇░▒!@#$%01';
|
|
15
|
+
const BOOT_MESSAGES = [
|
|
16
|
+
{ prefix: '⬡', text: 'Initializing creation studio...', frame: 18, color: HONEY },
|
|
17
|
+
{ prefix: '◆', text: 'Loading agent templates...', frame: 24, color: HONEY },
|
|
18
|
+
{ prefix: '◇', text: 'Connecting to hive network...', frame: 30, color: HONEY },
|
|
19
|
+
{ prefix: '✓', text: 'Ready', frame: 36, color: GREEN },
|
|
20
|
+
];
|
|
21
|
+
function isHexEdge(r, c) {
|
|
22
|
+
const rowInHex = ((r % HEX_H) + HEX_H) % HEX_H;
|
|
23
|
+
const isOddHex = Math.floor(r / HEX_H) % 2 === 1;
|
|
24
|
+
const colOffset = isOddHex ? HEX_W / 2 : 0;
|
|
25
|
+
const colInHex = (((c - colOffset) % HEX_W) + HEX_W) % HEX_W;
|
|
26
|
+
if (rowInHex === 0 || rowInHex === HEX_H - 1) {
|
|
27
|
+
return colInHex >= 2 && colInHex <= 5;
|
|
28
|
+
}
|
|
29
|
+
if (rowInHex === 1 || rowInHex === 2) {
|
|
30
|
+
return colInHex === 1 || colInHex === 6;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
export function showWelcome() {
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
const cols = process.stdout.columns || 60;
|
|
37
|
+
const gridRows = process.stdout.rows || 24;
|
|
38
|
+
let frame = 0;
|
|
39
|
+
// Init bees
|
|
40
|
+
const bees = [];
|
|
41
|
+
for (let i = 0; i < NUM_BEES; i++) {
|
|
42
|
+
bees.push({
|
|
43
|
+
r: Math.floor(Math.random() * gridRows),
|
|
44
|
+
c: Math.floor(Math.random() * cols),
|
|
45
|
+
vr: Math.random() > 0.5 ? 1 : -1,
|
|
46
|
+
vc: Math.random() > 0.5 ? 1 : -1,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
// Init stream columns
|
|
50
|
+
const streamCols = [];
|
|
51
|
+
const spacing = Math.floor(cols / (NUM_STREAMS + 1));
|
|
52
|
+
for (let i = 1; i <= NUM_STREAMS; i++) {
|
|
53
|
+
streamCols.push(spacing * i);
|
|
54
|
+
}
|
|
55
|
+
let pulses = [];
|
|
56
|
+
// Title positioning
|
|
57
|
+
const title = '\u2B21 HIVE';
|
|
58
|
+
const subtitle = 'Agent Creation Studio';
|
|
59
|
+
const titleRow = Math.floor(gridRows / 2) - 1;
|
|
60
|
+
const subtitleRow = titleRow + 1;
|
|
61
|
+
const titleCol = Math.floor((cols - title.length) / 2);
|
|
62
|
+
const subtitleCol = Math.floor((cols - subtitle.length) / 2);
|
|
63
|
+
// Boot message row positions
|
|
64
|
+
const msgStartRow = subtitleRow + 2;
|
|
65
|
+
// Quiet zone around title + boot messages: no animation renders here
|
|
66
|
+
const PADDING_H = 3;
|
|
67
|
+
const PADDING_V = 1;
|
|
68
|
+
const longestMsg = BOOT_MESSAGES.reduce((max, m) => Math.max(max, m.prefix.length + 1 + m.text.length), 0);
|
|
69
|
+
const msgLeftEdge = Math.floor((cols - longestMsg) / 2);
|
|
70
|
+
const msgRightEdge = msgLeftEdge + longestMsg;
|
|
71
|
+
const quietLeft = Math.min(titleCol, subtitleCol, msgLeftEdge) - PADDING_H;
|
|
72
|
+
const quietRight = Math.max(titleCol + title.length, subtitleCol + subtitle.length, msgRightEdge) + PADDING_H;
|
|
73
|
+
const quietTop = titleRow - PADDING_V;
|
|
74
|
+
const quietBottom = msgStartRow + BOOT_MESSAGES.length + PADDING_V;
|
|
75
|
+
// Hide cursor
|
|
76
|
+
process.stdout.write('\x1b[?25l');
|
|
77
|
+
// Clear screen
|
|
78
|
+
process.stdout.write('\x1b[2J');
|
|
79
|
+
function renderFrame() {
|
|
80
|
+
// Move cursor to top-left
|
|
81
|
+
process.stdout.write('\x1b[H');
|
|
82
|
+
// Advance bees every other frame
|
|
83
|
+
if (frame > 0 && frame % 2 === 0) {
|
|
84
|
+
for (const bee of bees) {
|
|
85
|
+
bee.r += bee.vr;
|
|
86
|
+
bee.c += bee.vc;
|
|
87
|
+
if (bee.r <= 0 || bee.r >= gridRows - 1) {
|
|
88
|
+
bee.vr *= -1;
|
|
89
|
+
bee.r = Math.max(0, Math.min(gridRows - 1, bee.r));
|
|
90
|
+
}
|
|
91
|
+
if (bee.c <= 0 || bee.c >= cols - 1) {
|
|
92
|
+
bee.vc *= -1;
|
|
93
|
+
bee.c = Math.max(0, Math.min(cols - 1, bee.c));
|
|
94
|
+
}
|
|
95
|
+
if (Math.random() > 0.3) {
|
|
96
|
+
bee.vc = Math.random() > 0.5 ? 1 : -1;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Spawn pulses
|
|
101
|
+
if (frame % 4 === 0) {
|
|
102
|
+
for (let i = 0; i < 3; i++) {
|
|
103
|
+
const pr = Math.floor(Math.random() * gridRows);
|
|
104
|
+
const pc = Math.floor(Math.random() * cols);
|
|
105
|
+
if (isHexEdge(pr, pc)) {
|
|
106
|
+
const pulseColors = [GREEN, RED, HONEY];
|
|
107
|
+
const color = pulseColors[Math.floor(Math.random() * pulseColors.length)];
|
|
108
|
+
pulses.push({ r: pr, c: pc, ttl: 8, color });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
pulses = pulses.filter((p) => p.ttl > 0).map((p) => ({ ...p, ttl: p.ttl - 1 }));
|
|
112
|
+
}
|
|
113
|
+
// Build grid: char + color pairs
|
|
114
|
+
const charGrid = [];
|
|
115
|
+
const colorGrid = [];
|
|
116
|
+
for (let r = 0; r < gridRows; r++) {
|
|
117
|
+
const chars = [];
|
|
118
|
+
const clrs = [];
|
|
119
|
+
for (let c = 0; c < cols; c++) {
|
|
120
|
+
// Skip animation in quiet zone around title
|
|
121
|
+
if (r >= quietTop && r <= quietBottom && c >= quietLeft && c < quietRight) {
|
|
122
|
+
chars.push(' ');
|
|
123
|
+
clrs.push(DIM);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const hexEdge = isHexEdge(r, c);
|
|
127
|
+
// Scanning wave
|
|
128
|
+
const scanRow = frame % (gridRows + 6);
|
|
129
|
+
const dist = Math.abs(r - scanRow);
|
|
130
|
+
if (hexEdge && dist === 0) {
|
|
131
|
+
chars.push('⬢');
|
|
132
|
+
clrs.push(HONEY);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (hexEdge && dist <= 1) {
|
|
136
|
+
chars.push('⬡');
|
|
137
|
+
clrs.push(HONEY);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
// Data streams
|
|
141
|
+
let isStream = false;
|
|
142
|
+
for (const sc of streamCols) {
|
|
143
|
+
if (c === sc) {
|
|
144
|
+
const streamOffset = (frame * 2 + sc) % (gridRows * 3);
|
|
145
|
+
const streamDist = (((r - streamOffset) % gridRows) + gridRows) % gridRows;
|
|
146
|
+
if (streamDist < 6) {
|
|
147
|
+
const charIdx = (frame + r) % DATA_CHARS.length;
|
|
148
|
+
const streamChar = DATA_CHARS[charIdx];
|
|
149
|
+
chars.push(streamChar);
|
|
150
|
+
if (streamDist === 0) {
|
|
151
|
+
clrs.push(WHITE);
|
|
152
|
+
}
|
|
153
|
+
else if (streamDist < 3) {
|
|
154
|
+
clrs.push(GREEN);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
clrs.push(DIM);
|
|
158
|
+
}
|
|
159
|
+
isStream = true;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (isStream)
|
|
165
|
+
continue;
|
|
166
|
+
// Default
|
|
167
|
+
if (hexEdge) {
|
|
168
|
+
chars.push('·');
|
|
169
|
+
clrs.push(DIM);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
chars.push(' ');
|
|
173
|
+
clrs.push(DIM);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
charGrid.push(chars);
|
|
177
|
+
colorGrid.push(clrs);
|
|
178
|
+
}
|
|
179
|
+
// Overlay pulses (skip quiet zone)
|
|
180
|
+
for (const pulse of pulses) {
|
|
181
|
+
if (pulse.r >= 0 && pulse.r < gridRows && pulse.c >= 0 && pulse.c < cols) {
|
|
182
|
+
const inQuietZone = pulse.r >= quietTop &&
|
|
183
|
+
pulse.r <= quietBottom &&
|
|
184
|
+
pulse.c >= quietLeft &&
|
|
185
|
+
pulse.c < quietRight;
|
|
186
|
+
if (inQuietZone)
|
|
187
|
+
continue;
|
|
188
|
+
const brightness = pulse.ttl / 8;
|
|
189
|
+
const cell = charGrid[pulse.r][pulse.c];
|
|
190
|
+
if (cell === '·' || cell === ' ') {
|
|
191
|
+
charGrid[pulse.r][pulse.c] = brightness > 0.5 ? '⬡' : '·';
|
|
192
|
+
colorGrid[pulse.r][pulse.c] = pulse.color;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Overlay bees (skip quiet zone)
|
|
197
|
+
for (const bee of bees) {
|
|
198
|
+
const br = Math.max(0, Math.min(gridRows - 1, Math.round(bee.r)));
|
|
199
|
+
const bc = Math.max(0, Math.min(cols - 1, Math.round(bee.c)));
|
|
200
|
+
const inQuietZone = br >= quietTop && br <= quietBottom && bc >= quietLeft && bc < quietRight;
|
|
201
|
+
if (!inQuietZone) {
|
|
202
|
+
charGrid[br][bc] = '◆';
|
|
203
|
+
colorGrid[br][bc] = HONEY;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Overlay title with scramble→reveal effect
|
|
207
|
+
const TITLE_START_FRAME = 6;
|
|
208
|
+
const TITLE_REVEAL_FRAMES = 8;
|
|
209
|
+
if (frame >= TITLE_START_FRAME && titleRow >= 0 && titleRow < gridRows) {
|
|
210
|
+
const scrambleProgress = Math.min(1, (frame - TITLE_START_FRAME) / TITLE_REVEAL_FRAMES);
|
|
211
|
+
for (let i = 0; i < title.length; i++) {
|
|
212
|
+
const tc = titleCol + i;
|
|
213
|
+
if (tc < 0 || tc >= cols)
|
|
214
|
+
continue;
|
|
215
|
+
const charThreshold = i / title.length;
|
|
216
|
+
if (charThreshold <= scrambleProgress) {
|
|
217
|
+
charGrid[titleRow][tc] = title[i];
|
|
218
|
+
colorGrid[titleRow][tc] = HONEY;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
const scrambleIdx = Math.floor(Math.random() * SCRAMBLE_CHARS.length);
|
|
222
|
+
charGrid[titleRow][tc] = SCRAMBLE_CHARS[scrambleIdx];
|
|
223
|
+
colorGrid[titleRow][tc] = DIM;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Overlay subtitle with scramble→reveal (starts a few frames after title)
|
|
228
|
+
const SUB_START_FRAME = 10;
|
|
229
|
+
const SUB_REVEAL_FRAMES = 8;
|
|
230
|
+
if (frame >= SUB_START_FRAME && subtitleRow >= 0 && subtitleRow < gridRows) {
|
|
231
|
+
const scrambleProgress = Math.min(1, (frame - SUB_START_FRAME) / SUB_REVEAL_FRAMES);
|
|
232
|
+
for (let i = 0; i < subtitle.length; i++) {
|
|
233
|
+
const sc = subtitleCol + i;
|
|
234
|
+
if (sc < 0 || sc >= cols)
|
|
235
|
+
continue;
|
|
236
|
+
const charThreshold = i / subtitle.length;
|
|
237
|
+
if (charThreshold <= scrambleProgress) {
|
|
238
|
+
charGrid[subtitleRow][sc] = subtitle[i];
|
|
239
|
+
colorGrid[subtitleRow][sc] = DIM;
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
const scrambleIdx = Math.floor(Math.random() * SCRAMBLE_CHARS.length);
|
|
243
|
+
charGrid[subtitleRow][sc] = SCRAMBLE_CHARS[scrambleIdx];
|
|
244
|
+
colorGrid[subtitleRow][sc] = DIM;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Overlay typewriter boot messages
|
|
249
|
+
for (let idx = 0; idx < BOOT_MESSAGES.length; idx++) {
|
|
250
|
+
const msg = BOOT_MESSAGES[idx];
|
|
251
|
+
if (frame < msg.frame)
|
|
252
|
+
continue;
|
|
253
|
+
const r = msgStartRow + idx;
|
|
254
|
+
if (r < 0 || r >= gridRows)
|
|
255
|
+
continue;
|
|
256
|
+
const fullText = `${msg.prefix} ${msg.text}`;
|
|
257
|
+
const msgCol = Math.floor((cols - fullText.length) / 2);
|
|
258
|
+
const visibleChars = Math.min(fullText.length, (frame - msg.frame) * 3);
|
|
259
|
+
for (let i = 0; i < visibleChars; i++) {
|
|
260
|
+
const c = msgCol + i;
|
|
261
|
+
if (c < 0 || c >= cols)
|
|
262
|
+
continue;
|
|
263
|
+
charGrid[r][c] = fullText[i];
|
|
264
|
+
colorGrid[r][c] = msg.color;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Render to stdout
|
|
268
|
+
let output = '';
|
|
269
|
+
for (let r = 0; r < gridRows; r++) {
|
|
270
|
+
let line = '';
|
|
271
|
+
let runColor = colorGrid[r][0];
|
|
272
|
+
let runChars = '';
|
|
273
|
+
for (let c = 0; c < cols; c++) {
|
|
274
|
+
const curColor = colorGrid[r][c];
|
|
275
|
+
const curChar = charGrid[r][c];
|
|
276
|
+
if (curColor === runColor) {
|
|
277
|
+
runChars += curChar;
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
line += chalk.hex(runColor)(runChars);
|
|
281
|
+
runColor = curColor;
|
|
282
|
+
runChars = curChar;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (runChars.length > 0) {
|
|
286
|
+
line += chalk.hex(runColor)(runChars);
|
|
287
|
+
}
|
|
288
|
+
output += line;
|
|
289
|
+
if (r < gridRows - 1) {
|
|
290
|
+
output += '\n';
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
process.stdout.write(output);
|
|
294
|
+
frame++;
|
|
295
|
+
}
|
|
296
|
+
const timer = setInterval(renderFrame, TICK_MS);
|
|
297
|
+
setTimeout(() => {
|
|
298
|
+
clearInterval(timer);
|
|
299
|
+
// Clear screen, show cursor, move to top
|
|
300
|
+
process.stdout.write('\x1b[2J\x1b[H\x1b[?25h');
|
|
301
|
+
resolve();
|
|
302
|
+
}, DURATION_MS);
|
|
303
|
+
});
|
|
304
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
import { render } from 'ink';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { CreateApp } from './create/CreateApp.js';
|
|
6
|
+
import { ListApp } from './list/ListApp.js';
|
|
7
|
+
import { showWelcome } from './create/welcome.js';
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
const pkg = require('../package.json');
|
|
10
|
+
const HELP_TEXT = `@hive-org/cli v${pkg.version}
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
npx @hive-org/cli create [agent-name] Scaffold a new Hive agent
|
|
14
|
+
npx @hive-org/cli list List existing agents
|
|
15
|
+
npx @hive-org/cli --help Show this help message
|
|
16
|
+
npx @hive-org/cli --version Print version
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
npx @hive-org/cli create alpha Creates ~/.hive/agents/alpha/
|
|
20
|
+
npx @hive-org/cli create Interactive setup
|
|
21
|
+
npx @hive-org/cli list Show all agents`;
|
|
22
|
+
const command = process.argv[2];
|
|
23
|
+
const arg = process.argv[3];
|
|
24
|
+
if (command === '--version' || command === '-v') {
|
|
25
|
+
console.log(pkg.version);
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
if (!command || command === '--help' || command === '-h') {
|
|
29
|
+
console.log(HELP_TEXT);
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
if (command === 'list') {
|
|
33
|
+
const { waitUntilExit } = render(React.createElement(ListApp));
|
|
34
|
+
await waitUntilExit();
|
|
35
|
+
}
|
|
36
|
+
else if (command === 'create') {
|
|
37
|
+
const projectName = arg;
|
|
38
|
+
await showWelcome();
|
|
39
|
+
const { waitUntilExit } = render(React.createElement(CreateApp, { initialName: projectName }));
|
|
40
|
+
await waitUntilExit();
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
console.error(`Unknown command: ${command}\n`);
|
|
44
|
+
console.log(HELP_TEXT);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|