@zhive/cli 0.6.0 → 0.6.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.
Files changed (88) hide show
  1. package/dist/{services/agent → agent}/analysis.js +5 -5
  2. package/dist/agent/app.js +122 -0
  3. package/dist/agent/commands/registry.js +12 -0
  4. package/dist/agent/components/AsciiTicker.js +81 -0
  5. package/dist/agent/components/CommandInput.js +65 -0
  6. package/dist/agent/components/HoneycombBoot.js +291 -0
  7. package/dist/agent/components/Spinner.js +37 -0
  8. package/dist/agent/hooks/useAgent.js +480 -0
  9. package/dist/{services/agent/prompts → agent}/memory-prompt.js +22 -20
  10. package/dist/{services/agent/helpers → agent}/model.js +2 -2
  11. package/dist/agent/process-lifecycle.js +18 -0
  12. package/dist/{services/agent/prompts → agent}/prompt.js +54 -80
  13. package/dist/agent/run-headless.js +189 -0
  14. package/dist/agent/theme.js +41 -0
  15. package/dist/{services/agent → agent}/tools/market/client.js +1 -1
  16. package/dist/{services/agent → agent}/tools/mindshare/client.js +1 -1
  17. package/dist/agent/types.js +1 -0
  18. package/dist/{services/config/agent.js → agents.js} +2 -2
  19. package/dist/avatar.js +34 -0
  20. package/dist/backtest/default-backtest-data.js +200 -0
  21. package/dist/backtest/fetch.js +41 -0
  22. package/dist/backtest/import.js +106 -0
  23. package/dist/backtest/index.js +10 -0
  24. package/dist/backtest/results.js +113 -0
  25. package/dist/backtest/runner.js +134 -0
  26. package/dist/backtest/storage.js +11 -0
  27. package/dist/backtest/types.js +1 -0
  28. package/dist/commands/install.js +50 -0
  29. package/dist/commands/start/ui/PollText.js +23 -0
  30. package/dist/commands/start/ui/PredictionsPanel.js +88 -0
  31. package/dist/commands/start/ui/SpinnerContext.js +20 -0
  32. package/dist/components/InputGuard.js +6 -0
  33. package/dist/components/stdout-spinner.js +48 -0
  34. package/dist/{services/config/config.js → config.js} +7 -1
  35. package/dist/create/CreateApp.js +153 -0
  36. package/dist/create/ai-generate.js +147 -0
  37. package/dist/create/generate.js +73 -0
  38. package/dist/create/steps/ApiKeyStep.js +97 -0
  39. package/dist/create/steps/AvatarStep.js +16 -0
  40. package/dist/create/steps/BioStep.js +14 -0
  41. package/dist/create/steps/DoneStep.js +14 -0
  42. package/dist/create/steps/IdentityStep.js +163 -0
  43. package/dist/create/steps/NameStep.js +71 -0
  44. package/dist/create/steps/ScaffoldStep.js +58 -0
  45. package/dist/create/steps/SoulStep.js +58 -0
  46. package/dist/create/steps/StrategyStep.js +58 -0
  47. package/dist/create/validate-api-key.js +47 -0
  48. package/dist/create/welcome.js +304 -0
  49. package/dist/list/ListApp.js +79 -0
  50. package/dist/{services/agent/env.js → load-agent-env.js} +1 -1
  51. package/dist/migrate-templates/MigrateApp.js +131 -0
  52. package/dist/migrate-templates/migrate.js +86 -0
  53. package/dist/presets.js +613 -0
  54. package/dist/start/AgentProcessManager.js +98 -0
  55. package/dist/start/Dashboard.js +92 -0
  56. package/dist/start/SelectAgentApp.js +81 -0
  57. package/dist/start/StartApp.js +189 -0
  58. package/dist/start/patch-headless.js +101 -0
  59. package/dist/start/patch-managed-mode.js +142 -0
  60. package/dist/start/start-command.js +24 -0
  61. package/dist/theme.js +54 -0
  62. package/package.json +2 -2
  63. package/dist/CLAUDE.md +0 -7
  64. package/dist/backtest/CLAUDE.md +0 -7
  65. package/dist/cli.js +0 -20
  66. package/dist/services/config/constant.js +0 -8
  67. package/dist/shared/agent/config.js +0 -75
  68. package/dist/shared/agent/env.js +0 -30
  69. package/dist/shared/agent/helpers/model.js +0 -92
  70. package/dist/shared/ai-providers.js +0 -66
  71. /package/dist/{services/agent/prompts → agent}/chat-prompt.js +0 -0
  72. /package/dist/{services/agent → agent}/config.js +0 -0
  73. /package/dist/{services/agent/tools → agent}/edit-section.js +0 -0
  74. /package/dist/{services/agent/tools → agent}/fetch-rules.js +0 -0
  75. /package/dist/{services/agent → agent}/helpers.js +0 -0
  76. /package/dist/{services/agent/skills/types.js → agent/objects.js} +0 -0
  77. /package/dist/{services/agent → agent}/skills/index.js +0 -0
  78. /package/dist/{services/agent → agent}/skills/skill-parser.js +0 -0
  79. /package/dist/{services/agent → agent/skills}/types.js +0 -0
  80. /package/dist/{services/agent → agent}/tools/index.js +0 -0
  81. /package/dist/{services/agent → agent}/tools/market/index.js +0 -0
  82. /package/dist/{services/agent → agent}/tools/market/tools.js +0 -0
  83. /package/dist/{services/agent → agent}/tools/mindshare/index.js +0 -0
  84. /package/dist/{services/agent → agent}/tools/mindshare/tools.js +0 -0
  85. /package/dist/{services/agent → agent}/tools/read-skill-tool.js +0 -0
  86. /package/dist/{services/agent → agent}/tools/ta/index.js +0 -0
  87. /package/dist/{services/agent → agent}/tools/ta/indicators.js +0 -0
  88. /package/dist/{services/ai-providers.js → ai-providers.js} +0 -0
