@antomanc/aginit 0.4.1 → 0.5.0

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 (112) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +108 -28
  3. package/THIRD_PARTY_NOTICES.md +92 -16
  4. package/dist/adapters/agent-docs.d.ts +32 -0
  5. package/dist/adapters/agent-docs.d.ts.map +1 -0
  6. package/dist/adapters/agent-docs.js +146 -0
  7. package/dist/adapters/agent-docs.js.map +1 -0
  8. package/dist/adapters/agents-md.d.ts +13 -1
  9. package/dist/adapters/agents-md.d.ts.map +1 -1
  10. package/dist/adapters/agents-md.js +137 -120
  11. package/dist/adapters/agents-md.js.map +1 -1
  12. package/dist/adapters/e2e.d.ts +15 -0
  13. package/dist/adapters/e2e.d.ts.map +1 -0
  14. package/dist/adapters/e2e.js +55 -0
  15. package/dist/adapters/e2e.js.map +1 -0
  16. package/dist/adapters/skills.d.ts +5 -0
  17. package/dist/adapters/skills.d.ts.map +1 -1
  18. package/dist/adapters/skills.js +17 -0
  19. package/dist/adapters/skills.js.map +1 -1
  20. package/dist/cli/capabilities.d.ts +17 -0
  21. package/dist/cli/capabilities.d.ts.map +1 -0
  22. package/dist/cli/capabilities.js +149 -0
  23. package/dist/cli/capabilities.js.map +1 -0
  24. package/dist/cli/doctor.d.ts +16 -0
  25. package/dist/cli/doctor.d.ts.map +1 -1
  26. package/dist/cli/doctor.js +90 -70
  27. package/dist/cli/doctor.js.map +1 -1
  28. package/dist/cli/init.js +1 -1
  29. package/dist/cli/init.js.map +1 -1
  30. package/dist/cli/main.d.ts.map +1 -1
  31. package/dist/cli/main.js +117 -20
  32. package/dist/cli/main.js.map +1 -1
  33. package/dist/cli/new.d.ts +15 -1
  34. package/dist/cli/new.d.ts.map +1 -1
  35. package/dist/cli/new.js +157 -68
  36. package/dist/cli/new.js.map +1 -1
  37. package/dist/cli/wizard.d.ts +5 -0
  38. package/dist/cli/wizard.d.ts.map +1 -1
  39. package/dist/cli/wizard.js +104 -102
  40. package/dist/cli/wizard.js.map +1 -1
  41. package/dist/config/defaults.d.ts +14 -6
  42. package/dist/config/defaults.d.ts.map +1 -1
  43. package/dist/config/defaults.js +38 -174
  44. package/dist/config/defaults.js.map +1 -1
  45. package/dist/config/schema.d.ts +14 -0
  46. package/dist/config/schema.d.ts.map +1 -1
  47. package/dist/config/schema.js.map +1 -1
  48. package/dist/config/validation.d.ts +1 -0
  49. package/dist/config/validation.d.ts.map +1 -1
  50. package/dist/config/validation.js +63 -0
  51. package/dist/config/validation.js.map +1 -1
  52. package/dist/detect/index.d.ts +18 -0
  53. package/dist/detect/index.d.ts.map +1 -0
  54. package/dist/detect/index.js +120 -0
  55. package/dist/detect/index.js.map +1 -0
  56. package/dist/index.d.ts +5 -0
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +5 -0
  59. package/dist/index.js.map +1 -1
  60. package/dist/presets/cli.d.ts.map +1 -1
  61. package/dist/presets/cli.js +7 -0
  62. package/dist/presets/cli.js.map +1 -1
  63. package/dist/presets/generic.d.ts.map +1 -1
  64. package/dist/presets/generic.js +4 -1
  65. package/dist/presets/generic.js.map +1 -1
  66. package/dist/presets/testing.d.ts +1 -1
  67. package/dist/presets/testing.d.ts.map +1 -1
  68. package/dist/presets/testing.js +22 -8
  69. package/dist/presets/testing.js.map +1 -1
  70. package/dist/presets/web.d.ts.map +1 -1
  71. package/dist/presets/web.js +5 -2
  72. package/dist/presets/web.js.map +1 -1
  73. package/dist/registry/catalog.d.ts +13 -0
  74. package/dist/registry/catalog.d.ts.map +1 -0
  75. package/dist/registry/catalog.js +544 -0
  76. package/dist/registry/catalog.js.map +1 -0
  77. package/dist/registry/index.d.ts +84 -0
  78. package/dist/registry/index.d.ts.map +1 -0
  79. package/dist/registry/index.js +309 -0
  80. package/dist/registry/index.js.map +1 -0
  81. package/dist/registry/setup.d.ts +14 -0
  82. package/dist/registry/setup.d.ts.map +1 -0
  83. package/dist/registry/setup.js +25 -0
  84. package/dist/registry/setup.js.map +1 -0
  85. package/dist/registry/types.d.ts +115 -0
  86. package/dist/registry/types.d.ts.map +1 -0
  87. package/dist/registry/types.js +2 -0
  88. package/dist/registry/types.js.map +1 -0
  89. package/package.json +3 -2
  90. package/src/adapters/agent-docs.ts +184 -0
  91. package/src/adapters/agents-md.ts +160 -137
  92. package/src/adapters/e2e.ts +58 -0
  93. package/src/adapters/skills.ts +18 -0
  94. package/src/cli/capabilities.ts +176 -0
  95. package/src/cli/doctor.ts +83 -66
  96. package/src/cli/init.ts +1 -1
  97. package/src/cli/main.ts +125 -20
  98. package/src/cli/new.ts +195 -74
  99. package/src/cli/wizard.ts +130 -103
  100. package/src/config/defaults.ts +57 -179
  101. package/src/config/schema.ts +15 -0
  102. package/src/config/validation.ts +56 -0
  103. package/src/detect/index.ts +134 -0
  104. package/src/index.ts +5 -0
  105. package/src/presets/cli.ts +6 -0
  106. package/src/presets/generic.ts +4 -1
  107. package/src/presets/testing.ts +29 -12
  108. package/src/presets/web.ts +4 -2
  109. package/src/registry/catalog.ts +569 -0
  110. package/src/registry/index.ts +395 -0
  111. package/src/registry/setup.ts +40 -0
  112. package/src/registry/types.ts +109 -0
