@antomanc/aginit 0.4.0 → 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 +37 -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/adapters/tool-ignores.d.ts +9 -0
- package/dist/adapters/tool-ignores.d.ts.map +1 -0
- package/dist/adapters/tool-ignores.js +121 -0
- package/dist/adapters/tool-ignores.js.map +1 -0
- 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 +104 -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 +123 -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 +163 -67
- 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 +39 -5
- 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/adapters/tool-ignores.ts +158 -0
- package/src/cli/capabilities.ts +176 -0
- package/src/cli/doctor.ts +100 -66
- package/src/cli/init.ts +1 -1
- package/src/cli/main.ts +130 -20
- package/src/cli/new.ts +199 -73
- 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 +39 -5
- 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
package/src/cli/main.ts
CHANGED
|
@@ -3,12 +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 {
|
|
7
|
-
import {
|
|
6
|
+
import { ensureClaudeSkillLinks, updateSkills } from '../adapters/skills.js';
|
|
7
|
+
import { ignoreVendoredSkills } from '../adapters/tool-ignores.js';
|
|
8
8
|
import { buildGraft } from '../adapters/graft.js';
|
|
9
9
|
import { readProjectConfig } from '../config/validation.js';
|
|
10
10
|
import { logger } from '../utils/logger.js';
|
|
11
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';
|
|
12
15
|
import {
|
|
13
16
|
isInteractive,
|
|
14
17
|
promptNewProject,
|
|
@@ -29,25 +32,85 @@ function readVersion(): string {
|
|
|
29
32
|
}
|
|
30
33
|
}
|
|
31
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
|
+
|
|
32
69
|
export function createCliProgram(): Command {
|
|
33
70
|
const program = new Command();
|
|
34
71
|
|
|
35
72
|
program
|
|
36
73
|
.name('aginit')
|
|
37
74
|
.description('Set up a new or existing project for AI coding agents')
|
|
38
|
-
.version(readVersion())
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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;
|
|
46
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;
|
|
47
110
|
});
|
|
48
111
|
|
|
49
112
|
// Command: new
|
|
50
|
-
program
|
|
113
|
+
const newCommand = program
|
|
51
114
|
.command('new [projectName]')
|
|
52
115
|
.description('Create a brand new AI-first project')
|
|
53
116
|
.option('-p, --preset <preset>', 'Project preset: web | cli | generic', 'generic')
|
|
@@ -56,12 +119,14 @@ export function createCliProgram(): Command {
|
|
|
56
119
|
.option('--no-skills', 'Skip installing AI skills')
|
|
57
120
|
.option('--no-graft', 'Skip codebase graph (Graft) setup')
|
|
58
121
|
.option('--no-git', 'Skip git repository initialization')
|
|
59
|
-
.option('--dry-run', 'Simulate creation without writing changes to disk')
|
|
122
|
+
.option('--dry-run', 'Simulate creation without writing changes to disk');
|
|
123
|
+
withProfileOptions(newCommand)
|
|
60
124
|
.action(async (projectName: string | undefined, options: any, command: Command) => {
|
|
61
125
|
let finalName = projectName;
|
|
62
126
|
let finalPreset = options.preset;
|
|
63
127
|
let finalFramework = options.framework;
|
|
64
128
|
let finalPackageManager = options.packageManager;
|
|
129
|
+
let finalProfile = profileFlags(options);
|
|
65
130
|
|
|
66
131
|
if (!finalName) {
|
|
67
132
|
if (!isInteractive()) {
|
|
@@ -79,12 +144,14 @@ export function createCliProgram(): Command {
|
|
|
79
144
|
packageManager:
|
|
80
145
|
command.getOptionValueSource('packageManager') === 'cli'
|
|
81
146
|
? (options.packageManager as PackageManager)
|
|
82
|
-
: undefined
|
|
147
|
+
: undefined,
|
|
148
|
+
profile: finalProfile
|
|
83
149
|
});
|
|
84
150
|
finalName = answers.projectName;
|
|
85
151
|
finalPreset = answers.preset;
|
|
86
152
|
finalFramework = answers.framework;
|
|
87
153
|
finalPackageManager = answers.packageManager;
|
|
154
|
+
finalProfile = answers.profile;
|
|
88
155
|
}
|
|
89
156
|
|
|
90
157
|
const complete = await createProject(finalName, {
|
|
@@ -94,13 +161,14 @@ export function createCliProgram(): Command {
|
|
|
94
161
|
skills: options.skills,
|
|
95
162
|
graft: options.graft,
|
|
96
163
|
git: options.git,
|
|
97
|
-
dryRun: options.dryRun
|
|
164
|
+
dryRun: options.dryRun,
|
|
165
|
+
profile: finalProfile
|
|
98
166
|
});
|
|
99
167
|
if (!complete && !options.dryRun) process.exitCode = 1;
|
|
100
168
|
});
|
|
101
169
|
|
|
102
170
|
// Command: init
|
|
103
|
-
program
|
|
171
|
+
const initCommand = program
|
|
104
172
|
.command('init')
|
|
105
173
|
.description('Initialize or update AI-first capabilities in the current directory')
|
|
106
174
|
.option('-p, --preset <preset>', 'Project preset: web | cli | generic')
|
|
@@ -110,25 +178,30 @@ export function createCliProgram(): Command {
|
|
|
110
178
|
.option('--no-graft', 'Skip codebase graph (Graft) setup')
|
|
111
179
|
.option('--no-git', 'Skip git repository initialization')
|
|
112
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)
|
|
113
183
|
.action(async (options: any, command: Command) => {
|
|
114
184
|
let finalPreset = options.preset;
|
|
115
185
|
let finalFramework = options.framework;
|
|
116
186
|
let finalPackageManager = options.packageManager;
|
|
187
|
+
let finalProfile = profileFlags(options);
|
|
117
188
|
|
|
118
189
|
const isPresetExplicit = command.getOptionValueSource('preset') === 'cli';
|
|
119
190
|
const isFrameworkExplicit = command.getOptionValueSource('framework') === 'cli';
|
|
120
191
|
const isPmExplicit = command.getOptionValueSource('packageManager') === 'cli';
|
|
121
192
|
|
|
122
193
|
const savedConfig = readProjectConfig(process.cwd());
|
|
123
|
-
if (isInteractive() && !isPresetExplicit && !options.dryRun && !savedConfig) {
|
|
194
|
+
if (isInteractive() && !isPresetExplicit && !options.dryRun && !options.yes && !savedConfig) {
|
|
124
195
|
const answers = await promptInitProject({
|
|
125
196
|
preset: isPresetExplicit ? (options.preset as PresetType) : undefined,
|
|
126
197
|
framework: isFrameworkExplicit ? (options.framework as WebFramework) : undefined,
|
|
127
|
-
packageManager: isPmExplicit ? (options.packageManager as PackageManager) : undefined
|
|
198
|
+
packageManager: isPmExplicit ? (options.packageManager as PackageManager) : undefined,
|
|
199
|
+
profile: finalProfile
|
|
128
200
|
});
|
|
129
201
|
finalPreset = answers.preset;
|
|
130
202
|
finalFramework = answers.framework;
|
|
131
203
|
finalPackageManager = answers.packageManager;
|
|
204
|
+
finalProfile = answers.profile;
|
|
132
205
|
}
|
|
133
206
|
|
|
134
207
|
const complete = await initCurrentDirectory({
|
|
@@ -138,11 +211,45 @@ export function createCliProgram(): Command {
|
|
|
138
211
|
skills: options.skills,
|
|
139
212
|
graft: options.graft,
|
|
140
213
|
git: options.git,
|
|
141
|
-
dryRun: options.dryRun
|
|
214
|
+
dryRun: options.dryRun,
|
|
215
|
+
profile: finalProfile
|
|
142
216
|
});
|
|
143
217
|
if (!complete && !options.dryRun) process.exitCode = 1;
|
|
144
218
|
});
|
|
145
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
|
+
|
|
146
253
|
// Command: doctor
|
|
147
254
|
program
|
|
148
255
|
.command('doctor')
|
|
@@ -166,8 +273,11 @@ export function createCliProgram(): Command {
|
|
|
166
273
|
? true
|
|
167
274
|
: await updateSkills(targetDir, { dryRun: options.dryRun })) &&
|
|
168
275
|
(config ? await ensureClaudeSkillLinks(targetDir, config, { dryRun: options.dryRun }) : true);
|
|
169
|
-
if (config
|
|
170
|
-
|
|
276
|
+
if (config) syncAgentDocs(targetDir, config, { dryRun: options.dryRun });
|
|
277
|
+
if (config?.skills.sources.length !== 0) {
|
|
278
|
+
const advice = ignoreVendoredSkills(targetDir, { dryRun: options.dryRun, silent: true });
|
|
279
|
+
for (const line of advice) logger.warn(line);
|
|
280
|
+
}
|
|
171
281
|
const graftOk =
|
|
172
282
|
config?.codebase.graft === false
|
|
173
283
|
? true
|
package/src/cli/new.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import pc from 'picocolors';
|
|
3
|
-
import { getDefaultConfig, upgradeLegacySkillConfig } from '../config/defaults.js';
|
|
3
|
+
import { getDefaultConfig, presetPlatform, upgradeLegacySkillConfig } from '../config/defaults.js';
|
|
4
4
|
import {
|
|
5
|
+
AginitConfig,
|
|
5
6
|
AGINIT_CONFIG_FILENAME,
|
|
6
7
|
PresetType,
|
|
7
8
|
WebFramework,
|
|
@@ -11,13 +12,28 @@ import { getPreset } from '../presets/registry.js';
|
|
|
11
12
|
import { setupGit } from '../adapters/git.js';
|
|
12
13
|
import { setupAgentsMarkdown, setupClaudeMarkdown } from '../adapters/agents-md.js';
|
|
13
14
|
import { claudeCodeEnabled, installAllSkills, skillAgents } from '../adapters/skills.js';
|
|
14
|
-
import {
|
|
15
|
+
import { ignoreVendoredSkills } from '../adapters/tool-ignores.js';
|
|
16
|
+
import { agentBrowserStubInstalled } from '../adapters/agent-browser.js';
|
|
17
|
+
import { microDocPath, projectProfile, resolvePlatformStack, setupMicroDocs } from '../adapters/agent-docs.js';
|
|
18
|
+
import { detectProject } from '../detect/index.js';
|
|
15
19
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
activeCapabilities,
|
|
21
|
+
applyResolvedProfile,
|
|
22
|
+
CapabilityDescriptor,
|
|
23
|
+
e2eDirFor,
|
|
24
|
+
hasProfileFlags,
|
|
25
|
+
inferCapabilities,
|
|
26
|
+
isNodeStack,
|
|
27
|
+
MicroDoc,
|
|
28
|
+
Platform,
|
|
29
|
+
PLATFORMS,
|
|
30
|
+
ProfileFlags,
|
|
31
|
+
resolveCapabilities,
|
|
32
|
+
Stack
|
|
33
|
+
} from '../registry/index.js';
|
|
34
|
+
import { SETUP_LABELS, SETUPS } from '../registry/setup.js';
|
|
35
|
+
import { commandExists } from '../utils/shell.js';
|
|
36
|
+
import { ensureDir, fileExists, writeJsonFile } from '../utils/fs.js';
|
|
21
37
|
import {
|
|
22
38
|
readProjectConfig,
|
|
23
39
|
readPackageManifest,
|
|
@@ -39,6 +55,103 @@ export interface NewProjectOptions {
|
|
|
39
55
|
dryRun?: boolean;
|
|
40
56
|
targetDir?: string;
|
|
41
57
|
silent?: boolean;
|
|
58
|
+
/** v2 profile flags: --platform, --methodology, --design, --testing, --ui-driver, ... */
|
|
59
|
+
profile?: ProfileFlags;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Work out the config to save. A saved config is reused as written, so plain
|
|
64
|
+
* reruns are deterministic; profile flags (and --preset/--framework for the
|
|
65
|
+
* browser switches) are applied on top of it.
|
|
66
|
+
*/
|
|
67
|
+
function resolveConfig(
|
|
68
|
+
projectName: string,
|
|
69
|
+
targetDir: string,
|
|
70
|
+
options: NewProjectOptions,
|
|
71
|
+
existingConfig: AginitConfig | null,
|
|
72
|
+
manifest: Record<string, any> | null
|
|
73
|
+
): AginitConfig {
|
|
74
|
+
const flags = options.profile ?? {};
|
|
75
|
+
const detected = detectProject(targetDir);
|
|
76
|
+
const saved = existingConfig ? resolvePlatformStack(existingConfig, targetDir) : null;
|
|
77
|
+
const presetHint = options.preset ?? existingConfig?.preset;
|
|
78
|
+
const platform: Platform =
|
|
79
|
+
flags.platform ??
|
|
80
|
+
existingConfig?.platform ??
|
|
81
|
+
(options.preset && options.preset !== 'generic'
|
|
82
|
+
? presetPlatform(options.preset)
|
|
83
|
+
: (saved?.platform ?? detected.platform ?? presetPlatform(presetHint ?? 'generic')));
|
|
84
|
+
const stack: Stack =
|
|
85
|
+
existingConfig?.stack ??
|
|
86
|
+
(detected.stack !== 'unknown'
|
|
87
|
+
? detected.stack
|
|
88
|
+
: platform === 'mobile-native'
|
|
89
|
+
? 'unknown'
|
|
90
|
+
: 'node');
|
|
91
|
+
const presetName = options.preset ?? existingConfig?.preset ?? 'generic';
|
|
92
|
+
const framework =
|
|
93
|
+
options.framework ?? (presetName === 'web' ? existingConfig?.framework : undefined);
|
|
94
|
+
if (presetName !== 'web' && framework && framework !== 'none' && framework !== 'existing')
|
|
95
|
+
throw new Error('A web framework requires --preset web.');
|
|
96
|
+
if (presetName === 'web' && framework === 'existing' && !manifest)
|
|
97
|
+
throw new Error(
|
|
98
|
+
'An existing web framework requires an existing package.json. Use --framework none for a new baseline.'
|
|
99
|
+
);
|
|
100
|
+
if (!isNodeStack(stack) && options.preset && options.preset !== 'generic')
|
|
101
|
+
throw new Error(
|
|
102
|
+
`--preset ${options.preset} scaffolds a Node.js project, but this is a ${stack} project. ` +
|
|
103
|
+
'Use --preset generic (the default) to keep the project root free of package.json.'
|
|
104
|
+
);
|
|
105
|
+
const packageManager = options.packageManager ?? existingConfig?.packageManager ?? 'pnpm';
|
|
106
|
+
const defaults = getDefaultConfig(projectName, presetName, {
|
|
107
|
+
framework,
|
|
108
|
+
packageManager,
|
|
109
|
+
platform,
|
|
110
|
+
stack,
|
|
111
|
+
flags,
|
|
112
|
+
skills: options.skills !== false,
|
|
113
|
+
// A generic preset leaves an existing package.json (and its test runner) alone.
|
|
114
|
+
harness: !(presetName === 'generic' && manifest !== null)
|
|
115
|
+
});
|
|
116
|
+
if (!existingConfig) return validateConfig(defaults);
|
|
117
|
+
|
|
118
|
+
let config: AginitConfig = {
|
|
119
|
+
...existingConfig,
|
|
120
|
+
name: projectName,
|
|
121
|
+
preset: presetName,
|
|
122
|
+
...(isNodeStack(stack) || existingConfig.packageManager !== undefined ? { packageManager } : {}),
|
|
123
|
+
...(presetName === 'web' ? { framework: framework ?? 'none' } : {}),
|
|
124
|
+
browser:
|
|
125
|
+
options.framework !== undefined || options.preset !== undefined
|
|
126
|
+
? { ...existingConfig.browser, ...defaults.browser }
|
|
127
|
+
: existingConfig.browser,
|
|
128
|
+
skills: upgradeLegacySkillConfig(existingConfig.skills)
|
|
129
|
+
};
|
|
130
|
+
if (hasProfileFlags(flags)) {
|
|
131
|
+
const base = existingConfig.capabilities ?? inferCapabilities(existingConfig);
|
|
132
|
+
const profile = resolveCapabilities(
|
|
133
|
+
{
|
|
134
|
+
platform,
|
|
135
|
+
stack,
|
|
136
|
+
framework,
|
|
137
|
+
base,
|
|
138
|
+
basePlatform: saved!.platform,
|
|
139
|
+
flags,
|
|
140
|
+
custom: existingConfig.customCapabilities
|
|
141
|
+
},
|
|
142
|
+
existingConfig.disabledCapabilities
|
|
143
|
+
);
|
|
144
|
+
config = applyResolvedProfile({ ...config, platform, stack }, profile, {
|
|
145
|
+
skills: true,
|
|
146
|
+
previous: [...base, ...(existingConfig.customCapabilities ?? []).map((c) => c.id)]
|
|
147
|
+
});
|
|
148
|
+
} else if (config.capabilities && config.browser.playwright !== existingConfig.browser.playwright) {
|
|
149
|
+
// --framework turned the Playwright switch; keep the capability list in step.
|
|
150
|
+
config.capabilities = config.browser.playwright
|
|
151
|
+
? [...config.capabilities, 'playwright']
|
|
152
|
+
: config.capabilities.filter((id) => id !== 'playwright');
|
|
153
|
+
}
|
|
154
|
+
return validateConfig(config);
|
|
42
155
|
}
|
|
43
156
|
|
|
44
157
|
export async function createProject(
|
|
@@ -55,6 +168,8 @@ export async function createProject(
|
|
|
55
168
|
!SUPPORTED_PACKAGE_MANAGERS.includes(options.packageManager)
|
|
56
169
|
)
|
|
57
170
|
throw new Error('Invalid package manager.');
|
|
171
|
+
if (options.profile?.platform !== undefined && !PLATFORMS.includes(options.profile.platform))
|
|
172
|
+
throw new Error(`Invalid platform. Valid platforms are: ${PLATFORMS.join(', ')}`);
|
|
58
173
|
const defaultDir =
|
|
59
174
|
projectName.startsWith('@') && projectName.includes('/')
|
|
60
175
|
? projectName.split('/').pop()!
|
|
@@ -62,54 +177,35 @@ export async function createProject(
|
|
|
62
177
|
const targetDir = path.resolve(options.targetDir || path.join(process.cwd(), defaultDir));
|
|
63
178
|
const existingConfig = readProjectConfig(targetDir);
|
|
64
179
|
const manifest = readPackageManifest(targetDir); // Validate before creating or modifying anything.
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
'An existing web framework requires an existing package.json. Use --framework none for a new baseline.'
|
|
73
|
-
);
|
|
74
|
-
const packageManager = options.packageManager ?? existingConfig?.packageManager ?? 'pnpm';
|
|
180
|
+
const config = resolveConfig(projectName, targetDir, options, existingConfig, manifest);
|
|
181
|
+
if (config.preset !== 'web') delete config.framework;
|
|
182
|
+
if (options.graft === false) config.codebase = { ...config.codebase, graft: false };
|
|
183
|
+
if (options.skills === false && !existingConfig) config.skills.sources = [];
|
|
184
|
+
const presetName = config.preset;
|
|
185
|
+
const packageManager = config.packageManager ?? 'pnpm';
|
|
186
|
+
const { platform, stack } = resolvePlatformStack(config, targetDir);
|
|
75
187
|
const dryRun = !!options.dryRun;
|
|
76
188
|
const silent = !!options.silent;
|
|
77
189
|
const presetHandler = getPreset(presetName);
|
|
78
|
-
const
|
|
79
|
-
framework,
|
|
80
|
-
packageManager
|
|
81
|
-
});
|
|
82
|
-
const config = validateConfig(
|
|
83
|
-
existingConfig
|
|
84
|
-
? {
|
|
85
|
-
...existingConfig,
|
|
86
|
-
name: projectName,
|
|
87
|
-
preset: presetName,
|
|
88
|
-
packageManager,
|
|
89
|
-
...(presetName === 'web' ? { framework: framework ?? 'none' } : {}),
|
|
90
|
-
browser:
|
|
91
|
-
options.framework !== undefined || options.preset !== undefined
|
|
92
|
-
? { ...existingConfig.browser, ...defaults.browser }
|
|
93
|
-
: existingConfig.browser,
|
|
94
|
-
skills: upgradeLegacySkillConfig(existingConfig.skills)
|
|
95
|
-
}
|
|
96
|
-
: defaults
|
|
97
|
-
);
|
|
98
|
-
if (presetName !== 'web') delete config.framework;
|
|
99
|
-
if (options.graft === false) config.codebase = { ...config.codebase, graft: false };
|
|
100
|
-
if (options.skills === false && !existingConfig) config.skills.sources = [];
|
|
190
|
+
const active = activeCapabilities(config);
|
|
101
191
|
if (!silent)
|
|
102
192
|
logger.banner(
|
|
103
193
|
`Aginit — ${projectName}`,
|
|
104
|
-
`Preset: ${presetName} | PM: ${packageManager} | Target: ${targetDir}${dryRun ? ' (DRY RUN)' : ''}`
|
|
194
|
+
`Preset: ${presetName} | Platform: ${platform} (${stack})${isNodeStack(stack) ? ` | PM: ${packageManager}` : ''} | Target: ${targetDir}${dryRun ? ' (DRY RUN)' : ''}`
|
|
105
195
|
);
|
|
196
|
+
if (!silent) logger.dim(`Capabilities: ${active.map((c) => c.id).join(', ') || 'none'}`);
|
|
106
197
|
let complete = true;
|
|
107
198
|
|
|
108
|
-
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
199
|
+
const installSkills = options.skills !== false && config.skills.sources.length > 0;
|
|
200
|
+
// Upstream setups (agent-browser runtime, e2e, Graft) in registry order, Graft last
|
|
201
|
+
// so its first index sees every generated file.
|
|
202
|
+
const setups = active
|
|
203
|
+
.flatMap((capability) =>
|
|
204
|
+
capability.install.flatMap((step) => (step.kind === 'setup' ? [step.setup] : []))
|
|
205
|
+
)
|
|
206
|
+
.filter((setup) => !(setup === 'graft' && options.graft === false))
|
|
207
|
+
.sort((a, b) => Number(a === 'graft') - Number(b === 'graft'));
|
|
208
|
+
const totalSteps = 6 + setups.length;
|
|
113
209
|
let currentStep = 1;
|
|
114
210
|
|
|
115
211
|
// Scaffold into the clean directory before Git adds files that upstream tools reject.
|
|
@@ -134,15 +230,14 @@ export async function createProject(
|
|
|
134
230
|
});
|
|
135
231
|
if (!silent) logger.success(`Wrote ${AGINIT_CONFIG_FILENAME}`);
|
|
136
232
|
|
|
137
|
-
// Step 4:
|
|
138
|
-
if (!silent) logger.step(currentStep++, totalSteps, 'Generating AGENTS.md
|
|
139
|
-
|
|
140
|
-
if (
|
|
141
|
-
if (!silent) logger.success('Configured AGENTS.md');
|
|
233
|
+
// Step 4: AGENTS.md (task routing) and the docs/agents/ micro-docs it routes to
|
|
234
|
+
if (!silent) logger.step(currentStep++, totalSteps, 'Generating AGENTS.md and docs/agents/');
|
|
235
|
+
const docs = syncAgentDocs(targetDir, config, { dryRun, silent });
|
|
236
|
+
if (!silent) logger.success(`Configured AGENTS.md${docs.length ? ` and ${docs.map(microDocPath).join(', ')}` : ''}`);
|
|
142
237
|
|
|
143
238
|
// Step 5: Install skills via skills.sh engine
|
|
144
239
|
if (!silent) logger.step(currentStep++, totalSteps, 'Installing upstream skills via skills.sh');
|
|
145
|
-
if (
|
|
240
|
+
if (installSkills) {
|
|
146
241
|
const { installed, failed } = await installAllSkills(targetDir, config.skills.sources, {
|
|
147
242
|
agents: skillAgents(config),
|
|
148
243
|
dryRun,
|
|
@@ -160,29 +255,28 @@ export async function createProject(
|
|
|
160
255
|
} else if (!silent) {
|
|
161
256
|
logger.dim('Skills installation skipped (--no-skills or empty configuration)');
|
|
162
257
|
}
|
|
258
|
+
const agentBrowserStub = agentBrowserStubInstalled(targetDir);
|
|
259
|
+
if (installSkills || agentBrowserStub) {
|
|
260
|
+
const advice = ignoreVendoredSkills(targetDir, { dryRun, silent: true });
|
|
261
|
+
if (!silent) for (const line of advice) logger.warn(line);
|
|
262
|
+
}
|
|
263
|
+
// A stub installed earlier still needs its runtime, even when skills are skipped now.
|
|
264
|
+
if (agentBrowserStub && !setups.includes('agent-browser')) setups.unshift('agent-browser');
|
|
163
265
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (!silent && abOk) logger.success('agent-browser runtime ready (CLI + browser)');
|
|
266
|
+
for (const setup of setups) {
|
|
267
|
+
if (!silent) logger.step(currentStep++, totalSteps, SETUP_LABELS[setup]);
|
|
268
|
+
const ok = await SETUPS[setup]({ targetDir, config, platform, stack, dryRun, silent });
|
|
269
|
+
complete &&= ok;
|
|
270
|
+
if (ok && !silent) logger.success(`${setup} ready`);
|
|
170
271
|
}
|
|
171
272
|
|
|
172
|
-
//
|
|
173
|
-
if (!silent) logger.step(currentStep++, totalSteps, '
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
});
|
|
180
|
-
complete &&= graftOk;
|
|
181
|
-
if (graftOk && !silent) {
|
|
182
|
-
logger.success('Graft context graph initialized');
|
|
183
|
-
}
|
|
184
|
-
} else if (!silent) {
|
|
185
|
-
logger.dim('Graft setup skipped (--no-graft or codebase.graft: false)');
|
|
273
|
+
// CLIs aginit does not install: report the missing ones with their install command.
|
|
274
|
+
if (!silent) logger.step(currentStep++, totalSteps, 'Checking capability CLIs');
|
|
275
|
+
const missing = await missingBinaries(active, setups);
|
|
276
|
+
if (!silent) {
|
|
277
|
+
if (missing.length === 0) logger.success('Every capability CLI is available');
|
|
278
|
+
for (const { name, bin, install } of missing)
|
|
279
|
+
logger.warn(`${name} needs the \`${bin}\` CLI: ${install}`);
|
|
186
280
|
}
|
|
187
281
|
|
|
188
282
|
if (!silent) {
|
|
@@ -196,7 +290,10 @@ export async function createProject(
|
|
|
196
290
|
console.log('Next steps:');
|
|
197
291
|
const relativeTarget = path.relative(process.cwd(), targetDir);
|
|
198
292
|
if (relativeTarget) console.log(pc.cyan(` cd ${relativeTarget}`));
|
|
199
|
-
|
|
293
|
+
if (isNodeStack(stack) && (manifest || fileExists(path.join(targetDir, 'package.json')) || dryRun))
|
|
294
|
+
console.log(pc.cyan(` ${packageManager} install`));
|
|
295
|
+
if (active.some((c) => c.id === 'e2e') && e2eDirFor(stack))
|
|
296
|
+
console.log(pc.cyan(` (cd ${e2eDirFor(stack)} && npm install)`));
|
|
200
297
|
console.log(pc.cyan(' Open the repository in your AI coding agent'));
|
|
201
298
|
console.log(pc.dim(' Prompt the agent: ') + pc.bold(pc.green('"bootstrap this project"')));
|
|
202
299
|
console.log();
|
|
@@ -204,3 +301,32 @@ export async function createProject(
|
|
|
204
301
|
|
|
205
302
|
return complete;
|
|
206
303
|
}
|
|
304
|
+
|
|
305
|
+
/** Binaries of active capabilities that are not on PATH (setups report their own). */
|
|
306
|
+
export async function missingBinaries(
|
|
307
|
+
active: CapabilityDescriptor[],
|
|
308
|
+
setups: string[]
|
|
309
|
+
): Promise<Array<{ name: string; bin: string; install: string }>> {
|
|
310
|
+
const missing: Array<{ name: string; bin: string; install: string }> = [];
|
|
311
|
+
for (const capability of active) {
|
|
312
|
+
if (capability.install.some((step) => step.kind === 'setup' && setups.includes(step.setup)))
|
|
313
|
+
continue;
|
|
314
|
+
for (const step of capability.install)
|
|
315
|
+
if (step.kind === 'binary' && !(await commandExists(step.bin)))
|
|
316
|
+
missing.push({ name: capability.name, bin: step.bin, install: step.install });
|
|
317
|
+
}
|
|
318
|
+
return missing;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Refresh AGENTS.md, CLAUDE.md and docs/agents/ from the config. */
|
|
322
|
+
export function syncAgentDocs(
|
|
323
|
+
targetDir: string,
|
|
324
|
+
config: AginitConfig,
|
|
325
|
+
options: { dryRun?: boolean; silent?: boolean } = {}
|
|
326
|
+
): MicroDoc[] {
|
|
327
|
+
const scripts = readPackageManifest(targetDir)?.scripts;
|
|
328
|
+
const docs = setupMicroDocs(targetDir, projectProfile(config, { targetDir, scripts }), options);
|
|
329
|
+
setupAgentsMarkdown(targetDir, config, { ...options, docs });
|
|
330
|
+
if (claudeCodeEnabled(config)) setupClaudeMarkdown(targetDir, options);
|
|
331
|
+
return docs;
|
|
332
|
+
}
|