@@ -0,0 +1,97 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { Box, Text } from 'ink';
4
+ import { SelectPrompt } from '../../components/SelectPrompt.js';
5
+ import { TextPrompt } from '../../components/TextPrompt.js';
6
+ import { Spinner } from '../../components/Spinner.js';
7
+ import { colors, symbols } from '../../theme.js';
8
+ import { AI_PROVIDERS } from '../../ai-providers.js';
9
+ import { validateApiKey } from '../validate-api-key.js';
10
+ import { readConfig, writeConfig } from '../../config.js';
11
+ function maskKey(key) {
12
+ if (key.length <= 8) {
13
+ return '****';
14
+ }
15
+ const visible = key.slice(0, 4) + '...' + key.slice(-4);
16
+ return visible;
17
+ }
18
+ export function ApiKeyStep({ onComplete }) {
19
+ const [phase, setPhase] = useState('check-saved');
20
+ const [savedConfig, setSavedConfig] = useState(null);
21
+ const [providerId, setProviderId] = useState(null);
22
+ const [error, setError] = useState('');
23
+ useEffect(() => {
24
+ const loadConfig = async () => {
25
+ const config = await readConfig();
26
+ if (config) {
27
+ setSavedConfig(config);
28
+ setPhase('use-saved');
29
+ }
30
+ else {
31
+ setPhase('select-provider');
32
+ }
33
+ };
34
+ void loadConfig();
35
+ }, []);
36
+ const providerItems = AI_PROVIDERS.map((p) => ({
37
+ label: p.label,
38
+ value: p.id,
39
+ description: p.envVar,
40
+ }));
41
+ const handleProviderSelect = (item) => {
42
+ setProviderId(item.value);
43
+ setPhase('enter-key');
44
+ };
45
+ const handleKeySubmit = async (key, selectedProviderId) => {
46
+ setPhase('validating');
47
+ setError('');
48
+ const result = await validateApiKey(selectedProviderId, key);
49
+ if (result === true) {
50
+ await writeConfig({ providerId: selectedProviderId, apiKey: key });
51
+ onComplete({ providerId: selectedProviderId, apiKey: key });
52
+ }
53
+ else {
54
+ setError(result);
55
+ setPhase('error');
56
+ }
57
+ };
58
+ const handleUseSaved = async (item) => {
59
+ if (item.value === 'yes' && savedConfig) {
60
+ setPhase('validating');
61
+ const result = await validateApiKey(savedConfig.providerId, savedConfig.apiKey);
62
+ if (result === true) {
63
+ onComplete({ providerId: savedConfig.providerId, apiKey: savedConfig.apiKey });
64
+ }
65
+ else {
66
+ setError(`Saved key is no longer valid: ${result}`);
67
+ setPhase('select-provider');
68
+ }
69
+ }
70
+ else {
71
+ setPhase('select-provider');
72
+ }
73
+ };
74
+ const selectedProvider = providerId
75
+ ? AI_PROVIDERS.find((p) => p.id === providerId)
76
+ : null;
77
+ const savedProvider = savedConfig
78
+ ? AI_PROVIDERS.find((p) => p.id === savedConfig.providerId)
79
+ : null;
80
+ return (_jsxs(Box, { flexDirection: "column", children: [phase === 'check-saved' && (_jsx(Spinner, { label: "Checking for saved API key..." })), phase === 'use-saved' && savedConfig && savedProvider && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, marginLeft: 2, children: _jsxs(Text, { color: colors.gray, children: [symbols.diamond, " Saved key: ", _jsx(Text, { color: colors.honey, children: savedProvider.label }), ' ', _jsxs(Text, { color: colors.grayDim, children: ["(", maskKey(savedConfig.apiKey), ")"] })] }) }), _jsx(SelectPrompt, { label: "Use saved API key?", items: [
81
+ { label: 'Yes, use saved key', value: 'yes' },
82
+ { label: 'No, enter a new key', value: 'no' },
83
+ ], onSelect: (item) => { void handleUseSaved(item); } })] })), phase === 'select-provider' && (_jsxs(Box, { flexDirection: "column", children: [error !== '' && (_jsx(Box, { marginBottom: 1, marginLeft: 2, children: _jsxs(Text, { color: colors.red, children: [symbols.cross, " ", error] }) })), _jsx(SelectPrompt, { label: "Select your AI provider", items: providerItems, onSelect: handleProviderSelect })] })), phase === 'enter-key' && selectedProvider && providerId && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: colors.gray, children: [symbols.diamond, " Provider: ", _jsx(Text, { color: colors.honey, children: selectedProvider.label })] }) }), _jsx(TextPrompt, { label: `Enter your ${selectedProvider.envVar}`, placeholder: "sk-...", onSubmit: (key) => { void handleKeySubmit(key, providerId); }, validate: (val) => {
84
+ if (!val) {
85
+ return 'API key is required';
86
+ }
87
+ if (val.length < 10) {
88
+ return 'API key seems too short';
89
+ }
90
+ return true;
91
+ } })] })), phase === 'validating' && (_jsx(Spinner, { label: "Validating API key..." })), phase === 'error' && providerId && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: colors.red, children: [symbols.cross, " ", error] }) }), _jsx(TextPrompt, { label: "Try again \u2014 enter your API key", placeholder: "sk-...", onSubmit: (key) => { void handleKeySubmit(key, providerId); }, validate: (val) => {
92
+ if (!val) {
93
+ return 'API key is required';
94
+ }
95
+ return true;
96
+ } })] }))] }));
97
+ }
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { TextPrompt } from '../../components/TextPrompt.js';
4
+ import { colors, symbols } from '../../theme.js';
5
+ export function AvatarStep({ agentName, onComplete }) {
6
+ const defaultUrl = `https://api.dicebear.com/9.x/bottts-neutral/svg?seed=${encodeURIComponent(agentName)}`;
7
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, marginLeft: 2, children: _jsxs(Text, { color: colors.gray, children: [symbols.diamond, " Default: ", _jsx(Text, { color: colors.honey, children: defaultUrl })] }) }), _jsx(TextPrompt, { label: "Avatar image URL (press Enter for default)", placeholder: defaultUrl, onSubmit: (val) => onComplete(val || defaultUrl), validate: (val) => {
8
+ if (!val) {
9
+ return true;
10
+ }
11
+ if (!val.startsWith('http://') && !val.startsWith('https://')) {
12
+ return 'Must start with http:// or https://';
13
+ }
14
+ return true;
15
+ } })] }));
16
+ }
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box } from 'ink';
3
+ import { TextPrompt } from '../../components/TextPrompt.js';
4
+ export function BioStep({ onComplete }) {
5
+ return (_jsx(Box, { flexDirection: "column", children: _jsx(TextPrompt, { label: "Write your agent's bio in their voice", placeholder: "macro degen. mass liquidated in luna. rebuilt during the bear.", onSubmit: onComplete, maxLength: 1000, validate: (val) => {
6
+ if (!val) {
7
+ return 'Bio is required';
8
+ }
9
+ if (val.length > 1000) {
10
+ return `Bio must be 1000 characters or less (currently ${val.length})`;
11
+ }
12
+ return true;
13
+ } }) }));
14
+ }
@@ -0,0 +1,14 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect } from 'react';
3
+ import { Box, Text, useApp } from 'ink';
4
+ import { colors, symbols, border } from '../../theme.js';
5
+ export function DoneStep({ projectDir }) {
6
+ const { exit } = useApp();
7
+ useEffect(() => {
8
+ exit();
9
+ }, []);
10
+ const termWidth = process.stdout.columns || 60;
11
+ const boxWidth = Math.min(termWidth - 4, 60);
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" })] }) }), _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
+ }
@@ -0,0 +1,163 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useCallback } from 'react';
3
+ import { Box, Text } from 'ink';
4
+ import { SelectPrompt } from '../../components/SelectPrompt.js';
5
+ import { MultiSelectPrompt } from '../../components/MultiSelectPrompt.js';
6
+ import { TextPrompt } from '../../components/TextPrompt.js';
7
+ import { CharacterSummaryCard } from '../../components/CharacterSummaryCard.js';
8
+ import { PERSONALITY_OPTIONS, VOICE_OPTIONS, TRADING_STYLE_OPTIONS, PROJECT_CATEGORY_OPTIONS, SENTIMENT_OPTIONS, TIMEFRAME_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,
15
+ }));
16
+ items.push({ label: 'Custom...', value: '__custom__' });
17
+ return items;
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
+ function buildSentimentItems() {
46
+ const items = SENTIMENT_OPTIONS.map((opt) => ({
47
+ label: opt.label,
48
+ value: opt.value,
49
+ description: opt.description,
50
+ }));
51
+ return items;
52
+ }
53
+ function buildTimeframeItems() {
54
+ const items = TIMEFRAME_OPTIONS.map((opt) => ({
55
+ label: opt.label,
56
+ value: opt.value,
57
+ description: opt.description,
58
+ }));
59
+ return items;
60
+ }
61
+ const personalityItems = buildPersonalityItems();
62
+ const voiceItems = buildVoiceItems();
63
+ const tradingStyleItems = buildTradingStyleItems();
64
+ const categoryItems = buildCategoryItems();
65
+ const sentimentItems = buildSentimentItems();
66
+ const timeframeItems = buildTimeframeItems();
67
+ export function IdentityStep({ agentName, onComplete }) {
68
+ const [subStep, setSubStep] = useState('personality');
69
+ const [personalityLabel, setPersonalityLabel] = useState('');
70
+ const [personality, setPersonality] = useState('');
71
+ const [tone, setTone] = useState('');
72
+ const [voiceStyle, setVoiceStyle] = useState('');
73
+ const [voiceLabel, setVoiceLabel] = useState('');
74
+ const [tradingStyle, setTradingStyle] = useState('');
75
+ const [tradingStyleLabel, setTradingStyleLabel] = useState('');
76
+ const [sectors, setSectors] = useState([]);
77
+ const [sectorsLabel, setSectorsLabel] = useState('');
78
+ const [sentiment, setSentiment] = useState('');
79
+ const [sentimentLabel, setSentimentLabel] = useState('');
80
+ const [timeframes, setTimeframes] = useState([]);
81
+ const [timeframesLabel, setTimeframesLabel] = useState('');
82
+ const handlePersonalitySelect = useCallback((item) => {
83
+ if (item.value === '__custom__') {
84
+ setSubStep('personality-custom');
85
+ return;
86
+ }
87
+ const description = item.description ?? '';
88
+ const personalityValue = `${item.label} — ${description}`;
89
+ setPersonality(personalityValue);
90
+ setPersonalityLabel(item.label);
91
+ setSubStep('voice');
92
+ }, []);
93
+ const handlePersonalityCustom = useCallback((value) => {
94
+ setPersonality(value);
95
+ setPersonalityLabel(value);
96
+ setSubStep('voice');
97
+ }, []);
98
+ const handleVoiceSelect = useCallback((item) => {
99
+ if (item.value === '__custom__') {
100
+ setSubStep('voice-custom');
101
+ return;
102
+ }
103
+ const voiceOption = VOICE_OPTIONS.find((v) => v.value === item.value);
104
+ setTone(voiceOption.tone);
105
+ setVoiceStyle(voiceOption.voiceStyle);
106
+ setVoiceLabel(item.label);
107
+ setSubStep('trading');
108
+ }, []);
109
+ const handleVoiceCustom = useCallback((value) => {
110
+ setTone(value);
111
+ setVoiceStyle(value);
112
+ setVoiceLabel(value);
113
+ setSubStep('trading');
114
+ }, []);
115
+ const handleTradingStyleSelect = useCallback((item) => {
116
+ if (item.value === '__custom__') {
117
+ setSubStep('trading-custom');
118
+ return;
119
+ }
120
+ setTradingStyle(item.value);
121
+ setTradingStyleLabel(item.label);
122
+ setSubStep('sectors');
123
+ }, []);
124
+ const handleTradingStyleCustom = useCallback((value) => {
125
+ setTradingStyle(value);
126
+ setTradingStyleLabel(value);
127
+ setSubStep('sectors');
128
+ }, []);
129
+ const handleSectors = useCallback((selected) => {
130
+ const values = selected.map((s) => s.value);
131
+ const labels = selected.map((s) => s.label);
132
+ setSectors(values);
133
+ const displayLabel = values.length === categoryItems.length ? 'All' : labels.join(', ');
134
+ setSectorsLabel(displayLabel);
135
+ setSubStep('sentiment');
136
+ }, []);
137
+ const handleSentimentSelect = useCallback((item) => {
138
+ setSentiment(item.value);
139
+ setSentimentLabel(item.label);
140
+ setSubStep('timeframe');
141
+ }, []);
142
+ const handleTimeframes = useCallback((selected) => {
143
+ const values = selected.map((s) => s.value);
144
+ const labels = selected.map((s) => s.label);
145
+ setTimeframes(values);
146
+ const displayLabel = values.length === timeframeItems.length ? 'All' : labels.join(', ');
147
+ setTimeframesLabel(displayLabel);
148
+ setSubStep('bio');
149
+ }, []);
150
+ const handleBio = useCallback((value) => {
151
+ const result = { personality, tone, voiceStyle, tradingStyle, sectors, sentiment, timeframes, bio: value };
152
+ onComplete(result);
153
+ }, [personality, tone, voiceStyle, tradingStyle, sectors, sentiment, timeframes, onComplete]);
154
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(CharacterSummaryCard, { name: agentName, personality: personalityLabel || undefined, voice: voiceLabel || undefined, tradingStyle: tradingStyleLabel || undefined, sectors: sectorsLabel || undefined, sentiment: sentimentLabel || undefined, timeframe: timeframesLabel || 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 === 'sentiment' && (_jsx(SelectPrompt, { label: "What's your agent's market sentiment?", items: sentimentItems, onSelect: handleSentimentSelect })), subStep === 'timeframe' && (_jsx(MultiSelectPrompt, { label: "Which timeframes should your agent participate in?", items: timeframeItems, onSubmit: handleTimeframes })), 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) => {
155
+ if (!val) {
156
+ return 'Bio is required';
157
+ }
158
+ if (val.length > 1000) {
159
+ return `Bio must be 1000 characters or less (currently ${val.length})`;
160
+ }
161
+ return true;
162
+ } })] }))] }));
163
+ }
@@ -0,0 +1,71 @@
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
+ import { HIVE_API_URL } from '../../config.js';
9
+ const ADJECTIVES = [
10
+ 'royal', 'golden', 'buzzy', 'honey', 'sweet', 'stung', 'waxed', 'bold',
11
+ 'swift', 'wild', 'keen', 'warm', 'hazy', 'calm', 'busy', 'amber',
12
+ 'pollen', 'nectar', 'floral', 'sunny', 'misty', 'fuzzy', 'striped',
13
+ 'waggle', 'silent', 'fierce', 'humble', 'lunar', 'solar', 'bloomed',
14
+ 'bullish', 'bearish', 'staked', 'minted', 'forked', 'based', 'degen',
15
+ 'pumped', 'longed', 'shorted', 'bridged', 'pegged', 'hodl', 'mega',
16
+ 'alpha', 'sigma', 'hyper', 'ultra', 'rapid', 'atomic',
17
+ ];
18
+ const NOUNS = [
19
+ 'bee', 'drone', 'queen', 'swarm', 'hive', 'comb', 'larva', 'pupa',
20
+ 'sting', 'apiary', 'keeper', 'mead', 'pollen', 'nectar', 'propolis',
21
+ 'colony', 'brood', 'waggle', 'cell', 'wax', 'bloom', 'blossom',
22
+ 'hornet', 'bumble', 'worker', 'forager', 'scout', 'smoker',
23
+ 'whale', 'bull', 'bear', 'shard', 'block', 'node', 'vault',
24
+ 'ledger', 'oracle', 'miner', 'staker', 'bridge', 'token', 'chain',
25
+ 'wick', 'candle', 'pump', 'moon', 'floor', 'whale',
26
+ ];
27
+ function generateRandomName() {
28
+ const adjective = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
29
+ const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
30
+ const number = Math.floor(Math.random() * 900) + 100;
31
+ const name = `${adjective}-${noun}-${number}`;
32
+ return name;
33
+ }
34
+ export function NameStep({ onComplete }) {
35
+ const [checking, setChecking] = useState(false);
36
+ const [error, setError] = useState('');
37
+ const placeholder = useMemo(() => generateRandomName(), []);
38
+ const handleSubmit = useCallback(async (name) => {
39
+ setChecking(true);
40
+ setError('');
41
+ try {
42
+ const apiUrl = HIVE_API_URL;
43
+ const response = await axios.get(`${apiUrl}/agent/check-name`, {
44
+ params: { name },
45
+ timeout: 3000,
46
+ });
47
+ if (!response.data.available) {
48
+ setError(`Name "${name}" is already taken`);
49
+ setChecking(false);
50
+ return;
51
+ }
52
+ }
53
+ catch {
54
+ // best-effort: silently proceed if backend is unavailable
55
+ }
56
+ setChecking(false);
57
+ onComplete(name);
58
+ }, [onComplete]);
59
+ if (checking) {
60
+ return (_jsx(Box, { flexDirection: "column", children: _jsx(Spinner, { label: "Checking name availability..." }) }));
61
+ }
62
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(TextPrompt, { label: "Enter your agent name", placeholder: placeholder, onSubmit: handleSubmit, validate: (val) => {
63
+ if (!val) {
64
+ return 'Agent name is required';
65
+ }
66
+ if (!/^[a-zA-Z0-9-_]+$/.test(val)) {
67
+ return 'Only letters, numbers, hyphens, and underscores allowed';
68
+ }
69
+ return true;
70
+ } }), error !== '' && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: colors.red, children: [symbols.cross, " ", error] }) }))] }));
71
+ }
@@ -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,58 @@
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, tradingStyle, sectors, sentiment, timeframes, onComplete, }) {
11
+ const [phase, setPhase] = useState('streaming');
12
+ const [draft, setDraft] = useState('');
13
+ const [errorMessage, setErrorMessage] = useState('');
14
+ const [feedbackCount, setFeedbackCount] = useState(0);
15
+ const stream = useMemo(() => streamSoul(providerId, apiKey, agentName, bio, avatarUrl, personality, tone, voiceStyle, tradingStyle, sectors, sentiment, timeframes), []);
16
+ const [currentStream, setCurrentStream] = useState(stream);
17
+ const handleStreamComplete = useCallback((fullText) => {
18
+ const trimmed = fullText.trim();
19
+ if (trimmed.length === 0) {
20
+ setErrorMessage('LLM returned empty content. Try regenerating.');
21
+ setPhase('error');
22
+ return;
23
+ }
24
+ setDraft(trimmed);
25
+ setPhase('review');
26
+ }, []);
27
+ const handleStreamError = useCallback((error) => {
28
+ setErrorMessage(error);
29
+ setPhase('error');
30
+ }, []);
31
+ const handleAccept = useCallback(() => {
32
+ onComplete(draft);
33
+ }, [draft, onComplete]);
34
+ const handleRetry = useCallback(() => {
35
+ setFeedbackCount((prev) => prev + 1);
36
+ setPhase('streaming');
37
+ setDraft('');
38
+ setErrorMessage('');
39
+ const newStream = streamSoul(providerId, apiKey, agentName, bio, avatarUrl, personality, tone, voiceStyle, tradingStyle, sectors, sentiment, timeframes);
40
+ setCurrentStream(newStream);
41
+ }, [providerId, apiKey, agentName, bio, avatarUrl, personality, tone, voiceStyle, tradingStyle, sectors, sentiment, timeframes]);
42
+ const handleFeedback = useCallback((feedback) => {
43
+ setFeedbackCount((prev) => prev + 1);
44
+ setPhase('streaming');
45
+ setDraft('');
46
+ setErrorMessage('');
47
+ const newStream = streamSoul(providerId, apiKey, agentName, bio, avatarUrl, personality, tone, voiceStyle, tradingStyle, sectors, sentiment, timeframes, feedback);
48
+ setCurrentStream(newStream);
49
+ }, [providerId, apiKey, agentName, bio, avatarUrl, personality, tone, voiceStyle, tradingStyle, sectors, sentiment, timeframes]);
50
+ 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, onError: handleStreamError })] })), phase === 'error' && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: colors.red, children: [symbols.cross, " "] }), _jsx(Text, { color: colors.white, children: "Failed to generate SOUL.md" })] }), _jsx(Box, { marginLeft: 2, marginBottom: 1, children: _jsx(Text, { color: colors.red, children: errorMessage }) }), _jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: colors.gray, children: ["Press ", _jsx(Text, { color: colors.honey, bold: true, children: "Enter" }), " to retry"] }) }), _jsx(Box, { marginTop: 1, children: _jsx(TextPrompt, { label: "", placeholder: "Enter to retry...", onSubmit: () => handleRetry() }) })] })), 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) => {
51
+ if (!val) {
52
+ handleAccept();
53
+ }
54
+ else {
55
+ handleFeedback(val);
56
+ }
57
+ } }) })] }))] }));
58
+ }
@@ -0,0 +1,58 @@
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, tradingStyle, sectors, sentiment, timeframes, onComplete, }) {
11
+ const [phase, setPhase] = useState('streaming');
12
+ const [draft, setDraft] = useState('');
13
+ const [errorMessage, setErrorMessage] = useState('');
14
+ const [feedbackCount, setFeedbackCount] = useState(0);
15
+ const stream = useMemo(() => streamStrategy(providerId, apiKey, agentName, bio, personality, tone, voiceStyle, tradingStyle, sectors, sentiment, timeframes), []);
16
+ const [currentStream, setCurrentStream] = useState(stream);
17
+ const handleStreamComplete = useCallback((fullText) => {
18
+ const trimmed = fullText.trim();
19
+ if (trimmed.length === 0) {
20
+ setErrorMessage('LLM returned empty content. Try regenerating.');
21
+ setPhase('error');
22
+ return;
23
+ }
24
+ setDraft(trimmed);
25
+ setPhase('review');
26
+ }, []);
27
+ const handleStreamError = useCallback((error) => {
28
+ setErrorMessage(error);
29
+ setPhase('error');
30
+ }, []);
31
+ const handleAccept = useCallback(() => {
32
+ onComplete(draft);
33
+ }, [draft, onComplete]);
34
+ const handleRetry = useCallback(() => {
35
+ setFeedbackCount((prev) => prev + 1);
36
+ setPhase('streaming');
37
+ setDraft('');
38
+ setErrorMessage('');
39
+ const newStream = streamStrategy(providerId, apiKey, agentName, bio, personality, tone, voiceStyle, tradingStyle, sectors, sentiment, timeframes);
40
+ setCurrentStream(newStream);
41
+ }, [providerId, apiKey, agentName, bio, personality, tone, voiceStyle, tradingStyle, sectors, sentiment, timeframes]);
42
+ const handleFeedback = useCallback((feedback) => {
43
+ setFeedbackCount((prev) => prev + 1);
44
+ setPhase('streaming');
45
+ setDraft('');
46
+ setErrorMessage('');
47
+ const newStream = streamStrategy(providerId, apiKey, agentName, bio, personality, tone, voiceStyle, tradingStyle, sectors, sentiment, timeframes, feedback);
48
+ setCurrentStream(newStream);
49
+ }, [providerId, apiKey, agentName, bio, personality, tone, voiceStyle, tradingStyle, sectors, sentiment, timeframes]);
50
+ 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, onError: handleStreamError })] })), phase === 'error' && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: colors.red, children: [symbols.cross, " "] }), _jsx(Text, { color: colors.white, children: "Failed to generate STRATEGY.md" })] }), _jsx(Box, { marginLeft: 2, marginBottom: 1, children: _jsx(Text, { color: colors.red, children: errorMessage }) }), _jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: colors.gray, children: ["Press ", _jsx(Text, { color: colors.honey, bold: true, children: "Enter" }), " to retry"] }) }), _jsx(Box, { marginTop: 1, children: _jsx(TextPrompt, { label: "", placeholder: "Enter to retry...", onSubmit: () => handleRetry() }) })] })), 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) => {
51
+ if (!val) {
52
+ handleAccept();
53
+ }
54
+ else {
55
+ handleFeedback(val);
56
+ }
57
+ } }) })] }))] }));
58
+ }
@@ -0,0 +1,47 @@
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
+ import { getProvider } from '../ai-providers.js';
8
+ /**
9
+ * Make a lightweight test call to validate the user's API key.
10
+ * Returns true if the key works, or an error message string on failure.
11
+ */
12
+ export async function validateApiKey(providerId, apiKey) {
13
+ try {
14
+ const model = buildTestModel(providerId, apiKey);
15
+ await generateText({
16
+ model,
17
+ prompt: 'Say "ok"',
18
+ maxOutputTokens: 16,
19
+ });
20
+ return true;
21
+ }
22
+ catch (err) {
23
+ const message = err instanceof Error ? err.message : String(err);
24
+ if (message.includes('401') ||
25
+ message.includes('Unauthorized') ||
26
+ message.includes('invalid')) {
27
+ return 'Invalid API key. Please check and try again.';
28
+ }
29
+ return `API validation failed: ${message.slice(0, 120)}`;
30
+ }
31
+ }
32
+ function buildTestModel(providerId, apiKey) {
33
+ const provider = getProvider(providerId);
34
+ const modelId = provider.models.validation;
35
+ switch (providerId) {
36
+ case 'openai':
37
+ return createOpenAI({ apiKey })(modelId);
38
+ case 'anthropic':
39
+ return createAnthropic({ apiKey })(modelId);
40
+ case 'google':
41
+ return createGoogleGenerativeAI({ apiKey })(modelId);
42
+ case 'xai':
43
+ return createXai({ apiKey })(modelId);
44
+ case 'openrouter':
45
+ return createOpenRouter({ apiKey }).chat(modelId);
46
+ }
47
+ }