aiwg
Version:
Cognitive architecture for AI-augmented software development with structured memory, ensemble validation, and closed-loop correction. FAIR-aligned artifacts, 84% cost reduction via human-in-the-loop, standards adopted by 100+ organizations.
407 lines (366 loc) • 16.5 kB
JavaScript
/**
* Deploy Agents and Commands - Orchestrator
*
* Deploy agents/commands from this repository to a target project.
* Delegates to provider-specific modules for platform-specific deployment.
*
* Usage:
* node tools/agents/deploy-agents.mjs [options]
*
* Options:
* --source <path> Source directory (defaults to repo root)
* --target <path> Target directory (defaults to cwd)
* --mode <type> Deployment mode: general, sdlc, marketing (alias: mmk), media-curator, research, both, or all (default)
* --deploy-commands Deploy commands in addition to agents
* --deploy-skills Deploy skills in addition to agents
* --deploy-rules Deploy rules in addition to agents
* --commands-only Deploy only commands (skip agents)
* --skills-only Deploy only skills (skip agents)
* --rules-only Deploy only rules (skip agents)
* --dry-run Show what would be deployed without writing
* --force Overwrite existing files
* --provider <name> Target provider: claude (default), openai, codex, cursor, opencode, copilot, factory, warp, or windsurf
* --reasoning-model <name> Override model for reasoning tasks
* --coding-model <name> Override model for coding tasks
* --efficiency-model <name> Override model for efficiency tasks
* --as-agents-md Aggregate to single AGENTS.md (OpenAI/Codex)
* --create-agents-md Create/update AGENTS.md template (Factory/Codex/OpenCode/Cursor)
*
* Modes:
* general - Deploy only writing-quality addon agents and commands (alias: writing)
* writing - Deploy only writing-quality addon agents (alias for general)
* sdlc - Deploy only SDLC Complete framework agents and commands
* marketing - Deploy only Media/Marketing Kit framework agents and commands (alias: mmk)
* media-curator - Deploy only Media Curator framework agents and commands
* research - Deploy only Research Complete framework agents and commands
* both - Deploy writing + SDLC (legacy compatibility)
* all - Deploy all frameworks + addons (default)
*
* Providers:
* claude - Claude Code (default) - .claude/agents/, .claude/commands/, .claude/skills/, .claude/rules/
* factory - Factory AI - .factory/droids/, .factory/commands/, .factory/skills/, .factory/rules/
* codex - OpenAI Codex - .codex/agents/, .codex/commands/, .codex/skills/, .codex/rules/
* openai - Alias for codex
* opencode - OpenCode - .opencode/agent/, .opencode/command/, .opencode/skill/, .opencode/rule/
* copilot - GitHub Copilot - .github/agents/, .github/commands/, .github/skills/, .github/copilot-rules/
* cursor - Cursor IDE - .cursor/agents/, .cursor/commands/, .cursor/skills/, .cursor/rules/
* warp - Warp Terminal - .warp/agents/, .warp/commands/, .warp/skills/, .warp/rules/ + WARP.md
* windsurf - Windsurf - .windsurf/agents/, .windsurf/workflows/, .windsurf/skills/, .windsurf/rules/
*
* Defaults:
* --source resolves relative to this script's repo root (../..)
* --target is process.cwd()
* --mode is 'all'
*/
import fs from 'fs';
import path from 'path';
import os from 'os';
import { fileURLToPath } from 'url';
// ============================================================================
// Provider Registry
// ============================================================================
const PROVIDER_ALIASES = {
'openai': 'codex'
};
const AVAILABLE_PROVIDERS = ['claude', 'factory', 'codex', 'opencode', 'copilot', 'cursor', 'warp', 'windsurf'];
// ============================================================================
// Argument Parsing
// ============================================================================
function parseArgs() {
const args = process.argv.slice(2);
const cfg = {
source: null,
target: process.cwd(),
mode: 'all', // 'general', 'sdlc', 'marketing', 'media-curator', 'research', 'both' (legacy), or 'all'
dryRun: false,
force: false,
provider: 'claude',
reasoningModel: null,
codingModel: null,
efficiencyModel: null,
asAgentsMd: false,
createAgentsMd: false,
deployCommands: false,
deploySkills: false,
deployRules: false,
commandsOnly: false,
skillsOnly: false,
rulesOnly: false,
filter: null, // Glob pattern for agent names
filterRole: null, // Filter by role: reasoning|coding|efficiency
save: false, // Save model config to project models.json
saveUser: false // Save model config to ~/.config/aiwg/models.json
};
for (let i = 0; i < args.length; i++) {
const a = args[i];
if (a === '--source' && args[i + 1]) cfg.source = path.resolve(args[++i]);
else if (a === '--target' && args[i + 1]) cfg.target = path.resolve(args[++i]);
else if (a === '--mode' && args[i + 1]) cfg.mode = String(args[++i]).toLowerCase();
else if (a === '--dry-run') cfg.dryRun = true;
else if (a === '--force') cfg.force = true;
else if ((a === '--provider' || a === '--platform') && args[i + 1]) cfg.provider = String(args[++i]).toLowerCase();
else if (a === '--reasoning-model' && args[i + 1]) cfg.reasoningModel = args[++i];
else if (a === '--coding-model' && args[i + 1]) cfg.codingModel = args[++i];
else if (a === '--efficiency-model' && args[i + 1]) cfg.efficiencyModel = args[++i];
else if (a === '--as-agents-md') cfg.asAgentsMd = true;
else if (a === '--create-agents-md') cfg.createAgentsMd = true;
else if (a === '--deploy-commands') cfg.deployCommands = true;
else if (a === '--deploy-skills') cfg.deploySkills = true;
else if (a === '--deploy-rules') cfg.deployRules = true;
else if (a === '--commands-only') cfg.commandsOnly = true;
else if (a === '--skills-only') cfg.skillsOnly = true;
else if (a === '--rules-only') cfg.rulesOnly = true;
else if (a === '--filter' && args[i + 1]) cfg.filter = args[++i];
else if (a === '--filter-role' && args[i + 1]) cfg.filterRole = args[++i];
else if (a === '--save') cfg.save = true;
else if (a === '--save-user') cfg.saveUser = true;
else if (a === '--help' || a === '-h') {
printHelp();
process.exit(0);
}
}
return cfg;
}
function printHelp() {
console.log(`
Deploy Agents and Commands
Usage:
node tools/agents/deploy-agents.mjs [options]
aiwg -deploy-agents [options]
Options:
--source <path> Source directory (defaults to repo root)
--target <path> Target directory (defaults to cwd)
--mode <type> Deployment mode: general, sdlc, marketing (alias: mmk), media-curator, research, both, or all (default)
--deploy-commands Deploy commands in addition to agents
--deploy-skills Deploy skills in addition to agents
--deploy-rules Deploy rules in addition to agents
--commands-only Deploy only commands (skip agents)
--skills-only Deploy only skills (skip agents)
--rules-only Deploy only rules (skip agents)
--dry-run Show what would be deployed without writing
--force Overwrite existing files
--provider <name> Target provider (see below)
--reasoning-model <name> Override model for reasoning tasks
--coding-model <name> Override model for coding tasks
--efficiency-model <name> Override model for efficiency tasks
--filter <pattern> Only deploy agents matching pattern (glob)
--filter-role <role> Only deploy agents of role: reasoning|coding|efficiency
--save Save model config to project models.json
--save-user Save model config to ~/.config/aiwg/models.json
--as-agents-md Aggregate to single AGENTS.md (Codex)
--create-agents-md Create/update AGENTS.md template
Providers (all deploy agents, commands, skills, and rules):
claude - Claude Code (default)
Paths: .claude/agents/, .claude/commands/, .claude/skills/, .claude/rules/
factory - Factory AI
Paths: .factory/droids/, .factory/commands/, .factory/skills/, .factory/rules/
codex - OpenAI Codex (alias: openai)
Paths: .codex/agents/, .codex/commands/, .codex/skills/, .codex/rules/
opencode - OpenCode
Paths: .opencode/agent/, .opencode/command/, .opencode/skill/, .opencode/rule/
copilot - GitHub Copilot
Paths: .github/agents/, .github/commands/, .github/skills/, .github/copilot-rules/
cursor - Cursor IDE
Paths: .cursor/agents/, .cursor/commands/, .cursor/skills/, .cursor/rules/
warp - Warp Terminal
Paths: .warp/agents/, .warp/commands/, .warp/skills/, .warp/rules/ + WARP.md
windsurf - Windsurf
Paths: .windsurf/agents/, .windsurf/workflows/, .windsurf/skills/, .windsurf/rules/
Modes:
general - Writing-quality addon agents and commands (alias: writing)
sdlc - SDLC Complete framework agents and commands
marketing - Media/Marketing Kit framework agents and commands (alias: mmk)
media-curator - Media Curator framework agents and commands
research - Research Complete framework agents and commands
both - writing + SDLC (legacy compatibility)
all - All frameworks + addons (default)
Examples:
# Deploy SDLC framework to Claude Code
aiwg -deploy-agents --mode sdlc
# Deploy to Factory with commands and AGENTS.md
aiwg -deploy-agents --provider factory --mode sdlc --deploy-commands --create-agents-md
# Deploy to GitHub Copilot
aiwg -deploy-agents --provider copilot --mode sdlc
# Dry run to preview deployment
aiwg -deploy-agents --provider cursor --mode sdlc --dry-run
`);
}
// ============================================================================
// Provider Loading
// ============================================================================
/**
* Resolve provider name (handle aliases)
*/
function resolveProvider(name) {
const resolved = PROVIDER_ALIASES[name] || name;
if (!AVAILABLE_PROVIDERS.includes(resolved)) {
console.error(`Unknown provider: ${name}`);
console.error(`Available providers: ${AVAILABLE_PROVIDERS.join(', ')}`);
console.error(`Aliases: ${Object.entries(PROVIDER_ALIASES).map(([a, p]) => `${a} -> ${p}`).join(', ')}`);
process.exit(1);
}
return resolved;
}
/**
* Dynamically load provider module
*/
async function loadProvider(providerName) {
const resolved = resolveProvider(providerName);
const providerPath = `./providers/${resolved}.mjs`;
try {
const provider = await import(providerPath);
return provider.default || provider;
} catch (err) {
console.error(`Failed to load provider '${resolved}':`, err.message);
process.exit(1);
}
}
// ============================================================================
// Model Configuration Persistence
// ============================================================================
/**
* Save model configuration to project or user config file
* @param {object} cfg - Configuration with model overrides
* @param {string} providerName - Provider name for provider-specific config
*/
async function saveModelConfig(cfg, providerName) {
// Build config object with only the provided overrides
const modelConfig = {};
// Provider-specific tier configuration
if (cfg.reasoningModel || cfg.codingModel || cfg.efficiencyModel) {
modelConfig[providerName] = {};
if (cfg.reasoningModel) {
modelConfig[providerName].reasoning = { model: cfg.reasoningModel };
}
if (cfg.codingModel) {
modelConfig[providerName].coding = { model: cfg.codingModel };
}
if (cfg.efficiencyModel) {
modelConfig[providerName].efficiency = { model: cfg.efficiencyModel };
}
}
// Shorthand mappings
if (cfg.reasoningModel || cfg.codingModel || cfg.efficiencyModel) {
modelConfig.shorthand = {};
if (cfg.reasoningModel) modelConfig.shorthand.opus = cfg.reasoningModel;
if (cfg.codingModel) modelConfig.shorthand.sonnet = cfg.codingModel;
if (cfg.efficiencyModel) modelConfig.shorthand.haiku = cfg.efficiencyModel;
}
// Determine target path
let targetPath;
if (cfg.saveUser) {
const configDir = path.join(os.homedir(), '.config', 'aiwg');
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
targetPath = path.join(configDir, 'models.json');
} else {
targetPath = path.join(cfg.target, 'models.json');
}
// Merge with existing config if it exists
let existingConfig = {};
if (fs.existsSync(targetPath)) {
try {
existingConfig = JSON.parse(fs.readFileSync(targetPath, 'utf8'));
} catch {
// Ignore parse errors, start fresh
}
}
// Deep merge: existing config + new overrides
const mergedConfig = deepMerge(existingConfig, modelConfig);
// Write the config
fs.writeFileSync(targetPath, JSON.stringify(mergedConfig, null, 2) + '\n', 'utf8');
console.log(`\nModel configuration saved to: ${targetPath}`);
}
/**
* Deep merge two objects
*/
function deepMerge(target, source) {
const result = { ...target };
for (const key of Object.keys(source)) {
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
result[key] = deepMerge(result[key] || {}, source[key]);
} else {
result[key] = source[key];
}
}
return result;
}
// ============================================================================
// Main Entry Point
// ============================================================================
(async () => {
const cfg = parseArgs();
// Resolve source directory (default to repo root relative to this script)
const __filename = fileURLToPath(import.meta.url);
const scriptDir = path.dirname(__filename);
const srcRoot = cfg.source || path.resolve(scriptDir, '..', '..');
// Validate source directory
if (!fs.existsSync(srcRoot)) {
console.error(`Source directory not found: ${srcRoot}`);
process.exit(1);
}
// Validate target directory
if (!fs.existsSync(cfg.target)) {
console.log(`Creating target directory: ${cfg.target}`);
fs.mkdirSync(cfg.target, { recursive: true });
}
// Normalize mode aliases
if (cfg.mode === 'writing') cfg.mode = 'general';
if (cfg.mode === 'mmk') cfg.mode = 'marketing';
console.log(`\n=== AIWG Agent Deployment ===`);
console.log(`Provider: ${cfg.provider}`);
console.log(`Source: ${srcRoot}`);
console.log(`Target: ${cfg.target}`);
console.log(`Mode: ${cfg.mode}`);
if (cfg.dryRun) console.log(`Dry run: enabled`);
if (cfg.filter) console.log(`Filter: ${cfg.filter}`);
if (cfg.filterRole) console.log(`Filter role: ${cfg.filterRole}`);
if (cfg.reasoningModel) console.log(`Reasoning model: ${cfg.reasoningModel}`);
if (cfg.codingModel) console.log(`Coding model: ${cfg.codingModel}`);
if (cfg.efficiencyModel) console.log(`Efficiency model: ${cfg.efficiencyModel}`);
if (cfg.save) console.log(`Save to project: enabled`);
if (cfg.saveUser) console.log(`Save to user config: enabled`);
// Load provider module
const provider = await loadProvider(cfg.provider);
console.log(`\nLoaded provider: ${provider.name}`);
// Build options for provider
const opts = {
srcRoot,
target: cfg.target,
mode: cfg.mode,
dryRun: cfg.dryRun,
force: cfg.force,
reasoningModel: cfg.reasoningModel,
codingModel: cfg.codingModel,
efficiencyModel: cfg.efficiencyModel,
asAgentsMd: cfg.asAgentsMd,
createAgentsMd: cfg.createAgentsMd,
deployCommands: cfg.deployCommands,
deploySkills: cfg.deploySkills,
deployRules: cfg.deployRules,
commandsOnly: cfg.commandsOnly,
skillsOnly: cfg.skillsOnly,
rulesOnly: cfg.rulesOnly,
filter: cfg.filter,
filterRole: cfg.filterRole,
save: cfg.save,
saveUser: cfg.saveUser
};
// Delegate to provider
try {
await provider.deploy(opts);
// Save model configuration if requested
if ((cfg.save || cfg.saveUser) && !cfg.dryRun) {
await saveModelConfig(cfg, provider.name);
}
console.log(`\n=== Deployment complete ===\n`);
} catch (err) {
console.error(`\nDeployment failed:`, err.message);
if (cfg.dryRun) {
console.error('(dry-run mode - no files were modified)');
}
process.exit(1);
}
})();