import * as fs from 'fs-extra'; import * as path from 'path'; import * as os from 'os'; import { GlobalConfig, WorkspaceConfig, WorkspaceInfo } from '../types/index'; import { GitAgentSyncError } from './errors'; const GLOBAL_CONFIG_DIR = '.git-agent-sync'; const GLOBAL_CONFIG_FILE = 'config.json'; const WORKSPACE_CONFIG_FILE = '.agent-workspace.json'; export function getGlobalConfigDir(): string { return process.env.GIT_AGENT_SYNC_CONFIG ? path.dirname(process.env.GIT_AGENT_SYNC_CONFIG) : path.join(os.homedir(), GLOBAL_CONFIG_DIR); } export function getGlobalConfigPath(): string { return process.env.GIT_AGENT_SYNC_CONFIG || path.join(getGlobalConfigDir(), GLOBAL_CONFIG_FILE); } export async function loadGlobalConfig(): Promise { const configPath = getGlobalConfigPath(); if (await fs.pathExists(configPath)) { const config = await fs.readJson(configPath); return { ...getDefaultGlobalConfig(), ...config }; } return getDefaultGlobalConfig(); } export function getDefaultGlobalConfig(): GlobalConfig { return { workspacePath: process.env.GIT_AGENT_SYNC_PATH || './.agent-workspaces', defaultBranch: 'main', autoInstall: process.env.GIT_AGENT_SYNC_AUTO_INSTALL !== 'false', templates: { default: process.env.GIT_AGENT_SYNC_TEMPLATE || path.join(os.homedir(), GLOBAL_CONFIG_DIR, 'templates', 'default') } }; } export async function saveGlobalConfig(config: GlobalConfig): Promise { await fs.ensureDir(getGlobalConfigDir()); await fs.writeJson(getGlobalConfigPath(), config, { spaces: 2 }); } export function getWorkspacePath(basePath: string, agentName: string): string { return path.join(basePath, '.agent-workspaces', `agent-${agentName}`); } export async function loadWorkspaceConfig(workspacePath: string): Promise { const configPath = path.join(workspacePath, WORKSPACE_CONFIG_FILE); if (await fs.pathExists(configPath)) { return fs.readJson(configPath); } return null; } export async function saveWorkspaceConfig(workspacePath: string, config: WorkspaceConfig): Promise { const configPath = path.join(workspacePath, WORKSPACE_CONFIG_FILE); await fs.writeJson(configPath, config, { spaces: 2 }); } export async function getAllWorkspaces(basePath: string): Promise { const workspaceRoot = path.join(basePath, '.agent-workspaces'); if (!await fs.pathExists(workspaceRoot)) return []; const entries = await fs.readdir(workspaceRoot); const workspaces: WorkspaceInfo[] = []; for (const entry of entries) { if (entry.startsWith('agent-')) { const workspacePath = path.join(workspaceRoot, entry); const stat = await fs.stat(workspacePath); if (stat.isDirectory()) { workspaces.push({ name: entry, path: workspacePath, branch: entry, exists: true, hasChanges: false, changeCount: 0 }); } } } return workspaces; } export function sanitizeAgentName(name: string): string { return name.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, ''); } export function isValidAgentName(name: string): boolean { return /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(name) && name.length >= 1 && name.length <= 100; } export async function isGitRepository(basePath: string): Promise { try { await fs.access(path.join(basePath, '.git'), fs.constants.R_OK); return true; } catch { return false; } } export async function hasUncommittedChanges(workspacePath: string): Promise { const { execa } = await import('execa'); try { const result = await execa('git', ['status', '--porcelain'], { cwd: workspacePath }); return result.stdout.trim().length > 0; } catch { return false; } } export async function detectPackageJson(workspacePath: string): Promise { return fs.pathExists(path.join(workspacePath, 'package.json')); } export async function installDependencies(workspacePath: string): Promise { const { execa } = await import('execa'); await execa('npm', ['install'], { cwd: workspacePath }); } export async function readPackageJson(workspacePath: string): Promise<{ scripts?: Record } | null> { const pkgPath = path.join(workspacePath, 'package.json'); if (await fs.pathExists(pkgPath)) { return fs.readJson(pkgPath); } return null; } export async function getGitUserInfo(): Promise<{ name: string; email: string }> { const { execa } = await import('execa'); try { const nameResult = await execa('git', ['config', 'user.name']); const emailResult = await execa('git', ['config', 'user.email']); return { name: nameResult.stdout.trim(), email: emailResult.stdout.trim() }; } catch { return { name: 'Unknown', email: 'unknown@example.com' }; } }