@@ -0,0 +1,176 @@
1
+ import path from 'node:path';
2
+ import pc from 'picocolors';
3
+ import { AGINIT_CONFIG_FILENAME, AginitConfig } from '../config/schema.js';
4
+ import { readProjectConfig, validateConfig } from '../config/validation.js';
5
+ import { resolvePlatformStack } from '../adapters/agent-docs.js';
6
+ import { installAllSkills, removeSkills, skillAgents } from '../adapters/skills.js';
7
+ import { uninstallGraft } from '../adapters/graft.js';
8
+ import { ignoreVendoredSkills } from '../adapters/tool-ignores.js';
9
+ import {
10
+ activeIds,
11
+ applyResolvedProfile,
12
+ CapabilityCategory,
13
+ CapabilityDescriptor,
14
+ catalogFor,
15
+ findCapability,
16
+ inferCapabilities,
17
+ ProfileFlags,
18
+ resolveCapabilities,
19
+ sourcesFor
20
+ } from '../registry/index.js';
21
+ import { SETUPS } from '../registry/setup.js';
22
+ import { writeJsonFile } from '../utils/fs.js';
23
+ import { logger } from '../utils/logger.js';
24
+ import { missingBinaries, syncAgentDocs } from './new.js';
25
+
26
+ const CATEGORY_ORDER: CapabilityCategory[] = [
27
+ 'codebase-graph',
28
+ 'methodology',
29
+ 'discipline',
30
+ 'design',
31
+ 'ui-driver',
32
+ 'testing-engine',
33
+ 'context-guard',
34
+ 'custom'
35
+ ];
36
+
37
+ /** Print the registry grouped by category, marking what the project has enabled. */
38
+ export function listCapabilities(targetDir: string = process.cwd()): void {
39
+ const config = readProjectConfig(targetDir);
40
+ const enabled = config ? activeIds(config) : new Set<string>();
41
+ logger.banner(
42
+ 'Aginit — Capability registry',
43
+ config ? `Enabled in ${AGINIT_CONFIG_FILENAME}: ●` : `No ${AGINIT_CONFIG_FILENAME} here; showing the catalog`
44
+ );
45
+ const catalog = catalogFor(config);
46
+ for (const category of CATEGORY_ORDER) {
47
+ const entries = catalog.filter((c) => c.category === category);
48
+ if (entries.length === 0) continue;
49
+ console.log(pc.bold(pc.underline(category)));
50
+ for (const c of entries) {
51
+ const mark = enabled.has(c.id) ? pc.green('●') : pc.dim('○');
52
+ const platforms = c.supportedPlatforms.join(', ');
53
+ console.log(` ${mark} ${pc.bold(c.id.padEnd(22))} ${c.description}`);
54
+ console.log(pc.dim(` ${platforms} · ${c.license} · ${c.repoUrl}`));
55
+ }
56
+ console.log();
57
+ }
58
+ }
59
+
60
+ export interface ChangeOptions {
61
+ dryRun?: boolean;
62
+ silent?: boolean;
63
+ }
64
+
65
+ function requireConfig(targetDir: string): AginitConfig {
66
+ const config = readProjectConfig(targetDir);
67
+ if (!config)
68
+ throw new Error(`No ${AGINIT_CONFIG_FILENAME} in ${targetDir}. Run \`aginit init\` first.`);
69
+ return config;
70
+ }
71
+
72
+ /** Skill names to remove; a source installed whole is removed by its repo. */
73
+ const skillsOf = (capabilities: CapabilityDescriptor[]) =>
74
+ capabilities.flatMap((c) =>
75
+ c.install.flatMap((s) => (s.kind !== 'skills-sh' ? [] : s.skills.length ? s.skills : [s.repo]))
76
+ );
77
+
78
+ /**
79
+ * Enable or disable capabilities, then bring the project in line: config,
80
+ * skills (installed or removed through the Skills CLI), upstream setups,
81
+ * AGENTS.md and docs/agents/. Picking a methodology, design or UI driver
82
+ * replaces the current one in that category.
83
+ */
84
+ export async function changeCapabilities(
85
+ targetDir: string,
86
+ change: { add?: string[]; remove?: string[] },
87
+ options: ChangeOptions = {}
88
+ ): Promise<boolean> {
89
+ const { dryRun = false, silent = false } = options;
90
+ const config = requireConfig(targetDir);
91
+ const { platform, stack } = resolvePlatformStack(config, targetDir);
92
+ const base = config.capabilities ?? inferCapabilities(config);
93
+ const previous = [...base, ...(config.customCapabilities ?? []).map((c) => c.id)];
94
+ const before = activeIds(config);
95
+
96
+ const flags: ProfileFlags = { addons: [], addSkills: [], without: [] };
97
+ for (const id of change.add ?? []) {
98
+ const capability = findCapability(id, config);
99
+ if (!capability) {
100
+ if (id.includes('/')) flags.addSkills!.push(id);
101
+ else throw new Error(`Unknown capability "${id}". Run \`aginit list\` to see the registry.`);
102
+ } else if (capability.category === 'methodology') flags.methodology = id;
103
+ else if (capability.category === 'design') flags.design = id;
104
+ else if (capability.category === 'ui-driver' && !capability.addon) flags.uiDriver = id;
105
+ else flags.addons!.push(id);
106
+ }
107
+ for (const id of change.remove ?? []) {
108
+ if (!findCapability(id, config))
109
+ throw new Error(`Unknown capability "${id}". Run \`aginit list\` to see the registry.`);
110
+ flags.without!.push(id);
111
+ }
112
+ const custom = config.customCapabilities ?? [];
113
+ const customIds = new Set(custom.map((c) => c.id));
114
+ const disabled = (config.disabledCapabilities ?? []).filter((id) => !(change.add ?? []).includes(id));
115
+ const resolved = resolveCapabilities(
116
+ { platform, stack, framework: config.framework, base, basePlatform: platform, flags, custom },
117
+ disabled
118
+ );
119
+ // A removed custom capability is gone, not disabled.
120
+ resolved.disabledCapabilities = resolved.disabledCapabilities.filter((id) => !customIds.has(id));
121
+ const next = validateConfig(
122
+ applyResolvedProfile({ ...config, platform, stack }, resolved, { skills: true, previous })
123
+ );
124
+ // Adding graft or playwright turns their legacy switch back on.
125
+ if (next.capabilities?.includes('graft')) next.codebase.graft = true;
126
+
127
+ const after = activeIds(next);
128
+ const nextCatalog = catalogFor(next);
129
+ const added = nextCatalog.filter((c) => after.has(c.id) && !before.has(c.id));
130
+ const removed = catalogFor(config).filter((c) => before.has(c.id) && !after.has(c.id));
131
+ if (added.length === 0 && removed.length === 0 && JSON.stringify(next) === JSON.stringify(config)) {
132
+ if (!silent) logger.info('Nothing to change.');
133
+ return true;
134
+ }
135
+ if (!silent) {
136
+ for (const c of added) logger.success(`Enabling ${c.id}`);
137
+ for (const c of removed) logger.warn(`Disabling ${c.id}`);
138
+ }
139
+
140
+ writeJsonFile(path.join(targetDir, AGINIT_CONFIG_FILENAME), next, { overwrite: true, dryRun, silent });
141
+ let ok = true;
142
+
143
+ ok = (await removeSkills(targetDir, skillsOf(removed), { dryRun, silent })) && ok;
144
+ if (removed.some((c) => c.id === 'graft')) await uninstallGraft(targetDir, { dryRun, silent });
145
+ const leftovers = removed.filter((c) =>
146
+ c.install.some((s) => s.kind === 'setup' && s.setup !== 'graft')
147
+ );
148
+ if (!silent)
149
+ for (const c of leftovers)
150
+ logger.dim(`${c.id}: files and global CLIs its setup created are left in place; remove them by hand.`);
151
+
152
+ const addedSources = sourcesFor(added);
153
+ if (addedSources.length > 0) {
154
+ const { failed } = await installAllSkills(targetDir, addedSources, {
155
+ agents: skillAgents(next),
156
+ dryRun,
157
+ silent
158
+ });
159
+ ok = failed.length === 0 && ok;
160
+ for (const line of ignoreVendoredSkills(targetDir, { dryRun, silent: true })) logger.warn(line);
161
+ }
162
+ for (const c of added)
163
+ for (const step of c.install)
164
+ if (step.kind === 'setup')
165
+ ok = (await SETUPS[step.setup]({ targetDir, config: next, platform, stack, dryRun, silent })) && ok;
166
+
167
+ syncAgentDocs(targetDir, next, { dryRun, silent });
168
+ if (!silent)
169
+ for (const { name, bin, install } of await missingBinaries(added, ['graft', 'agent-browser', 'e2e']))
170
+ logger.warn(`${name} needs the \`${bin}\` CLI: ${install}`);
171
+ if (!silent) {
172
+ if (ok) logger.success('Capabilities updated. AGENTS.md and docs/agents/ are in sync.');
173
+ else logger.warn('Capabilities updated with warnings. Resolve them and rerun `aginit init`.');
174
+ }
175
+ return ok;
176
+ }
package/src/cli/doctor.ts CHANGED
@@ -17,7 +17,6 @@ import {
17
17
  } from '../adapters/skills.js';
