@aiwg/cli 2026.8.0 → 2026.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +33 -0
  2. package/agentic/code/providers/capability-matrix.yaml +511 -0
  3. package/agentic/code/providers/model-capabilities.v1.json +120 -0
  4. package/agentic/code/providers/model-catalog.v1.json +96 -0
  5. package/agentic/code/providers/model-policy-evaluations.v1.json +50 -0
  6. package/agentic/code/providers/premium-model-allowlist.v1.json +36 -0
  7. package/bin/aiwg.mjs +14 -10
  8. package/dist/src/api/index.d.ts +1 -0
  9. package/dist/src/api/index.js +1 -0
  10. package/dist/src/artifacts/cli.js +2 -0
  11. package/dist/src/artifacts/types.js +4 -0
  12. package/dist/src/auth/client.js +209 -0
  13. package/dist/src/auth/config.js +38 -0
  14. package/dist/src/auth/credential-store.js +141 -0
  15. package/dist/src/auth/resource-credentials.js +25 -0
  16. package/dist/src/auth/types.js +2 -0
  17. package/dist/src/channel/manager.mjs +5 -5
  18. package/dist/src/cli/handlers/auth.js +125 -0
  19. package/dist/src/cli/handlers/help.js +1 -0
  20. package/dist/src/cli/handlers/index.js +3 -1
  21. package/dist/src/cli/handlers/install.js +42 -4
  22. package/dist/src/cli/handlers/marketplace.js +375 -122
  23. package/dist/src/cli/handlers/resource-versions.js +2 -0
  24. package/dist/src/cli/handlers/sessions.js +23 -5
  25. package/dist/src/cli/handlers/subcommands.js +10 -1
  26. package/dist/src/cli/handlers/use.js +342 -43
  27. package/dist/src/config/gitignore.js +1 -0
  28. package/dist/src/extensions/commands/definitions.js +19 -0
  29. package/dist/src/marketplace/exchange.js +602 -0
  30. package/dist/src/marketplace/provenance-types.js +19 -0
  31. package/dist/src/marketplace/provenance.js +834 -0
  32. package/dist/src/memory/canonical-context.js +342 -0
  33. package/dist/src/memory/context-pack.js +282 -0
  34. package/dist/src/memory/index.js +4 -0
  35. package/dist/src/memory/intake.js +118 -0
  36. package/dist/src/packages/adapters/git.js +79 -29
  37. package/dist/src/packages/package-discovery.js +81 -0
  38. package/dist/src/packages/package-registry.js +2 -0
  39. package/dist/src/packages/registry.js +119 -20
  40. package/dist/src/resources/resolver.js +1 -0
  41. package/dist/src/resources/web-release.d.ts +3 -1
  42. package/dist/src/resources/web-release.js +14 -6
  43. package/dist/src/serve/agentic-sandbox-fleet-client.js +213 -0
  44. package/dist/src/serve/fleet-mission-conductor.js +293 -0
  45. package/dist/src/sessions/index.js +1 -0
  46. package/dist/src/sessions/output-registration.js +338 -0
  47. package/dist/src/sessions/promotion.js +73 -2
  48. package/dist/src/sessions/repository.js +2 -1
  49. package/dist/src/update/notifier.mjs +13 -2
  50. package/package.json +8 -1
  51. package/tools/_resolve-impl.mjs +74 -0
  52. package/tools/agents/deploy-agents.mjs +962 -0
  53. package/tools/agents/providers/base.mjs +2954 -0
  54. package/tools/agents/providers/claude.mjs +711 -0
  55. package/tools/agents/providers/codex.mjs +699 -0
  56. package/tools/agents/providers/copilot.mjs +659 -0
  57. package/tools/agents/providers/cursor.mjs +714 -0
  58. package/tools/agents/providers/factory.mjs +1130 -0
  59. package/tools/agents/providers/hermes.mjs +663 -0
  60. package/tools/agents/providers/hook-capabilities.mjs +85 -0
  61. package/tools/agents/providers/model-role.mjs +56 -0
  62. package/tools/agents/providers/openclaw-translator.mjs +348 -0
  63. package/tools/agents/providers/openclaw.mjs +680 -0
  64. package/tools/agents/providers/opencode.mjs +675 -0
  65. package/tools/agents/providers/openhuman.mjs +292 -0
  66. package/tools/agents/providers/warp.mjs +413 -0
  67. package/tools/agents/providers/windsurf.mjs +748 -0
  68. package/tools/commands/deploy-prompts-codex.mjs +336 -0
  69. package/tools/plugin/package-plugins.mjs +1013 -0
  70. package/tools/skills/deploy-skills-codex.mjs +571 -0
