@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.
- package/CHANGELOG.md +28 -0
- package/README.md +108 -28
- package/THIRD_PARTY_NOTICES.md +92 -16
- package/dist/adapters/agent-docs.d.ts +32 -0
- package/dist/adapters/agent-docs.d.ts.map +1 -0
- package/dist/adapters/agent-docs.js +146 -0
- package/dist/adapters/agent-docs.js.map +1 -0
- package/dist/adapters/agents-md.d.ts +13 -1
- package/dist/adapters/agents-md.d.ts.map +1 -1
- package/dist/adapters/agents-md.js +137 -120
- package/dist/adapters/agents-md.js.map +1 -1
- package/dist/adapters/e2e.d.ts +15 -0
- package/dist/adapters/e2e.d.ts.map +1 -0
- package/dist/adapters/e2e.js +55 -0
- package/dist/adapters/e2e.js.map +1 -0
- package/dist/adapters/skills.d.ts +5 -0
- package/dist/adapters/skills.d.ts.map +1 -1
- package/dist/adapters/skills.js +17 -0
- package/dist/adapters/skills.js.map +1 -1
- package/dist/cli/capabilities.d.ts +17 -0
- package/dist/cli/capabilities.d.ts.map +1 -0
- package/dist/cli/capabilities.js +149 -0
- package/dist/cli/capabilities.js.map +1 -0
- package/dist/cli/doctor.d.ts +16 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +90 -70
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +117 -20
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/new.d.ts +15 -1
- package/dist/cli/new.d.ts.map +1 -1
- package/dist/cli/new.js +157 -68
- package/dist/cli/new.js.map +1 -1
- package/dist/cli/wizard.d.ts +5 -0
- package/dist/cli/wizard.d.ts.map +1 -1
- package/dist/cli/wizard.js +104 -102
- package/dist/cli/wizard.js.map +1 -1
- package/dist/config/defaults.d.ts +14 -6
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +38 -174
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/schema.d.ts +14 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/config/validation.d.ts +1 -0
- package/dist/config/validation.d.ts.map +1 -1
- package/dist/config/validation.js +63 -0
- package/dist/config/validation.js.map +1 -1
- package/dist/detect/index.d.ts +18 -0
- package/dist/detect/index.d.ts.map +1 -0
- package/dist/detect/index.js +120 -0
- package/dist/detect/index.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/presets/cli.d.ts.map +1 -1
- package/dist/presets/cli.js +7 -0
- package/dist/presets/cli.js.map +1 -1
- package/dist/presets/generic.d.ts.map +1 -1
- package/dist/presets/generic.js +4 -1
- package/dist/presets/generic.js.map +1 -1
- package/dist/presets/testing.d.ts +1 -1
- package/dist/presets/testing.d.ts.map +1 -1
- package/dist/presets/testing.js +22 -8
- package/dist/presets/testing.js.map +1 -1
- package/dist/presets/web.d.ts.map +1 -1
- package/dist/presets/web.js +5 -2
- package/dist/presets/web.js.map +1 -1
- package/dist/registry/catalog.d.ts +13 -0
- package/dist/registry/catalog.d.ts.map +1 -0
- package/dist/registry/catalog.js +544 -0
- package/dist/registry/catalog.js.map +1 -0
- package/dist/registry/index.d.ts +84 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +309 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/setup.d.ts +14 -0
- package/dist/registry/setup.d.ts.map +1 -0
- package/dist/registry/setup.js +25 -0
- package/dist/registry/setup.js.map +1 -0
- package/dist/registry/types.d.ts +115 -0
- package/dist/registry/types.d.ts.map +1 -0
- package/dist/registry/types.js +2 -0
- package/dist/registry/types.js.map +1 -0
- package/package.json +3 -2
- package/src/adapters/agent-docs.ts +184 -0
- package/src/adapters/agents-md.ts +160 -137
- package/src/adapters/e2e.ts +58 -0
- package/src/adapters/skills.ts +18 -0
- package/src/cli/capabilities.ts +176 -0
- package/src/cli/doctor.ts +83 -66
- package/src/cli/init.ts +1 -1
- package/src/cli/main.ts +125 -20
- package/src/cli/new.ts +195 -74
- package/src/cli/wizard.ts +130 -103
- package/src/config/defaults.ts +57 -179
- package/src/config/schema.ts +15 -0
- package/src/config/validation.ts +56 -0
- package/src/detect/index.ts +134 -0
- package/src/index.ts +5 -0
- package/src/presets/cli.ts +6 -0
- package/src/presets/generic.ts +4 -1
- package/src/presets/testing.ts +29 -12
- package/src/presets/web.ts +4 -2
- package/src/registry/catalog.ts +569 -0
- package/src/registry/index.ts +395 -0
- package/src/registry/setup.ts +40 -0
- 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:
|
|
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
|
|
93
|
-
|
|
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
|
-
(
|
|
297
|
-
if (agentBrowserWanted) {
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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:
|
|
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) ??
|
|
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 {
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
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);
|