This commit is contained in:
@@ -1,145 +1,67 @@
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as fs from 'fs-extra';
|
|
||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import ora from 'ora';
|
import ora from 'ora';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import {
|
import { createWorktree, createGit, getMainBranch } from '../utils/git-utils';
|
||||||
createWorktree,
|
import { loadGlobalConfig, saveWorkspaceConfig, getWorkspacePath, isValidAgentName, detectPackageJson, installDependencies, getGitUserInfo, sanitizeAgentName } from '../utils/file-utils';
|
||||||
createGit,
|
import { setupEnvironment } from '../utils/env-utils';
|
||||||
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 { GitAgentSyncError, InvalidAgentNameError } from '../utils/errors';
|
||||||
import { CreateOptions } from '../types';
|
import { CreateOptions } from '../types';
|
||||||
|
|
||||||
export function createCreateCommand(): Command {
|
export function createCreateCommand(): Command {
|
||||||
const cmd = new Command('create')
|
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', collectEnvVars).action(async (agentName, options) => {
|
||||||
.description('Create an isolated git worktree for an AI agent')
|
try {
|
||||||
.argument('<agent-name>', 'Name identifier for the agent (will become agent-{name})')
|
const sanitizedName = sanitizeAgentName(agentName);
|
||||||
.option('-p, --path <path>', 'Custom path for the workspace')
|
if (!isValidAgentName(sanitizedName)) throw new InvalidAgentNameError(agentName);
|
||||||
.option('-t, --template <template>', 'Path to environment template directory')
|
const currentPath = process.cwd();
|
||||||
.option('--no-install', 'Skip automatic dependency installation')
|
const spinner = ora('Initializing...').start();
|
||||||
.option('--env <key=value>', 'Set environment variable (can be used multiple times)', collectEnvVars)
|
const globalConfig = await loadGlobalConfig();
|
||||||
.action(async (agentName, options) => {
|
const branchName = `agent-${sanitizedName}`;
|
||||||
try {
|
const workspacePath = options.path ? path.resolve(currentPath, options.path) : getWorkspacePath(currentPath, sanitizedName);
|
||||||
const sanitizedName = sanitizeAgentName(agentName);
|
spinner.text = 'Checking git repository...';
|
||||||
|
const git = createGit(currentPath);
|
||||||
if (!isValidAgentName(sanitizedName)) {
|
try { await git.raw(['rev-parse', '--is-inside-work-tree']); } catch { throw new GitAgentSyncError('Not a git repository.'); }
|
||||||
throw new InvalidAgentNameError(agentName);
|
spinner.text = 'Checking workspace existence...';
|
||||||
|
if (await fs.pathExists(workspacePath)) throw new GitAgentSyncError(`Workspace already exists at ${workspacePath}.`);
|
||||||
|
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 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);
|
|
||||||
}
|
}
|
||||||
});
|
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)}`);
|
||||||
|
} 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;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
function collectEnvVars(value: string, previous: string[]): string[] { return [...(previous || []), value]; }
|
||||||
function collectEnvVars(value: string, previous: string[]): string[] {
|
|
||||||
return [...(previous || []), value];
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseEnvVars(envVars: string[]): Record<string, string> {
|
function parseEnvVars(envVars: string[]): Record<string, string> {
|
||||||
const result: Record<string, string> = {};
|
const result: Record<string, string> = {};
|
||||||
|
|
||||||
for (const envVar of envVars) {
|
for (const envVar of envVars) {
|
||||||
const [key, ...valueParts] = envVar.split('=');
|
const [key, ...valueParts] = envVar.split('=');
|
||||||
if (key && valueParts.length > 0) {
|
if (key && valueParts.length > 0) result[key] = valueParts.join('=');
|
||||||
result[key] = valueParts.join('=');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fs.pathExists(path: string): Promise<boolean> { return require('fs-extra').pathExists(path); }
|
||||||
Reference in New Issue
Block a user