@aiwg/cli 2026.8.0 → 2026.8.1

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 (61) 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/resource-versions.js +2 -0
  22. package/dist/src/cli/handlers/sessions.js +23 -5
  23. package/dist/src/cli/handlers/subcommands.js +10 -1
  24. package/dist/src/cli/handlers/use.js +342 -43
  25. package/dist/src/config/gitignore.js +1 -0
  26. package/dist/src/extensions/commands/definitions.js +19 -0
  27. package/dist/src/memory/canonical-context.js +342 -0
  28. package/dist/src/memory/context-pack.js +282 -0
  29. package/dist/src/memory/index.js +4 -0
  30. package/dist/src/memory/intake.js +118 -0
  31. package/dist/src/resources/resolver.js +1 -0
  32. package/dist/src/resources/web-release.d.ts +3 -1
  33. package/dist/src/resources/web-release.js +14 -6
  34. package/dist/src/serve/agentic-sandbox-fleet-client.js +213 -0
  35. package/dist/src/serve/fleet-mission-conductor.js +293 -0
  36. package/dist/src/sessions/index.js +1 -0
  37. package/dist/src/sessions/output-registration.js +338 -0
  38. package/dist/src/sessions/promotion.js +73 -2
  39. package/dist/src/sessions/repository.js +2 -1
  40. package/dist/src/update/notifier.mjs +13 -2
  41. package/package.json +8 -1
  42. package/tools/_resolve-impl.mjs +74 -0
  43. package/tools/agents/deploy-agents.mjs +962 -0
  44. package/tools/agents/providers/base.mjs +2954 -0
  45. package/tools/agents/providers/claude.mjs +711 -0
  46. package/tools/agents/providers/codex.mjs +699 -0
  47. package/tools/agents/providers/copilot.mjs +659 -0
  48. package/tools/agents/providers/cursor.mjs +714 -0
  49. package/tools/agents/providers/factory.mjs +1130 -0
  50. package/tools/agents/providers/hermes.mjs +663 -0
  51. package/tools/agents/providers/hook-capabilities.mjs +85 -0
  52. package/tools/agents/providers/model-role.mjs +56 -0
  53. package/tools/agents/providers/openclaw-translator.mjs +348 -0
  54. package/tools/agents/providers/openclaw.mjs +680 -0
  55. package/tools/agents/providers/opencode.mjs +675 -0
  56. package/tools/agents/providers/openhuman.mjs +292 -0
  57. package/tools/agents/providers/warp.mjs +413 -0
  58. package/tools/agents/providers/windsurf.mjs +748 -0
  59. package/tools/commands/deploy-prompts-codex.mjs +336 -0
  60. package/tools/plugin/package-plugins.mjs +1013 -0
  61. package/tools/skills/deploy-skills-codex.mjs +571 -0
@@ -21,11 +21,13 @@ import { loadCliCommandsContribution, registerCliCommands, registerHooks, } from
21
21
  import { translateSkillsToCommands, providerNeedsCommands } from '../../plugin/skill-command-translator.js';
22
22
  import * as ui from '../ui.js';
23
23
  import { readAiwgConfig, writeAiwgConfig, updateInstalled, hashManifest, emptyConfig, getProjectDir } from '../../config/aiwg-config.js';
24
+ import { appendGitignore } from '../../config/gitignore.js';
24
25
  import { getLogger } from '../log.js';
25
26
  import { installCockpit } from './cockpit.js';
26
27
  import { initHandler } from './init.js';
27
28
  import { checkCollisions, formatCollisionReport, hasBlockingCollisions, } from '../../smiths/skillsmith/collision-detector.js';
28
29
  import { discoverProjectLocalBundles, } from '../../extensions/project-local-discovery.js';
30
+ import { PROJECT_LOCAL_TYPE_TO_DIR } from '../../extensions/project-local-paths.js';
29
31
  import { buildUpstreamRegistry } from '../../extensions/upstream-registry.js';
30
32
  import { resolveShadows, formatShadowReport, } from '../../extensions/shadow-resolver.js';
31
33
  import { appendProjectLocalActivity, emitDiscoverEventsDeduped, } from '../../extensions/project-local-activity.js';
@@ -270,6 +272,55 @@ export function addonPath(frameworkRoot, name) {
270
272
  const folderName = resolveAddonFolderName(name);
271
273
  return path.join(frameworkRoot, 'agentic/code/addons', folderName);
272
274
  }
