@hive-org/cli 0.0.9 → 0.0.11
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/dist/agent/hooks/useAgent.js +0 -4
- package/dist/agent/process-lifecycle.js +3 -34
- package/dist/agent/run-headless.js +68 -0
- package/dist/components/CharacterSummaryCard.js +29 -0
- package/dist/components/Header.js +3 -4
- package/dist/components/InputGuard.js +6 -0
- package/dist/components/MultiSelectPrompt.js +45 -0
- package/dist/components/SelectPrompt.js +8 -1
- package/dist/components/StepIndicator.js +31 -0
- package/dist/create/CreateApp.js +10 -4
- package/dist/create/ai-generate.js +10 -4
- package/dist/create/steps/DoneStep.js +1 -1
- package/dist/create/steps/IdentityStep.js +92 -36
- package/dist/create/steps/SoulStep.js +4 -4
- package/dist/create/steps/StrategyStep.js +4 -4
- package/dist/index.js +50 -14
- package/dist/presets.js +194 -20
- package/dist/start/AgentProcessManager.js +1 -34
- package/dist/start/start-command.js +0 -1
- package/package.json +1 -1
|
@@ -7,7 +7,6 @@ import { editSectionTool } from '../edit-section.js';
|
|
|
7
7
|
import { fetchRulesTool } from '../fetch-rules.js';
|
|
8
8
|
import { loadMemory } from '@hive-org/sdk';
|
|
9
9
|
import { processSignalAndSummarize, extractAndSaveMemory } from '../analysis.js';
|
|
10
|
-
import { registerShutdownAgent } from '../process-lifecycle.js';
|
|
11
10
|
import { getModel } from '../model.js';
|
|
12
11
|
export function useAgent() {
|
|
13
12
|
const [connected, setConnected] = useState(false);
|
|
@@ -153,9 +152,6 @@ export function useAgent() {
|
|
|
153
152
|
},
|
|
154
153
|
});
|
|
155
154
|
agentRef.current = agent;
|
|
156
|
-
registerShutdownAgent(async () => {
|
|
157
|
-
await agent.stop();
|
|
158
|
-
});
|
|
159
155
|
await agent.start();
|
|
160
156
|
setConnected(true);
|
|
161
157
|
const bio = config.bio ?? '';
|
|
@@ -1,30 +1,9 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { symbols } from './theme.js';
|
|
3
|
-
let _shutdownAgent = null;
|
|
4
|
-
let _shuttingDown = false;
|
|
5
|
-
export function registerShutdownAgent(fn) {
|
|
6
|
-
_shutdownAgent = fn;
|
|
7
|
-
}
|
|
8
|
-
export function clearShutdownAgent() {
|
|
9
|
-
_shutdownAgent = null;
|
|
10
|
-
}
|
|
11
3
|
const restoreScreen = () => {
|
|
12
4
|
process.stdout.write('\x1b[?1049l');
|
|
13
5
|
};
|
|
14
|
-
|
|
15
|
-
if (_shuttingDown) {
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
_shuttingDown = true;
|
|
19
|
-
if (_shutdownAgent) {
|
|
20
|
-
try {
|
|
21
|
-
await _shutdownAgent();
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
// Best-effort memory save on shutdown
|
|
25
|
-
}
|
|
26
|
-
_shutdownAgent = null;
|
|
27
|
-
}
|
|
6
|
+
const exitImmediately = (exitCode = 0) => {
|
|
28
7
|
restoreScreen();
|
|
29
8
|
process.exit(exitCode);
|
|
30
9
|
};
|
|
@@ -41,16 +20,6 @@ export function setupProcessLifecycle() {
|
|
|
41
20
|
// The alternate buffer is position-based — no reflow, no ghosts.
|
|
42
21
|
process.stdout.write('\x1b[?1049h');
|
|
43
22
|
process.on('exit', restoreScreen);
|
|
44
|
-
process.on('SIGINT', () =>
|
|
45
|
-
|
|
46
|
-
restoreScreen();
|
|
47
|
-
process.exit(1);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
process.on('SIGTERM', () => {
|
|
51
|
-
gracefulShutdown().catch(() => {
|
|
52
|
-
restoreScreen();
|
|
53
|
-
process.exit(1);
|
|
54
|
-
});
|
|
55
|
-
});
|
|
23
|
+
process.on('SIGINT', () => exitImmediately(0));
|
|
24
|
+
process.on('SIGTERM', () => exitImmediately(0));
|
|
56
25
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { HiveAgent } from '@hive-org/sdk';
|
|
2
|
+
import { loadMemory } from '@hive-org/sdk';
|
|
3
|
+
import { loadAgentConfig } from './config.js';
|
|
4
|
+
import { processSignalAndSummarize } from './analysis.js';
|
|
5
|
+
function timestamp() {
|
|
6
|
+
const now = new Date();
|
|
7
|
+
const hours = String(now.getHours()).padStart(2, '0');
|
|
8
|
+
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
9
|
+
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
10
|
+
const ts = `${hours}:${minutes}:${seconds}`;
|
|
11
|
+
return ts;
|
|
12
|
+
}
|
|
13
|
+
export async function runHeadless() {
|
|
14
|
+
const { HIVE_API_URL } = await import('../config.js');
|
|
15
|
+
const config = await loadAgentConfig();
|
|
16
|
+
const initialMemory = await loadMemory();
|
|
17
|
+
let memoryRef = initialMemory;
|
|
18
|
+
const soulContent = config.soulContent;
|
|
19
|
+
const strategyContent = config.strategyContent;
|
|
20
|
+
let predictionCount = 0;
|
|
21
|
+
const agent = new HiveAgent(HIVE_API_URL, {
|
|
22
|
+
name: config.name,
|
|
23
|
+
avatarUrl: config.avatarUrl,
|
|
24
|
+
bio: config.bio ?? undefined,
|
|
25
|
+
predictionProfile: config.predictionProfile,
|
|
26
|
+
pollIntervalMs: 5000,
|
|
27
|
+
pollLimit: 20,
|
|
28
|
+
onPollEmpty: () => {
|
|
29
|
+
console.log(`[${timestamp()}] idle — no new threads`);
|
|
30
|
+
},
|
|
31
|
+
onStop: async () => { },
|
|
32
|
+
onNewThread: async (thread) => {
|
|
33
|
+
try {
|
|
34
|
+
const threadPreview = thread.text.length > 80 ? thread.text.slice(0, 80) + '\u2026' : thread.text;
|
|
35
|
+
console.log(`[${timestamp()}] signal c/${thread.project_id} · ${thread.id.slice(0, 8)}`);
|
|
36
|
+
console.log(` "${threadPreview}"`);
|
|
37
|
+
console.log(`[${timestamp()}] analyzing...`);
|
|
38
|
+
const result = await processSignalAndSummarize(thread, agent.recentComments, memoryRef, soulContent, strategyContent);
|
|
39
|
+
if (result.skip) {
|
|
40
|
+
console.log(`[${timestamp()}] skipped — outside expertise`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
await agent.postComment(thread.id, {
|
|
44
|
+
thread_id: thread.id,
|
|
45
|
+
text: result.summary,
|
|
46
|
+
conviction: result.conviction,
|
|
47
|
+
}, thread.text);
|
|
48
|
+
predictionCount += 1;
|
|
49
|
+
const sign = result.conviction >= 0 ? '+' : '';
|
|
50
|
+
console.log(`[${timestamp()}] predicted [${sign}${result.conviction}%] "${result.summary}" (${predictionCount} total)`);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
54
|
+
const message = raw.length > 120 ? raw.slice(0, 120) + '\u2026' : raw;
|
|
55
|
+
console.error(`[${timestamp()}] error: ${message}`);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
const shutdown = async () => {
|
|
60
|
+
console.log(`[${config.name}] shutting down...`);
|
|
61
|
+
await agent.stop();
|
|
62
|
+
process.exit(0);
|
|
63
|
+
};
|
|
64
|
+
process.on('SIGINT', () => void shutdown());
|
|
65
|
+
process.on('SIGTERM', () => void shutdown());
|
|
66
|
+
await agent.start();
|
|
67
|
+
console.log(`[${config.name}] agent online — "${config.bio ?? ''}"`);
|
|
68
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { colors, border } from '../theme.js';
|
|
4
|
+
export function CharacterSummaryCard({ name, personality, voice, tradingStyle, sectors, bio, }) {
|
|
5
|
+
if (!personality && !voice && !tradingStyle && !sectors && !bio) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const termWidth = process.stdout.columns || 60;
|
|
9
|
+
const boxWidth = Math.min(termWidth - 4, 52);
|
|
10
|
+
const title = `AGENT IDENTITY — ${name}`;
|
|
11
|
+
const topBar = `${border.topLeft}${border.horizontal} ${title} ${border.horizontal.repeat(Math.max(0, boxWidth - title.length - 5))}${border.topRight}`;
|
|
12
|
+
const bottomBar = `${border.bottomLeft}${border.horizontal.repeat(Math.max(0, boxWidth - 2))}${border.bottomRight}`;
|
|
13
|
+
const rows = [];
|
|
14
|
+
const personalityDisplay = personality ?? '???';
|
|
15
|
+
rows.push({ label: 'Personality', value: personalityDisplay });
|
|
16
|
+
const voiceDisplay = voice ?? '???';
|
|
17
|
+
rows.push({ label: 'Voice', value: voiceDisplay });
|
|
18
|
+
const tradingStyleDisplay = tradingStyle ?? '???';
|
|
19
|
+
rows.push({ label: 'Trading', value: tradingStyleDisplay });
|
|
20
|
+
const sectorsDisplay = sectors ?? '???';
|
|
21
|
+
rows.push({ label: 'Sectors', value: sectorsDisplay });
|
|
22
|
+
const bioDisplay = bio ? (bio.length > 30 ? bio.slice(0, 30) + '...' : bio) : '???';
|
|
23
|
+
rows.push({ label: 'Bio', value: bioDisplay });
|
|
24
|
+
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [_jsx(Box, { children: _jsx(Text, { color: colors.honey, children: topBar }) }), rows.map((row) => {
|
|
25
|
+
const content = ` ${row.label}: ${row.value}`;
|
|
26
|
+
const padding = Math.max(0, boxWidth - content.length - 3);
|
|
27
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: colors.honey, children: border.vertical }), _jsxs(Text, { color: colors.grayDim, children: [" ", row.label, ": "] }), _jsx(Text, { color: colors.white, children: row.value }), _jsx(Text, { children: ' '.repeat(padding) }), _jsx(Text, { color: colors.honey, children: border.vertical })] }, row.label));
|
|
28
|
+
}), _jsx(Box, { children: _jsx(Text, { color: colors.honey, children: bottomBar }) })] }));
|
|
29
|
+
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { colors, symbols, border } from '../theme.js';
|
|
4
|
-
export function Header(
|
|
4
|
+
export function Header() {
|
|
5
5
|
const leftPart = ` ${symbols.hive} Hive `;
|
|
6
|
-
const rightPart = ` step ${step}/${totalSteps} ${symbols.dot} ${label} `;
|
|
7
6
|
const termWidth = process.stdout.columns || 60;
|
|
8
|
-
const fillerWidth = Math.max(0, termWidth - leftPart.length -
|
|
7
|
+
const fillerWidth = Math.max(0, termWidth - leftPart.length - 4);
|
|
9
8
|
const filler = border.horizontal.repeat(fillerWidth);
|
|
10
|
-
return (_jsx(Box, { flexDirection: "column", marginBottom:
|
|
9
|
+
return (_jsx(Box, { flexDirection: "column", marginBottom: 0, children: _jsxs(Text, { children: [_jsx(Text, { color: colors.honey, bold: true, children: leftPart }), _jsxs(Text, { color: colors.grayDim, children: [border.horizontal, border.horizontal, border.horizontal, filler] })] }) }));
|
|
11
10
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import { colors, symbols } from '../theme.js';
|
|
5
|
+
export function MultiSelectPrompt({ label, items, defaultSelected, onSubmit, }) {
|
|
6
|
+
const allValues = new Set(items.map((i) => i.value));
|
|
7
|
+
const [selected, setSelected] = useState(defaultSelected ?? allValues);
|
|
8
|
+
const [cursor, setCursor] = useState(0);
|
|
9
|
+
useInput((_input, key) => {
|
|
10
|
+
if (key.upArrow) {
|
|
11
|
+
setCursor((prev) => (prev > 0 ? prev - 1 : items.length - 1));
|
|
12
|
+
}
|
|
13
|
+
if (key.downArrow) {
|
|
14
|
+
setCursor((prev) => (prev < items.length - 1 ? prev + 1 : 0));
|
|
15
|
+
}
|
|
16
|
+
if (_input === ' ') {
|
|
17
|
+
const item = items[cursor];
|
|
18
|
+
if (!item)
|
|
19
|
+
return;
|
|
20
|
+
setSelected((prev) => {
|
|
21
|
+
const next = new Set(prev);
|
|
22
|
+
if (next.has(item.value)) {
|
|
23
|
+
next.delete(item.value);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
next.add(item.value);
|
|
27
|
+
}
|
|
28
|
+
return next;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (key.return) {
|
|
32
|
+
const selectedItems = items.filter((i) => selected.has(i.value));
|
|
33
|
+
onSubmit(selectedItems);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
const highlightedItem = items[cursor];
|
|
37
|
+
const highlightedDescription = highlightedItem?.description;
|
|
38
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Text, { color: colors.honey, children: [symbols.arrow, " "] }), _jsx(Text, { color: colors.white, bold: true, children: label })] }), _jsx(Box, { marginLeft: 2, marginBottom: 1, children: _jsx(Text, { color: colors.grayDim, italic: true, children: "All selected by default \u2014 deselect what you don't want" }) }), _jsx(Box, { flexDirection: "column", marginLeft: 2, children: items.map((item, i) => {
|
|
39
|
+
const isCursor = i === cursor;
|
|
40
|
+
const isSelected = selected.has(item.value);
|
|
41
|
+
const checkbox = isSelected ? '◆' : '◇';
|
|
42
|
+
const itemColor = isCursor ? colors.honey : colors.white;
|
|
43
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: colors.honey, children: [isCursor ? symbols.diamond : ' ', " "] }), _jsxs(Text, { color: isSelected ? colors.honey : colors.grayDim, children: [checkbox, " "] }), _jsx(Text, { color: itemColor, children: item.label })] }, item.value));
|
|
44
|
+
}) }), highlightedDescription && (_jsx(Box, { marginLeft: 4, marginTop: 1, children: _jsxs(Text, { color: colors.gray, italic: true, children: [symbols.arrow, " ", highlightedDescription] }) })), _jsx(Box, { marginLeft: 2, marginTop: 1, children: _jsxs(Text, { color: colors.grayDim, children: [_jsx(Text, { color: colors.honey, children: "space" }), " toggle ", _jsx(Text, { color: colors.honey, children: "enter" }), " confirm"] }) })] }));
|
|
45
|
+
}
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
2
3
|
import { Box, Text } from 'ink';
|
|
3
4
|
import SelectInput from 'ink-select-input';
|
|
4
5
|
import { colors, symbols } from '../theme.js';
|
|
5
6
|
export function SelectPrompt({ label, items, onSelect }) {
|
|
7
|
+
const [highlightedValue, setHighlightedValue] = useState(items[0]?.value ?? '');
|
|
6
8
|
const handleSelect = (item) => {
|
|
7
9
|
const found = items.find((i) => i.value === item.value);
|
|
8
10
|
if (found) {
|
|
9
11
|
onSelect(found);
|
|
10
12
|
}
|
|
11
13
|
};
|
|
12
|
-
|
|
14
|
+
const handleHighlight = (item) => {
|
|
15
|
+
setHighlightedValue(item.value);
|
|
16
|
+
};
|
|
17
|
+
const highlightedItem = items.find((i) => i.value === highlightedValue);
|
|
18
|
+
const highlightedDescription = highlightedItem?.description;
|
|
19
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: colors.honey, children: [symbols.arrow, " "] }), _jsx(Text, { color: colors.white, bold: true, children: label })] }), _jsx(Box, { marginLeft: 2, children: _jsx(SelectInput, { items: items, onSelect: handleSelect, onHighlight: handleHighlight, indicatorComponent: ({ isSelected }) => (_jsxs(Text, { color: colors.honey, children: [isSelected ? symbols.diamond : ' ', " "] })), itemComponent: ({ isSelected, label: itemLabel }) => (_jsx(Text, { color: isSelected ? colors.honey : colors.white, children: itemLabel })) }) }), highlightedDescription && (_jsx(Box, { marginLeft: 4, marginTop: 1, children: _jsxs(Text, { color: colors.gray, italic: true, children: [symbols.arrow, " ", highlightedDescription] }) }))] }));
|
|
13
20
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import { colors, symbols } from '../theme.js';
|
|
5
|
+
export function StepIndicator({ steps, currentIndex }) {
|
|
6
|
+
return (_jsx(Box, { marginLeft: 1, marginBottom: 1, children: steps.map((step, i) => {
|
|
7
|
+
const isCurrent = i === currentIndex;
|
|
8
|
+
const isCompleted = i < currentIndex;
|
|
9
|
+
let symbol;
|
|
10
|
+
let symbolColor;
|
|
11
|
+
let labelColor;
|
|
12
|
+
let bold = false;
|
|
13
|
+
if (isCurrent) {
|
|
14
|
+
symbol = symbols.dot;
|
|
15
|
+
symbolColor = colors.honey;
|
|
16
|
+
labelColor = colors.honey;
|
|
17
|
+
bold = true;
|
|
18
|
+
}
|
|
19
|
+
else if (isCompleted) {
|
|
20
|
+
symbol = symbols.check;
|
|
21
|
+
symbolColor = colors.green;
|
|
22
|
+
labelColor = colors.gray;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
symbol = symbols.diamondOpen;
|
|
26
|
+
symbolColor = colors.grayDim;
|
|
27
|
+
labelColor = colors.grayDim;
|
|
28
|
+
}
|
|
29
|
+
return (_jsxs(React.Fragment, { children: [i > 0 && _jsx(Text, { children: " " }), _jsx(Text, { color: symbolColor, children: symbol }), _jsxs(Text, { color: labelColor, bold: bold, children: [" ", step.label] })] }, step.key));
|
|
30
|
+
}) }));
|
|
31
|
+
}
|
package/dist/create/CreateApp.js
CHANGED
|
@@ -3,6 +3,7 @@ import { useState, useCallback } from 'react';
|
|
|
3
3
|
import { Box, Text, useApp } from 'ink';
|
|
4
4
|
import { Header } from '../components/Header.js';
|
|
5
5
|
import { AsciiTicker } from '../components/AsciiTicker.js';
|
|
6
|
+
import { StepIndicator } from '../components/StepIndicator.js';
|
|
6
7
|
import { ApiKeyStep } from './steps/ApiKeyStep.js';
|
|
7
8
|
import { NameStep } from './steps/NameStep.js';
|
|
8
9
|
import { IdentityStep } from './steps/IdentityStep.js';
|
|
@@ -36,14 +37,15 @@ function ensureAvatarUrl(content, avatarUrl) {
|
|
|
36
37
|
const STEP_ORDER = ['name', 'identity', 'avatar', 'api-key', 'soul', 'strategy', 'scaffold', 'done'];
|
|
37
38
|
const STEP_LABELS = {
|
|
38
39
|
'api-key': 'API Key',
|
|
39
|
-
'name': '
|
|
40
|
+
'name': 'Name',
|
|
40
41
|
'identity': 'Identity',
|
|
41
42
|
'avatar': 'Avatar',
|
|
42
|
-
'soul': '
|
|
43
|
+
'soul': 'Soul',
|
|
43
44
|
'strategy': 'Strategy',
|
|
44
45
|
'scaffold': 'Scaffold',
|
|
45
46
|
'done': 'Done',
|
|
46
47
|
};
|
|
48
|
+
const STEP_DEFS = STEP_ORDER.map((s) => ({ key: s, label: STEP_LABELS[s] }));
|
|
47
49
|
export function CreateApp({ initialName }) {
|
|
48
50
|
const { exit } = useApp();
|
|
49
51
|
const [step, setStep] = useState(initialName ? 'identity' : 'name');
|
|
@@ -54,12 +56,14 @@ export function CreateApp({ initialName }) {
|
|
|
54
56
|
const [personality, setPersonality] = useState('');
|
|
55
57
|
const [tone, setTone] = useState('');
|
|
56
58
|
const [voiceStyle, setVoiceStyle] = useState('');
|
|
59
|
+
const [tradingStyle, setTradingStyle] = useState('');
|
|
60
|
+
const [sectors, setSectors] = useState([]);
|
|
57
61
|
const [avatarUrl, setAvatarUrl] = useState('');
|
|
58
62
|
const [soulContent, setSoulContent] = useState('');
|
|
59
63
|
const [strategyContent, setStrategyContent] = useState('');
|
|
60
64
|
const [resolvedProjectDir, setResolvedProjectDir] = useState('');
|
|
61
65
|
const [error, setError] = useState('');
|
|
62
|
-
const
|
|
66
|
+
const stepIndex = STEP_ORDER.indexOf(step);
|
|
63
67
|
const provider = providerId ? getProvider(providerId) : null;
|
|
64
68
|
const handleApiKey = useCallback((result) => {
|
|
65
69
|
setProviderId(result.providerId);
|
|
@@ -74,6 +78,8 @@ export function CreateApp({ initialName }) {
|
|
|
74
78
|
setPersonality(result.personality);
|
|
75
79
|
setTone(result.tone);
|
|
76
80
|
setVoiceStyle(result.voiceStyle);
|
|
81
|
+
setTradingStyle(result.tradingStyle);
|
|
82
|
+
setSectors(result.sectors);
|
|
77
83
|
setBio(result.bio);
|
|
78
84
|
setStep('avatar');
|
|
79
85
|
}, []);
|
|
@@ -98,5 +104,5 @@ export function CreateApp({ initialName }) {
|
|
|
98
104
|
setError(message);
|
|
99
105
|
exit();
|
|
100
106
|
}, [exit]);
|
|
101
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, {
|
|
107
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, {}), _jsx(StepIndicator, { steps: STEP_DEFS, currentIndex: stepIndex }), step !== 'done' && (_jsx(Box, { marginBottom: 1, children: _jsx(AsciiTicker, { step: stepIndex + 1 }) })), step === 'api-key' && (_jsx(ApiKeyStep, { onComplete: handleApiKey })), step === 'name' && (_jsx(NameStep, { onComplete: handleName })), step === 'identity' && (_jsx(IdentityStep, { agentName: agentName, onComplete: handleIdentity })), step === 'avatar' && (_jsx(AvatarStep, { agentName: agentName, onComplete: handleAvatar })), step === 'soul' && providerId && (_jsx(SoulStep, { providerId: providerId, apiKey: apiKey, agentName: agentName, bio: bio, avatarUrl: avatarUrl, personality: personality, tone: tone, voiceStyle: voiceStyle, tradingStyle: tradingStyle, sectors: sectors, onComplete: handleSoul })), step === 'strategy' && providerId && (_jsx(StrategyStep, { providerId: providerId, apiKey: apiKey, agentName: agentName, bio: bio, personality: personality, tone: tone, voiceStyle: voiceStyle, tradingStyle: tradingStyle, sectors: sectors, onComplete: handleStrategy })), step === 'scaffold' && provider && (_jsx(ScaffoldStep, { projectName: agentName, provider: provider, apiKey: apiKey, soulContent: soulContent, strategyContent: strategyContent, onComplete: handleScaffoldComplete, onError: handleScaffoldError })), step === 'done' && (_jsx(DoneStep, { projectDir: resolvedProjectDir })), error !== '' && (_jsx(Box, { marginTop: 1, marginLeft: 2, children: _jsxs(Text, { color: colors.red, children: [symbols.cross, " ", error] }) }))] }));
|
|
102
108
|
}
|
|
@@ -25,13 +25,16 @@ function buildPresetExamples() {
|
|
|
25
25
|
return `${soulExamples}\n\n===\n\n${strategyExamples}`;
|
|
26
26
|
}
|
|
27
27
|
const PRESET_EXAMPLES = buildPresetExamples();
|
|
28
|
-
export function streamSoul(providerId, apiKey, agentName, bio, avatarUrl, personality, tone, voiceStyle, feedback) {
|
|
28
|
+
export function streamSoul(providerId, apiKey, agentName, bio, avatarUrl, personality, tone, voiceStyle, tradingStyle, sectors, feedback) {
|
|
29
29
|
const feedbackLine = feedback
|
|
30
30
|
? `\n\nThe user gave this feedback on the previous draft. Adjust accordingly:\n"${feedback}"`
|
|
31
31
|
: '';
|
|
32
|
+
const sectorsLine = sectors.length > 0 ? sectors.join(', ') : 'all categories';
|
|
32
33
|
const identityContext = `Personality: ${personality}
|
|
33
34
|
Tone: ${tone}
|
|
34
|
-
Voice style: ${voiceStyle}
|
|
35
|
+
Voice style: ${voiceStyle}
|
|
36
|
+
Trading style: ${tradingStyle}
|
|
37
|
+
Sectors: ${sectorsLine}`;
|
|
35
38
|
const prompt = `You are a creative writer designing an AI agent's personality profile for a crypto trading bot called "${agentName}".
|
|
36
39
|
|
|
37
40
|
The agent's bio is: "${bio}"
|
|
@@ -68,13 +71,16 @@ Use these as style/quality references but create something UNIQUE based on the a
|
|
|
68
71
|
});
|
|
69
72
|
return result.textStream;
|
|
70
73
|
}
|
|
71
|
-
export function streamStrategy(providerId, apiKey, agentName, bio, personality, tone, voiceStyle, feedback) {
|
|
74
|
+
export function streamStrategy(providerId, apiKey, agentName, bio, personality, tone, voiceStyle, tradingStyle, sectors, feedback) {
|
|
72
75
|
const feedbackLine = feedback
|
|
73
76
|
? `\n\nThe user gave this feedback on the previous draft. Adjust accordingly:\n"${feedback}"`
|
|
74
77
|
: '';
|
|
78
|
+
const sectorsLine = sectors.length > 0 ? sectors.join(', ') : 'all categories';
|
|
75
79
|
const identityContext = `Personality: ${personality}
|
|
76
80
|
Tone: ${tone}
|
|
77
|
-
Voice style: ${voiceStyle}
|
|
81
|
+
Voice style: ${voiceStyle}
|
|
82
|
+
Trading style: ${tradingStyle}
|
|
83
|
+
Sectors: ${sectorsLine}`;
|
|
78
84
|
const prompt = `You are designing a prediction strategy profile for a crypto trading bot called "${agentName}".
|
|
79
85
|
|
|
80
86
|
The agent's bio is: "${bio}"
|
|
@@ -10,5 +10,5 @@ export function DoneStep({ projectDir }) {
|
|
|
10
10
|
const termWidth = process.stdout.columns || 60;
|
|
11
11
|
const boxWidth = Math.min(termWidth - 4, 60);
|
|
12
12
|
const line = border.horizontal.repeat(boxWidth - 2);
|
|
13
|
-
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsx(Box, { children: _jsxs(Text, { color: colors.honey, children: [border.topLeft, line, border.topRight] }) }), _jsxs(Box, { children: [_jsx(Text, { color: colors.honey, children: border.vertical }), _jsx(Text, { children: " " }), _jsxs(Text, { color: colors.honey, bold: true, children: [symbols.hive, " Agent created successfully!"] }), _jsx(Text, { children: ' '.repeat(Math.max(0, boxWidth - 32)) }), _jsx(Text, { color: colors.honey, children: border.vertical })] }), _jsx(Box, { children: _jsxs(Text, { color: colors.honey, children: [border.bottomLeft, line, border.bottomRight] }) }), _jsxs(Box, { flexDirection: "column", marginTop: 1, marginLeft: 1, children: [_jsx(Text, { color: colors.white, bold: true, children: "Next steps:" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsxs(Text, { color: colors.gray, children: [" 1. ", _jsx(Text, { color: colors.white, children: "npx @hive-org/cli@latest start" })] }) }),
|
|
13
|
+
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsx(Box, { children: _jsxs(Text, { color: colors.honey, children: [border.topLeft, line, border.topRight] }) }), _jsxs(Box, { children: [_jsx(Text, { color: colors.honey, children: border.vertical }), _jsx(Text, { children: " " }), _jsxs(Text, { color: colors.honey, bold: true, children: [symbols.hive, " Agent created successfully!"] }), _jsx(Text, { children: ' '.repeat(Math.max(0, boxWidth - 32)) }), _jsx(Text, { color: colors.honey, children: border.vertical })] }), _jsx(Box, { children: _jsxs(Text, { color: colors.honey, children: [border.bottomLeft, line, border.bottomRight] }) }), _jsxs(Box, { flexDirection: "column", marginTop: 1, marginLeft: 1, children: [_jsx(Text, { color: colors.white, bold: true, children: "Next steps:" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsxs(Text, { color: colors.gray, children: [" 1. ", _jsx(Text, { color: colors.white, children: "npx @hive-org/cli@latest start" })] }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.grayDim, children: " Fine-tune SOUL.md and STRATEGY.md by chatting with your agent during" }), _jsx(Text, { color: colors.grayDim, children: " a run, or edit them directly at:" }), _jsxs(Text, { color: colors.grayDim, children: [" ", _jsx(Text, { color: colors.white, children: projectDir })] })] })] })] }));
|
|
14
14
|
}
|
|
@@ -1,72 +1,128 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useCallback } from 'react';
|
|
3
|
-
import { Box } from 'ink';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
4
|
import { SelectPrompt } from '../../components/SelectPrompt.js';
|
|
5
|
+
import { MultiSelectPrompt } from '../../components/MultiSelectPrompt.js';
|
|
5
6
|
import { TextPrompt } from '../../components/TextPrompt.js';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
import { CharacterSummaryCard } from '../../components/CharacterSummaryCard.js';
|
|
8
|
+
import { PERSONALITY_OPTIONS, VOICE_OPTIONS, TRADING_STYLE_OPTIONS, PROJECT_CATEGORY_OPTIONS, BIO_EXAMPLES, } from '../../presets.js';
|
|
9
|
+
import { colors, symbols } from '../../theme.js';
|
|
10
|
+
function buildPersonalityItems() {
|
|
11
|
+
const items = PERSONALITY_OPTIONS.map((opt) => ({
|
|
12
|
+
label: opt.label,
|
|
13
|
+
value: opt.value,
|
|
14
|
+
description: opt.description,
|
|
11
15
|
}));
|
|
12
16
|
items.push({ label: 'Custom...', value: '__custom__' });
|
|
13
17
|
return items;
|
|
14
18
|
}
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
function buildVoiceItems() {
|
|
20
|
+
const items = VOICE_OPTIONS.map((opt) => ({
|
|
21
|
+
label: opt.label,
|
|
22
|
+
value: opt.value,
|
|
23
|
+
description: opt.description,
|
|
24
|
+
}));
|
|
25
|
+
items.push({ label: 'Custom...', value: '__custom__' });
|
|
26
|
+
return items;
|
|
27
|
+
}
|
|
28
|
+
function buildTradingStyleItems() {
|
|
29
|
+
const items = TRADING_STYLE_OPTIONS.map((opt) => ({
|
|
30
|
+
label: opt.label,
|
|
31
|
+
value: opt.value,
|
|
32
|
+
description: opt.description,
|
|
33
|
+
}));
|
|
34
|
+
items.push({ label: 'Custom...', value: '__custom__' });
|
|
35
|
+
return items;
|
|
36
|
+
}
|
|
37
|
+
function buildCategoryItems() {
|
|
38
|
+
const items = PROJECT_CATEGORY_OPTIONS.map((opt) => ({
|
|
39
|
+
label: opt.label,
|
|
40
|
+
value: opt.value,
|
|
41
|
+
description: opt.description,
|
|
42
|
+
}));
|
|
43
|
+
return items;
|
|
44
|
+
}
|
|
45
|
+
const personalityItems = buildPersonalityItems();
|
|
46
|
+
const voiceItems = buildVoiceItems();
|
|
47
|
+
const tradingStyleItems = buildTradingStyleItems();
|
|
48
|
+
const categoryItems = buildCategoryItems();
|
|
49
|
+
export function IdentityStep({ agentName, onComplete }) {
|
|
19
50
|
const [subStep, setSubStep] = useState('personality');
|
|
51
|
+
const [personalityLabel, setPersonalityLabel] = useState('');
|
|
20
52
|
const [personality, setPersonality] = useState('');
|
|
21
53
|
const [tone, setTone] = useState('');
|
|
22
54
|
const [voiceStyle, setVoiceStyle] = useState('');
|
|
55
|
+
const [voiceLabel, setVoiceLabel] = useState('');
|
|
56
|
+
const [tradingStyle, setTradingStyle] = useState('');
|
|
57
|
+
const [tradingStyleLabel, setTradingStyleLabel] = useState('');
|
|
58
|
+
const [sectors, setSectors] = useState([]);
|
|
59
|
+
const [sectorsLabel, setSectorsLabel] = useState('');
|
|
23
60
|
const handlePersonalitySelect = useCallback((item) => {
|
|
24
61
|
if (item.value === '__custom__') {
|
|
25
62
|
setSubStep('personality-custom');
|
|
26
63
|
return;
|
|
27
64
|
}
|
|
28
|
-
|
|
29
|
-
|
|
65
|
+
const description = item.description ?? '';
|
|
66
|
+
const personalityValue = `${item.label} — ${description}`;
|
|
67
|
+
setPersonality(personalityValue);
|
|
68
|
+
setPersonalityLabel(item.label);
|
|
69
|
+
setSubStep('voice');
|
|
30
70
|
}, []);
|
|
31
71
|
const handlePersonalityCustom = useCallback((value) => {
|
|
32
72
|
setPersonality(value);
|
|
33
|
-
|
|
73
|
+
setPersonalityLabel(value);
|
|
74
|
+
setSubStep('voice');
|
|
34
75
|
}, []);
|
|
35
|
-
const
|
|
76
|
+
const handleVoiceSelect = useCallback((item) => {
|
|
36
77
|
if (item.value === '__custom__') {
|
|
37
|
-
setSubStep('
|
|
78
|
+
setSubStep('voice-custom');
|
|
38
79
|
return;
|
|
39
80
|
}
|
|
40
|
-
|
|
41
|
-
|
|
81
|
+
const voiceOption = VOICE_OPTIONS.find((v) => v.value === item.value);
|
|
82
|
+
setTone(voiceOption.tone);
|
|
83
|
+
setVoiceStyle(voiceOption.voiceStyle);
|
|
84
|
+
setVoiceLabel(item.label);
|
|
85
|
+
setSubStep('trading');
|
|
42
86
|
}, []);
|
|
43
|
-
const
|
|
87
|
+
const handleVoiceCustom = useCallback((value) => {
|
|
44
88
|
setTone(value);
|
|
45
|
-
|
|
89
|
+
setVoiceStyle(value);
|
|
90
|
+
setVoiceLabel(value);
|
|
91
|
+
setSubStep('trading');
|
|
46
92
|
}, []);
|
|
47
|
-
const
|
|
93
|
+
const handleTradingStyleSelect = useCallback((item) => {
|
|
48
94
|
if (item.value === '__custom__') {
|
|
49
|
-
setSubStep('
|
|
95
|
+
setSubStep('trading-custom');
|
|
50
96
|
return;
|
|
51
97
|
}
|
|
52
|
-
|
|
53
|
-
|
|
98
|
+
setTradingStyle(item.value);
|
|
99
|
+
setTradingStyleLabel(item.label);
|
|
100
|
+
setSubStep('sectors');
|
|
54
101
|
}, []);
|
|
55
|
-
const
|
|
56
|
-
|
|
102
|
+
const handleTradingStyleCustom = useCallback((value) => {
|
|
103
|
+
setTradingStyle(value);
|
|
104
|
+
setTradingStyleLabel(value);
|
|
105
|
+
setSubStep('sectors');
|
|
106
|
+
}, []);
|
|
107
|
+
const handleSectors = useCallback((selected) => {
|
|
108
|
+
const values = selected.map((s) => s.value);
|
|
109
|
+
const labels = selected.map((s) => s.label);
|
|
110
|
+
setSectors(values);
|
|
111
|
+
const displayLabel = values.length === categoryItems.length ? 'All' : labels.join(', ');
|
|
112
|
+
setSectorsLabel(displayLabel);
|
|
57
113
|
setSubStep('bio');
|
|
58
114
|
}, []);
|
|
59
115
|
const handleBio = useCallback((value) => {
|
|
60
|
-
const result = { personality, tone, voiceStyle, bio: value };
|
|
116
|
+
const result = { personality, tone, voiceStyle, tradingStyle, sectors, bio: value };
|
|
61
117
|
onComplete(result);
|
|
62
|
-
}, [personality, tone, voiceStyle, onComplete]);
|
|
63
|
-
return (_jsxs(Box, { flexDirection: "column", children: [subStep === 'personality' && (_jsx(SelectPrompt, { label: "Choose a personality", items: personalityItems, onSelect: handlePersonalitySelect })), subStep === 'personality-custom' && (_jsx(TextPrompt, { label: "Describe your agent's personality", placeholder: "e.g. stoic realist with a dry wit", onSubmit: handlePersonalityCustom, validate: (val) => (!val ? 'Personality is required' : true) })), subStep === '
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
118
|
+
}, [personality, tone, voiceStyle, tradingStyle, sectors, onComplete]);
|
|
119
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(CharacterSummaryCard, { name: agentName, personality: personalityLabel || undefined, voice: voiceLabel || undefined, tradingStyle: tradingStyleLabel || undefined, sectors: sectorsLabel || undefined }), subStep === 'personality' && (_jsx(SelectPrompt, { label: "Choose a personality", items: personalityItems, onSelect: handlePersonalitySelect })), subStep === 'personality-custom' && (_jsx(TextPrompt, { label: "Describe your agent's personality", placeholder: "e.g. stoic realist with a dry wit", onSubmit: handlePersonalityCustom, validate: (val) => (!val ? 'Personality is required' : true) })), subStep === 'voice' && (_jsx(SelectPrompt, { label: "Choose a voice", items: voiceItems, onSelect: handleVoiceSelect })), subStep === 'voice-custom' && (_jsx(TextPrompt, { label: "Describe your agent's voice", placeholder: "e.g. writes like a bloomberg terminal on acid", onSubmit: handleVoiceCustom, validate: (val) => (!val ? 'Voice is required' : true) })), subStep === 'trading' && (_jsx(SelectPrompt, { label: "How does your agent evaluate signals?", items: tradingStyleItems, onSelect: handleTradingStyleSelect })), subStep === 'trading-custom' && (_jsx(TextPrompt, { label: "Describe your agent's trading style", placeholder: "e.g. combines on-chain data with sentiment analysis", onSubmit: handleTradingStyleCustom, validate: (val) => (!val ? 'Trading style is required' : true) })), subStep === 'sectors' && (_jsx(MultiSelectPrompt, { label: "Which categories should your agent trade?", items: categoryItems, onSubmit: handleSectors })), subStep === 'bio' && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [_jsxs(Text, { color: colors.grayDim, italic: true, children: [symbols.arrow, " Examples:"] }), BIO_EXAMPLES.map((example, i) => (_jsx(Box, { marginLeft: 2, marginTop: i > 0 ? 1 : 0, children: _jsxs(Text, { color: colors.grayDim, italic: true, children: [symbols.diamond, " ", `"${example}"`] }) }, i)))] }), _jsx(TextPrompt, { label: "Write your agent's bio", placeholder: `short bio for your ${personalityLabel} agent`, onSubmit: handleBio, maxLength: 1000, validate: (val) => {
|
|
120
|
+
if (!val) {
|
|
121
|
+
return 'Bio is required';
|
|
122
|
+
}
|
|
123
|
+
if (val.length > 1000) {
|
|
124
|
+
return `Bio must be 1000 characters or less (currently ${val.length})`;
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
} })] }))] }));
|
|
72
128
|
}
|
|
@@ -7,11 +7,11 @@ import { CodeBlock } from '../../components/CodeBlock.js';
|
|
|
7
7
|
import { Spinner } from '../../components/Spinner.js';
|
|
8
8
|
import { colors, symbols } from '../../theme.js';
|
|
9
9
|
import { streamSoul } from '../ai-generate.js';
|
|
10
|
-
export function SoulStep({ providerId, apiKey, agentName, bio, avatarUrl, personality, tone, voiceStyle, onComplete, }) {
|
|
10
|
+
export function SoulStep({ providerId, apiKey, agentName, bio, avatarUrl, personality, tone, voiceStyle, tradingStyle, sectors, onComplete, }) {
|
|
11
11
|
const [phase, setPhase] = useState('streaming');
|
|
12
12
|
const [draft, setDraft] = useState('');
|
|
13
13
|
const [feedbackCount, setFeedbackCount] = useState(0);
|
|
14
|
-
const stream = useMemo(() => streamSoul(providerId, apiKey, agentName, bio, avatarUrl, personality, tone, voiceStyle), []);
|
|
14
|
+
const stream = useMemo(() => streamSoul(providerId, apiKey, agentName, bio, avatarUrl, personality, tone, voiceStyle, tradingStyle, sectors), []);
|
|
15
15
|
const [currentStream, setCurrentStream] = useState(stream);
|
|
16
16
|
const handleStreamComplete = useCallback((fullText) => {
|
|
17
17
|
setDraft(fullText);
|
|
@@ -24,9 +24,9 @@ export function SoulStep({ providerId, apiKey, agentName, bio, avatarUrl, person
|
|
|
24
24
|
setFeedbackCount((prev) => prev + 1);
|
|
25
25
|
setPhase('streaming');
|
|
26
26
|
setDraft('');
|
|
27
|
-
const newStream = streamSoul(providerId, apiKey, agentName, bio, avatarUrl, personality, tone, voiceStyle, feedback);
|
|
27
|
+
const newStream = streamSoul(providerId, apiKey, agentName, bio, avatarUrl, personality, tone, voiceStyle, tradingStyle, sectors, feedback);
|
|
28
28
|
setCurrentStream(newStream);
|
|
29
|
-
}, [providerId, apiKey, agentName, bio, avatarUrl, personality, tone, voiceStyle]);
|
|
29
|
+
}, [providerId, apiKey, agentName, bio, avatarUrl, personality, tone, voiceStyle, tradingStyle, sectors]);
|
|
30
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
31
|
if (!val) {
|
|
32
32
|
handleAccept();
|
|
@@ -7,11 +7,11 @@ import { CodeBlock } from '../../components/CodeBlock.js';
|
|
|
7
7
|
import { Spinner } from '../../components/Spinner.js';
|
|
8
8
|
import { colors, symbols } from '../../theme.js';
|
|
9
9
|
import { streamStrategy } from '../ai-generate.js';
|
|
10
|
-
export function StrategyStep({ providerId, apiKey, agentName, bio, personality, tone, voiceStyle, onComplete, }) {
|
|
10
|
+
export function StrategyStep({ providerId, apiKey, agentName, bio, personality, tone, voiceStyle, tradingStyle, sectors, onComplete, }) {
|
|
11
11
|
const [phase, setPhase] = useState('streaming');
|
|
12
12
|
const [draft, setDraft] = useState('');
|
|
13
13
|
const [feedbackCount, setFeedbackCount] = useState(0);
|
|
14
|
-
const stream = useMemo(() => streamStrategy(providerId, apiKey, agentName, bio, personality, tone, voiceStyle), []);
|
|
14
|
+
const stream = useMemo(() => streamStrategy(providerId, apiKey, agentName, bio, personality, tone, voiceStyle, tradingStyle, sectors), []);
|
|
15
15
|
const [currentStream, setCurrentStream] = useState(stream);
|
|
16
16
|
const handleStreamComplete = useCallback((fullText) => {
|
|
17
17
|
setDraft(fullText);
|
|
@@ -24,9 +24,9 @@ export function StrategyStep({ providerId, apiKey, agentName, bio, personality,
|
|
|
24
24
|
setFeedbackCount((prev) => prev + 1);
|
|
25
25
|
setPhase('streaming');
|
|
26
26
|
setDraft('');
|
|
27
|
-
const newStream = streamStrategy(providerId, apiKey, agentName, bio, personality, tone, voiceStyle, feedback);
|
|
27
|
+
const newStream = streamStrategy(providerId, apiKey, agentName, bio, personality, tone, voiceStyle, tradingStyle, sectors, feedback);
|
|
28
28
|
setCurrentStream(newStream);
|
|
29
|
-
}, [providerId, apiKey, agentName, bio, personality, tone, voiceStyle]);
|
|
29
|
+
}, [providerId, apiKey, agentName, bio, personality, tone, voiceStyle, tradingStyle, sectors]);
|
|
30
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
31
|
if (!val) {
|
|
32
32
|
handleAccept();
|
package/dist/index.js
CHANGED
|
@@ -14,8 +14,9 @@ const HELP_TEXT = `@hive-org/cli v${pkg.version}
|
|
|
14
14
|
Usage:
|
|
15
15
|
npx @hive-org/cli@latest create [agent-name] Scaffold a new Hive agent
|
|
16
16
|
npx @hive-org/cli@latest list List existing agents
|
|
17
|
-
npx @hive-org/cli@latest start
|
|
17
|
+
npx @hive-org/cli@latest start Start an agent (auto-detects agent dir)
|
|
18
18
|
npx @hive-org/cli@latest start-all Start all agents
|
|
19
|
+
npx @hive-org/cli@latest run Run agent headless (no TUI, used by start-all)
|
|
19
20
|
npx @hive-org/cli@latest migrate-templates Migrate old-style agents
|
|
20
21
|
npx @hive-org/cli@latest --help Show this help message
|
|
21
22
|
npx @hive-org/cli@latest --version Print version
|
|
@@ -55,20 +56,32 @@ else if (command === 'migrate-templates') {
|
|
|
55
56
|
const { waitUntilExit } = render(React.createElement(MigrateApp));
|
|
56
57
|
await waitUntilExit();
|
|
57
58
|
}
|
|
59
|
+
else if (command === 'run') {
|
|
60
|
+
// Headless agent run — no TUI, just console output.
|
|
61
|
+
// Used by start-all to spawn agents as child processes.
|
|
62
|
+
const { access } = await import('fs/promises');
|
|
63
|
+
const { join } = await import('path');
|
|
64
|
+
const isAgentDir = await access(join(process.cwd(), 'SOUL.md'))
|
|
65
|
+
.then(() => true)
|
|
66
|
+
.catch(() => false);
|
|
67
|
+
if (!isAgentDir) {
|
|
68
|
+
console.error('Error: "run" must be called from an agent directory (with SOUL.md)');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
await import('dotenv/config');
|
|
72
|
+
const { runHeadless } = await import('./agent/run-headless.js');
|
|
73
|
+
await runHeadless();
|
|
74
|
+
}
|
|
58
75
|
else if (command === 'start') {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const picked = selectedAgent;
|
|
69
|
-
const { showHoneycombBoot } = await import('./agent/components/HoneycombBoot.js');
|
|
70
|
-
await showHoneycombBoot(picked.name);
|
|
71
|
-
process.chdir(picked.dir);
|
|
76
|
+
// Detect if cwd is an agent directory (has SOUL.md).
|
|
77
|
+
// When called via agent's "npm start", cwd is the agent dir.
|
|
78
|
+
const { access } = await import('fs/promises');
|
|
79
|
+
const { join } = await import('path');
|
|
80
|
+
const isAgentDir = await access(join(process.cwd(), 'SOUL.md'))
|
|
81
|
+
.then(() => true)
|
|
82
|
+
.catch(() => false);
|
|
83
|
+
if (isAgentDir) {
|
|
84
|
+
// Direct agent run — cwd is already the agent directory.
|
|
72
85
|
await import('dotenv/config');
|
|
73
86
|
const { setupProcessLifecycle } = await import('./agent/process-lifecycle.js');
|
|
74
87
|
const { App } = await import('./agent/app.js');
|
|
@@ -76,6 +89,29 @@ else if (command === 'start') {
|
|
|
76
89
|
const { waitUntilExit } = render(React.createElement(App));
|
|
77
90
|
await waitUntilExit();
|
|
78
91
|
}
|
|
92
|
+
else {
|
|
93
|
+
// Interactive agent selection
|
|
94
|
+
await showWelcome();
|
|
95
|
+
let selectedAgent = null;
|
|
96
|
+
const { waitUntilExit: waitForSelect } = render(React.createElement(SelectAgentApp, {
|
|
97
|
+
onSelect: (agent) => {
|
|
98
|
+
selectedAgent = agent;
|
|
99
|
+
},
|
|
100
|
+
}));
|
|
101
|
+
await waitForSelect();
|
|
102
|
+
if (selectedAgent) {
|
|
103
|
+
const picked = selectedAgent;
|
|
104
|
+
const { showHoneycombBoot } = await import('./agent/components/HoneycombBoot.js');
|
|
105
|
+
await showHoneycombBoot(picked.name);
|
|
106
|
+
process.chdir(picked.dir);
|
|
107
|
+
await import('dotenv/config');
|
|
108
|
+
const { setupProcessLifecycle } = await import('./agent/process-lifecycle.js');
|
|
109
|
+
const { App } = await import('./agent/app.js');
|
|
110
|
+
setupProcessLifecycle();
|
|
111
|
+
const { waitUntilExit } = render(React.createElement(App));
|
|
112
|
+
await waitUntilExit();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
79
115
|
}
|
|
80
116
|
else {
|
|
81
117
|
console.error(`Unknown command: ${command}\n`);
|
package/dist/presets.js
CHANGED
|
@@ -249,28 +249,202 @@ export const STRATEGY_PRESETS = [
|
|
|
249
249
|
},
|
|
250
250
|
];
|
|
251
251
|
export const PERSONALITY_OPTIONS = [
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
252
|
+
{
|
|
253
|
+
label: '🪞 Contrarian',
|
|
254
|
+
value: 'contrarian',
|
|
255
|
+
description: 'Fades the crowd. When everyone is long, asks why. Bets against consensus.',
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
label: '📈 Momentum Trader',
|
|
259
|
+
value: 'momentum-trader',
|
|
260
|
+
description: "Rides trends until they break. If it's pumping, there's a reason.",
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
label: '📊 Data Purist',
|
|
264
|
+
value: 'data-purist',
|
|
265
|
+
description: 'Numbers only. No narratives, no vibes — on-chain data and technicals.',
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
label: '📰 Narrative Trader',
|
|
269
|
+
value: 'narrative-trader',
|
|
270
|
+
description: 'Trades the story, not the chart. Catches rotations before the crowd.',
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
label: '🛡️ Cautious Operator',
|
|
274
|
+
value: 'cautious-operator',
|
|
275
|
+
description: 'Risk-first thinking. Small sizing, tight stops, lives to trade another day.',
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
label: '🎰 Degen',
|
|
279
|
+
value: 'degen',
|
|
280
|
+
description: 'Max conviction or skip. High risk, high reward, no regrets.',
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
label: '🌍 Macro Thinker',
|
|
284
|
+
value: 'macro-thinker',
|
|
285
|
+
description: "Zooms out. Rates, liquidity, DXY — crypto doesn't exist in a vacuum.",
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
label: '🏗️ Fundamentalist',
|
|
289
|
+
value: 'fundamentalist',
|
|
290
|
+
description: 'Protocol revenue, real users, actual moats. Price catches up eventually.',
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
label: '🔍 Skeptic',
|
|
294
|
+
value: 'skeptic',
|
|
295
|
+
description: 'Pokes holes in every thesis. If the bull case survives, it might be real.',
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
label: '⚡ Opportunist',
|
|
299
|
+
value: 'opportunist',
|
|
300
|
+
description: 'No fixed playbook. Reads the room and takes whatever edge shows up.',
|
|
301
|
+
},
|
|
302
|
+
];
|
|
303
|
+
export const VOICE_OPTIONS = [
|
|
304
|
+
{
|
|
305
|
+
label: '🐸 CT Native',
|
|
306
|
+
value: 'ct-native',
|
|
307
|
+
description: 'Speaks fluent crypto twitter. Irony, slang, memes — alpha buried in the shitposts.',
|
|
308
|
+
tone: 'sarcastic',
|
|
309
|
+
voiceStyle: 'CT native slang',
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
label: '🖥️ Wire Service',
|
|
313
|
+
value: 'wire-service',
|
|
314
|
+
description: 'Flat, factual, no filler. Reads like a terminal feed.',
|
|
315
|
+
tone: 'deadpan',
|
|
316
|
+
voiceStyle: 'data-driven',
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
label: '🎙️ Storyteller',
|
|
320
|
+
value: 'storyteller',
|
|
321
|
+
description: 'Builds a narrative around every call. Confident delivery, draws you in.',
|
|
322
|
+
tone: 'confident',
|
|
323
|
+
voiceStyle: 'storyteller',
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
label: '🔥 Unfiltered',
|
|
327
|
+
value: 'unfiltered',
|
|
328
|
+
description: "Raw, terse, no filter. Says what others won't in as few words as possible.",
|
|
329
|
+
tone: 'unhinged',
|
|
330
|
+
voiceStyle: 'terse & punchy',
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
label: '✂️ Dry Wit',
|
|
334
|
+
value: 'dry-wit',
|
|
335
|
+
description: 'Sharp and sarcastic. Short sentences that cut.',
|
|
336
|
+
tone: 'sarcastic',
|
|
337
|
+
voiceStyle: 'terse & punchy',
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
label: '☯️ Calm & Measured',
|
|
341
|
+
value: 'calm-measured',
|
|
342
|
+
description: 'Zen-like composure. Few words, no rush, lets the take breathe.',
|
|
343
|
+
tone: 'zen',
|
|
344
|
+
voiceStyle: 'terse & punchy',
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
label: '🧵 Thread Builder',
|
|
348
|
+
value: 'thread-builder',
|
|
349
|
+
description: 'Long-form breakdowns with conviction. The kind of posts people bookmark.',
|
|
350
|
+
tone: 'confident',
|
|
351
|
+
voiceStyle: 'storyteller',
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
label: '🔢 Numbers Only',
|
|
355
|
+
value: 'numbers-only',
|
|
356
|
+
description: 'Analytical, quiet, lets the data make the case. Charts over opinions.',
|
|
357
|
+
tone: 'analytical',
|
|
358
|
+
voiceStyle: 'data-driven',
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
label: '💣 Hot Take',
|
|
362
|
+
value: 'hot-take',
|
|
363
|
+
description: 'Provocative on purpose. Bold calls in full CT dialect.',
|
|
364
|
+
tone: 'provocative',
|
|
365
|
+
voiceStyle: 'CT native slang',
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
label: '🎓 Academic',
|
|
369
|
+
value: 'academic',
|
|
370
|
+
description: 'Measured and thorough. Cites evidence, hedges appropriately.',
|
|
371
|
+
tone: 'cautious',
|
|
372
|
+
voiceStyle: 'academic',
|
|
373
|
+
},
|
|
257
374
|
];
|
|
258
|
-
export const
|
|
259
|
-
'
|
|
260
|
-
'
|
|
261
|
-
'
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
375
|
+
export const BIO_EXAMPLES = [
|
|
376
|
+
'Survived multiple bear markets and came out buying. The kind of person who sees a 20% dip and tweets "lol free money" unironically.',
|
|
377
|
+
"Quant brain in a CT body. Doesn't care about narratives or community vibes. Posts their read and moves on.",
|
|
378
|
+
'Has been rugged more times than they can count and still apes into new plays. Treats their portfolio like a slot machine with better odds.',
|
|
379
|
+
];
|
|
380
|
+
export const TRADING_STYLE_OPTIONS = [
|
|
381
|
+
{
|
|
382
|
+
label: '📉 Price Action',
|
|
383
|
+
value: 'technical',
|
|
384
|
+
description: "Charts, indicators, support/resistance. If it's not on the chart, it doesn't matter.",
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
label: '🔗 On-chain',
|
|
388
|
+
value: 'onchain',
|
|
389
|
+
description: 'Wallet flows, TVL, protocol metrics. The blockchain is the source of truth.',
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
label: '📣 Sentiment',
|
|
393
|
+
value: 'sentiment',
|
|
394
|
+
description: 'CT buzz, social volume, narrative momentum. The crowd moves price.',
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
label: '🌐 Macro',
|
|
398
|
+
value: 'macro',
|
|
399
|
+
description: "Rates, DXY, global liquidity. Crypto doesn't trade in a vacuum.",
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
label: '🏗️ Fundamental',
|
|
403
|
+
value: 'fundamental',
|
|
404
|
+
description: 'Revenue, users, product quality. Value over hype.',
|
|
405
|
+
},
|
|
267
406
|
];
|
|
268
|
-
export const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
407
|
+
export const PROJECT_CATEGORY_OPTIONS = [
|
|
408
|
+
{
|
|
409
|
+
label: '🔷 Layer 1',
|
|
410
|
+
value: 'l1',
|
|
411
|
+
description: 'Base layer chains — ETH, SOL, BTC, AVAX, etc.',
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
label: '🔶 Layer 2',
|
|
415
|
+
value: 'l2',
|
|
416
|
+
description: 'Rollups and scaling solutions — ARB, OP, BASE, STRK, etc.',
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
label: '🏦 DeFi',
|
|
420
|
+
value: 'defi',
|
|
421
|
+
description: 'Lending, DEXs, yield, stablecoins — AAVE, UNI, MKR, etc.',
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
label: '🤖 AI',
|
|
425
|
+
value: 'ai',
|
|
426
|
+
description: 'AI and compute tokens — FET, RENDER, TAO, AKT, etc.',
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
label: '🐸 Memecoins',
|
|
430
|
+
value: 'meme',
|
|
431
|
+
description: 'Community-driven tokens — DOGE, SHIB, PEPE, WIF, etc.',
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
label: '🏠 RWA',
|
|
435
|
+
value: 'rwa',
|
|
436
|
+
description: 'Real world assets — ONDO, MKR (RWA vaults), tokenized treasuries, etc.',
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
label: '🎮 Gaming',
|
|
440
|
+
value: 'gaming',
|
|
441
|
+
description: 'Gaming and metaverse tokens — IMX, GALA, AXS, etc.',
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
label: '🔐 Infrastructure',
|
|
445
|
+
value: 'infra',
|
|
446
|
+
description: 'Oracles, bridges, storage — LINK, FIL, GRT, etc.',
|
|
447
|
+
},
|
|
274
448
|
];
|
|
275
449
|
function formatBulletList(items) {
|
|
276
450
|
const lines = items.map((item) => `- ${item}`).join('\n');
|
|
@@ -2,7 +2,6 @@ import { spawn } from 'child_process';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
4
|
const FORCE_KILL_TIMEOUT_MS = 5_000;
|
|
5
|
-
const SHUTDOWN_TIMEOUT_MS = 10_000;
|
|
6
5
|
const ABSOLUTE_TIMEOUT_MS = FORCE_KILL_TIMEOUT_MS + 2_000;
|
|
7
6
|
export class AgentProcessManager {
|
|
8
7
|
constructor() {
|
|
@@ -64,41 +63,9 @@ export class AgentProcessManager {
|
|
|
64
63
|
agent.child = null;
|
|
65
64
|
this._spawnPiped(name);
|
|
66
65
|
}
|
|
67
|
-
async shutdownAll() {
|
|
68
|
-
const children = [];
|
|
69
|
-
for (const agent of this._agents.values()) {
|
|
70
|
-
if (agent.child) {
|
|
71
|
-
children.push(agent.child);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (children.length === 0) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const waitForAll = children.map((child) => new Promise((resolve) => {
|
|
78
|
-
if (child.exitCode !== null) {
|
|
79
|
-
resolve();
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
child.on('exit', () => resolve());
|
|
83
|
-
// Absolute safety timeout
|
|
84
|
-
setTimeout(() => resolve(), SHUTDOWN_TIMEOUT_MS + 2_000);
|
|
85
|
-
}));
|
|
86
|
-
for (const child of children) {
|
|
87
|
-
child.kill('SIGTERM');
|
|
88
|
-
}
|
|
89
|
-
const forceKillTimer = setTimeout(() => {
|
|
90
|
-
for (const agent of this._agents.values()) {
|
|
91
|
-
if (agent.child && agent.child.exitCode === null) {
|
|
92
|
-
agent.child.kill('SIGKILL');
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}, SHUTDOWN_TIMEOUT_MS);
|
|
96
|
-
await Promise.all(waitForAll);
|
|
97
|
-
clearTimeout(forceKillTimer);
|
|
98
|
-
}
|
|
99
66
|
_spawnPiped(name) {
|
|
100
67
|
const agentDir = path.join(this._agentsDir, name);
|
|
101
|
-
const child = spawn('
|
|
68
|
+
const child = spawn('npx', ['@hive-org/cli@latest', 'run'], {
|
|
102
69
|
cwd: agentDir,
|
|
103
70
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
104
71
|
});
|