@@ -0,0 +1,962 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Deploy Agents and Commands - Orchestrator
4
+ *
5
+ * Deploy agents/commands from this repository to a target project.
6
+ * Delegates to provider-specific modules for platform-specific deployment.
7
+ *
8
+ * Usage:
9
+ * node tools/agents/deploy-agents.mjs [options]
10
+ *
11
+ * Options:
12
+ * --source <path> Source directory (defaults to repo root)
13
+ * --target <path> Target directory (defaults to cwd)
14
+ * --mode <type> Deployment mode: general, sdlc, marketing (alias: mmk), media-curator, research, both, or all (default)
15
+ * --deploy-commands Deploy commands in addition to agents
16
+ * --deploy-skills Deploy skills in addition to agents
17
+ * --deploy-rules Deploy rules in addition to agents
18
+ * --commands-only Deploy only commands (skip agents)
19
+ * --skills-only Deploy only skills (skip agents)
20
+ * --rules-only Deploy only rules (skip agents)
21
+ * --dry-run Show what would be deployed without writing
22
+ * --force Overwrite existing files
23
+ * --provider <name> Target provider: claude (default), openai, codex, cursor, opencode, copilot, factory, warp, windsurf, hermes, or openclaw
24
+ * --model <name> Override model for all tiers (blanket)
25
+ * --reasoning-model <name> Override model for reasoning tasks
26
+ * --coding-model <name> Override model for coding tasks
27
+ * --efficiency-model <name> Override model for efficiency tasks
28
+ * --model-tier <tier> Override all artifacts with economy|standard|premium
29
+ * --as-agents-md Aggregate to single AGENTS.md (OpenAI/Codex)
30
+ * --create-agents-md Create/update AGENTS.md template
31
+ * --skip-commands-migration Skip deleting the commands directory (warns about duplicate TUI entries) (Factory/Codex/OpenCode/Cursor)
32
+ *
33
+ * Modes:
34
+ * general - Deploy only writing-quality addon agents and commands (alias: writing)
35
+ * writing - Deploy only writing-quality addon agents (alias for general)
36
+ * sdlc - Deploy only SDLC Complete framework agents and commands
37
+ * marketing - Deploy only Media/Marketing Kit framework agents and commands (alias: mmk)
38
+ * media-curator - Deploy only Media Curator framework agents and commands
39
+ * research - Deploy only Research Complete framework agents and commands
40
+ * both - Deploy writing + SDLC (legacy compatibility)
41
+ * all - Deploy all frameworks + addons (default)
42
+ *
43
+ * Providers:
44
+ * claude - Claude Code (default) - .claude/agents/, .claude/commands/, .claude/skills/, .claude/rules/
45
+ * factory - Factory AI - .factory/droids/, .factory/commands/, .factory/skills/, .factory/rules/
46
+ * codex - OpenAI Codex - .codex/agents/, .codex/commands/, .codex/skills/, .codex/rules/
47
+ * openai - Alias for codex
48
+ * opencode - OpenCode - .opencode/skill/, .opencode/rule/ (agents+commands not file-based)
49
+ * copilot - GitHub Copilot - .github/agents/, .github/commands/, .github/skills/, .github/copilot-rules/
50
+ * cursor - Cursor IDE - .cursor/agents/, .cursor/commands/, .cursor/skills/, .cursor/rules/
51
+ * warp - Warp Terminal - .warp/agents/, .warp/commands/, .warp/skills/, .warp/rules/ + WARP.md
52
+ * windsurf - Windsurf - .windsurf/agents/, .windsurf/workflows/, .windsurf/skills/, .windsurf/rules/
53
+ * openclaw - OpenClaw - ~/.openclaw/agents/, ~/.openclaw/commands/, ~/.openclaw/skills/, ~/.openclaw/rules/, ~/.openclaw/behaviors/
54
+ *
55
+ * Defaults:
56
+ * --source resolves relative to this script's repo root (../..)
57
+ * --target is process.cwd()
58
+ * --mode is 'all'
59
+ */
60
+
61
+ import realFs from 'fs';
62
+ import { createRequire } from 'module';
63
+ const _require = createRequire(import.meta.url);
64
+ const staticModelCatalog = _require('../../agentic/code/providers/model-catalog.v1.json');
65
+ let fs;
66
+ try { const gfs = _require('graceful-fs'); gfs.gracefulify(realFs); fs = realFs; } catch { fs = realFs; }
67
+ import path from 'path';
68
+ import os from 'os';
69
+ import readline from 'readline';
70
+ import { fileURLToPath } from 'url';
71
+ import {
72
+ collectBehaviorDirs,
73
+ collectFrameworkArtifacts,
74
+ computeAllArtifactBasenames,
75
+ deployEmulatedBehaviors,
76
+ getAddonSkillDirs,
77
+ listSkillDirs,
78
+ loadModelConfig,
79
+ loadRuntimeModelCatalog,
80
+ migrateCommandsDirectory,
81
+ parseFrontmatter,
82
+ pruneStaleAiwgFiles,
83
+ resolveAiwgRoot,
84
+ } from './providers/base.mjs';
85
+ const modelCatalog = loadRuntimeModelCatalog(staticModelCatalog);
86
+
87
+ /**
88
+ * Read version from package.json at the source root.
89
+ * Falls back to 'unknown' if unavailable.
90
+ */
91
+ function getDeployVersion(srcRoot) {
92
+ try {
93
+ const pkg = JSON.parse(fs.readFileSync(path.join(srcRoot, 'package.json'), 'utf8'));
94
+ return pkg.version || 'unknown';
95
+ } catch {
96
+ return 'unknown';
97
+ }
98
+ }
99
+
100
+ // ============================================================================
101
+ // Provider Registry
102
+ // ============================================================================
103
+
104
+ const PROVIDER_ALIASES = {
105
+ 'openai': 'codex',
106
+ 'devin-desktop': 'windsurf',
107
+ 'devin-local': 'windsurf',
108
+ 'cascade': 'windsurf',
109
+ };
110
+
111
+ const AVAILABLE_PROVIDERS = ['claude', 'factory', 'codex', 'opencode', 'copilot', 'cursor', 'warp', 'windsurf', 'hermes', 'openclaw', 'openhuman'];
112
+
113
+ const UNSUPPORTED_PROVIDER_HINTS = {
114
+ devin: [
115
+ 'Devin Desktop is supported through the Windsurf compatibility adapter:',
116
+ ' aiwg use sdlc --provider windsurf',
117
+ ' aiwg use sdlc --provider devin-desktop',
118
+ 'Devin CLI has distinct rules/skills surfaces and is recorded as future-provider metadata; AIWG does not emit .devin/ provider output yet.',
119
+ ],
120
+ 'devin-cli': [
121
+ 'Devin CLI has distinct rules/skills surfaces and is not a deployable AIWG provider yet.',
122
+ 'Use --provider windsurf or --provider devin-desktop for Devin Desktop/Windsurf local IDE deployments.',
123
+ ],
124
+ };
125
+
126
+ const MIRRORED_STANDARD_COMMAND_SKILLS = new Set([
127
+ 'aiwg-setup-project',
128
+ 'aiwg-update-claude',
129
+ 'aiwg-update-agents-md',
130
+ 'sdlc-accelerate',
131
+ 'project-status',
132
+ 'intake-wizard',
133
+ 'intake-from-codebase',
134
+ 'intake-start',
135
+ // Issue-workflow entry commands — invoked directly by users; mirror as
136
+ // commands so they reach .opencode/command/, .claude/commands/, etc. (#1549).
137
+ 'address-issues',
138
+ 'issue-audit',
139
+ ]);
140
+
141
+ const MIRRORED_KERNEL_COMMAND_SKILLS = new Set([
142
+ 'aiwg-refresh',
143
+ 'aiwg-doctor',
144
+ 'aiwg-status',
145
+ 'aiwg-help',
146
+ 'aiwg-regenerate',
147
+ 'aiwg-regenerate-claude',
148
+ 'aiwg-regenerate-codex',
149
+ 'aiwg-regenerate-opencode',
150
+ 'aiwg-regenerate-agents',
151
+ 'aiwg-issue',
152
+ 'aiwg-pr',
153
+ 'aiwg-mission',
154
+ 'use',
155
+ 'steward',
156
+ ]);
157
+
158
+ function providerUsesSkillsNatively(providerName) {
159
+ return ['claude', 'cursor', 'hermes', 'openhuman'].includes(providerName);
160
+ }
161
+
162
+ function shouldMirrorStandardCommandSkill(skillName) {
163
+ return (
164
+ skillName.startsWith('flow-') ||
165
+ MIRRORED_STANDARD_COMMAND_SKILLS.has(skillName)
166
+ );
167
+ }
168
+
169
+ function shouldMirrorCommandSkill(providerName, skillName) {
170
+ if (MIRRORED_KERNEL_COMMAND_SKILLS.has(skillName)) {
171
+ return !providerUsesSkillsNatively(providerName);
172
+ }
173
+
174
+ return shouldMirrorStandardCommandSkill(skillName);
175
+ }
176
+
177
+ function uniquePaths(paths) {
178
+ return [...new Set(paths.filter(Boolean))];
179
+ }
180
+
181
+ function collectMirrorSkillDirs(srcRoot, mode) {
182
+ const dirs = [];
183
+
184
+ const directSkillsDir = path.join(srcRoot, 'skills');
185
+ dirs.push(...listSkillDirs(directSkillsDir));
186
+
187
+ // If --source itself is a skills directory, support that shape too.
188
+ dirs.push(...listSkillDirs(srcRoot));
189
+
190
+ const repoFrameworksRoot = path.join(srcRoot, 'agentic', 'code', 'frameworks');
191
+ if (fs.existsSync(repoFrameworksRoot)) {
192
+ const artifacts = collectFrameworkArtifacts(srcRoot, mode, {
193
+ includeAgents: false,
194
+ includeCommands: false,
195
+ includeSkills: true,
196
+ includeRules: false,
197
+ });
198
+ dirs.push(...artifacts.skills);
199
+
200
+ if (mode === 'all') {
201
+ dirs.push(...getAddonSkillDirs(srcRoot));
202
+ }
203
+ }
204
+
205
+ return uniquePaths(dirs);
206
+ }
207
+
208
+ function resolveCommandMirrorDir(provider, target) {
209
+ const commandsPath = provider.paths?.commands;
210
+ if (commandsPath) {
211
+ return path.isAbsolute(commandsPath) ? commandsPath : path.join(target, commandsPath);
212
+ }
213
+
214
+ // Closest conventional location for providers whose primary command
215
+ // surface is MCP/aggregation rather than a documented command directory.
216
+ if (provider.name === 'hermes') {
217
+ return path.join(os.homedir(), '.hermes', 'commands');
218
+ }
219
+
220
+ return null;
221
+ }
222
+
223
+ function commandFileExtensionForProvider(provider) {
224
+ if (provider.name === 'copilot') return '.prompt.md';
225
+ return '.md';
226
+ }
227
+
228
+ function resolveSkillCommandPolicy(commandHint = {}) {
229
+ const legacy = String(commandHint.model || '').trim().toLowerCase();
230
+ const legacyRole = legacy === 'opus' ? 'reasoning'
231
+ : legacy === 'haiku' ? 'efficiency'
232
+ : legacy ? 'coding' : null;
233
+ const modelRole = commandHint.modelRole || legacyRole;
234
+ if (!modelRole) return null;
235
+ const modelTier = commandHint.modelTier
236
+ || (modelRole === 'reasoning' ? 'premium'
237
+ : modelRole === 'efficiency' ? 'economy' : 'standard');
238
+ return {
239
+ modelRole,
240
+ modelTier,
241
+ modelEffort: commandHint.modelEffort,
242
+ modelRationale: commandHint.modelRationale,
243
+ };
244
+ }
245
+
246
+ function parseSkillCommandHint(raw) {
247
+ const { frontmatter } = parseFrontmatter(raw);
248
+ const block = frontmatter?.match(/^commandHint:\s*\n((?:[ \t]+[^\n]*\n?)*)/m)?.[1] || '';
249
+ const hint = {};
250
+ for (const line of block.split('\n')) {
251
+ const match = line.trim().match(/^(model|modelRole|modelTier|modelEffort|modelRationale):\s*(.+)$/);
252
+ if (match) hint[match[1]] = match[2].trim().replace(/^['"]|['"]$/g, '');
253
+ }
254
+ return hint;
255
+ }
256
+
257
+ function skillToCommandContent(skillDir) {
258
+ const skillName = path.basename(skillDir);
259
+ const skillPath = path.join(skillDir, 'SKILL.md');
260
+ const raw = fs.readFileSync(skillPath, 'utf8');
261
+ const { metadata, body } = parseFrontmatter(raw);
262
+ const description = metadata.description || `Run AIWG skill ${skillName}`;
263
+ const policy = resolveSkillCommandPolicy(parseSkillCommandHint(raw));
264
+ const policyLines = policy ? [
265
+ `aiwg-model-role: ${policy.modelRole}`,
266
+ `aiwg-model-tier: ${policy.modelTier}`,
267
+ ...(policy.modelEffort ? [`aiwg-model-effort: ${policy.modelEffort}`] : []),
268
+ ...(policy.modelRationale ? [`aiwg-model-rationale: ${policy.modelRationale}`] : []),
269
+ ] : [];
270
+
271
+ return `---\nname: ${skillName}\ndescription: ${description}\n${policyLines.join('\n')}${policyLines.length ? '\n' : ''}---\n\n${body.trim()}\n`;
272
+ }
273
+
274
+ function mirrorSkillsAsCommands(provider, target, srcRoot, opts) {
275
+ if (!opts.deployCommands && !opts.commandsOnly) return 0;
276
+
277
+ const targetDir = resolveCommandMirrorDir(provider, target);
278
+ if (!targetDir) return 0;
279
+
280
+ const skillDirs = collectMirrorSkillDirs(srcRoot, opts.mode)
281
+ .filter((dir) => shouldMirrorCommandSkill(provider.name, path.basename(dir)));
282
+
283
+ if (skillDirs.length === 0) return 0;
284
+ if (!opts.dryRun) fs.mkdirSync(targetDir, { recursive: true });
285
+
286
+ const ext = commandFileExtensionForProvider(provider);
287
+ let count = 0;
288
+
289
+ for (const skillDir of skillDirs) {
290
+ const skillName = path.basename(skillDir);
291
+ let content = skillToCommandContent(skillDir);
292
+ if (typeof provider.transformCommand === 'function') {
293
+ content = provider.transformCommand(path.join(skillDir, `${skillName}.md`), content, opts);
294
+ }
295
+
296
+ const dest = path.join(targetDir, `${skillName}${ext}`);
297
+ if (opts.dryRun) {
298
+ if (opts.verbose) console.log(`[dry-run] mirror skill command ${skillName} -> ${dest}`);
299
+ const raw = fs.readFileSync(path.join(skillDir, 'SKILL.md'), 'utf8');
300
+ const policy = resolveSkillCommandPolicy(parseSkillCommandHint(raw));
301
+ if (policy && opts.verbose) {
302
+ const outcome = provider.name === 'claude' ? 'native'
303
+ : provider.name === 'factory' ? 'informational' : 'unsupported';
304
+ console.log(`[dry-run] skill model policy ${skillName}: ${outcome} (${policy.modelRole}/${policy.modelTier})`);
305
+ }
306
+ } else {
307
+ fs.writeFileSync(dest, content, 'utf8');
308
+ }
309
+ count++;
310
+ }
311
+
312
+ return count;
313
+ }
314
+
315
+ // ============================================================================
316
+ // Stale-Artifact Prune (agents / commands / rules) — #1627
317
+ // ============================================================================
318
+
319
+ /**
320
+ * After a deploy, prune AIWG-managed flat artifacts (agents/commands/rules)
321
+ * whose source no longer ships them — mirroring the skills holistic prune.
322
+ *
323
+ * Safety boundaries:
324
+ * - Runs ONLY for AIWG framework/addon deploys (default source = AIWG root,
325
+ * or `--source` under `<aiwgRoot>/agentic/code`). Project-local bundle
326
+ * deploys (`--source <bundle>`) are skipped so a bundle's just-deployed
327
+ * artifacts are never mistaken for stale framework files.
328
+ * - `pruneStaleAiwgFiles` itself only removes files carrying an AIWG
329
+ * ownership signal (sidecar entry or `aiwg:managed` marker) AND whose
330
+ * stem is absent from the global, mode-independent source set, so
331
+ * user-authored files and sibling-framework artifacts always survive.
332
+ * - Aggregate-file paths (e.g. `AGENTS.md`) and unset paths are skipped.
333
+ *
334
+ * @param {object} provider loaded provider module
335
+ * @param {string} target deploy target dir
336
+ * @param {string} srcRoot effective deploy source
337
+ * @param {object} opts deploy opts (dryRun/verbose/quiet + deploy flags)
338
+ * @param {string|null} explicitSource the raw `--source` value (null when unset)
339
+ */
340
+ function pruneStaleAiwgArtifacts(provider, target, srcRoot, opts, explicitSource) {
341
+ if (opts.skillsOnly) return; // skills run their own prune in the provider
342
+
343
+ const aiwgRoot = resolveAiwgRoot(srcRoot);
344
+ if (!aiwgRoot) return; // no AIWG tree → bundle/standalone deploy; never prune
345
+
346
+ // Gate: only framework/addon deploys are eligible. A default deploy (no
347
+ // --source) sources from the AIWG root. An explicit --source is eligible
348
+ // only when it lives under <aiwgRoot>/agentic/code (bundled fw/addon).
349
+ if (explicitSource) {
350
+ const codeRoot = path.join(aiwgRoot, 'agentic', 'code');
351
+ const rel = path.relative(codeRoot, path.resolve(explicitSource));
352
+ const underCode = !!rel && !rel.startsWith('..') && !path.isAbsolute(rel);
353
+ if (!underCode) return; // project-local bundle / external source → skip
354
+ }
355
+
356
+ const typesThisRun = [];
357
+ if (!opts.commandsOnly && !opts.rulesOnly) typesThisRun.push('agents');
358
+ if (opts.deployCommands || opts.commandsOnly) typesThisRun.push('commands');
359
+ if (opts.deployRules || opts.rulesOnly) typesThisRun.push('rules');
360
+
361
+ for (const type of typesThisRun) {
362
+ const relPath = provider.paths?.[type];
363
+ if (!relPath) continue; // type not file-deployed by provider
364
+ if (relPath.endsWith('.md')) continue; // aggregate file (e.g. AGENTS.md)
365
+
366
+ const desired = computeAllArtifactBasenames(srcRoot, type);
367
+ if (!desired) continue; // no global desired set → skip
368
+
369
+ const destDir = path.isAbsolute(relPath) ? relPath : path.join(target, relPath);
370
+ const removed = pruneStaleAiwgFiles(destDir, desired, {
371
+ dryRun: opts.dryRun,
372
+ verbose: opts.verbose,
373
+ });
374
+ if (removed.length > 0 && !opts.quiet) {
375
+ console.log(` Pruned: ${removed.length} stale AIWG ${type} file${removed.length === 1 ? '' : 's'}`);
376
+ }
377
+ }
378
+ }
379
+
380
+ // ============================================================================
381
+ // Argument Parsing
382
+ // ============================================================================
383
+
384
+ function parseArgs() {
385
+ const args = process.argv.slice(2);
386
+ const cfg = {
387
+ source: null,
388
+ target: process.cwd(),
389
+ mode: 'all', // 'general', 'sdlc', 'marketing', 'media-curator', 'research', 'both' (legacy), or 'all'
390
+ dryRun: false,
391
+ force: false,
392
+ provider: 'claude',
393
+ model: null, // Blanket override for all tiers
394
+ modelTier: null, // Blanket canonical tier override
395
+ reasoningModel: null,
396
+ codingModel: null,
397
+ efficiencyModel: null,
398
+ asAgentsMd: false,
399
+ createAgentsMd: false,
400
+ deployCommands: false,
401
+ deploySkills: false,
402
+ deployRules: false,
403
+ deployBehaviors: false, // Deploy behaviors (native on openclaw, emulated on others)
404
+ commandsOnly: false,
405
+ skillsOnly: false,
406
+ rulesOnly: false,
407
+ filter: null, // Glob pattern for agent names
408
+ filterRole: null, // Filter by role: reasoning|coding|efficiency
409
+ save: false, // Save model config to project models.json
410
+ saveUser: false, // Save model config to ~/.config/aiwg/models.json
411
+ verbose: false, // Show per-file deployment details
412
+ quiet: false, // Suppress all non-error output (for embedding in use.ts)
413
+ asPlugin: false, // Generate .factory-plugin/ bundle (Factory provider only)
414
+ deployBehaviors: false, // Deploy behaviors in addition to agents
415
+ skipCommandsMigration: false // Skip commands → skills migration (warns about duplicates)
416
+ };
417
+ for (let i = 0; i < args.length; i++) {
418
+ const a = args[i];
419
+ if (a === '--source' && args[i + 1]) cfg.source = path.resolve(args[++i]);
420
+ else if (a === '--target' && args[i + 1]) cfg.target = path.resolve(args[++i]);
421
+ else if (a === '--mode' && args[i + 1]) cfg.mode = String(args[++i]).toLowerCase();
422
+ else if (a === '--dry-run') cfg.dryRun = true;
423
+ else if (a === '--force') cfg.force = true;
424
+ else if ((a === '--provider' || a === '--platform') && args[i + 1]) cfg.provider = String(args[++i]).toLowerCase();
425
+ else if (a === '--model' && args[i + 1]) cfg.model = args[++i];
426
+ else if (a === '--model-tier' && args[i + 1]) cfg.modelTier = String(args[++i]).toLowerCase();
427
+ else if ((a === '--reasoning-model' || a === '--reasoning') && args[i + 1]) cfg.reasoningModel = args[++i];
428
+ else if ((a === '--coding-model' || a === '--coding') && args[i + 1]) cfg.codingModel = args[++i];
429
+ else if ((a === '--efficiency-model' || a === '--efficiency') && args[i + 1]) cfg.efficiencyModel = args[++i];
430
+ else if (a === '--as-agents-md') cfg.asAgentsMd = true;
431
+ else if (a === '--create-agents-md') cfg.createAgentsMd = true;
432
+ else if (a === '--deploy-commands') cfg.deployCommands = true;
433
+ else if (a === '--deploy-skills') cfg.deploySkills = true;
434
+ else if (a === '--deploy-rules') cfg.deployRules = true;
435
+ else if (a === '--deploy-behaviors') cfg.deployBehaviors = true;
436
+ else if (a === '--commands-only') cfg.commandsOnly = true;
437
+ else if (a === '--skills-only') cfg.skillsOnly = true;
438
+ else if (a === '--rules-only') cfg.rulesOnly = true;
439
+ else if (a === '--deploy-behaviors') cfg.deployBehaviors = true;
440
+ else if (a === '--filter' && args[i + 1]) cfg.filter = args[++i];
441
+ else if (a === '--filter-role' && args[i + 1]) cfg.filterRole = args[++i];
442
+ else if (a === '--save') cfg.save = true;
443
+ else if (a === '--save-user') cfg.saveUser = true;
444
+ else if (a === '--verbose' || a === '-v') cfg.verbose = true;
445
+ else if (a === '--quiet' || a === '-q') cfg.quiet = true;
446
+ else if (a === '--as-plugin') cfg.asPlugin = true;
447
+ else if (a === '--skip-commands-migration') cfg.skipCommandsMigration = true;
448
+ else if (a === '--copy-all' || a === '--copy-standard-skills') cfg.copyStandardSkills = true;
449
+ else if (a === '--help' || a === '-h') {
450
+ printHelp();
451
+ process.exit(0);
452
+ }
453
+ }
454
+ return cfg;
455
+ }
456
+
457
+ function printHelp() {
458
+ console.log(`
459
+ Deploy Agents and Commands
460
+
461
+ Usage:
462
+ node tools/agents/deploy-agents.mjs [options]
463
+ aiwg -deploy-agents [options]
464
+
465
+ Options:
466
+ --source <path> Source directory (defaults to repo root)
467
+ --target <path> Target directory (defaults to cwd)
468
+ --mode <type> Deployment mode: general, sdlc, marketing (alias: mmk), media-curator, research, both, or all (default)
469
+ --deploy-commands Deploy commands in addition to agents
470
+ --deploy-skills Deploy skills in addition to agents
471
+ --deploy-rules Deploy rules in addition to agents
472
+ --commands-only Deploy only commands (skip agents)
473
+ --skills-only Deploy only skills (skip agents)
474
+ --rules-only Deploy only rules (skip agents)
475
+ --dry-run Show what would be deployed without writing
476
+ --force Overwrite existing files
477
+ --provider <name> Target provider (see below)
478
+ --model <name> Override model for all tiers (blanket)
479
+ --reasoning-model <name> Override model for reasoning tasks (alias: --reasoning)
480
+ --coding-model <name> Override model for coding tasks (alias: --coding)
481
+ --efficiency-model <name> Override model for efficiency tasks (alias: --efficiency)
482
+ --model-tier <tier> Override all artifacts with economy|standard|premium
483
+ --filter <pattern> Only deploy agents matching pattern (glob)
484
+ --filter-role <role> Only deploy agents of role: reasoning|coding|efficiency
485
+ --save Save model config to project models.json
486
+ --save-user Save model config to ~/.config/aiwg/models.json
487
+ --as-agents-md Aggregate to single AGENTS.md (Codex)
488
+ --create-agents-md Create/update AGENTS.md template
489
+ --skip-commands-migration Skip deleting the commands directory before skills deployment
490
+ --copy-all Copy ALL skills per-project (legacy mirror at <provider>/.aiwg/skills/).
491
+ Default is kernel-only + index-driven discovery for the rest (#1217).
492
+ Use this for sandboxed runtimes / air-gapped corpora where
493
+ $AIWG_ROOT isn't readable from the agent's working dir.
494
+ Alias: --copy-standard-skills — rc.29 era).
495
+ (warns about duplicate entries in the provider command palette)
496
+
497
+ Model Override Examples:
498
+ # Use sonnet for everything
499
+ aiwg use sdlc --model sonnet
500
+
501
+ # Override individual tiers
502
+ aiwg use sdlc --reasoning opus --coding sonnet --efficiency haiku
503
+
504
+ # Use a specific model ID for coding tier
505
+ aiwg use sdlc --provider factory --coding-model gpt-5.3-codex
506
+
507
+ # Blanket with per-tier override (reasoning uses opus, others use sonnet)
508
+ aiwg use sdlc --model sonnet --reasoning opus
509
+
510
+ # Save overrides to project models.json for future deployments
511
+ aiwg use sdlc --model sonnet --save
512
+
513
+ Model Precedence:
514
+ CLI flags > project models.json > user ~/.config/aiwg/models.json > AIWG defaults
515
+
516
+ Shorthand Values:
517
+ opus, sonnet, haiku, inherit — resolved per provider to full model IDs
518
+
519
+ Providers (all deploy agents, commands, skills, and rules):
520
+ claude - Claude Code (default)
521
+ Paths: .claude/agents/, .claude/commands/, .claude/skills/, .claude/rules/
522
+ factory - Factory AI
523
+ Paths: .factory/droids/, .factory/commands/, .factory/skills/, .factory/rules/
524
+ codex - OpenAI Codex (alias: openai)
525
+ Paths: .codex/agents/, .codex/commands/, .codex/skills/, .codex/rules/
526
+ opencode - OpenCode
527
+ Paths: .opencode/skill/, .opencode/rule/ (agents/commands not file-based in OpenCode)
528
+ copilot - GitHub Copilot
529
+ Paths: .github/agents/, .github/commands/, .github/skills/, .github/copilot-rules/
530
+ cursor - Cursor IDE
531
+ Paths: .cursor/agents/, .cursor/commands/, .cursor/skills/, .cursor/rules/
532
+ warp - Warp Terminal
533
+ Paths: .warp/agents/, .warp/commands/, .warp/skills/, .warp/rules/ + WARP.md
534
+ windsurf - Windsurf
535
+ Paths: .windsurf/agents/, .windsurf/workflows/, .windsurf/skills/, .windsurf/rules/
536
+ hermes - Hermes Agent (MCP-based integration)
537
+ Skills: ~/.hermes/skills/ (user-global) | Agents: AGENTS.md (lean routing guide)
538
+ Commands/Rules: served via MCP, not file-deployed
539
+
540
+ Modes:
541
+ general - Writing-quality addon agents and commands (alias: writing)
542
+ sdlc - SDLC Complete framework agents and commands
543
+ marketing - Media/Marketing Kit framework agents and commands (alias: mmk)
544
+ media-curator - Media Curator framework agents and commands
545
+ research - Research Complete framework agents and commands
546
+ both - writing + SDLC (legacy compatibility)
547
+ all - All frameworks + addons (default)
548
+
549
+ Examples:
550
+ # Deploy SDLC framework to Claude Code
551
+ aiwg -deploy-agents --mode sdlc
552
+
553
+ # Deploy to Factory with commands and AGENTS.md
554
+ aiwg -deploy-agents --provider factory --mode sdlc --deploy-commands --create-agents-md
555
+
556
+ # Deploy to GitHub Copilot
557
+ aiwg -deploy-agents --provider copilot --mode sdlc
558
+
559
+ # Override model for all tiers
560
+ aiwg -deploy-agents --mode sdlc --model sonnet
561
+
562
+ # Override individual tiers
563
+ aiwg -deploy-agents --mode sdlc --reasoning opus --coding sonnet --efficiency haiku
564
+
565
+ # Dry run to preview deployment
566
+ aiwg -deploy-agents --provider cursor --mode sdlc --dry-run
567
+ `);
568
+ }
569
+
570
+ // ============================================================================
571
+ // Provider Loading
572
+ // ============================================================================
573
+
574
+ /**
575
+ * Resolve provider name (handle aliases)
576
+ */
577
+ function resolveProvider(name) {
578
+ const candidate = String(name || '').trim().toLowerCase();
579
+ const resolved = PROVIDER_ALIASES[candidate] || candidate;
580
+ if (!AVAILABLE_PROVIDERS.includes(resolved)) {
581
+ console.error(`Unknown provider: ${name}`);
582
+ console.error(`Available providers: ${AVAILABLE_PROVIDERS.join(', ')}`);
583
+ console.error(`Aliases: ${Object.entries(PROVIDER_ALIASES).map(([a, p]) => `${a} -> ${p}`).join(', ')}`);
584
+ if (UNSUPPORTED_PROVIDER_HINTS[candidate]) {
585
+ console.error('');
586
+ for (const line of UNSUPPORTED_PROVIDER_HINTS[candidate]) console.error(line);
587
+ }
588
+ process.exit(1);
589
+ }
590
+ return resolved;
591
+ }
592
+
593
+ /**
594
+ * Dynamically load provider module
595
+ */
596
+ async function loadProvider(providerName) {
597
+ const resolved = resolveProvider(providerName);
598
+ const providerPath = `./providers/${resolved}.mjs`;
599
+
600
+ try {
601
+ const provider = await import(providerPath);
602
+ return provider.default || provider;
603
+ } catch (err) {
604
+ console.error(`Failed to load provider '${resolved}':`, err.message);
605
+ process.exit(1);
606
+ }
607
+ }
608
+
609
+ // ============================================================================
610
+ // Model Shorthand Resolution
611
+ // ============================================================================
612
+
613
+ /**
614
+ * Resolve a shorthand model value to a full model ID
615
+ *
616
+ * If the value is a known shorthand (opus, sonnet, haiku, inherit), resolve it
617
+ * through the provider's shorthand map. Otherwise treat it as a literal model ID.
618
+ *
619
+ * @param {string|null} value - CLI flag value (e.g., "opus" or "claude-opus-4-6")
620
+ * @param {object} shorthandMap - Provider shorthand map (e.g., { opus: "claude-opus-4-6" })
621
+ * @param {object} modelsConfig - Full models config from loadModelConfig()
622
+ * @param {string} provider - Provider name
623
+ * @param {string} tier - Tier name: "reasoning", "coding", or "efficiency"
624
+ * @returns {string|null} Resolved model ID or null if input was null
625
+ */
626
+ function resolveShorthand(value, shorthandMap, modelsConfig, provider, tier) {
627
+ if (!value) return null;
628
+
629
+ const clean = value.toLowerCase().replace(/['"]/g, '');
630
+
631
+ // Check shorthand map first
632
+ if (shorthandMap[clean]) return shorthandMap[clean];
633
+
634
+ // Check provider-specific tier config (e.g., modelsConfig.factory.reasoning.model)
635
+ const providerConfig = modelsConfig?.[provider];
636
+ if (providerConfig?.[tier]?.model && clean === tier) {
637
+ return providerConfig[tier].model;
638
+ }
639
+
640
+ // Not a shorthand — return as literal model ID
641
+ return value;
642
+ }
643
+
644
+ // ============================================================================
645
+ // Model Configuration Persistence
646
+ // ============================================================================
647
+
648
+ /**
649
+ * Save model configuration to project or user config file
650
+ * @param {object} cfg - Configuration with model overrides
651
+ * @param {string} providerName - Provider name for provider-specific config
652
+ */
653
+ async function saveModelConfig(cfg, providerName) {
654
+ // Build config object with only the provided overrides
655
+ const modelConfig = {};
656
+ if (cfg.modelTier) {
657
+ modelConfig.defaults = { tier: cfg.modelTier };
658
+ }
659
+
660
+ // Provider-specific tier configuration
661
+ if (cfg.reasoningModel || cfg.codingModel || cfg.efficiencyModel) {
662
+ modelConfig[providerName] = {};
663
+ if (cfg.reasoningModel) {
664
+ modelConfig[providerName].reasoning = { model: cfg.reasoningModel };
665
+ }
666
+ if (cfg.codingModel) {
667
+ modelConfig[providerName].coding = { model: cfg.codingModel };
668
+ }
669
+ if (cfg.efficiencyModel) {
670
+ modelConfig[providerName].efficiency = { model: cfg.efficiencyModel };
671
+ }
672
+ }
673
+
674
+ // Shorthand mappings
675
+ if (cfg.reasoningModel || cfg.codingModel || cfg.efficiencyModel) {
676
+ modelConfig.shorthand = {};
677
+ if (cfg.reasoningModel) modelConfig.shorthand.opus = cfg.reasoningModel;
678
+ if (cfg.codingModel) modelConfig.shorthand.sonnet = cfg.codingModel;
679
+ if (cfg.efficiencyModel) modelConfig.shorthand.haiku = cfg.efficiencyModel;
680
+ }
681
+
682
+ // Determine target path
683
+ let targetPath;
684
+ if (cfg.saveUser) {
685
+ const configDir = path.join(os.homedir(), '.config', 'aiwg');
686
+ if (!fs.existsSync(configDir)) {
687
+ fs.mkdirSync(configDir, { recursive: true });
688
+ }
689
+ targetPath = path.join(configDir, 'models.json');
690
+ } else {
691
+ targetPath = path.join(cfg.target, 'models.json');
692
+ }
693
+
694
+ // Merge with existing config if it exists
695
+ let existingConfig = {};
696
+ if (fs.existsSync(targetPath)) {
697
+ try {
698
+ existingConfig = JSON.parse(fs.readFileSync(targetPath, 'utf8'));
699
+ } catch {
700
+ // Ignore parse errors, start fresh
701
+ }
702
+ }
703
+
704
+ // Deep merge: existing config + new overrides
705
+ const mergedConfig = deepMerge(existingConfig, modelConfig);
706
+
707
+ // Write the config
708
+ fs.writeFileSync(targetPath, JSON.stringify(mergedConfig, null, 2) + '\n', 'utf8');
709
+ console.log(`\nModel configuration saved to: ${targetPath}`);
710
+ }
711
+
712
+ /**
713
+ * Deep merge two objects
714
+ */
715
+ function deepMerge(target, source) {
716
+ const result = { ...target };
717
+ for (const key of Object.keys(source)) {
718
+ if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
719
+ result[key] = deepMerge(result[key] || {}, source[key]);
720
+ } else {
721
+ result[key] = source[key];
722
+ }
723
+ }
724
+ return result;
725
+ }
726
+
727
+ // ============================================================================
728
+ // Commands → Skills Migration
729
+ // ============================================================================
730
+
731
+ /**
732
+ * Ask the user whether to delete the provider's commands directory before
733
+ * deploying skills. Skipped automatically when not running in a TTY (CI/pipe)
734
+ * or when --quiet is set.
735
+ *
736
+ * @param {object} cfg - Parsed CLI config
737
+ * @param {object} provider - Loaded provider module
738
+ * @param {string} targetDir - Resolved target directory
739
+ * @returns {Promise<boolean>} true = proceed with migration, false = skip
740
+ */
741
+ async function promptCommandsMigration(cfg, provider, targetDir) {
742
+ // Home-directory providers share commands across projects — do not delete.
743
+ if (provider.capabilities?.homeDirectoryDeploy) return false;
744
+
745
+ const commandsRelPath = provider.paths?.commands;
746
+ if (!commandsRelPath) return false;
747
+
748
+ const commandsDir = path.join(targetDir, commandsRelPath);
749
+ if (!fs.existsSync(commandsDir)) return false;
750
+
751
+ const entries = fs.readdirSync(commandsDir).filter(e => e.endsWith('.md'));
752
+ if (entries.length === 0) return false;
753
+
754
+ // Non-interactive context: migrate silently.
755
+ if (cfg.quiet || !process.stdout.isTTY) return true;
756
+
757
+ const rel = path.relative(process.cwd(), commandsDir);
758
+ console.log(`\n⚠ Commands → Skills Migration`);
759
+ console.log(` ${rel} contains ${entries.length} legacy command file(s).`);
760
+ console.log(` AIWG now serves these as skills (.claude/skills/).`);
761
+ console.log(` Keeping both causes duplicate entries in the Claude Code command palette.`);
762
+ console.log('');
763
+
764
+ const answer = await new Promise(resolve => {
765
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
766
+ rl.question(' Remove commands directory and migrate? [Y/n] ', ans => {
767
+ rl.close();
768
+ resolve(ans.trim().toLowerCase());
769
+ });
770
+ });
771
+
772
+ if (answer === 'n' || answer === 'no') {
773
+ console.log('');
774
+ console.log(' Skipping migration. Run `aiwg use` again without --skip-commands-migration');
775
+ console.log(` to clean up, or delete ${rel} manually.`);
776
+ console.log('');
777
+ return false;
778
+ }
779
+
780
+ return true;
781
+ }
782
+
783
+ // ============================================================================
784
+ // Main Entry Point
785
+ // ============================================================================
786
+
787
+ export async function main() {
788
+ const cfg = parseArgs();
789
+
790
+ // Resolve source directory (default to repo root relative to this script)
791
+ const __filename = fileURLToPath(import.meta.url);
792
+ const scriptDir = path.dirname(__filename);
793
+ const srcRoot = cfg.source || path.resolve(scriptDir, '..', '..');
794
+
795
+ // Validate source directory
796
+ if (!fs.existsSync(srcRoot)) {
797
+ console.error(`Source directory not found: ${srcRoot}`);
798
+ process.exit(1);
799
+ }
800
+
801
+ // Validate target directory
802
+ if (!fs.existsSync(cfg.target)) {
803
+ console.log(`Creating target directory: ${cfg.target}`);
804
+ fs.mkdirSync(cfg.target, { recursive: true });
805
+ }
806
+
807
+ // Normalize mode aliases
808
+ if (cfg.mode === 'writing') cfg.mode = 'general';
809
+ if (cfg.mode === 'mmk') cfg.mode = 'marketing';
810
+
811
+ // Apply --model blanket override: sets all three tiers unless individually overridden
812
+ if (cfg.modelTier) {
813
+ const tierRole = cfg.modelTier === 'economy' ? 'efficiency'
814
+ : cfg.modelTier === 'premium' || cfg.modelTier === 'max-quality' ? 'reasoning'
815
+ : cfg.modelTier === 'standard' ? 'coding' : null;
816
+ if (!tierRole) throw new Error(`Invalid --model-tier: ${cfg.modelTier}`);
817
+ const catalogProvider = cfg.provider === 'openai' ? 'codex' : cfg.provider;
818
+ const providerCatalog = modelCatalog.providers[catalogProvider];
819
+ if (!providerCatalog) throw new Error(`No model catalog entry for --model-tier provider ${cfg.provider}`);
820
+ cfg.model = providerCatalog.roles[tierRole].id;
821
+ }
822
+ if (cfg.model) {
823
+ if (!cfg.reasoningModel) cfg.reasoningModel = cfg.model;
824
+ if (!cfg.codingModel) cfg.codingModel = cfg.model;
825
+ if (!cfg.efficiencyModel) cfg.efficiencyModel = cfg.model;
826
+ }
827
+
828
+ // Load model configuration (project > user > AIWG defaults)
829
+ const modelsConfig = loadModelConfig(srcRoot);
830
+
831
+ // Resolve shorthand values in CLI flags using provider-specific config
832
+ if (cfg.reasoningModel || cfg.codingModel || cfg.efficiencyModel) {
833
+ const resolvedProvider = resolveProvider(cfg.provider);
834
+ const providerShorthand = modelsConfig?.[`${resolvedProvider}_shorthand`] || modelsConfig?.shorthand || {};
835
+
836
+ cfg.reasoningModel = resolveShorthand(cfg.reasoningModel, providerShorthand, modelsConfig, resolvedProvider, 'reasoning');
837
+ cfg.codingModel = resolveShorthand(cfg.codingModel, providerShorthand, modelsConfig, resolvedProvider, 'coding');
838
+ cfg.efficiencyModel = resolveShorthand(cfg.efficiencyModel, providerShorthand, modelsConfig, resolvedProvider, 'efficiency');
839
+ }
840
+
841
+ if (!cfg.quiet) {
842
+ console.log(`\n=== AIWG Agent Deployment ===`);
843
+ console.log(`Provider: ${cfg.provider}`);
844
+ console.log(`Source: ${srcRoot}`);
845
+ console.log(`Target: ${cfg.target}`);
846
+ console.log(`Mode: ${cfg.mode}`);
847
+ if (cfg.dryRun) console.log(`Dry run: enabled`);
848
+ if (cfg.filter) console.log(`Filter: ${cfg.filter}`);
849
+ if (cfg.filterRole) console.log(`Filter role: ${cfg.filterRole}`);
850
+ if (cfg.model) console.log(`Model (all tiers): ${cfg.model}`);
851
+ if (cfg.modelTier) console.log(`Canonical tier override: ${cfg.modelTier}`);
852
+ if (cfg.reasoningModel) console.log(`Reasoning model: ${cfg.reasoningModel}`);
853
+ if (cfg.codingModel) console.log(`Coding model: ${cfg.codingModel}`);
854
+ if (cfg.efficiencyModel) console.log(`Efficiency model: ${cfg.efficiencyModel}`);
855
+ if (cfg.save) console.log(`Save to project: enabled`);
856
+ if (cfg.saveUser) console.log(`Save to user config: enabled`);
857
+ }
858
+
859
+ // Load provider module
860
+ const provider = await loadProvider(cfg.provider);
861
+ if (!cfg.quiet) console.log(`\nLoaded provider: ${provider.name}`);
862
+
863
+ // Build options for provider
864
+ const opts = {
865
+ srcRoot,
866
+ target: cfg.target,
867
+ mode: cfg.mode,
868
+ provider: cfg.provider,
869
+ dryRun: cfg.dryRun,
870
+ force: cfg.force,
871
+ reasoningModel: cfg.reasoningModel,
872
+ codingModel: cfg.codingModel,
873
+ efficiencyModel: cfg.efficiencyModel,
874
+ modelsConfig,
875
+ asAgentsMd: cfg.asAgentsMd,
876
+ createAgentsMd: cfg.createAgentsMd,
877
+ deployCommands: cfg.deployCommands,
878
+ deploySkills: cfg.deploySkills,
879
+ deployRules: cfg.deployRules,
880
+ deployBehaviors: cfg.deployBehaviors,
881
+ commandsOnly: cfg.commandsOnly,
882
+ skillsOnly: cfg.skillsOnly,
883
+ rulesOnly: cfg.rulesOnly,
884
+ filter: cfg.filter,
885
+ filterRole: cfg.filterRole,
886
+ save: cfg.save,
887
+ saveUser: cfg.saveUser,
888
+ verbose: cfg.verbose,
889
+ quiet: cfg.quiet,
890
+ asPlugin: cfg.asPlugin,
891
+ deployBehaviors: cfg.deployBehaviors,
892
+ skipCommandsMigration: cfg.skipCommandsMigration,
893
+ // #1217 / #1219: --copy-all flag forces legacy per-project mirror
894
+ // for the standard tier. Default is no-copy + index-driven discovery.
895
+ // Replaces the legacy AIWG_COPY_STANDARD_SKILLS env var (removed rc.30).
896
+ // Default (#1217) is no-copy + index-driven discovery.
897
+ copyStandardSkills: cfg.copyStandardSkills === true,
898
+ deployVersion: getDeployVersion(srcRoot),
899
+ deploySource: 'bundled',
900
+ };
901
+
902
+ // Commands → Skills migration: prompt then delete the commands directory
903
+ // so stale command files don't create duplicates in the provider TUI.
904
+ if (!cfg.dryRun && !cfg.skipCommandsMigration) {
905
+ const doMigrate = await promptCommandsMigration(cfg, provider, cfg.target);
906
+ if (doMigrate) {
907
+ const commandsRelPath = provider.paths?.commands;
908
+ if (commandsRelPath) {
909
+ migrateCommandsDirectory(path.join(cfg.target, commandsRelPath), opts);
910
+ }
911
+ } else {
912
+ // User said no — flip the flag so the provider knows to emit the duplicate warning
913
+ opts.skipCommandsMigration = true;
914
+ }
915
+ } else if (cfg.skipCommandsMigration) {
916
+ // Flag was passed explicitly — emit the duplicate warning now via a dry migration call
917
+ const commandsRelPath = provider.paths?.commands;
918
+ if (commandsRelPath && !provider.capabilities?.homeDirectoryDeploy) {
919
+ migrateCommandsDirectory(path.join(cfg.target, commandsRelPath), opts);
920
+ }
921
+ }
922
+
923
+ // Delegate to provider
924
+ try {
925
+ await provider.deploy(opts);
926
+
927
+ const mirroredCommandCount = mirrorSkillsAsCommands(provider, cfg.target, srcRoot, opts);
928
+ if (mirroredCommandCount > 0 && !cfg.quiet) {
929
+ console.log(` Mirrored: ${mirroredCommandCount} skill command wrapper${mirroredCommandCount === 1 ? '' : 's'}`);
930
+ }
931
+
932
+ // Prune stale AIWG-managed agents/commands/rules whose source was renamed
933
+ // or removed (the flat-file analogue of the skills holistic prune). #1627.
934
+ pruneStaleAiwgArtifacts(provider, cfg.target, srcRoot, opts, cfg.source);
935
+
936
+ if (!opts.commandsOnly && !opts.skillsOnly && !opts.rulesOnly) {
937
+ const behaviorDirs = collectBehaviorDirs(srcRoot);
938
+ const behaviorCount = deployEmulatedBehaviors(behaviorDirs, provider.name, cfg.target, opts);
939
+ if (behaviorCount > 0 && !cfg.quiet) {
940
+ console.log(` Deployed: ${behaviorCount} behavior emulation${behaviorCount === 1 ? '' : 's'}`);
941
+ }
942
+ }
943
+
944
+ // Save model configuration if requested
945
+ if ((cfg.save || cfg.saveUser) && !cfg.dryRun) {
946
+ await saveModelConfig(cfg, provider.name);
947
+ }
948
+
949
+ if (!cfg.quiet) console.log(`\n=== Deployment complete ===\n`);
950
+ } catch (err) {
951
+ console.error(`\nDeployment failed:`, err.message);
952
+ if (cfg.dryRun) {
953
+ console.error('(dry-run mode - no files were modified)');
954
+ }
955
+ process.exit(1);
956
+ }
957
+ }
958
+
959
+ const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : null;
960
+ if (invokedPath === fileURLToPath(import.meta.url)) {
961
+ await main();
962
+ }