275
+ /**
276
+ * Resolve a selected addon's required addon dependencies in deterministic
277
+ * dependency-first order. Optional dependencies remain descriptive and are
278
+ * never activated implicitly.
279
+ */
280
+ export async function resolveRequiredAddonActivationOrder(frameworkRoot, selectedAddon) {
281
+ const visiting = new Set();
282
+ const visited = new Set();
283
+ const order = [];
284
+ const visit = async (requestedName, ancestry) => {
285
+ const name = resolveAddonFolderName(requestedName);
286
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(name)) {
287
+ throw new Error(`Invalid required addon identifier '${requestedName}'`);
288
+ }
289
+ if (visited.has(name))
290
+ return;
291
+ if (visiting.has(name)) {
292
+ throw new Error(`Required addon dependency cycle: ${[...ancestry, name].join(' -> ')}`);
293
+ }
294
+ const source = addonPath(frameworkRoot, name);
295
+ let manifest;
296
+ try {
297
+ manifest = JSON.parse(await fs.readFile(path.join(source, 'manifest.json'), 'utf8'));
298
+ }
299
+ catch (error) {
300
+ throw new Error(`Cannot load required addon '${name}': ${error instanceof Error ? error.message : String(error)}`);
301
+ }
302
+ if (manifest.id !== name) {
303
+ throw new Error(`Required addon manifest identity mismatch: expected '${name}'`);
304
+ }
305
+ const required = manifest.dependencies?.required ?? [];
306
+ if (!Array.isArray(required) || required.some(item => typeof item !== 'string')) {
307
+ throw new Error(`Addon '${name}' has an invalid dependencies.required declaration`);
308
+ }
309
+ visiting.add(name);
310
+ for (const dependency of [...required].sort()) {
311
+ const dependencyName = resolveAddonFolderName(dependency);
312
+ if (!await isValidAddon(frameworkRoot, dependencyName)) {
313
+ throw new Error(`Addon '${name}' requires unavailable addon '${dependencyName}'`);
314
+ }
315
+ await visit(dependencyName, [...ancestry, name]);
316
+ }
317
+ visiting.delete(name);
318
+ visited.add(name);
319
+ order.push(name);
320
+ };
321
+ await visit(selectedAddon, []);
322
+ return order;
323
+ }
273
324
  async function registerSourceCliCommands(opts) {
274
325
  const contribution = await loadCliCommandsContribution(opts.source);
275
326
  if (!contribution)
@@ -298,6 +349,25 @@ function getProviderPaths(provider) {
298
349
  function getProviderKernelSkillsPath(provider) {
299
350
  return getProviderKernelSkillPath(provider) || getProviderKernelSkillPath('claude');
300
351
  }
352
+ const PROVIDER_GENERATED_GITIGNORE_PATTERNS = {
353
+ codex: ['.codex/', '.agents/'],
354
+ };
355
+ async function ensureProviderGeneratedDirsIgnored(projectRoot, provider, opts) {
356
+ if (opts.dryRun)
357
+ return;
358
+ const patterns = PROVIDER_GENERATED_GITIGNORE_PATTERNS[provider];
359
+ if (!patterns || patterns.length === 0)
360
+ return;
361
+ try {
362
+ const result = await appendGitignore(projectRoot, patterns);
363
+ if (opts.verbose && result.added.length > 0) {
364
+ ui.dim(` Gitignore: added ${result.added.join(', ')}`);
365
+ }
366
+ }
367
+ catch (error) {
368
+ ui.warn(`Failed to update .gitignore for generated ${provider} artifacts: ${error instanceof Error ? error.message : String(error)}`);
369
+ }
370
+ }
301
371
  const MIRRORED_STANDARD_COMMAND_SKILLS = new Set([
302
372
  'aiwg-setup-project',
303
373
  'aiwg-update-claude',
@@ -460,7 +530,7 @@ function agenticNextSteps(openStep) {
460
530
  return [
461
531
  openStep,
462
532
  'Ask the steward: "Check that AIWG is installed correctly and tell me what I can do here."',
463
- 'Regenerate: Use aiwg-regenerate in-session when context files need rebuilding.',
533
+ 'Regenerate: Invoke {{aiwg-regenerate}} in-session when context files need rebuilding.',
464
534
  'Install runbook: docs/agentic-install-runbook.md',
465
535
  'Diagnostics: aiwg doctor',
466
536
  ];
@@ -499,7 +569,11 @@ const NEXT_STEPS = {
499
569
  };
500
570
  export function nextStepsFor(framework, provider = 'claude') {
501
571
  const providerKey = `${provider}/${framework}`;
502
- return NEXT_STEPS[providerKey] ?? NEXT_STEPS[framework] ?? NEXT_STEPS.sdlc;
572
+ const regenerateInvocation = provider === 'codex' || provider === 'openai'
573
+ ? '$aiwg-regenerate'
574
+ : '/aiwg-regenerate';
575
+ const steps = NEXT_STEPS[providerKey] ?? NEXT_STEPS[framework] ?? NEXT_STEPS.sdlc;
576
+ return steps.map((step) => step.replace('{{aiwg-regenerate}}', regenerateInvocation));
503
577
  }
504
578
  function printNextSteps(framework, provider = 'claude') {
505
579
  ui.section('Next steps:', nextStepsFor(framework, provider));
@@ -523,8 +597,8 @@ const SESSION_RELOAD_NOTICE = {
523
597
  rationale: 'Claude Code reads .claude/agents/ at session start. A running session retains its old registry until reloaded.',
524
598
  },
525
599
  codex: {
526
- action: 'Restart your Codex session to pick up newly deployed agents and home-dir skills.',
527
- rationale: 'Codex caches its agent and skill registry per session. ~/.codex/skills/ and .codex/agents/ are scanned on startup.',
600
+ action: 'Restart/open Codex in this target workspace so it picks up newly deployed agents and .agents/skills entries.',
601
+ rationale: 'Codex caches its agent and skill registry per session. Project .agents/skills/ and .codex/agents/ are scanned from the Codex working directory up to the repo root on startup.',
528
602
  },
529
603
  copilot: {
530
604
  action: 'Reload the VS Code window (`Developer: Reload Window`) so Copilot picks up the new .github/agents/ entries.',
@@ -1638,6 +1712,103 @@ async function deploySourceDirectory(opts) {
1638
1712
  }
1639
1713
  return result;
1640
1714
  }
1715
+ async function assertNoSymlinks(root) {
1716
+ for (const entry of await fs.readdir(root, { withFileTypes: true })) {
1717
+ const candidate = path.join(root, entry.name);
1718
+ if (entry.isSymbolicLink()) {
1719
+ throw new Error(`Refusing symbolic link in external bundle source: ${candidate}`);
1720
+ }
1721
+ if (entry.isDirectory())
1722
+ await assertNoSymlinks(candidate);
1723
+ }
1724
+ }
1725
+ async function installProjectLocalBundleInUserCatalog(bundle) {
1726
+ await assertNoSymlinks(bundle.bundlePath);
1727
+ const catalogRoot = path.join(os.homedir(), '.aiwg', PROJECT_LOCAL_TYPE_TO_DIR[bundle.type]);
1728
+ await fs.mkdir(catalogRoot, { recursive: true });
1729
+ const destination = path.join(catalogRoot, bundle.id);
1730
+ const stage = await fs.mkdtemp(path.join(catalogRoot, `.${bundle.id}-stage-`));
1731
+ const backup = path.join(catalogRoot, `.${bundle.id}-backup-${process.pid}`);
1732
+ let movedExisting = false;
1733
+ try {
1734
+ await fs.cp(bundle.bundlePath, stage, { recursive: true, force: true });
1735
+ try {
1736
+ await fs.rename(destination, backup);
1737
+ movedExisting = true;
1738
+ }
1739
+ catch (error) {
1740
+ if (error.code !== 'ENOENT')
1741
+ throw error;
1742
+ }
1743
+ await fs.rename(stage, destination);
1744
+ if (movedExisting)
1745
+ await fs.rm(backup, { recursive: true, force: true });
1746
+ return destination;
1747
+ }
1748
+ catch (error) {
1749
+ await fs.rm(stage, { recursive: true, force: true });
1750
+ if (movedExisting) {
1751
+ await fs.rm(destination, { recursive: true, force: true });
1752
+ await fs.rename(backup, destination).catch(() => { });
1753
+ }
1754
+ throw error;
1755
+ }
1756
+ }
1757
+ async function rebuildExternalBundleIndex(projectDir, graph, verbose) {
1758
+ const originalLog = console.log;
1759
+ if (!verbose)
1760
+ console.log = () => { };
1761
+ try {
1762
+ const [{ buildIndex }, { syncFortemiCoreIndex }] = await Promise.all([
1763
+ import('../../artifacts/index-builder.js'),
1764
+ import('../../artifacts/fortemi-core-sync.js'),
1765
+ ]);
1766
+ await buildIndex(projectDir, { graph, force: true, explicit: false });
1767
+ syncFortemiCoreIndex(projectDir, { graph });
1768
+ }
1769
+ finally {
1770
+ console.log = originalLog;
1771
+ }
1772
+ ui.dim(` Refreshed ${graph}-scope capability index`);
1773
+ }
1774
+ async function mirrorProjectLocalBundleToUserScope(opts) {
1775
+ const paths = getProviderPaths(opts.provider);
1776
+ const resolveProjectPath = (value) => !value ? '' : path.isAbsolute(value) ? value : path.join(opts.target, value);
1777
+ const mirrored = await mirrorToUserScope(opts.provider, {
1778
+ agents: resolveProjectPath(paths.agents),
1779
+ skills: resolveProjectPath(paths.skills),
1780
+ kernelSkills: resolveProjectPath(getProviderKernelSkillsPath(opts.provider)),
1781
+ commands: resolveProjectPath(paths.commands),
1782
+ rules: resolveProjectPath(paths.rules),
1783
+ behaviors: resolveProjectPath(paths.behaviors),
1784
+ });
1785
+ const counts = {
1786
+ agents: mirrored.agents.count,
1787
+ commands: mirrored.commands.count,
1788
+ skills: mirrored.skills.count,
1789
+ rules: mirrored.rules.count,
1790
+ };
1791
+ const total = Object.values(counts).reduce((sum, count) => sum + count, 0);
1792
+ if (total === 0) {
1793
+ throw new Error(`External bundle '${opts.bundle.id}' produced no user-scope artifacts for ${opts.provider}`);
1794
+ }
1795
+ const { recordUserDeploy } = await import('../../config/user-registry.js');
1796
+ await recordUserDeploy({
1797
+ framework: opts.bundle.id,
1798
+ provider: opts.provider,
1799
+ version: opts.bundle.manifest.version,
1800
+ source: 'project-local',
1801
+ counts,
1802
+ entries: {
1803
+ agents: mirrored.agents.entries,
1804
+ commands: mirrored.commands.entries,
1805
+ skills: mirrored.skills.entries,
1806
+ rules: mirrored.rules.entries,
1807
+ behaviors: mirrored.behaviors.entries,
1808
+ },
1809
+ manifestHash: await hashManifest(path.join(opts.bundle.bundlePath, 'manifest.json')),
1810
+ });
1811
+ }
1641
1812
  /**
1642
1813
  * Use command handler
1643
1814
  *
@@ -1702,23 +1873,31 @@ export class UseHandler {
1702
1873
  if (scopeIdx >= 0 && remainingArgs[scopeIdx + 1] === 'project') {
1703
1874
  return { exitCode: 1, message: 'Error: --global conflicts with --scope project' };
1704
1875
  }
1705
- if (!framework || !VALID_FRAMEWORKS.includes(framework)) {
1706
- return {
1707
- exitCode: 1,
1708
- message: 'Error: --global currently supports framework targets; addons and project-local bundles require project deployment',
1709
- };
1710
- }
1711
1876
  const contextTargetIdx = remainingArgs.indexOf('--target');
1712
1877
  const contextTarget = path.resolve(contextTargetIdx >= 0 && remainingArgs[contextTargetIdx + 1]
1713
1878
  ? remainingArgs[contextTargetIdx + 1]
1714
1879
  : (ctx.cwd || process.cwd()));
1880
+ const externalDiscovery = await discoverProjectLocalBundles(contextTarget);
1881
+ const externalBundle = framework
1882
+ ? externalDiscovery.bundles.find(bundle => bundle.id === framework)
1883
+ : undefined;
1884
+ const isFrameworkTarget = Boolean(framework && VALID_FRAMEWORKS.includes(framework));
1885
+ if (!framework || (!isFrameworkTarget && !externalBundle)) {
1886
+ return {
1887
+ exitCode: 1,
1888
+ message: 'Error: --global target must be a bundled framework or a valid external project-local bundle',
1889
+ };
1890
+ }
1715
1891
  const originalConfig = await readAiwgConfig(contextTarget);
1716
1892
  const providers = configuredGlobalProviders(remainingArgs, originalConfig);
1717
1893
  const dryRun = remainingArgs.includes('--dry-run');
1718
- const stageRoot = dryRun
1719
- ? path.join(os.tmpdir(), 'aiwg-global-bootstrap-dry-run')
1720
- : await fs.mkdtemp(path.join(os.tmpdir(), 'aiwg-global-bootstrap-'));
1894
+ const stageRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'aiwg-global-bootstrap-'));
1721
1895
  try {
1896
+ if (externalBundle) {
1897
+ const stagedBundle = path.join(stageRoot, '.aiwg', PROJECT_LOCAL_TYPE_TO_DIR[externalBundle.type], externalBundle.id);
1898
+ await fs.mkdir(path.dirname(stagedBundle), { recursive: true });
1899
+ await fs.cp(externalBundle.bundlePath, stagedBundle, { recursive: true, force: true });
1900
+ }
1722
1901
  for (const provider of providers) {
1723
1902
  const innerArgs = [
1724
1903
  framework,
@@ -1726,7 +1905,7 @@ export class UseHandler {
1726
1905
  '--provider', provider,
1727
1906
  '--scope', 'user',
1728
1907
  '--target', stageRoot,
1729
- '--no-project-local',
1908
+ ...(externalBundle ? [] : ['--no-project-local']),
1730
1909
  '--no-context-files',
1731
1910
  '--no-hooks',
1732
1911
  '--no-workspace-signals',
@@ -1745,14 +1924,13 @@ export class UseHandler {
1745
1924
  }
1746
1925
  }
1747
1926
  finally {
1748
- if (!dryRun)
1749
- await fs.rm(stageRoot, { recursive: true, force: true });
1927
+ await fs.rm(stageRoot, { recursive: true, force: true });
1750
1928
  }
1751
1929
  return {
1752
1930
  exitCode: 0,
1753
1931
  message: dryRun
1754
- ? `Global bootstrap preview complete; project context target: ${contextTarget}`
1755
- : `Global bootstrap complete; user assets installed and lightweight project context generated at ${contextTarget}`,
1932
+ ? `Global ${externalBundle ? 'external bundle' : 'bootstrap'} preview complete; project context target: ${contextTarget}`
1933
+ : `Global ${externalBundle ? 'external bundle' : 'bootstrap'} complete; user assets installed and capability index refreshed`,
1756
1934
  };
1757
1935
  }
1758
1936
  // Project-isolation warning (UC-NUA-002 / SAD §5.1). Fires once per CLI
@@ -1849,7 +2027,10 @@ export class UseHandler {
1849
2027
  }
1850
2028
  return { exitCode: 0 };
1851
2029
  }
1852
- const frameworkRoot = await getFrameworkRoot();
2030
+ // Handler contexts already carry the active installation root. Respect it
2031
+ // so linked worktrees, embedded callers, and tests do not silently deploy
2032
+ // artifacts from a different channel checkout.
2033
+ const frameworkRoot = ctx.frameworkRoot || await getFrameworkRoot();
1853
2034
  if (framework === 'all' && explicitTarget !== 'all' && !remainingArgs.includes('--no-workspace-signals')) {
1854
2035
  const profileIdx = remainingArgs.findIndex((a) => a === '--profile');
1855
2036
  const profile = profileIdx >= 0 && remainingArgs[profileIdx + 1]
@@ -1962,6 +2143,7 @@ export class UseHandler {
1962
2143
  ui.warn(`${plResult.failed} project-local bundle(s) failed to deploy`);
1963
2144
  }
1964
2145
  }
2146
+ await ensureProviderGeneratedDirsIgnored(target, providerName, { dryRun, verbose });
1965
2147
  if (!dryRun) {
1966
2148
  try {
1967
2149
  const registry = getRegistry();
@@ -2028,7 +2210,20 @@ export class UseHandler {
2028
2210
  const dryRunSingle = remainingArgs.includes('--dry-run');
2029
2211
  const verboseSingle = remainingArgs.includes('--verbose') || remainingArgs.includes('-v');
2030
2212
  const targetIdxSingle = remainingArgs.findIndex(a => a === '--target');
2031
- const targetSingle = targetIdxSingle >= 0 && remainingArgs[targetIdxSingle + 1] ? remainingArgs[targetIdxSingle + 1] : process.cwd();
2213
+ const targetSingle = targetIdxSingle >= 0 && remainingArgs[targetIdxSingle + 1] ? remainingArgs[targetIdxSingle + 1] : projectDir;
2214
+ let scopeSingle;
2215
+ try {
2216
+ scopeSingle = detectScope(remainingArgs);
2217
+ if (scopeSingle === 'project' && remainingArgs.includes('--user')) {
2218
+ scopeSingle = 'user';
2219
+ }
2220
+ }
2221
+ catch (error) {
2222
+ return {
2223
+ exitCode: 1,
2224
+ message: `Error: ${error instanceof Error ? error.message : String(error)}`,
2225
+ };
2226
+ }
2032
2227
  // Multi-provider expansion mirrors the framework path
2033
2228
  let providersForSingle;
2034
2229
  if (explicitProvider)
@@ -2037,9 +2232,38 @@ export class UseHandler {
2037
2232
  providersForSingle = config.providers;
2038
2233
  else
2039
2234
  providersForSingle = ['claude'];
2235
+ const resolvedProviders = [];
2236
+ for (const requestedProvider of providersForSingle) {
2237
+ const provider = normalizeProviderDefinitionId(requestedProvider);
2238
+ if (!provider) {
2239
+ return {
2240
+ exitCode: 1,
2241
+ message: `External bundle '${match.id}' cannot deploy to unsupported provider '${requestedProvider}'. Choose a provider declared in its manifest.`,
2242
+ };
2243
+ }
2244
+ const support = match.manifest.platforms[provider]
2245
+ ?? match.manifest.platforms.generic;
2246
+ if (!support || support === 'none') {
2247
+ const declared = Object.entries(match.manifest.platforms)
2248
+ .filter(([, level]) => level && level !== 'none')
2249
+ .map(([name]) => name)
2250
+ .join(', ');
2251
+ return {
2252
+ exitCode: 1,
2253
+ message: `External bundle '${match.id}' does not declare support for provider '${provider}'. Declared providers: ${declared || 'none'}.`,
2254
+ };
2255
+ }
2256
+ if (scopeSingle === 'user' && !USER_SCOPE_PATHS[provider]) {
2257
+ return {
2258
+ exitCode: 1,
2259
+ message: `External bundle '${match.id}' cannot install at user scope for provider '${provider}' because AIWG has no user-scope path contract for it.`,
2260
+ };
2261
+ }
2262
+ resolvedProviders.push(provider);
2263
+ }
2040
2264
  let totalDeployed = 0;
2041
2265
  let totalFailed = 0;
2042
- for (const p of providersForSingle) {
2266
+ for (const p of resolvedProviders) {
2043
2267
  const r = await deployProjectLocalBundles({
2044
2268
  ctx, frameworkRoot, projectDir, provider: p, target: targetSingle,
2045
2269
  dryRun: dryRunSingle, verbose: verboseSingle, quiet: !verboseSingle && !dryRunSingle,
@@ -2048,10 +2272,39 @@ export class UseHandler {
2048
2272
  });
2049
2273
  totalDeployed += r.deployed;
2050
2274
  totalFailed += r.failed;
2275
+ if (r.deployed > 0 && scopeSingle === 'user' && !dryRunSingle) {
2276
+ try {
2277
+ await mirrorProjectLocalBundleToUserScope({
2278
+ bundle: match,
2279
+ provider: p,
2280
+ target: targetSingle,
2281
+ });
2282
+ }
2283
+ catch (error) {
2284
+ totalFailed += 1;
2285
+ ui.warn(`Failed to install external bundle '${match.id}' for user-scope provider '${p}': ${error instanceof Error ? error.message : String(error)}`);
2286
+ }
2287
+ }
2288
+ }
2289
+ if (totalDeployed > 0 && totalFailed === 0 && !dryRunSingle) {
2290
+ try {
2291
+ await rebuildExternalBundleIndex(projectDir, 'project', verboseSingle);
2292
+ if (scopeSingle === 'user') {
2293
+ await installProjectLocalBundleInUserCatalog(match);
2294
+ await rebuildExternalBundleIndex(projectDir, 'user', verboseSingle);
2295
+ }
2296
+ }
2297
+ catch (error) {
2298
+ totalFailed += 1;
2299
+ ui.warn(`External bundle '${match.id}' index refresh failed: ${error instanceof Error ? error.message : String(error)}`);
2300
+ }
2051
2301
  }
2052
2302
  if (!verboseSingle && !dryRunSingle) {
2053
2303
  ui.blank();
2054
- ui.success(`project-local ${match.type} '${match.id}' deployed (${totalDeployed} provider(s))`);
2304
+ if (totalFailed === 0) {
2305
+ const scopeLabel = scopeSingle === 'user' ? 'project + user' : 'project';
2306
+ ui.success(`external ${match.type} '${match.id}' installed at ${scopeLabel} scope (${totalDeployed} provider(s)); capability index refreshed`);
2307
+ }
2055
2308
  }
2056
2309
  return {
2057
2310
  exitCode: totalFailed > 0 ? 1 : 0,
@@ -2071,7 +2324,8 @@ export class UseHandler {
2071
2324
  const provider = explicitAddonProvider ?? (config?.providers?.[0] ?? 'claude');
2072
2325
  const targetIdx = remainingArgs.findIndex(a => a === '--target');
2073
2326
  const target = targetIdx >= 0 && remainingArgs[targetIdx + 1] ? remainingArgs[targetIdx + 1] : process.cwd();
2074
- const runner = createScriptRunner(ctx.frameworkRoot);
2327
+ const dryRunAddon = remainingArgs.includes('--dry-run');
2328
+ const runner = createScriptRunner(frameworkRoot);
2075
2329
  const addonBaseArgs = ['--deploy-commands', '--deploy-skills', '--deploy-rules'];
2076
2330
  addonBaseArgs.push(...modelDeployArgs);
2077
2331
  if (provider)
@@ -2082,7 +2336,47 @@ export class UseHandler {
2082
2336
  if (remainingArgs.includes('--copy-all') || remainingArgs.includes('--copy-standard-skills')) {
2083
2337
  addonBaseArgs.push('--copy-all');
2084
2338
  }
2339
+ if (dryRunAddon)
2340
+ addonBaseArgs.push('--dry-run');
2085
2341
  const kind = isExtension ? 'extension' : 'addon';
2342
+ const activationOrder = isAddon
2343
+ ? await resolveRequiredAddonActivationOrder(frameworkRoot, framework)
2344
+ : [framework];
2345
+ const requiredAddons = activationOrder.slice(0, -1);
2346
+ for (const dependency of requiredAddons) {
2347
+ ui.blank();
2348
+ ui.header(` Deploying required ${dependency} addon...`);
2349
+ const dependencySource = addonPath(frameworkRoot, dependency);
2350
+ const dependencyResult = await runner.run('tools/agents/deploy-agents.mjs', [
2351
+ '--quiet', '--source', dependencySource,
2352
+ ...addonBaseArgs,
2353
+ ], { capture: true });
2354
+ if (dependencyResult.exitCode !== 0) {
2355
+ return {
2356
+ ...dependencyResult,
2357
+ message: dependencyResult.message
2358
+ || `Failed to deploy required addon '${dependency}'`,
2359
+ };
2360
+ }
2361
+ try {
2362
+ await registerSourceCliCommands({
2363
+ source: dependencySource,
2364
+ target,
2365
+ provider,
2366
+ dryRun: dryRunAddon,
2367
+ fallbackDescription: `${dependency} addon commands`,
2368
+ });
2369
+ }
2370
+ catch (error) {
2371
+ return {
2372
+ exitCode: 1,
2373
+ message: `Failed to register required addon '${dependency}' CLI commands: ${error instanceof Error ? error.message : String(error)}`,
2374
+ };
2375
+ }
2376
+ ui.success(dryRunAddon
2377
+ ? `Required ${dependency} addon activation previewed`
2378
+ : `Required ${dependency} addon deployed`);
2379
+ }
2086
2380
  ui.blank();
2087
2381
  ui.header(` Deploying ${framework} ${kind}...`);
2088
2382
  const addonSource = isExtension
@@ -2095,23 +2389,25 @@ export class UseHandler {
2095
2389
  if (addonResult.exitCode !== 0) {
2096
2390
  return addonResult;
2097
2391
  }
2098
- // Register deployed extensions
2099
- try {
2100
- const registry = getRegistry();
2101
- const paths = getProviderPaths(provider);
2102
- await registerDeployedExtensions(registry, {
2103
- agentsPath: paths.agents,
2104
- skillsPath: paths.skills,
2105
- commandsPath: paths.commands,
2106
- rulesPath: paths.rules,
2107
- behaviorsPath: paths.behaviors,
2108
- provider,
2109
- cwd: target,
2110
- });
2111
- ui.success('Extension registration complete');
2112
- }
2113
- catch (error) {
2114
- ui.warn(`Failed to register extensions: ${error instanceof Error ? error.message : String(error)}`);
2392
+ // Register only artifacts actually written by a confirmed deployment.
2393
+ if (!dryRunAddon) {
2394
+ try {
2395
+ const registry = getRegistry();
2396
+ const paths = getProviderPaths(provider);
2397
+ await registerDeployedExtensions(registry, {
2398
+ agentsPath: paths.agents,
2399
+ skillsPath: paths.skills,
2400
+ commandsPath: paths.commands,
2401
+ rulesPath: paths.rules,
2402
+ behaviorsPath: paths.behaviors,
2403
+ provider,
2404
+ cwd: target,
2405
+ });
2406
+ ui.success('Extension registration complete');
2407
+ }
2408
+ catch (error) {
2409
+ ui.warn(`Failed to register extensions: ${error instanceof Error ? error.message : String(error)}`);
2410
+ }
2115
2411
  }
2116
2412
  // Register CLI commands if addon declares them
2117
2413
  try {
@@ -2119,7 +2415,7 @@ export class UseHandler {
2119
2415
  source: addonSource,
2120
2416
  target,
2121
2417
  provider,
2122
- dryRun: false,
2418
+ dryRun: dryRunAddon,
2123
2419
  fallbackDescription: `${framework} addon commands`,
2124
2420
  });
2125
2421
  }
@@ -2177,7 +2473,7 @@ export class UseHandler {
2177
2473
  }
2178
2474
  }
2179
2475
  // Write profile config to project namespace
2180
- if (selectedProfile) {
2476
+ if (selectedProfile && !dryRunAddon) {
2181
2477
  const namespace = topology.namespace || `.aiwg/${framework}`;
2182
2478
  const configDir = path.join(target, namespace);
2183
2479
  await fs.mkdir(configDir, { recursive: true });
@@ -2207,7 +2503,9 @@ export class UseHandler {
2207
2503
  return wrapperValidation;
2208
2504
  }
2209
2505
  ui.blank();
2210
- ui.success(`${framework} addon deployed`);
2506
+ ui.success(dryRunAddon
2507
+ ? `${framework} addon activation preview complete`
2508
+ : `${framework} addon deployed`);
2211
2509
  return {
2212
2510
  exitCode: 0,
2213
2511
  };
@@ -2484,6 +2782,7 @@ export class UseHandler {
2484
2782
  ui.warn(`${plResult.failed} project-local bundle(s) failed to deploy`);
2485
2783
  }
2486
2784
  }
2785
+ await ensureProviderGeneratedDirsIgnored(target, provider, { dryRun, verbose });
2487
2786
  const paths = getProviderPaths(provider);
2488
2787
  if (!dryRun && !skipUtils) {
2489
2788
  const wrapperValidation = await validateDeployedModelWrappers({
@@ -35,6 +35,7 @@ export const AIWG_RUNTIME_PATTERNS = [
35
35
  * excluded from this list — they are authored artifacts worth versioning.
36
36
  */
37
37
  export const PROVIDER_CONVENTIONAL_PATTERNS = [
38
+ '.agents/',
38
39
  '.codex/',
39
40
  '.cursor/agents/',
40
41
  '.cursor/commands/',
@@ -66,6 +66,24 @@ export const versionCommand = {
66
66
  },
67
67
  },
68
68
  };
69
+ export const authCommand = {
70
+ id: 'auth',
71
+ type: 'command',
72
+ name: 'Authentication',
73
+ description: 'Log in, inspect access, and log out of paid AIWG web resources',
74
+ version: '1.0.0',
75
+ capabilities: ['cli', 'authentication', 'resources'],
76
+ keywords: ['auth', 'login', 'logout', 'status', 'releases'],
77
+ category: 'maintenance',
78
+ platforms: { claude: 'full', generic: 'full' },
79
+ deployment: { pathTemplate: '.{platform}/commands/{id}.md', core: true },
80
+ metadata: {
81
+ type: 'command',
82
+ template: 'utility',
83
+ argumentHint: '<login|status|logout> [--device] [--json] [--all]',
84
+ allowedTools: ['Bash'],
85
+ },
86
+ };
69
87
  export const doctorCommand = {
70
88
  id: 'doctor',
71
89
  type: 'skill',
@@ -3461,6 +3479,7 @@ export const commandDefinitions = [
3461
3479
  // Maintenance (7)
3462
3480
  helpCommand,
3463
3481
  versionCommand,
3482
+ authCommand,
3464
3483
  doctorCommand,
3465
3484
  updateCommand,
3466
3485
  refreshCommand,