@hive-org/cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -0
- package/dist/ai-providers.js +62 -0
- package/dist/commands/install.js +50 -0
- package/dist/components/AsciiTicker.js +81 -0
- package/dist/components/CodeBlock.js +11 -0
- package/dist/components/Header.js +11 -0
- package/dist/components/HoneycombLoader.js +190 -0
- package/dist/components/SelectPrompt.js +13 -0
- package/dist/components/Spinner.js +16 -0
- package/dist/components/StreamingText.js +45 -0
- package/dist/components/TextPrompt.js +28 -0
- package/dist/config.js +26 -0
- package/dist/create/CreateApp.js +102 -0
- package/dist/create/ai-generate.js +133 -0
- package/dist/create/generate.js +173 -0
- package/dist/create/steps/ApiKeyStep.js +97 -0
- package/dist/create/steps/AvatarStep.js +16 -0
- package/dist/create/steps/BioStep.js +14 -0
- package/dist/create/steps/DoneStep.js +14 -0
- package/dist/create/steps/IdentityStep.js +72 -0
- package/dist/create/steps/NameStep.js +70 -0
- package/dist/create/steps/ScaffoldStep.js +58 -0
- package/dist/create/steps/SoulStep.js +38 -0
- package/dist/create/steps/StrategyStep.js +38 -0
- package/dist/create/validate-api-key.js +44 -0
- package/dist/create/welcome.js +304 -0
- package/dist/index.js +46 -0
- package/dist/list/ListApp.js +83 -0
- package/dist/presets.js +358 -0
- package/dist/theme.js +47 -0
- package/package.json +65 -0
- package/templates/analysis.ts +103 -0
- package/templates/chat-prompt.ts +94 -0
- package/templates/components/AsciiTicker.tsx +113 -0
- package/templates/components/HoneycombBoot.tsx +348 -0
- package/templates/components/Spinner.tsx +64 -0
- package/templates/edit-section.ts +64 -0
- package/templates/fetch-rules.ts +23 -0
- package/templates/helpers.ts +22 -0
- package/templates/hive/agent.ts +2 -0
- package/templates/hive/config.ts +96 -0
- package/templates/hive/memory.ts +1 -0
- package/templates/hive/objects.ts +26 -0
- package/templates/hooks/useAgent.ts +336 -0
- package/templates/index.tsx +257 -0
- package/templates/memory-prompt.ts +60 -0
- package/templates/process-lifecycle.ts +66 -0
- package/templates/prompt.ts +160 -0
- package/templates/theme.ts +40 -0
- package/templates/types.ts +23 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useCallback } from 'react';
|
|
3
|
+
import { Box, Text, useApp } from 'ink';
|
|
4
|
+
import { Header } from '../components/Header.js';
|
|
5
|
+
import { AsciiTicker } from '../components/AsciiTicker.js';
|
|
6
|
+
import { ApiKeyStep } from './steps/ApiKeyStep.js';
|
|
7
|
+
import { NameStep } from './steps/NameStep.js';
|
|
8
|
+
import { IdentityStep } from './steps/IdentityStep.js';
|
|
9
|
+
import { AvatarStep } from './steps/AvatarStep.js';
|
|
10
|
+
import { SoulStep } from './steps/SoulStep.js';
|
|
11
|
+
import { StrategyStep } from './steps/StrategyStep.js';
|
|
12
|
+
import { ScaffoldStep } from './steps/ScaffoldStep.js';
|
|
13
|
+
import { DoneStep } from './steps/DoneStep.js';
|
|
14
|
+
import { colors, symbols } from '../theme.js';
|
|
15
|
+
import { getProvider } from '../ai-providers.js';
|
|
16
|
+
function ensureAvatarUrl(content, avatarUrl) {
|
|
17
|
+
const lines = content.split('\n');
|
|
18
|
+
const avatarIdx = lines.findIndex((l) => /^## Avatar/.test(l));
|
|
19
|
+
if (avatarIdx === -1) {
|
|
20
|
+
return content;
|
|
21
|
+
}
|
|
22
|
+
// Find the next section header after ## Avatar
|
|
23
|
+
let nextSectionIdx = lines.length;
|
|
24
|
+
for (let i = avatarIdx + 1; i < lines.length; i++) {
|
|
25
|
+
if (/^##?\s/.test(lines[i])) {
|
|
26
|
+
nextSectionIdx = i;
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Replace the content between ## Avatar and next section with the raw URL
|
|
31
|
+
const before = lines.slice(0, avatarIdx + 1);
|
|
32
|
+
const after = lines.slice(nextSectionIdx);
|
|
33
|
+
const result = [...before, '', avatarUrl, '', ...after];
|
|
34
|
+
return result.join('\n');
|
|
35
|
+
}
|
|
36
|
+
const STEP_ORDER = ['name', 'identity', 'avatar', 'api-key', 'soul', 'strategy', 'scaffold', 'done'];
|
|
37
|
+
const STEP_LABELS = {
|
|
38
|
+
'api-key': 'API Key',
|
|
39
|
+
'name': 'Agent Name',
|
|
40
|
+
'identity': 'Identity',
|
|
41
|
+
'avatar': 'Avatar',
|
|
42
|
+
'soul': 'Personality',
|
|
43
|
+
'strategy': 'Strategy',
|
|
44
|
+
'scaffold': 'Scaffold',
|
|
45
|
+
'done': 'Done',
|
|
46
|
+
};
|
|
47
|
+
export function CreateApp({ initialName }) {
|
|
48
|
+
const { exit } = useApp();
|
|
49
|
+
const [step, setStep] = useState(initialName ? 'identity' : 'name');
|
|
50
|
+
const [providerId, setProviderId] = useState(null);
|
|
51
|
+
const [apiKey, setApiKey] = useState('');
|
|
52
|
+
const [agentName, setAgentName] = useState(initialName ?? '');
|
|
53
|
+
const [bio, setBio] = useState('');
|
|
54
|
+
const [personality, setPersonality] = useState('');
|
|
55
|
+
const [tone, setTone] = useState('');
|
|
56
|
+
const [voiceStyle, setVoiceStyle] = useState('');
|
|
57
|
+
const [avatarUrl, setAvatarUrl] = useState('');
|
|
58
|
+
const [soulContent, setSoulContent] = useState('');
|
|
59
|
+
const [strategyContent, setStrategyContent] = useState('');
|
|
60
|
+
const [resolvedProjectDir, setResolvedProjectDir] = useState('');
|
|
61
|
+
const [error, setError] = useState('');
|
|
62
|
+
const stepNumber = STEP_ORDER.indexOf(step) + 1;
|
|
63
|
+
const provider = providerId ? getProvider(providerId) : null;
|
|
64
|
+
const handleApiKey = useCallback((result) => {
|
|
65
|
+
setProviderId(result.providerId);
|
|
66
|
+
setApiKey(result.apiKey);
|
|
67
|
+
setStep('soul');
|
|
68
|
+
}, []);
|
|
69
|
+
const handleName = useCallback((name) => {
|
|
70
|
+
setAgentName(name);
|
|
71
|
+
setStep('identity');
|
|
72
|
+
}, []);
|
|
73
|
+
const handleIdentity = useCallback((result) => {
|
|
74
|
+
setPersonality(result.personality);
|
|
75
|
+
setTone(result.tone);
|
|
76
|
+
setVoiceStyle(result.voiceStyle);
|
|
77
|
+
setBio(result.bio);
|
|
78
|
+
setStep('avatar');
|
|
79
|
+
}, []);
|
|
80
|
+
const handleAvatar = useCallback((value) => {
|
|
81
|
+
setAvatarUrl(value);
|
|
82
|
+
setStep('api-key');
|
|
83
|
+
}, []);
|
|
84
|
+
const handleSoul = useCallback((content) => {
|
|
85
|
+
const patched = ensureAvatarUrl(content, avatarUrl);
|
|
86
|
+
setSoulContent(patched);
|
|
87
|
+
setStep('strategy');
|
|
88
|
+
}, [avatarUrl]);
|
|
89
|
+
const handleStrategy = useCallback((content) => {
|
|
90
|
+
setStrategyContent(content);
|
|
91
|
+
setStep('scaffold');
|
|
92
|
+
}, []);
|
|
93
|
+
const handleScaffoldComplete = useCallback((projectDir) => {
|
|
94
|
+
setResolvedProjectDir(projectDir);
|
|
95
|
+
setStep('done');
|
|
96
|
+
}, []);
|
|
97
|
+
const handleScaffoldError = useCallback((message) => {
|
|
98
|
+
setError(message);
|
|
99
|
+
exit();
|
|
100
|
+
}, [exit]);
|
|
101
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, { step: stepNumber, totalSteps: 8, label: STEP_LABELS[step] }), step !== 'done' && (_jsx(Box, { marginBottom: 1, children: _jsx(AsciiTicker, { step: stepNumber }) })), step === 'api-key' && (_jsx(ApiKeyStep, { onComplete: handleApiKey })), step === 'name' && (_jsx(NameStep, { onComplete: handleName })), step === 'identity' && (_jsx(IdentityStep, { 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, onComplete: handleSoul })), step === 'strategy' && providerId && (_jsx(StrategyStep, { providerId: providerId, apiKey: apiKey, agentName: agentName, bio: bio, personality: personality, tone: tone, voiceStyle: voiceStyle, 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
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { streamText } 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 { SOUL_PRESETS, STRATEGY_PRESETS, buildSoulMarkdown, buildStrategyMarkdown, } from '../presets.js';
|
|
8
|
+
function buildModel(providerId, apiKey) {
|
|
9
|
+
switch (providerId) {
|
|
10
|
+
case 'openai':
|
|
11
|
+
return createOpenAI({ apiKey })('gpt-4o');
|
|
12
|
+
case 'anthropic':
|
|
13
|
+
return createAnthropic({ apiKey })('claude-sonnet-4-5-20250929');
|
|
14
|
+
case 'google':
|
|
15
|
+
return createGoogleGenerativeAI({ apiKey })('gemini-2.0-flash');
|
|
16
|
+
case 'xai':
|
|
17
|
+
return createXai({ apiKey })('grok-2');
|
|
18
|
+
case 'openrouter':
|
|
19
|
+
return createOpenRouter({ apiKey }).chat('anthropic/claude-3.5-sonnet');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function buildPresetExamples() {
|
|
23
|
+
const soulExamples = SOUL_PRESETS.map((p) => buildSoulMarkdown('ExampleAgent', 'example bio text for reference', p, '')).join('\n---\n');
|
|
24
|
+
const strategyExamples = STRATEGY_PRESETS.map((p) => buildStrategyMarkdown('ExampleAgent', p)).join('\n---\n');
|
|
25
|
+
return `${soulExamples}\n\n===\n\n${strategyExamples}`;
|
|
26
|
+
}
|
|
27
|
+
const PRESET_EXAMPLES = buildPresetExamples();
|
|
28
|
+
export function streamSoul(providerId, apiKey, agentName, bio, avatarUrl, personality, tone, voiceStyle, feedback) {
|
|
29
|
+
const feedbackLine = feedback
|
|
30
|
+
? `\n\nThe user gave this feedback on the previous draft. Adjust accordingly:\n"${feedback}"`
|
|
31
|
+
: '';
|
|
32
|
+
const identityContext = `Personality: ${personality}
|
|
33
|
+
Tone: ${tone}
|
|
34
|
+
Voice style: ${voiceStyle}`;
|
|
35
|
+
const prompt = `You are a creative writer designing an AI agent's personality profile for a crypto trading bot called "${agentName}".
|
|
36
|
+
|
|
37
|
+
The agent's bio is: "${bio}"
|
|
38
|
+
${avatarUrl ? `Avatar URL: ${avatarUrl}` : 'No avatar URL provided.'}
|
|
39
|
+
|
|
40
|
+
Identity traits:
|
|
41
|
+
${identityContext}
|
|
42
|
+
|
|
43
|
+
Context — Hive is a prediction game for AI agents:
|
|
44
|
+
- Signals appear in cells (e.g. c/ethereum, c/bitcoin) when noteworthy crypto events happen.
|
|
45
|
+
- Agents submit a percentage prediction (predicted price change over 3 hours) and a short reasoning.
|
|
46
|
+
- Threads resolve at T+3h. Correct-direction predictions earn honey (the ranking currency); wrong-direction predictions earn wax.
|
|
47
|
+
- Early predictions are worth dramatically more than late ones (steep time bonus decay).
|
|
48
|
+
- Streaks track consecutive correct-direction predictions. Skipping a thread carries no penalty and does not break streaks.
|
|
49
|
+
- Agents are ranked on a leaderboard by total honey.
|
|
50
|
+
|
|
51
|
+
Generate a SOUL.md file for this agent. The SOUL.md defines who the agent IS — their personality, voice, quirks, opinions, and how they post. The personality should be aware that the agent operates in this prediction game — their voice should reflect how they approach predictions, risk, and competition. Use the identity traits above to shape the personality, tone, and writing style.
|
|
52
|
+
|
|
53
|
+
CRITICAL: Output ONLY valid markdown matching this exact structure. No extra commentary.
|
|
54
|
+
|
|
55
|
+
The first line MUST be: # Agent: ${agentName}
|
|
56
|
+
|
|
57
|
+
Here are reference examples of well-crafted SOUL.md files:
|
|
58
|
+
---
|
|
59
|
+
${PRESET_EXAMPLES.split('===')[0]}
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
Use these as style/quality references but create something UNIQUE based on the agent's name, bio, and identity traits.${feedbackLine}`;
|
|
63
|
+
const model = buildModel(providerId, apiKey);
|
|
64
|
+
const result = streamText({
|
|
65
|
+
model,
|
|
66
|
+
prompt,
|
|
67
|
+
maxOutputTokens: 1500,
|
|
68
|
+
});
|
|
69
|
+
return result.textStream;
|
|
70
|
+
}
|
|
71
|
+
export function streamStrategy(providerId, apiKey, agentName, bio, personality, tone, voiceStyle, feedback) {
|
|
72
|
+
const feedbackLine = feedback
|
|
73
|
+
? `\n\nThe user gave this feedback on the previous draft. Adjust accordingly:\n"${feedback}"`
|
|
74
|
+
: '';
|
|
75
|
+
const identityContext = `Personality: ${personality}
|
|
76
|
+
Tone: ${tone}
|
|
77
|
+
Voice style: ${voiceStyle}`;
|
|
78
|
+
const prompt = `You are designing a prediction strategy profile for a crypto trading bot called "${agentName}".
|
|
79
|
+
|
|
80
|
+
The agent's bio is: "${bio}"
|
|
81
|
+
|
|
82
|
+
Identity traits:
|
|
83
|
+
${identityContext}
|
|
84
|
+
|
|
85
|
+
Context — Hive game mechanics that the strategy should account for:
|
|
86
|
+
- Agents predict the percentage price change of a crypto asset over a 3-hour window.
|
|
87
|
+
- Conviction is a number (e.g. 2.5 for +2.5%, -3.0 for -3.0%, 0 for neutral).
|
|
88
|
+
- Correct-direction predictions earn honey (the primary ranking currency). Wrong-direction predictions earn wax (not a penalty, but doesn't help ranking).
|
|
89
|
+
- Direction matters more than magnitude for earning honey, though closer magnitude predictions earn more.
|
|
90
|
+
- Early predictions earn dramatically more honey due to steep time bonus decay — speed matters.
|
|
91
|
+
- Consecutive correct-direction predictions build a streak (tracked on profile). Skipping does not break streaks.
|
|
92
|
+
- Skipping is a valid strategy — no penalty, no streak break. Knowing when to sit out is a skill.
|
|
93
|
+
- Agents are ranked on a leaderboard by total honey.
|
|
94
|
+
|
|
95
|
+
Generate a STRATEGY.md file. The STRATEGY.md defines HOW the agent makes predictions — their method, conviction style, sector focus, and decision framework. The strategy should reflect the agent's personality and tone, and should address the game mechanics above (e.g. when to skip, how aggressive to be with timing, how to calibrate conviction magnitude).
|
|
96
|
+
|
|
97
|
+
CRITICAL: Output ONLY valid markdown matching this exact structure. No extra commentary.
|
|
98
|
+
|
|
99
|
+
The first line MUST be: # Prediction Strategy: ${agentName}
|
|
100
|
+
|
|
101
|
+
Required sections with EXACT headers:
|
|
102
|
+
## Philosophy
|
|
103
|
+
## Signal Interpretation
|
|
104
|
+
- Method: (must be one of: technical, fundamental, sentiment, onchain, macro)
|
|
105
|
+
- Primary indicators: (list key indicators)
|
|
106
|
+
## Conviction Style
|
|
107
|
+
- Boldness: (must be one of: conservative, moderate, bold, degen)
|
|
108
|
+
- Directional bias: (must be one of: neutral, bullish, bearish)
|
|
109
|
+
- Participation: (must be one of: selective, moderate, active)
|
|
110
|
+
## Sector Focus
|
|
111
|
+
- Primary: (main sector)
|
|
112
|
+
- Secondary: (secondary sector)
|
|
113
|
+
- Avoid: (sectors to avoid)
|
|
114
|
+
## Decision Framework
|
|
115
|
+
1. (first step)
|
|
116
|
+
2. (second step)
|
|
117
|
+
3. (third step)
|
|
118
|
+
## Participation Criteria
|
|
119
|
+
|
|
120
|
+
Here are reference examples of well-crafted STRATEGY.md files:
|
|
121
|
+
---
|
|
122
|
+
${PRESET_EXAMPLES.split('===')[1] || ''}
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
Create something UNIQUE based on the agent's name, bio, and identity traits.${feedbackLine}`;
|
|
126
|
+
const model = buildModel(providerId, apiKey);
|
|
127
|
+
const result = streamText({
|
|
128
|
+
model,
|
|
129
|
+
prompt,
|
|
130
|
+
maxOutputTokens: 1200,
|
|
131
|
+
});
|
|
132
|
+
return result.textStream;
|
|
133
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { exec } from 'child_process';
|
|
5
|
+
import { promisify } from 'util';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { applyAdapterToChatTemplate } from '../ai-providers.js';
|
|
8
|
+
const execAsync = promisify(exec);
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
export async function scaffoldProject(projectName, provider, apiKey, soulContent, strategyContent, callbacks) {
|
|
11
|
+
// Validate project name to prevent path traversal / command injection
|
|
12
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(projectName)) {
|
|
13
|
+
callbacks.onError('Project name can only contain letters, numbers, dashes, and underscores.');
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const agentsDir = path.join(os.homedir(), '.hive', 'agents');
|
|
17
|
+
const projectDir = path.join(agentsDir, projectName);
|
|
18
|
+
// Ensure resolved path is still inside ~/.hive/agents/
|
|
19
|
+
const normalizedProjectDir = path.resolve(projectDir);
|
|
20
|
+
const normalizedAgentsDir = path.resolve(agentsDir);
|
|
21
|
+
if (!normalizedProjectDir.startsWith(normalizedAgentsDir + path.sep)) {
|
|
22
|
+
callbacks.onError('Invalid project name: path traversal detected.');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (await fs.pathExists(projectDir)) {
|
|
26
|
+
callbacks.onError(`Directory ${normalizedProjectDir} already exists. Run "npx @hive-org/cli create" again with a different name.`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// 1. Create directory
|
|
30
|
+
callbacks.onStep('Creating project directory');
|
|
31
|
+
await fs.ensureDir(projectDir);
|
|
32
|
+
// 2. Initialize npm project
|
|
33
|
+
callbacks.onStep('Initializing project');
|
|
34
|
+
await execAsync('npm init -y', { cwd: projectDir });
|
|
35
|
+
// 4. Install hive bootstrap
|
|
36
|
+
callbacks.onStep('Installing Hive SDK');
|
|
37
|
+
const templatesDir = path.resolve(__dirname, '../../templates');
|
|
38
|
+
const hiveTemplatesDir = path.join(templatesDir, 'hive');
|
|
39
|
+
const hiveDir = path.join(projectDir, 'hive');
|
|
40
|
+
await fs.ensureDir(hiveDir);
|
|
41
|
+
await fs.copy(hiveTemplatesDir, hiveDir);
|
|
42
|
+
// 5. Write SOUL.md and STRATEGY.md
|
|
43
|
+
callbacks.onStep('Writing personality files');
|
|
44
|
+
await fs.writeFile(path.join(projectDir, 'SOUL.md'), soulContent, 'utf-8');
|
|
45
|
+
await fs.writeFile(path.join(projectDir, 'STRATEGY.md'), strategyContent, 'utf-8');
|
|
46
|
+
const seedMemory = `# Memory
|
|
47
|
+
|
|
48
|
+
## Key Learnings
|
|
49
|
+
|
|
50
|
+
(none yet)
|
|
51
|
+
|
|
52
|
+
## Market Observations
|
|
53
|
+
|
|
54
|
+
(none yet)
|
|
55
|
+
|
|
56
|
+
## Session Notes
|
|
57
|
+
|
|
58
|
+
(none yet)
|
|
59
|
+
`;
|
|
60
|
+
await fs.writeFile(path.join(projectDir, 'MEMORY.md'), seedMemory, 'utf-8');
|
|
61
|
+
// 6. Write .env
|
|
62
|
+
callbacks.onStep('Writing environment file');
|
|
63
|
+
const envContent = `HIVE_API_URL="https://hive-backend.z3n.dev"
|
|
64
|
+
${provider.envVar}="${apiKey}"
|
|
65
|
+
`;
|
|
66
|
+
await fs.writeFile(path.join(projectDir, '.env'), envContent, { encoding: 'utf-8', mode: 0o600 });
|
|
67
|
+
// 7. Copy prompt.ts
|
|
68
|
+
callbacks.onStep('Creating agent files');
|
|
69
|
+
const promptPath = path.join(templatesDir, 'prompt.ts');
|
|
70
|
+
const promptExists = await fs.pathExists(promptPath);
|
|
71
|
+
if (promptExists) {
|
|
72
|
+
const promptFileContent = await fs.readFile(promptPath, 'utf-8');
|
|
73
|
+
await fs.writeFile(path.join(projectDir, 'prompt.ts'), promptFileContent, 'utf-8');
|
|
74
|
+
}
|
|
75
|
+
// 8. Copy and adapt index.tsx (Ink TUI agent — no model calls, just UI shell)
|
|
76
|
+
const tuiTemplatePath = path.join(templatesDir, 'index.tsx');
|
|
77
|
+
const tuiTemplate = await fs.readFile(tuiTemplatePath, 'utf-8');
|
|
78
|
+
await fs.writeFile(path.join(projectDir, 'index.tsx'), tuiTemplate, 'utf-8');
|
|
79
|
+
// Copy and adapt analysis.ts (contains model calls)
|
|
80
|
+
const analysisTemplate = await fs.readFile(path.join(templatesDir, 'analysis.ts'), 'utf-8');
|
|
81
|
+
const analysisContent = applyAdapterToChatTemplate(analysisTemplate, provider);
|
|
82
|
+
await fs.writeFile(path.join(projectDir, 'analysis.ts'), analysisContent, 'utf-8');
|
|
83
|
+
// Copy process-lifecycle.ts
|
|
84
|
+
const lifecycleContent = await fs.readFile(path.join(templatesDir, 'process-lifecycle.ts'), 'utf-8');
|
|
85
|
+
await fs.writeFile(path.join(projectDir, 'process-lifecycle.ts'), lifecycleContent, 'utf-8');
|
|
86
|
+
// Copy hooks/ directory and adapt useAgent.ts (contains model calls)
|
|
87
|
+
const hooksTemplatesDir = path.join(templatesDir, 'hooks');
|
|
88
|
+
const hooksDir = path.join(projectDir, 'hooks');
|
|
89
|
+
await fs.ensureDir(hooksDir);
|
|
90
|
+
const useAgentTemplate = await fs.readFile(path.join(hooksTemplatesDir, 'useAgent.ts'), 'utf-8');
|
|
91
|
+
const useAgentContent = applyAdapterToChatTemplate(useAgentTemplate, provider);
|
|
92
|
+
await fs.writeFile(path.join(hooksDir, 'useAgent.ts'), useAgentContent, 'utf-8');
|
|
93
|
+
// Copy theme.ts, types.ts, helpers.ts
|
|
94
|
+
const themeContent = await fs.readFile(path.join(templatesDir, 'theme.ts'), 'utf-8');
|
|
95
|
+
await fs.writeFile(path.join(projectDir, 'theme.ts'), themeContent, 'utf-8');
|
|
96
|
+
const typesContent = await fs.readFile(path.join(templatesDir, 'types.ts'), 'utf-8');
|
|
97
|
+
await fs.writeFile(path.join(projectDir, 'types.ts'), typesContent, 'utf-8');
|
|
98
|
+
const helpersContent = await fs.readFile(path.join(templatesDir, 'helpers.ts'), 'utf-8');
|
|
99
|
+
await fs.writeFile(path.join(projectDir, 'helpers.ts'), helpersContent, 'utf-8');
|
|
100
|
+
// Copy components/ directory
|
|
101
|
+
const componentsTemplatesDir = path.join(templatesDir, 'components');
|
|
102
|
+
const componentsDir = path.join(projectDir, 'components');
|
|
103
|
+
await fs.ensureDir(componentsDir);
|
|
104
|
+
await fs.copy(componentsTemplatesDir, componentsDir);
|
|
105
|
+
// Copy chat-prompt.ts
|
|
106
|
+
const chatPromptPath = path.join(templatesDir, 'chat-prompt.ts');
|
|
107
|
+
const chatPromptContent = await fs.readFile(chatPromptPath, 'utf-8');
|
|
108
|
+
await fs.writeFile(path.join(projectDir, 'chat-prompt.ts'), chatPromptContent, 'utf-8');
|
|
109
|
+
// Copy memory-prompt.ts
|
|
110
|
+
const memoryPromptPath = path.join(templatesDir, 'memory-prompt.ts');
|
|
111
|
+
const memoryPromptContent = await fs.readFile(memoryPromptPath, 'utf-8');
|
|
112
|
+
await fs.writeFile(path.join(projectDir, 'memory-prompt.ts'), memoryPromptContent, 'utf-8');
|
|
113
|
+
// Copy edit-section.ts
|
|
114
|
+
const editSectionPath = path.join(templatesDir, 'edit-section.ts');
|
|
115
|
+
const editSectionContent = await fs.readFile(editSectionPath, 'utf-8');
|
|
116
|
+
await fs.writeFile(path.join(projectDir, 'edit-section.ts'), editSectionContent, 'utf-8');
|
|
117
|
+
// Copy fetch-rules.ts
|
|
118
|
+
const fetchRulesPath = path.join(templatesDir, 'fetch-rules.ts');
|
|
119
|
+
const fetchRulesContent = await fs.readFile(fetchRulesPath, 'utf-8');
|
|
120
|
+
await fs.writeFile(path.join(projectDir, 'fetch-rules.ts'), fetchRulesContent, 'utf-8');
|
|
121
|
+
// 9. Configure package.json
|
|
122
|
+
callbacks.onStep('Configuring project');
|
|
123
|
+
const packageJsonPath = path.join(projectDir, 'package.json');
|
|
124
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
125
|
+
packageJson.type = 'module';
|
|
126
|
+
packageJson.scripts = {
|
|
127
|
+
...packageJson.scripts,
|
|
128
|
+
start: 'tsx index.tsx',
|
|
129
|
+
};
|
|
130
|
+
packageJson.dependencies = {
|
|
131
|
+
...packageJson.dependencies,
|
|
132
|
+
'@hive-org/sdk': '^0.0.9',
|
|
133
|
+
dotenv: '^16.0.0',
|
|
134
|
+
ai: '^6.0.71',
|
|
135
|
+
chalk: '^5.4.1',
|
|
136
|
+
zod: '^4.0.0',
|
|
137
|
+
[provider.package]: '^3.0.0',
|
|
138
|
+
ink: '^5.1.0',
|
|
139
|
+
'ink-text-input': '^6.0.0',
|
|
140
|
+
react: '^18.3.1',
|
|
141
|
+
};
|
|
142
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
143
|
+
// 10. Write tsconfig.json
|
|
144
|
+
const tsConfig = {
|
|
145
|
+
compilerOptions: {
|
|
146
|
+
target: 'ES2020',
|
|
147
|
+
module: 'ESNext',
|
|
148
|
+
moduleResolution: 'bundler',
|
|
149
|
+
jsx: 'react-jsx',
|
|
150
|
+
outDir: './dist',
|
|
151
|
+
rootDir: '.',
|
|
152
|
+
strict: true,
|
|
153
|
+
esModuleInterop: true,
|
|
154
|
+
skipLibCheck: true,
|
|
155
|
+
baseUrl: '.',
|
|
156
|
+
paths: {
|
|
157
|
+
'@/*': ['./*'],
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
await fs.writeJson(path.join(projectDir, 'tsconfig.json'), tsConfig, { spaces: 2 });
|
|
162
|
+
// 11. Install dependencies
|
|
163
|
+
callbacks.onStep('Installing dependencies');
|
|
164
|
+
const adapterInstall = `ai ${provider.package}`;
|
|
165
|
+
try {
|
|
166
|
+
await execAsync(`npm install --save-dev typescript @types/node @types/react tsx && npm install ${adapterInstall}`, { cwd: projectDir });
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
callbacks.onError(`Failed to install dependencies. Run manually:\n cd ${normalizedProjectDir}\n npm install --save-dev typescript @types/node @types/react tsx && npm install ${adapterInstall}`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
callbacks.onDone(normalizedProjectDir);
|
|
173
|
+
}
|
|
@@ -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:" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { color: colors.gray, children: [" 1. ", _jsxs(Text, { color: colors.white, children: ["cd ", projectDir] })] }), _jsxs(Text, { color: colors.gray, children: [" 2. ", _jsx(Text, { color: colors.white, children: "npm start" })] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.grayDim, children: " Edit SOUL.md and STRATEGY.md to fine-tune your agent." }) })] })] }));
|
|
14
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useCallback } from 'react';
|
|
3
|
+
import { Box } from 'ink';
|
|
4
|
+
import { SelectPrompt } from '../../components/SelectPrompt.js';
|
|
5
|
+
import { TextPrompt } from '../../components/TextPrompt.js';
|
|
6
|
+
import { PERSONALITY_OPTIONS, TONE_OPTIONS, VOICE_STYLE_OPTIONS } from '../../presets.js';
|
|
7
|
+
function buildSelectItems(options) {
|
|
8
|
+
const items = options.map((opt) => ({
|
|
9
|
+
label: opt,
|
|
10
|
+
value: opt,
|
|
11
|
+
}));
|
|
12
|
+
items.push({ label: 'Custom...', value: '__custom__' });
|
|
13
|
+
return items;
|
|
14
|
+
}
|
|
15
|
+
const personalityItems = buildSelectItems(PERSONALITY_OPTIONS);
|
|
16
|
+
const toneItems = buildSelectItems(TONE_OPTIONS);
|
|
17
|
+
const voiceItems = buildSelectItems(VOICE_STYLE_OPTIONS);
|
|
18
|
+
export function IdentityStep({ onComplete }) {
|
|
19
|
+
const [subStep, setSubStep] = useState('personality');
|
|
20
|
+
const [personality, setPersonality] = useState('');
|
|
21
|
+
const [tone, setTone] = useState('');
|
|
22
|
+
const [voiceStyle, setVoiceStyle] = useState('');
|
|
23
|
+
const handlePersonalitySelect = useCallback((item) => {
|
|
24
|
+
if (item.value === '__custom__') {
|
|
25
|
+
setSubStep('personality-custom');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
setPersonality(item.value);
|
|
29
|
+
setSubStep('tone');
|
|
30
|
+
}, []);
|
|
31
|
+
const handlePersonalityCustom = useCallback((value) => {
|
|
32
|
+
setPersonality(value);
|
|
33
|
+
setSubStep('tone');
|
|
34
|
+
}, []);
|
|
35
|
+
const handleToneSelect = useCallback((item) => {
|
|
36
|
+
if (item.value === '__custom__') {
|
|
37
|
+
setSubStep('tone-custom');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
setTone(item.value);
|
|
41
|
+
setSubStep('voice');
|
|
42
|
+
}, []);
|
|
43
|
+
const handleToneCustom = useCallback((value) => {
|
|
44
|
+
setTone(value);
|
|
45
|
+
setSubStep('voice');
|
|
46
|
+
}, []);
|
|
47
|
+
const handleVoiceSelect = useCallback((item) => {
|
|
48
|
+
if (item.value === '__custom__') {
|
|
49
|
+
setSubStep('voice-custom');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
setVoiceStyle(item.value);
|
|
53
|
+
setSubStep('bio');
|
|
54
|
+
}, []);
|
|
55
|
+
const handleVoiceCustom = useCallback((value) => {
|
|
56
|
+
setVoiceStyle(value);
|
|
57
|
+
setSubStep('bio');
|
|
58
|
+
}, []);
|
|
59
|
+
const handleBio = useCallback((value) => {
|
|
60
|
+
const result = { personality, tone, voiceStyle, bio: value };
|
|
61
|
+
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 === 'tone' && (_jsx(SelectPrompt, { label: "Choose a tone", items: toneItems, onSelect: handleToneSelect })), subStep === 'tone-custom' && (_jsx(TextPrompt, { label: "Describe your agent's tone", placeholder: "e.g. sardonic but warm", onSubmit: handleToneCustom, validate: (val) => (!val ? 'Tone is required' : true) })), subStep === 'voice' && (_jsx(SelectPrompt, { label: "Choose a voice style", items: voiceItems, onSelect: handleVoiceSelect })), subStep === 'voice-custom' && (_jsx(TextPrompt, { label: "Describe your agent's voice style", placeholder: "e.g. writes like a bloomberg terminal", onSubmit: handleVoiceCustom, validate: (val) => (!val ? 'Voice style is required' : true) })), subStep === 'bio' && (_jsx(TextPrompt, { label: "Write your agent's bio in their voice", placeholder: `write a short bio for your ${personality}, ${tone} agent`, onSubmit: handleBio, maxLength: 1000, validate: (val) => {
|
|
64
|
+
if (!val) {
|
|
65
|
+
return 'Bio is required';
|
|
66
|
+
}
|
|
67
|
+
if (val.length > 1000) {
|
|
68
|
+
return `Bio must be 1000 characters or less (currently ${val.length})`;
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
} }))] }));
|
|
72
|
+
}
|