kaibanjs
Version:
AI Multi-Agent library for Javascript Developers.
571 lines (505 loc) • 17 kB
JavaScript
import path from 'path';
import { spawn, execSync } from 'child_process';
import fs from 'fs';
import dotenv from 'dotenv';
import chalk from 'chalk';
import ora from 'ora';
import figlet from 'figlet';
import readline from 'readline';
import TelemetryDeck from '@telemetrydeck/sdk';
// NOTE: it will break on Windows if you don't use this import
// eslint-disable-next-line no-redeclare
import crypto from 'crypto';
// Mock telemetry instance for when users opt out
const mockTelemetry = {
signal: () => {}, // No-op function
// Add other methods as needed to match TelemetryDeck's interface
};
function generateProjectId() {
const projectName = path.basename(process.cwd());
const userHome = process.env.HOME || process.env.USERPROFILE || '';
const machineId = crypto.createHash('md5').update(userHome).digest('hex');
const uniqueString = `${projectName}-${machineId}`;
return crypto.createHash('md5').update(uniqueString).digest('hex');
}
function initializeTelemetry(appID = '95BF7A3E-9D86-432D-9633-3526DD3A8977') {
if (process.env.KAIBAN_TELEMETRY_OPT_OUT) {
console.log(
'Telemetry is disabled due to KAIBAN_TELEMETRY_OPT_OUT environment variable.'
);
return mockTelemetry;
}
const projectId = generateProjectId();
return new TelemetryDeck({
appID,
clientUser: projectId,
subtleCrypto: crypto.webcrypto.subtle,
});
}
// Initialize telemetry at the beginning
const td = initializeTelemetry();
// Function to display a banner
function displayBanner() {
console.log(
chalk.cyan(
figlet.textSync('Kaiban CLI', {
font: 'Standard',
horizontalLayout: 'default',
verticalLayout: 'default',
})
)
);
}
// Function to recursively find all *.kban.js files, excluding .kaiban and node_modules directories
function findTeamFiles(dir) {
let results = [];
const list = fs.readdirSync(dir);
list.forEach((file) => {
const filePath = path.resolve(dir, file);
const stat = fs.statSync(filePath);
if (stat && stat.isDirectory()) {
if (!filePath.includes('.kaiban') && !filePath.includes('node_modules')) {
// Exclude .kaiban and node_modules directories
results = results.concat(findTeamFiles(filePath));
}
} else if (
file.endsWith('.kban.js') &&
!filePath.includes('.kaiban') &&
!filePath.includes('node_modules')
) {
results.push(filePath);
}
});
return results;
}
// Function to copy a sample team.kban.js file to the user's project root if no team files are found
function copySampleTeamFile() {
const sampleFilePath = path.resolve(
'.kaiban',
'src',
'samples',
'team.kban.js'
);
const destinationFilePath = path.resolve(process.cwd(), 'team.kban.js');
if (fs.existsSync(sampleFilePath)) {
fs.copyFileSync(sampleFilePath, destinationFilePath);
console.log(
chalk.green(
'Sample team.kban.js file has been copied to the root of your project.'
)
);
} else {
console.error(
chalk.red('Sample team.kban.js file not found inside .kaiban/samples.')
);
}
}
// Function to write the teams.js file inside `.kaiban`
function writeTeamsFile(teamFiles) {
const teamsFilePath = path.resolve('.kaiban', 'src', 'teams.js');
const fileContent = `
// Auto-generated file
${teamFiles
.map(
(file, index) =>
`import team${index + 1} from '${file.replace(/\\/g, '/')}';`
)
.join('\n')}
const teams = [
${teamFiles.map((_, index) => `team${index + 1}`).join(',\n ')}
];
export default teams;
`;
fs.writeFileSync(teamsFilePath, fileContent, 'utf8');
// console.log(chalk.blue(`teams.js file has been written successfully.`));
}
// Function to clone the kaibanjs-devtools repo if .kaiban folder doesn't exist
function cloneDevtoolsRepo() {
const kaibanPath = path.resolve('.kaiban');
if (!fs.existsSync(kaibanPath)) {
const spinner = ora('Downloading kaibanjs-devtools...').start();
try {
execSync('npx degit kaiban-ai/kaibanjs-devtools#main .kaiban', {
stdio: 'inherit',
});
spinner.succeed('kaibanjs-devtools downloaded successfully.');
} catch (error) {
spinner.fail('Failed to download kaibanjs-devtools.');
console.error(chalk.red('Error:'), error);
throw error;
}
console.log(chalk.yellow('Installing dependencies...'));
execSync('npm install', { cwd: '.kaiban', stdio: 'inherit' });
// Add .kaiban to .gitignore
const gitignorePath = path.resolve('.gitignore');
if (fs.existsSync(gitignorePath)) {
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
if (!gitignoreContent.includes('.kaiban')) {
fs.appendFileSync(gitignorePath, '\n.kaiban\n');
console.log(
chalk.green('\n.kaiban has been added to your .gitignore.')
);
}
} else {
fs.writeFileSync(gitignorePath, '.kaiban\n');
console.log(
chalk.green('\n.gitignore file created and .kaiban has been added.')
);
}
} else {
console.log(
chalk.magenta('.kaiban folder already exists, skipping download.')
);
}
}
// Function to load .env.* files in the root project
function loadEnvVariables() {
const envDir = process.cwd(); // Root directory of the project
const envFiles = fs
.readdirSync(envDir)
.filter((file) => file.startsWith('.env'));
envFiles.forEach((file) => {
console.log(chalk.blue(`Loading environment variables from ${file}`));
dotenv.config({ path: path.join(envDir, file) });
});
}
// Function to run Vite server using spawn
function runViteServer() {
// const spinner = ora('Starting Vite server...').start();
const viteProcess = spawn('npm', ['run', 'dev'], {
cwd: '.kaiban',
stdio: 'inherit',
shell: true,
});
viteProcess.on('close', (code) => {
if (code === 0) {
// spinner.succeed('Vite server stopped successfully.');
} else {
// spinner.fail(`Vite server exited with code ${code}`);
}
});
// Handle CTRL+C to stop the Vite server
process.on('SIGINT', () => {
viteProcess.kill('SIGINT');
// spinner.info('Vite server terminated.');
process.exit();
});
}
// Function to copy VITE environment variables to .kaiban/.env
function copyViteEnvVariables() {
const envDir = process.cwd(); // Root directory of the project
const envFiles = fs
.readdirSync(envDir)
.filter((file) => file.startsWith('.env'));
const viteEnvFilePath = path.resolve('.kaiban', '.env');
let viteEnvContent = '';
envFiles.forEach((file) => {
const envConfig = dotenv.parse(fs.readFileSync(path.join(envDir, file)));
Object.keys(envConfig).forEach((key) => {
if (key.startsWith('VITE')) {
viteEnvContent += `${key}=${envConfig[key]}\n`;
}
});
});
fs.writeFileSync(viteEnvFilePath, viteEnvContent, 'utf8');
if (viteEnvContent) {
console.log(
chalk.green(
'All VITE environment variables have been copied to .kaiban/.env.'
)
);
} else {
console.log(
chalk.yellow(
'\n----------------------------------------------------------'
)
);
console.log(chalk.yellow.bold('| Warning:\n'));
console.log(chalk.yellow('| No VITE environment variables were found.\n'));
console.log(
chalk.yellow(
'| Most likely, your agents and tools will need these variables.\n'
)
);
console.log(
chalk.yellow(
'| Please check your .kban.js file to find what is needed.\n'
)
);
console.log(chalk.yellow('| You can add the variables to the .env file'));
console.log(chalk.yellow('| in your root directory.\n'));
console.log(
chalk.yellow('----------------------------------------------------------')
);
}
}
// Function to build the project inside the .kaiban folder
function buildKaibanProject() {
const spinner = ora('Building the project...').start();
try {
execSync('npm run build', { cwd: '.kaiban', stdio: 'inherit' });
spinner.succeed('Project built successfully.');
} catch (error) {
spinner.fail('Failed to build the project.');
throw error;
}
}
// Function to check if Vercel is installed globally and deploy
function deployToVercel() {
// Warm message about API key security
console.log(chalk.yellow.bold('Important Notice:'));
console.log(
chalk.yellow(
'\nThe API keys used by your Agents and Tools in this project **VITE_MY_API_KEY** can be accessed by anyone with programming knowledge who has the deploy URL. This is perfectly acceptable for hustlers, people testing ideas quickly, or in scenarios like:\n'
)
);
// console.log(chalk.yellow(''));
console.log(chalk.yellow(' - Rapid prototyping'));
console.log(chalk.yellow(' - MVPs (Minimum Viable Products)'));
console.log(chalk.yellow(' - Hackathons'));
console.log(chalk.yellow(' - Personal projects'));
console.log(
chalk.yellow(
' - Controlled environments (e.g., internal tools, sandbox testing)'
)
);
console.log(
chalk.yellow(
'\nHowever, for production, we highly recommend securing your keys.'
)
);
console.log(
chalk.yellow(
'For a more secure production setup, consider using a proxy. Learn more at:'
)
);
console.log(
chalk.blue.underline(
'\nhttps://docs.kaibanjs.com/how-to/API%20Key%20Management\n'
)
);
// Prompt user for confirmation
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question(
chalk.cyan('Do you want to continue with the deployment? (yes/no): '),
(answer) => {
if (answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y') {
const spinner = ora('Checking Vercel installation...').start();
try {
// Check if Vercel is installed globally
execSync('vercel --version', { stdio: 'ignore' });
spinner.succeed('Vercel is installed globally.');
} catch {
spinner.warn('Vercel is not installed globally. Installing now...');
try {
execSync('npm install -g vercel', { stdio: 'inherit' });
spinner.succeed('Vercel has been installed globally.');
} catch (installError) {
spinner.fail('Failed to install Vercel globally.');
console.error(chalk.red('Error installing Vercel:'), installError);
rl.close();
return;
}
}
// Build the project before deploying
buildKaibanProject();
// Run npm run deploy inside the .kaiban folder
spinner.start('Deploying with Vercel...');
try {
execSync('npm run deploy', { cwd: '.kaiban', stdio: 'inherit' });
spinner.succeed('Deployment completed successfully.');
} catch (deployError) {
spinner.fail('Deployment failed.');
console.error(chalk.red('Error during deployment:'), deployError);
}
} else {
console.log(chalk.red('Deployment aborted by the user.'));
}
rl.close();
}
);
}
// Function to update package.json with Kaiban scripts
function updatePackageJson() {
const packageJsonPath = path.resolve(process.cwd(), 'package.json');
let packageJson;
let spinner = ora('Updating package.json with Kaiban scripts...').start();
if (!fs.existsSync(packageJsonPath)) {
spinner.text = 'Creating a basic package.json...';
packageJson = {
name: path.basename(process.cwd()),
version: '1.0.0',
description: 'A Kaiban project',
scripts: {},
keywords: ['kaiban'],
author: '',
license: 'ISC',
};
spinner.succeed('Created a basic package.json');
spinner = ora('Adding Kaiban scripts...').start();
} else {
try {
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
} catch (error) {
spinner.fail('Failed to read package.json');
console.error(chalk.red('Error reading package.json:'), error);
return;
}
}
if (!packageJson.scripts) {
packageJson.scripts = {};
}
packageJson.scripts.kaiban = 'kaiban run';
packageJson.scripts['kaiban:deploy'] = 'kaiban deploy';
try {
fs.writeFileSync(
packageJsonPath,
JSON.stringify(packageJson, null, 2),
'utf8'
);
spinner.succeed('package.json updated successfully.');
} catch (error) {
spinner.fail('Failed to update package.json.');
console.error(chalk.red('Error updating package.json:'), error);
}
}
// Function to initialize the Kaiban project
async function initKaibanProject() {
// Step 1: Load environment variables from .env.* files
console.log(chalk.bold('Step 1: Loading environment variables...'));
loadEnvVariables();
console.log('\n');
// Step 2: Clone the kaibanjs-devtools repo if not already cloned
console.log(chalk.bold('Step 2: Setting up the development tools...'));
cloneDevtoolsRepo();
console.log('\n');
// Step 3: Find all *.kban.js files
console.log(chalk.bold('Step 3: Searching for team files...'));
const teamFiles = findTeamFiles(process.cwd());
// Step 4: If no team files are found, copy the sample file
if (teamFiles.length === 0) {
console.log(
chalk.red(
'No .kban.js files found. Copying a sample file to the project root...'
)
);
copySampleTeamFile();
teamFiles.push(path.resolve(process.cwd(), 'team.kban.js')); // Add the copied sample file to the list
} else {
console.log(chalk.green('Found the following team files:'));
console.log(teamFiles.map((file) => `- ${chalk.cyan(file)}`).join('\n'));
}
console.log('\n');
// Step 5: Write teams to `.kaiban/src/teams.js`
writeTeamsFile(teamFiles);
console.log('\n');
// Step 6: Copy VITE environment variables to .kaiban/.env
console.log(chalk.bold('Step 4: Copying VITE environment variables...'));
copyViteEnvVariables();
console.log('\n');
// New Step 7: Update package.json with Kaiban scripts
console.log(chalk.bold('Step 5: Updating package.json...'));
updatePackageJson();
console.log('\n');
}
// Function to run the Vite server
function runKaibanServer() {
// Check if .kaiban folder exists
if (!fs.existsSync(path.resolve('.kaiban'))) {
console.log(
chalk.red(
'Error: .kaiban folder not found. Please run "npx kaibanjs@latest init" first.'
)
);
process.exit(1);
}
// Run the Vite server inside `.kaiban` using spawn
console.log(chalk.bold('Running the Vite server...'));
runViteServer();
}
// Function to check if KaibanJS is installed
function isKaibanJSInstalled() {
try {
execSync('npm list kaibanjs', { stdio: 'ignore' });
return true;
} catch {
return false;
}
}
// Function to install KaibanJS
function installKaibanJS() {
const spinner = ora('Installing KaibanJS and tools...').start();
try {
execSync('npm install kaibanjs @kaibanjs/tools --legacy-peer-deps', {
stdio: 'inherit',
});
spinner.succeed('KaibanJS and tools installed successfully.');
} catch (error) {
spinner.fail('Failed to install KaibanJS and tools.');
console.error(chalk.red('Error installing packages:'), error);
process.exit(1);
}
}
// Add this new function
function ensurePackageJson() {
const packageJsonPath = path.resolve(process.cwd(), 'package.json');
if (!fs.existsSync(packageJsonPath)) {
const spinner = ora('Creating package.json...').start();
try {
execSync('npm init -y', { stdio: 'ignore' });
spinner.succeed('Created package.json');
} catch (error) {
spinner.fail('Failed to create package.json');
console.error(chalk.red('Error creating package.json:'), error);
process.exit(1);
}
}
}
// Main CLI flow
async function main() {
// Display CLI banner for all commands
displayBanner();
const command = process.argv[2];
if (command === 'init' || command === 'run') {
// Ensure package.json exists
ensurePackageJson();
if (!isKaibanJSInstalled()) {
console.log(
chalk.yellow(
'KaibanJS is not installed in this project. Installing now...'
)
);
td.signal('install_kaibanjs');
installKaibanJS();
}
if (command === 'init') {
await initKaibanProject();
td.signal('init_board');
runKaibanServer();
} else {
await initKaibanProject();
td.signal('run_board');
runKaibanServer();
}
} else if (command === 'deploy') {
td.signal('deploy_board');
deployToVercel();
} else {
console.log(
chalk.red(
'Invalid command. Use "init" to initialize, "run" to start the server, or "deploy" to deploy.'
)
);
}
}
// Handle CTRL+C to stop the Vite server
process.on('SIGINT', () => {
console.log(chalk.red('\nProcess terminated by the user.'));
process.exit();
});
// Run the main function
main();