146 lines
5.3 KiB
TypeScript
146 lines
5.3 KiB
TypeScript
import * as path from 'path';
|
|
import * as fs from 'fs-extra';
|
|
import { Command } from 'commander';
|
|
import ora from 'ora';
|
|
import chalk from 'chalk';
|
|
import {
|
|
createWorktree,
|
|
createGit,
|
|
getMainBranch
|
|
} from '../utils/git-utils';
|
|
import {
|
|
loadGlobalConfig,
|
|
saveWorkspaceConfig,
|
|
getWorkspacePath,
|
|
isValidAgentName,
|
|
detectPackageJson,
|
|
installDependencies,
|
|
getGitUserInfo
|
|
} from '../utils/file-utils';
|
|
import { setupEnvironment, runCustomSetupScript } from '../utils/env-utils';
|
|
import { sanitizeAgentName } from '../utils/file-utils';
|
|
import { GitAgentSyncError, InvalidAgentNameError } from '../utils/errors';
|
|
import { CreateOptions } from '../types';
|
|
|
|
export function createCreateCommand(): Command {
|
|
const cmd = new Command('create')
|
|
.description('Create an isolated git worktree for an AI agent')
|
|
.argument('<agent-name>', 'Name identifier for the agent (will become agent-{name})')
|
|
.option('-p, --path <path>', 'Custom path for the workspace')
|
|
.option('-t, --template <template>', 'Path to environment template directory')
|
|
.option('--no-install', 'Skip automatic dependency installation')
|
|
.option('--env <key=value>', 'Set environment variable (can be used multiple times)', collectEnvVars)
|
|
.action(async (agentName, options) => {
|
|
try {
|
|
const sanitizedName = sanitizeAgentName(agentName);
|
|
|
|
if (!isValidAgentName(sanitizedName)) {
|
|
throw new InvalidAgentNameError(agentName);
|
|
}
|
|
|
|
const currentPath = process.cwd();
|
|
const spinner = ora('Initializing...').start();
|
|
|
|
const globalConfig = await loadGlobalConfig();
|
|
const branchName = `agent-${sanitizedName}`;
|
|
const workspacePath = options.path
|
|
? path.resolve(currentPath, options.path)
|
|
: getWorkspacePath(currentPath, sanitizedName);
|
|
|
|
spinner.text = 'Checking git repository...';
|
|
const git = createGit(currentPath);
|
|
|
|
try {
|
|
await git.raw(['rev-parse', '--is-inside-work-tree']);
|
|
} catch {
|
|
throw new GitAgentSyncError('Not a git repository. Please run this command from within a git repository.');
|
|
}
|
|
|
|
spinner.text = 'Checking workspace existence...';
|
|
if (await fs.pathExists(workspacePath)) {
|
|
throw new GitAgentSyncError(`Workspace already exists at ${workspacePath}. Use a different agent name or destroy the existing workspace first.`);
|
|
}
|
|
|
|
spinner.text = 'Determining main branch...';
|
|
const mainBranch = await getMainBranch(git, globalConfig.defaultBranch);
|
|
|
|
spinner.text = 'Creating worktree...';
|
|
await createWorktree(currentPath, workspacePath, branchName, mainBranch);
|
|
|
|
const gitUserInfo = await getGitUserInfo();
|
|
|
|
const createOptions: CreateOptions = {
|
|
path: options.path,
|
|
template: options.template,
|
|
installDeps: options.install !== false,
|
|
environment: parseEnvVars(options.env || [])
|
|
};
|
|
|
|
spinner.text = 'Setting up environment...';
|
|
await setupEnvironment(workspacePath, sanitizedName, createOptions);
|
|
|
|
if (createOptions.installDeps) {
|
|
spinner.text = 'Checking for dependencies...';
|
|
if (await detectPackageJson(workspacePath)) {
|
|
spinner.text = 'Installing dependencies...';
|
|
await installDependencies(workspacePath);
|
|
}
|
|
}
|
|
|
|
const workspaceConfig = {
|
|
agentName: sanitizedName,
|
|
branch: branchName,
|
|
createdAt: new Date().toISOString(),
|
|
mainBranch,
|
|
environment: {
|
|
AGENT_ID: sanitizedName,
|
|
WORKSPACE_PATH: workspacePath,
|
|
GIT_USER_EMAIL: gitUserInfo.email,
|
|
GIT_USER_NAME: gitUserInfo.name
|
|
}
|
|
};
|
|
|
|
await saveWorkspaceConfig(workspacePath, workspaceConfig);
|
|
|
|
spinner.succeed(chalk.green('Workspace created successfully!'));
|
|
|
|
console.log(chalk.cyan('\n📁 Workspace Details:'));
|
|
console.log(` Agent Name: ${chalk.white(sanitizedName)}`);
|
|
console.log(` Branch: ${chalk.white(branchName)}`);
|
|
console.log(` Path: ${chalk.white(workspacePath)}`);
|
|
console.log(` Main Branch: ${chalk.white(mainBranch)}`);
|
|
console.log(` Created: ${chalk.white(new Date().toISOString())}`);
|
|
console.log(chalk.cyan('\n📝 Next Steps:'));
|
|
console.log(` 1. ${chalk.white(`cd ${workspacePath}`)}`);
|
|
console.log(` 2. ${chalk.white('Start your coding tasks')}`);
|
|
console.log(` 3. ${chalk.white('git-agent-sync status')} - Track changes`);
|
|
console.log(` 4. ${chalk.white('git-agent-sync diff')} - Generate diffs`);
|
|
console.log(` 5. ${chalk.white('git-agent-sync merge')} - Merge back to main`);
|
|
|
|
} catch (error) {
|
|
const gitError = error instanceof GitAgentSyncError ? error : new GitAgentSyncError(String(error));
|
|
console.error(chalk.red(`\n❌ Error: ${gitError.message}`));
|
|
process.exit(1);
|
|
}
|
|
});
|
|
|
|
return cmd;
|
|
}
|
|
|
|
function collectEnvVars(value: string, previous: string[]): string[] {
|
|
return [...(previous || []), value];
|
|
}
|
|
|
|
function parseEnvVars(envVars: string[]): Record<string, string> {
|
|
const result: Record<string, string> = {};
|
|
|
|
for (const envVar of envVars) {
|
|
const [key, ...valueParts] = envVar.split('=');
|
|
if (key && valueParts.length > 0) {
|
|
result[key] = valueParts.join('=');
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|