@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.
- package/README.md +33 -0
- package/agentic/code/providers/capability-matrix.yaml +511 -0
- package/agentic/code/providers/model-capabilities.v1.json +120 -0
- package/agentic/code/providers/model-catalog.v1.json +96 -0
- package/agentic/code/providers/model-policy-evaluations.v1.json +50 -0
- package/agentic/code/providers/premium-model-allowlist.v1.json +36 -0
- package/bin/aiwg.mjs +14 -10
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/cli.js +2 -0
- package/dist/src/artifacts/types.js +4 -0
- package/dist/src/auth/client.js +209 -0
- package/dist/src/auth/config.js +38 -0
- package/dist/src/auth/credential-store.js +141 -0
- package/dist/src/auth/resource-credentials.js +25 -0
- package/dist/src/auth/types.js +2 -0
- package/dist/src/channel/manager.mjs +5 -5
- package/dist/src/cli/handlers/auth.js +125 -0
- package/dist/src/cli/handlers/help.js +1 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/install.js +42 -4
- package/dist/src/cli/handlers/marketplace.js +375 -122
- package/dist/src/cli/handlers/resource-versions.js +2 -0
- package/dist/src/cli/handlers/sessions.js +23 -5
- package/dist/src/cli/handlers/subcommands.js +10 -1
- package/dist/src/cli/handlers/use.js +342 -43
- package/dist/src/config/gitignore.js +1 -0
- package/dist/src/extensions/commands/definitions.js +19 -0
- package/dist/src/marketplace/exchange.js +602 -0
- package/dist/src/marketplace/provenance-types.js +19 -0
- package/dist/src/marketplace/provenance.js +834 -0
- package/dist/src/memory/canonical-context.js +342 -0
- package/dist/src/memory/context-pack.js +282 -0
- package/dist/src/memory/index.js +4 -0
- package/dist/src/memory/intake.js +118 -0
- package/dist/src/packages/adapters/git.js +79 -29
- package/dist/src/packages/package-discovery.js +81 -0
- package/dist/src/packages/package-registry.js +2 -0
- package/dist/src/packages/registry.js +119 -20
- package/dist/src/resources/resolver.js +1 -0
- package/dist/src/resources/web-release.d.ts +3 -1
- package/dist/src/resources/web-release.js +14 -6
- package/dist/src/serve/agentic-sandbox-fleet-client.js +213 -0
- package/dist/src/serve/fleet-mission-conductor.js +293 -0
- package/dist/src/sessions/index.js +1 -0
- package/dist/src/sessions/output-registration.js +338 -0
- package/dist/src/sessions/promotion.js +73 -2
- package/dist/src/sessions/repository.js +2 -1
- package/dist/src/update/notifier.mjs +13 -2
- package/package.json +8 -1
- package/tools/_resolve-impl.mjs +74 -0
- package/tools/agents/deploy-agents.mjs +962 -0
- package/tools/agents/providers/base.mjs +2954 -0
- package/tools/agents/providers/claude.mjs +711 -0
- package/tools/agents/providers/codex.mjs +699 -0
- package/tools/agents/providers/copilot.mjs +659 -0
- package/tools/agents/providers/cursor.mjs +714 -0
- package/tools/agents/providers/factory.mjs +1130 -0
- package/tools/agents/providers/hermes.mjs +663 -0
- package/tools/agents/providers/hook-capabilities.mjs +85 -0
- package/tools/agents/providers/model-role.mjs +56 -0
- package/tools/agents/providers/openclaw-translator.mjs +348 -0
- package/tools/agents/providers/openclaw.mjs +680 -0
- package/tools/agents/providers/opencode.mjs +675 -0
- package/tools/agents/providers/openhuman.mjs +292 -0
- package/tools/agents/providers/warp.mjs +413 -0
- package/tools/agents/providers/windsurf.mjs +748 -0
- package/tools/commands/deploy-prompts-codex.mjs +336 -0
- package/tools/plugin/package-plugins.mjs +1013 -0
- 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
|
+
}
|