Co-authored-by: Cameron Pfiffer <cameron@pfiffer.org> Co-authored-by: Caren Thomas <carenthomas@gmail.com> Co-authored-by: Charles Packer <packercharles@gmail.com> Co-authored-by: Sarah Wooders <sarahwooders@gmail.com>
210 lines
5.4 KiB
TypeScript
210 lines
5.4 KiB
TypeScript
#!/usr/bin/env npx tsx
|
|
/**
|
|
* LettaBot Setup Wizard
|
|
*
|
|
* Interactive setup wizard
|
|
* Run with: npx tsx src/setup.ts
|
|
*/
|
|
|
|
import * as p from '@clack/prompts';
|
|
import { existsSync, writeFileSync, readFileSync } from 'node:fs';
|
|
import { spawn } from 'node:child_process';
|
|
import { resolve } from 'node:path';
|
|
|
|
const ENV_PATH = resolve(process.cwd(), '.env');
|
|
const ENV_EXAMPLE_PATH = resolve(process.cwd(), '.env.example');
|
|
|
|
interface SetupConfig {
|
|
telegramToken: string;
|
|
lettaApiKey: string;
|
|
allowedUsers: string;
|
|
workingDir: string;
|
|
}
|
|
|
|
async function main() {
|
|
console.clear();
|
|
|
|
p.intro('🤖 LettaBot Setup');
|
|
|
|
// Check if already configured
|
|
if (existsSync(ENV_PATH)) {
|
|
const overwrite = await p.confirm({
|
|
message: '.env already exists. Overwrite?',
|
|
initialValue: false,
|
|
});
|
|
|
|
if (p.isCancel(overwrite) || !overwrite) {
|
|
p.outro('Setup cancelled. Your existing config is preserved.');
|
|
process.exit(0);
|
|
}
|
|
}
|
|
|
|
// Security notice
|
|
p.note(
|
|
[
|
|
'LettaBot can execute tools on your machine (files, commands).',
|
|
'Only allow trusted users to interact with it.',
|
|
'',
|
|
'Recommended: Set ALLOWED_USERS to restrict access.',
|
|
].join('\n'),
|
|
'Security Note'
|
|
);
|
|
|
|
const proceed = await p.confirm({
|
|
message: 'I understand the security implications. Continue?',
|
|
initialValue: true,
|
|
});
|
|
|
|
if (p.isCancel(proceed) || !proceed) {
|
|
p.outro('Setup cancelled.');
|
|
process.exit(0);
|
|
}
|
|
|
|
// Collect configuration
|
|
const config = await p.group(
|
|
{
|
|
telegramToken: () => p.text({
|
|
message: 'Telegram Bot Token',
|
|
placeholder: 'Get from @BotFather on Telegram',
|
|
validate: (v) => {
|
|
if (!v) return 'Token is required';
|
|
if (!v.includes(':')) return 'Invalid token format (should contain ":")';
|
|
},
|
|
}),
|
|
|
|
lettaApiKey: () => p.text({
|
|
message: 'Letta API Key',
|
|
placeholder: 'Get from app.letta.com or leave empty for local server',
|
|
validate: () => undefined, // Optional
|
|
}),
|
|
|
|
allowedUsers: () => p.text({
|
|
message: 'Allowed Telegram User IDs (comma-separated, empty = all)',
|
|
placeholder: '123456789,987654321',
|
|
initialValue: '',
|
|
validate: (v) => {
|
|
if (!v) return undefined;
|
|
const ids = v.split(',').map(s => s.trim());
|
|
for (const id of ids) {
|
|
if (!/^\d+$/.test(id)) return `Invalid user ID: ${id}`;
|
|
}
|
|
},
|
|
}),
|
|
|
|
workingDir: () => p.text({
|
|
message: 'Working directory for agent workspaces',
|
|
initialValue: '/tmp/lettabot',
|
|
}),
|
|
},
|
|
{
|
|
onCancel: () => {
|
|
p.cancel('Setup cancelled.');
|
|
process.exit(0);
|
|
},
|
|
}
|
|
);
|
|
|
|
// Build .env content
|
|
const envContent = `# LettaBot Configuration
|
|
# Generated by setup wizard
|
|
|
|
# Required: Telegram Bot Token (from @BotFather)
|
|
TELEGRAM_BOT_TOKEN=${config.telegramToken}
|
|
|
|
# Letta API Key (from app.letta.com, or leave empty for local server)
|
|
LETTA_API_KEY=${config.lettaApiKey || ''}
|
|
|
|
# Optional: Letta server URL (default: https://api.letta.com)
|
|
# LETTA_BASE_URL=https://api.letta.com
|
|
|
|
# Security: Comma-separated Telegram user IDs (empty = allow all)
|
|
ALLOWED_USERS=${config.allowedUsers || ''}
|
|
|
|
# Working directory for user workspaces
|
|
WORKING_DIR=${config.workingDir || '/tmp/lettabot'}
|
|
|
|
# Optional: Default model
|
|
# DEFAULT_MODEL=claude-sonnet-4-20250514
|
|
`;
|
|
|
|
// Write .env
|
|
const s = p.spinner();
|
|
s.start('Writing configuration...');
|
|
|
|
try {
|
|
writeFileSync(ENV_PATH, envContent);
|
|
s.stop('Configuration saved to .env');
|
|
} catch (e) {
|
|
s.stop('Failed to write .env');
|
|
p.log.error(String(e));
|
|
process.exit(1);
|
|
}
|
|
|
|
// Validate Telegram token
|
|
s.start('Validating Telegram token...');
|
|
try {
|
|
const response = await fetch(`https://api.telegram.org/bot${config.telegramToken}/getMe`);
|
|
const data = await response.json() as { ok: boolean; result?: { username: string } };
|
|
|
|
if (data.ok && data.result) {
|
|
s.stop(`Telegram bot validated: @${data.result.username}`);
|
|
} else {
|
|
s.stop('Telegram token validation failed');
|
|
p.log.warn('Token might be invalid - check @BotFather');
|
|
}
|
|
} catch (e) {
|
|
s.stop('Could not validate Telegram token (network error)');
|
|
}
|
|
|
|
// Check Letta CLI (installed via @letta-ai/letta-code-sdk dependency chain)
|
|
s.start('Checking Letta Code CLI...');
|
|
try {
|
|
require.resolve('@letta-ai/letta-code/letta.js');
|
|
s.stop('Letta Code CLI found');
|
|
} catch {
|
|
s.stop('Letta Code CLI not found');
|
|
p.log.warn('Try running: npm install');
|
|
}
|
|
|
|
// Ask to start
|
|
const startNow = await p.confirm({
|
|
message: 'Start LettaBot now?',
|
|
initialValue: true,
|
|
});
|
|
|
|
if (p.isCancel(startNow)) {
|
|
p.outro('Setup complete! Run `npm run dev` to start.');
|
|
process.exit(0);
|
|
}
|
|
|
|
if (startNow) {
|
|
p.outro('Starting LettaBot...\n');
|
|
|
|
// Start the bot
|
|
const child = spawn('npx', ['tsx', 'src/index.ts'], {
|
|
stdio: 'inherit',
|
|
cwd: process.cwd(),
|
|
});
|
|
|
|
child.on('error', (e) => {
|
|
console.error('Failed to start:', e);
|
|
process.exit(1);
|
|
});
|
|
} else {
|
|
p.outro([
|
|
'Setup complete!',
|
|
'',
|
|
'Start the bot with:',
|
|
' npm run dev',
|
|
'',
|
|
'Or for production:',
|
|
' npm run build && npm start',
|
|
].join('\n'));
|
|
}
|
|
}
|
|
|
|
main().catch((e) => {
|
|
console.error('Setup failed:', e);
|
|
process.exit(1);
|
|
});
|