18
18
  import { claudeMarkdownImportsAgents } from '../adapters/agents-md.js';
19
19
  import {
20
- agentBrowserConfigured,
21
20
  agentBrowserStubInstalled,
22
21
  findUsernsRestriction,
23
22
  runAgentBrowserDiagnostics
@@ -25,10 +24,60 @@ import {
25
24
  import { readProjectConfig } from '../config/validation.js';
26
25
  import { vendoredSkillsExposure } from '../adapters/tool-ignores.js';
27
26
  import { logger } from '../utils/logger.js';
27
+ import { activeCapabilities, activeIds, doctorChecks, isNodeStack } from '../registry/index.js';
28
+ import { resolvePlatformStack } from '../adapters/agent-docs.js';
29
+
30
+ /**
31
+ * One row per active capability, built from its registry entry: every skill
32
+ * file it installs, every CLI it drives and any artifact it declares.
33
+ */
34
+ export async function capabilityChecks(
35
+ targetDir: string,
36
+ config: AginitConfig
37
+ ): Promise<CheckItem[]> {
38
+ const items: CheckItem[] = [];
39
+ for (const capability of activeCapabilities(config)) {
40
+ const problems: string[] = [];
41
+ const fixes: string[] = [];
42
+ const missingSkills: string[] = [];
43
+ const versions: string[] = [];
44
+ for (const check of doctorChecks(capability, config.skills.sources)) {
45
+ if (check.kind === 'file') {
46
+ if (fileExists(path.join(targetDir, check.path))) continue;
47
+ const skill = /^\.agents\/skills\/([^/]+)\/SKILL\.md$/.exec(check.path)?.[1];
48
+ if (skill) missingSkills.push(skill);
49
+ else {
50
+ problems.push(`${check.path} missing`);
51
+ fixes.push(check.fix);
52
+ }
53
+ } else if (!(await commandExists(check.bin))) {
54
+ problems.push(`${check.bin} CLI not found in PATH`);
55
+ fixes.push(check.fix);
56
+ } else {
57
+ const res = await runExecutable(check.bin, check.args, { silent: true, timeout: 30_000 });
58
+ versions.push(res.ok && res.stdout ? `${check.bin} ${res.stdout.split('\n')[0]}` : check.bin);
59
+ }
60
+ }
61
+ if (missingSkills.length) {
62
+ problems.unshift(`skills missing from .agents/skills/: ${missingSkills.join(', ')}`);
63
+ fixes.unshift('Run `aginit init` to reinstall the configured skills');
64
+ }
65
+ items.push({
66
+ name: `${capability.name} (${capability.category})`,
67
+ category: 'Capabilities',
68
+ ok: problems.length === 0,
69
+ detail: problems.length ? problems.join('; ') : `Ready${versions.length ? ` (${versions.join(', ')})` : ''}`,
70
+ suggestion: fixes.length ? fixes.join(' · ') : undefined
71
+ });
72
+ }
73
+ return items;
74
+ }
75
+
76
+ type CheckCategory = 'Environment' | 'AI Tooling' | 'Project Config' | 'Capabilities';
28
77
 
29
78
  interface CheckItem {
30
79
  name: string;
31
- category: 'Environment' | 'AI Tooling' | 'Project Config';
80
+ category: CheckCategory;
32
81
  ok: boolean;
33
82
  version?: string;
34
83
  detail: string;
@@ -88,9 +137,14 @@ export async function runDoctor(targetDir: string = process.cwd()): Promise<void
88
137
  suggestion: nodeSuggestion
89
138
  });
90
139
 
91
- // 2. Active Package Manager check (pnpm, npm, yarn, or bun)
92
- const pmRes = await runExecutable(activePm, ['-v'], { silent: true });
93
- checks.push({
140
+ // 2. Active Package Manager check (pnpm, npm, yarn, or bun); none for non-Node stacks
141
+ const { stack, platform } = projectConfig
142
+ ? resolvePlatformStack(projectConfig, targetDir)
143
+ : { stack: 'node' as const, platform: null };
144
+ const pmRes = isNodeStack(stack)
145
+ ? await runExecutable(activePm, ['-v'], { silent: true })
146
+ : null;
147
+ if (pmRes) checks.push({
94
148
  name: `${activePm} package manager`,
95
149
  category: 'Environment',
96
150
  ok: pmRes.ok,
@@ -133,22 +187,6 @@ export async function runDoctor(targetDir: string = process.cwd()): Promise<void
133
187
  : undefined
134
188
  });
135
189
 
136
- // 4. Graft CLI check
137
- const graftExists = await commandExists('graft');
138
- let graftVersion = '';
139
- if (graftExists) {
140
- const gv = await runExecutable('graft', ['--version'], { silent: true });
141
- graftVersion = gv.stdout;
142
- }
143
- checks.push({
144
- name: 'Graft codebase graph CLI',
145
- category: 'AI Tooling',
146
- ok: graftExists,
147
- version: graftVersion,
148
- detail: graftExists ? `Installed globally (${graftVersion})` : 'Graft CLI not found in PATH',
149
- suggestion: graftExists ? undefined : 'Install Graft: `npm install -g @nanonets/graft`'
150
- });
151
-
152
190
  // 5. Codex CLI check
153
191
  const codexExists = await commandExists('codex');
154
192
  checks.push({
@@ -176,7 +214,7 @@ export async function runDoctor(targetDir: string = process.cwd()): Promise<void
176
214
  category: 'Project Config',
177
215
  ok: !!projectConfig,
178
216
  detail: projectConfig
179
- ? `Preset: "${projectConfig.preset}" (v${projectConfig.schemaVersion || '1.0.0'}), pm: ${activePm}, agents: ${projectConfig.agents.primary}/${projectConfig.agents.secondary}${claudeCodeEnabled(projectConfig) ? '/claude-code' : ''}`
217
+ ? `Preset: "${projectConfig.preset}" (v${projectConfig.schemaVersion || '1.0.0'}), platform: ${platform} (${stack})${pmRes ? `, pm: ${activePm}` : ''}, agents: ${projectConfig.agents.primary}/${projectConfig.agents.secondary}${claudeCodeEnabled(projectConfig) ? '/claude-code' : ''}`
180
218
  : configError
181
219
  ? `Invalid configuration: ${configError}`
182
220
  : 'No aginit.config.json found in current directory',
@@ -290,47 +328,29 @@ export async function runDoctor(targetDir: string = process.cwd()): Promise<void
290
328
  });
291
329
  }
292
330
 
331
+ // 10. Registry: each active capability's checks (skill files, CLIs, artifacts)
332
+ if (projectConfig !== null) checks.push(...(await capabilityChecks(targetDir, projectConfig)));
333
+
293
334
  // 10. agent-browser runtime (only when the project ships the skill)
294
335
  const agentBrowserWanted =
295
336
  projectConfig !== null &&
296
- (agentBrowserConfigured(projectConfig.skills.sources) || agentBrowserStubInstalled(targetDir));
297
- if (agentBrowserWanted) {
298
- const abExists = await commandExists('agent-browser');
299
- let abVersion = '';
300
- if (abExists) {
301
- const av = await runExecutable('agent-browser', ['--version'], { silent: true });
302
- abVersion = av.ok ? av.stdout : '';
303
- }
304
- checks.push({
305
- name: 'agent-browser CLI',
306
- category: 'AI Tooling',
307
- ok: abExists,
308
- version: abVersion,
309
- detail: abExists
310
- ? `Installed globally (${abVersion || 'version unknown'})`
311
- : 'agent-browser skill is installed but the CLI is not in PATH',
312
- suggestion: abExists
313
- ? undefined
314
- : 'Install the runtime: `npm i -g agent-browser && agent-browser install`'
315
- });
316
-
317
- if (abExists) {
318
- const diagnostics = await runAgentBrowserDiagnostics();
319
- if (diagnostics !== null) {
320
- checks.push({
321
- name: 'agent-browser browser runtime',
322
- category: 'AI Tooling',
323
- ok: diagnostics.length === 0,
324
- detail:
325
- diagnostics.length === 0
326
- ? 'Upstream doctor reports browser and launch environment healthy'
327
- : diagnostics.map((d) => d.message).join('; '),
328
- suggestion:
329
- diagnostics.length === 0
330
- ? undefined
331
- : (diagnostics.find((d) => d.fix)?.fix ?? 'Run `agent-browser doctor` for details')
332
- });
333
- }
337
+ (activeIds(projectConfig).has('agent-browser') || agentBrowserStubInstalled(targetDir));
338
+ if (agentBrowserWanted && (await commandExists('agent-browser'))) {
339
+ const diagnostics = await runAgentBrowserDiagnostics();
340
+ if (diagnostics !== null) {
341
+ checks.push({
342
+ name: 'agent-browser browser runtime',
343
+ category: 'AI Tooling',
344
+ ok: diagnostics.length === 0,
345
+ detail:
346
+ diagnostics.length === 0
347
+ ? 'Upstream doctor reports browser and launch environment healthy'
348
+ : diagnostics.map((d) => d.message).join('; '),
349
+ suggestion:
350
+ diagnostics.length === 0
351
+ ? undefined
352
+ : (diagnostics.find((d) => d.fix)?.fix ?? 'Run `agent-browser doctor` for details')
353
+ });
334
354
  }
335
355
  }
336
356
 
@@ -358,15 +378,12 @@ export async function runDoctor(targetDir: string = process.cwd()): Promise<void
358
378
  }
359
379
 
360
380
  // Print grouped results
361
- const categories: Array<'Environment' | 'AI Tooling' | 'Project Config'> = [
362
- 'Environment',
363
- 'AI Tooling',
364
- 'Project Config'
365
- ];
381
+ const categories: CheckCategory[] = ['Environment', 'AI Tooling', 'Project Config', 'Capabilities'];
366
382
 
367
383
  for (const cat of categories) {
368
- console.log(pc.bold(pc.underline(cat)));
369
384
  const catChecks = checks.filter((c) => c.category === cat);
385
+ if (catChecks.length === 0) continue;
386
+ console.log(pc.bold(pc.underline(cat)));
370
387
  for (const c of catChecks) {
371
388
  const icon = c.ok ? pc.green('✓') : pc.yellow('⚠');
372
389
  const name = pc.bold(c.name);
package/src/cli/init.ts CHANGED
@@ -24,7 +24,7 @@ export async function initCurrentDirectory(options: NewProjectOptions = {}): Pro
24
24
  else projectName = sanitizeProjectName(projectName);
25
25
 
26
26
  const packageManager =
27
- options.packageManager ?? saved?.packageManager ?? detectPackageManager(currentDir) ?? 'pnpm';
27
+ options.packageManager ?? saved?.packageManager ?? detectPackageManager(currentDir) ?? undefined;
28
28
 
29
29
  return createProject(projectName, {
30
30
  ...options,
package/src/cli/main.ts CHANGED
@@ -3,13 +3,15 @@ import { readFileSync } from 'node:fs';
3
3
  import { createProject } from './new.js';
4
4
  import { initCurrentDirectory } from './init.js';
5
5
  import { runDoctor } from './doctor.js';
6
- import { claudeCodeEnabled, ensureClaudeSkillLinks, updateSkills } from '../adapters/skills.js';
6
+ import { ensureClaudeSkillLinks, updateSkills } from '../adapters/skills.js';
7
7
  import { ignoreVendoredSkills } from '../adapters/tool-ignores.js';
8
- import { setupClaudeMarkdown } from '../adapters/agents-md.js';
9
8
  import { buildGraft } from '../adapters/graft.js';
10
9
  import { readProjectConfig } from '../config/validation.js';
11
10
  import { logger } from '../utils/logger.js';
12
11
  import { PresetType, WebFramework, PackageManager } from '../config/schema.js';
12
+ import { Platform, ProfileFlags, TestingChoice } from '../registry/index.js';
13
+ import { changeCapabilities, listCapabilities } from './capabilities.js';
14
+ import { syncAgentDocs } from './new.js';
13
15
  import {
14
16
  isInteractive,
15
17
  promptNewProject,
@@ -30,25 +32,85 @@ function readVersion(): string {
30
32
  }
31
33
  }
32
34
 
35
+ const list = (value: string, previous: string[] = []) => [
36
+ ...previous,
37
+ ...value
38
+ .split(',')
39
+ .map((item) => item.trim())
40
+ .filter(Boolean)
41
+ ];
42
+
43
+ /** v2 profile options shared by the root command, `new` and `init`. */
44
+ function withProfileOptions(command: Command): Command {
45
+ return command
46
+ .option('--platform <platform>', 'Platform: auto | web | mobile-native | mobile-rn | backend | cli')
47
+ .option('--methodology <preset>', 'Workflow skills: pocock | superpowers | minimal')
48
+ .option('--design <design>', 'Design system: impeccable | none')
49
+ .option('--testing <engine>', 'Testing: e2e | playwright | vitest | native')
50
+ .option('--ui-driver <driver>', 'UI driver: auto | agent-browser | agent-device | none')
51
+ .option('--addons <ids>', 'Add-ons: context-mode, worktrunk, chrome-devtools-mcp, browserskill', list)
52
+ .option('--add-skill <owner/repo>', 'Add a skill repository as a custom capability (repeatable)', list)
53
+ .option('--without <id>', 'Disable a capability, e.g. graft or humanizer (repeatable)', list);
54
+ }
55
+
56
+ function profileFlags(options: any): ProfileFlags {
57
+ return {
58
+ platform: options.platform === 'auto' ? undefined : (options.platform as Platform | undefined),
59
+ methodology: options.methodology,
60
+ design: options.design,
61
+ testing: options.testing as TestingChoice | undefined,
62
+ uiDriver: options.uiDriver,
63
+ addons: options.addons,
64
+ addSkills: options.addSkill,
65
+ without: options.without
66
+ };
67
+ }
68
+
33
69
  export function createCliProgram(): Command {
34
70
  const program = new Command();
35
71
 
36
72
  program
37
73
  .name('aginit')
38
74
  .description('Set up a new or existing project for AI coding agents')
39
- .version(readVersion());
40
-
41
- // Root action: interactive wizard when in TTY, else display help
42
- program.action(async () => {
43
- if (isInteractive()) {
44
- await runInteractiveWizard();
45
- } else {
46
- program.outputHelp();
75
+ .version(readVersion())
76
+ .enablePositionalOptions()
77
+ .option('-p, --preset <preset>', 'Project preset: web | cli | generic')
78
+ .option('-f, --framework <framework>', 'Web framework: none | vite | next | existing')
79
+ .option('-m, --package-manager <pm>', 'Package manager: pnpm | npm | yarn | bun')
80
+ .option('--no-skills', 'Skip installing AI skills')
81
+ .option('--no-graft', 'Skip codebase graph (Graft) setup')
82
+ .option('--no-git', 'Skip git repository initialization')
83
+ .option('--dry-run', 'Simulate setup without writing changes to disk')
84
+ .option('-y, --yes', 'Initialize the current directory without prompts (saved or detected settings)');
85
+ withProfileOptions(program);
86
+
87
+ // Root action: `aginit [options]` initializes the current directory. Bare
88
+ // `aginit` opens the wizard in a TTY and prints help otherwise, so a stray
89
+ // invocation in CI never rewrites a project.
90
+ program.action(async (options: any, command: Command) => {
91
+ const explicit = command.options.some(
92
+ (option) => command.getOptionValueSource(option.attributeName()) === 'cli'
93
+ );
94
+ if (!explicit) {
95
+ if (isInteractive()) await runInteractiveWizard();
96
+ else program.outputHelp();
97
+ return;
47
98
  }
99
+ const complete = await initCurrentDirectory({
100
+ preset: options.preset as PresetType | undefined,
101
+ framework: options.framework as WebFramework | undefined,
102
+ packageManager: options.packageManager as PackageManager | undefined,
103
+ skills: options.skills,
104
+ graft: options.graft,
105
+ git: options.git,
106
+ dryRun: options.dryRun,
107
+ profile: profileFlags(options)
108
+ });
109
+ if (!complete && !options.dryRun) process.exitCode = 1;
48
110
  });
49
111
 
50
112
  // Command: new
51
- program
113
+ const newCommand = program
52
114
  .command('new [projectName]')
53
115
  .description('Create a brand new AI-first project')
54
116
  .option('-p, --preset <preset>', 'Project preset: web | cli | generic', 'generic')
@@ -57,12 +119,14 @@ export function createCliProgram(): Command {
57
119
  .option('--no-skills', 'Skip installing AI skills')
58
120
  .option('--no-graft', 'Skip codebase graph (Graft) setup')
59
121
  .option('--no-git', 'Skip git repository initialization')
60
- .option('--dry-run', 'Simulate creation without writing changes to disk')
122
+ .option('--dry-run', 'Simulate creation without writing changes to disk');
123
+ withProfileOptions(newCommand)
61
124
  .action(async (projectName: string | undefined, options: any, command: Command) => {
62
125
  let finalName = projectName;
63
126
  let finalPreset = options.preset;
64
127
  let finalFramework = options.framework;
65
128
  let finalPackageManager = options.packageManager;
129
+ let finalProfile = profileFlags(options);
66
130
 
67
131
  if (!finalName) {
68
132
  if (!isInteractive()) {
@@ -80,12 +144,14 @@ export function createCliProgram(): Command {
80
144
  packageManager:
81
145
  command.getOptionValueSource('packageManager') === 'cli'
82
146
  ? (options.packageManager as PackageManager)
83
- : undefined
147
+ : undefined,
148
+ profile: finalProfile
84
149
  });
85
150
  finalName = answers.projectName;
86
151
  finalPreset = answers.preset;
87
152
  finalFramework = answers.framework;
88
153
  finalPackageManager = answers.packageManager;
154
+ finalProfile = answers.profile;
89
155
  }
90
156
 
91
157
  const complete = await createProject(finalName, {
@@ -95,13 +161,14 @@ export function createCliProgram(): Command {
95
161
  skills: options.skills,
96
162
  graft: options.graft,
97
163
  git: options.git,
98
- dryRun: options.dryRun
164
+ dryRun: options.dryRun,
165
+ profile: finalProfile
99
166
  });
100
167
  if (!complete && !options.dryRun) process.exitCode = 1;
101
168
  });
102
169
 
103
170
  // Command: init
104
- program
171
+ const initCommand = program
105
172
  .command('init')
106
173
  .description('Initialize or update AI-first capabilities in the current directory')
107
174
  .option('-p, --preset <preset>', 'Project preset: web | cli | generic')
@@ -111,25 +178,30 @@ export function createCliProgram(): Command {
111
178
  .option('--no-graft', 'Skip codebase graph (Graft) setup')
112
179
  .option('--no-git', 'Skip git repository initialization')
113
180
  .option('--dry-run', 'Simulate setup without writing changes to disk')
181
+ .option('-y, --yes', 'Skip the prompts and use saved or detected settings');
182
+ withProfileOptions(initCommand)
114
183
  .action(async (options: any, command: Command) => {
115
184
  let finalPreset = options.preset;
116
185
  let finalFramework = options.framework;
117
186
  let finalPackageManager = options.packageManager;
187
+ let finalProfile = profileFlags(options);
118
188
 
119
189
  const isPresetExplicit = command.getOptionValueSource('preset') === 'cli';
120
190
  const isFrameworkExplicit = command.getOptionValueSource('framework') === 'cli';
121
191
  const isPmExplicit = command.getOptionValueSource('packageManager') === 'cli';
122
192
 
123
193
  const savedConfig = readProjectConfig(process.cwd());
124
- if (isInteractive() && !isPresetExplicit && !options.dryRun && !savedConfig) {
194
+ if (isInteractive() && !isPresetExplicit && !options.dryRun && !options.yes && !savedConfig) {
125
195
  const answers = await promptInitProject({
126
196
  preset: isPresetExplicit ? (options.preset as PresetType) : undefined,
127
197
  framework: isFrameworkExplicit ? (options.framework as WebFramework) : undefined,
128
- packageManager: isPmExplicit ? (options.packageManager as PackageManager) : undefined
198
+ packageManager: isPmExplicit ? (options.packageManager as PackageManager) : undefined,
199
+ profile: finalProfile
129
200
  });
130
201
  finalPreset = answers.preset;
131
202
  finalFramework = answers.framework;
132
203
  finalPackageManager = answers.packageManager;
204
+ finalProfile = answers.profile;
133
205
  }
134
206
 
135
207
  const complete = await initCurrentDirectory({
@@ -139,11 +211,45 @@ export function createCliProgram(): Command {
139
211
  skills: options.skills,
140
212
  graft: options.graft,
141
213
  git: options.git,
142
- dryRun: options.dryRun
214
+ dryRun: options.dryRun,
215
+ profile: finalProfile
143
216
  });
144
217
  if (!complete && !options.dryRun) process.exitCode = 1;
145
218
  });
146
219
 
220
+ // Command: list
221
+ program
222
+ .command('list')
223
+ .description('List every capability in the registry and which ones this project enables')
224
+ .option('-d, --dir <path>', 'Project directory', process.cwd())
225
+ .action((options: any) => listCapabilities(options.dir));
226
+
227
+ // Command: add
228
+ program
229
+ .command('add <capabilities...>')
230
+ .description('Enable capabilities (registry ids or owner/repo skill repositories)')
231
+ .option('--dry-run', 'Simulate the change')
232
+ .action(async (ids: string[], options: any) => {
233
+ const ok = await changeCapabilities(process.cwd(), { add: ids.flatMap((id) => list(id)) }, {
234
+ dryRun: options.dryRun
235
+ });
236
+ if (!ok && !options.dryRun) process.exitCode = 1;
237
+ });
238
+
239
+ // Command: remove
240
+ program
241
+ .command('remove <capabilities...>')
242
+ .description('Disable capabilities and remove the skills they installed')
243
+ .option('--dry-run', 'Simulate the change')
244
+ .action(async (ids: string[], options: any) => {
245
+ const ok = await changeCapabilities(
246
+ process.cwd(),
247
+ { remove: ids.flatMap((id) => list(id)) },
248
+ { dryRun: options.dryRun }
249
+ );
250
+ if (!ok && !options.dryRun) process.exitCode = 1;
251
+ });
252
+
147
253
  // Command: doctor
148
254
  program
149
255
  .command('doctor')
@@ -167,8 +273,7 @@ export function createCliProgram(): Command {
167
273
  ? true
168
274
  : await updateSkills(targetDir, { dryRun: options.dryRun })) &&
169
275
  (config ? await ensureClaudeSkillLinks(targetDir, config, { dryRun: options.dryRun }) : true);
170
- if (config && claudeCodeEnabled(config))
171
- setupClaudeMarkdown(targetDir, { dryRun: options.dryRun });
276
+ if (config) syncAgentDocs(targetDir, config, { dryRun: options.dryRun });
172
277
  if (config?.skills.sources.length !== 0) {
173
278
  const advice = ignoreVendoredSkills(targetDir, { dryRun: options.dryRun, silent: true });
174
279
  for (const line of advice) logger.warn(line);