wordpress-agent-kit 0.2.1 → 0.3.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 (37) hide show
  1. package/.github/agents/wp-architect.agent.md +1 -0
  2. package/.github/skills/wordpress-router/SKILL.md +1 -0
  3. package/.github/skills/wp-abilities-api/SKILL.md +1 -0
  4. package/.github/skills/wp-block-development/SKILL.md +1 -0
  5. package/.github/skills/wp-block-themes/SKILL.md +1 -0
  6. package/.github/skills/wp-interactivity-api/SKILL.md +1 -0
  7. package/.github/skills/wp-performance/SKILL.md +1 -0
  8. package/.github/skills/wp-phpstan/SKILL.md +1 -0
  9. package/.github/skills/wp-playground/SKILL.md +1 -0
  10. package/.github/skills/wp-plugin-development/SKILL.md +1 -0
  11. package/.github/skills/wp-project-triage/SKILL.md +1 -0
  12. package/.github/skills/wp-rest-api/SKILL.md +1 -0
  13. package/.github/skills/wp-wpcli-and-ops/SKILL.md +1 -0
  14. package/.github/skills/wpds/SKILL.md +1 -0
  15. package/.github/workflows/ci.yml +44 -0
  16. package/.husky/pre-commit +7 -0
  17. package/AGENTS.md +33 -10
  18. package/AGENTS.template.md +63 -18
  19. package/CLI_REVIEW.md +250 -0
  20. package/README.md +240 -68
  21. package/biome.json +39 -0
  22. package/dist/cli.js +75 -4
  23. package/dist/commands/install.js +84 -10
  24. package/dist/commands/run-playground.js +59 -14
  25. package/dist/commands/setup.js +222 -163
  26. package/dist/commands/sync-skills.js +33 -60
  27. package/dist/commands/upgrade.js +211 -0
  28. package/dist/lib/api.js +511 -0
  29. package/dist/lib/installer.js +114 -6
  30. package/dist/lib/triage-mapper.js +18 -20
  31. package/dist/lib/updater.js +260 -0
  32. package/dist/utils/exit-codes.js +60 -0
  33. package/dist/utils/output.js +96 -0
  34. package/dist/utils/paths.js +1 -1
  35. package/dist/utils/run.js +1 -1
  36. package/extensions/wp-agent-kit/index.ts +630 -0
  37. package/package.json +27 -4
@@ -1,6 +1,8 @@
1
- import { Command } from 'commander';
2
- import path from 'node:path';
3
1
  import { spawnSync } from 'node:child_process';
2
+ import path from 'node:path';
3
+ import { Command } from 'commander';
4
+ import { ExitCode } from '../utils/exit-codes.js';
5
+ import { OutputFormatter, createFormatter } from '../utils/output.js';
4
6
  import { PACKAGE_ROOT } from '../utils/paths.js';
5
7
  /**
6
8
  * Command to run a local WordPress Playground instance.
@@ -8,21 +10,64 @@ import { PACKAGE_ROOT } from '../utils/paths.js';
8
10
  */
9
11
  export const runPlaygroundCommand = new Command('playground')
10
12
  .description('Run local WordPress Playground')
11
- .action(() => {
12
- const port = process.env.PORT || '9400';
13
+ .option('--port <port>', 'Port to run on', '9400')
14
+ .option('--no-auto-mount', 'Disable auto-mount of current directory')
15
+ .action(async (options, command) => {
16
+ const globalOpts = command.parent?.opts() || {};
17
+ const formatter = createFormatter(globalOpts, 'playground', '0.0.0');
18
+ const port = process.env.PORT || options.port;
13
19
  const blueprintPath = path.join(PACKAGE_ROOT, 'playground', 'blueprint.json');
20
+ if (!globalOpts.json && !globalOpts.quiet) {
21
+ console.log(`Starting WordPress Playground on port ${port}...`);
22
+ console.log(`Blueprint: ${blueprintPath}`);
23
+ }
14
24
  const args = [
15
25
  '@wp-playground/cli@latest',
16
26
  'server',
17
- '--auto-mount',
27
+ options.autoMount ? '--auto-mount' : '',
18
28
  `--port=${port}`,
19
- `--blueprint=${blueprintPath}`
20
- ];
21
- console.log(`Starting WordPress Playground on port ${port}...`);
22
- const result = spawnSync('npx', args, {
23
- cwd: PACKAGE_ROOT,
24
- stdio: 'inherit',
25
- shell: process.platform === 'win32'
26
- });
27
- process.exit(result.status || 0);
29
+ `--blueprint=${blueprintPath}`,
30
+ ].filter(Boolean);
31
+ if (globalOpts.dryRun) {
32
+ const dryRunResult = formatter.success({
33
+ wouldExecute: true,
34
+ actions: [
35
+ {
36
+ type: 'create',
37
+ target: `localhost:${port}`,
38
+ description: 'Start WP Playground server with blueprint',
39
+ },
40
+ ],
41
+ summary: { port: Number(port), blueprint: blueprintPath },
42
+ });
43
+ process.exit(OutputFormatter.getExitCode(dryRunResult));
44
+ }
45
+ try {
46
+ const result = spawnSync('npx', args, {
47
+ cwd: PACKAGE_ROOT,
48
+ stdio: 'inherit',
49
+ shell: process.platform === 'win32',
50
+ });
51
+ if (result.status !== 0) {
52
+ const errorResult = formatter.fail({
53
+ code: 'PLAYGROUND_FAILED',
54
+ message: `Playground exited with code ${result.status}`,
55
+ exitCode: ExitCode.ERROR,
56
+ });
57
+ process.exit(OutputFormatter.getExitCode(errorResult));
58
+ }
59
+ if (globalOpts.json) {
60
+ const successResult = formatter.success({ port: Number(port), status: 'completed' });
61
+ process.exit(OutputFormatter.getExitCode(successResult));
62
+ }
63
+ }
64
+ catch (error) {
65
+ const errorMessage = error instanceof Error ? error.message : String(error);
66
+ const errorResult = formatter.fail({
67
+ code: 'PLAYGROUND_ERROR',
68
+ message: errorMessage,
69
+ exitCode: ExitCode.ERROR,
70
+ });
71
+ process.exit(OutputFormatter.getExitCode(errorResult));
72
+ }
28
73
  });
@@ -1,164 +1,217 @@
1
- import { Command } from 'commander';
2
- import * as p from '@clack/prompts';
1
+ import { spawnSync } from 'node:child_process';
3
2
  import fs from 'node:fs';
4
3
  import path from 'node:path';
5
- import { spawnSync } from 'node:child_process';
4
+ import * as p from '@clack/prompts';
5
+ import { Command } from 'commander';
6
+ import { configureAgentsMdApi, installKitApi, } from '../lib/api.js';
7
+ import { PLATFORM_FOLDERS } from '../lib/installer.js';
8
+ import { formatDetectionResults, hasConfidentDetection, mapProjectType, mapTechStack, } from '../lib/triage-mapper.js';
9
+ import { ExitCode } from '../utils/exit-codes.js';
10
+ import { OutputFormatter, createFormatter } from '../utils/output.js';
6
11
  import { PACKAGE_ROOT } from '../utils/paths.js';
7
- import { installKit, PLATFORM_FOLDERS } from '../lib/installer.js';
8
- import { mapProjectType, mapTechStack, hasConfidentDetection, formatDetectionResults } from '../lib/triage-mapper.js';
12
+ const VALID_PROJECT_TYPES = ['plugin', 'theme', 'block-theme', 'site', 'blocks', 'other'];
13
+ function isValidProjectType(type) {
14
+ return VALID_PROJECT_TYPES.includes(type);
15
+ }
16
+ function isDryRunResult(result) {
17
+ return result.success && 'wouldExecute' in (result.data || {});
18
+ }
19
+ function isRegularResult(result) {
20
+ return result.success && !('wouldExecute' in (result.data || {}));
21
+ }
9
22
  /**
10
- * Command to interactively set up the WordPress Agent Kit.
11
- * Handles detection of project type, installation, and configuration of AGENTS.md.
23
+ * Interactive/Headless setup for WordPress Agent Kit.
24
+ * Supports both interactive prompts and --auto/--project-type/--tech-stack for headless use.
12
25
  */
13
26
  export const setupCommand = new Command('setup')
14
- .description('Interactive setup for WordPress Agent Kit')
27
+ .description('Interactive or headless setup for WordPress Agent Kit')
15
28
  .argument('[dir]', 'Target directory', process.cwd())
16
29
  .option('--reset', 'Reset and overwrite existing configuration')
17
30
  .option('--platform <platform>', 'Target platform (github, cursor, claude, agent, pi)', 'github')
18
- .action(async (dir, options) => {
31
+ .option('--auto', 'Run triage, apply detected values, skip prompts', false)
32
+ .option('--project-type <type>', 'Project type: plugin, theme, block-theme, site, blocks, other')
33
+ .option('--tech-stack <list>', 'Comma-separated tech: gutenberg,interactivity,rest-api,wpcli,composer,phpstan,npm,playground')
34
+ .option('--package-manager <pm>', 'Package manager: npm, pnpm, yarn')
35
+ .option('-y, --yes', 'Accept all confirmations (requires --project-type in headless mode)')
36
+ .action(async (dir, options, command) => {
37
+ const globalOpts = command.parent?.opts() || {};
19
38
  const platform = options.platform;
20
39
  const validPlatforms = ['github', 'cursor', 'claude', 'agent', 'pi'];
21
40
  if (!validPlatforms.includes(platform)) {
22
- console.error(`Invalid platform: ${platform}. Valid options: ${validPlatforms.join(', ')}`);
23
- process.exit(1);
41
+ const formatter = createFormatter(globalOpts, 'setup', '0.0.0');
42
+ const result = formatter.fail({
43
+ code: 'INVALID_PLATFORM',
44
+ message: `Invalid platform: ${platform}. Valid options: ${validPlatforms.join(', ')}`,
45
+ exitCode: ExitCode.INVALID_ARGS,
46
+ });
47
+ process.exit(OutputFormatter.getExitCode(result));
24
48
  }
25
- const platformFolder = PLATFORM_FOLDERS[platform];
26
- console.clear();
27
- p.intro('WordPress Agent Kit Setup');
49
+ const formatter = createFormatter(globalOpts, 'setup', '0.0.0');
28
50
  const targetDir = path.resolve(dir);
29
- // Check if target directory exists
51
+ const platformFolder = PLATFORM_FOLDERS[platform];
52
+ const isHeadless = options.auto || options.projectType || globalOpts.json || globalOpts.quiet;
53
+ // Create target directory if needed
30
54
  if (!fs.existsSync(targetDir)) {
31
- const shouldCreate = await p.confirm({
32
- message: `Target directory doesn't exist: ${targetDir}\nCreate it?`,
33
- initialValue: true,
34
- });
35
- if (p.isCancel(shouldCreate) || !shouldCreate) {
36
- p.cancel('Setup cancelled.');
37
- process.exit(0);
38
- }
39
- try {
55
+ if (isHeadless) {
40
56
  fs.mkdirSync(targetDir, { recursive: true });
41
- p.log.success(`Created directory: ${targetDir}`);
42
57
  }
43
- catch (err) {
44
- p.log.error(`Failed to create directory: ${err.message}`);
45
- process.exit(1);
58
+ else {
59
+ const shouldCreate = await p.confirm({
60
+ message: `Target directory doesn't exist: ${targetDir}\nCreate it?`,
61
+ initialValue: true,
62
+ });
63
+ if (p.isCancel(shouldCreate) || !shouldCreate) {
64
+ const result = formatter.fail({
65
+ code: 'CANCELLED',
66
+ message: 'Setup cancelled.',
67
+ exitCode: ExitCode.CANCELLED,
68
+ });
69
+ process.exit(OutputFormatter.getExitCode(result));
70
+ }
71
+ fs.mkdirSync(targetDir, { recursive: true });
46
72
  }
47
73
  }
48
- p.log.info(`Setting up kit in: ${targetDir} (platform: ${platform})`);
49
- // Check if kit is already installed
74
+ // Install kit if not present or --reset
50
75
  const agentsPath = path.join(targetDir, 'AGENTS.md');
51
- const platformInstructionsPath = path.join(targetDir, platformFolder, 'instructions', 'wordpress-workflow.instructions.md');
52
- if (options.reset && fs.existsSync(agentsPath)) {
53
- const confirmReset = await p.confirm({
54
- message: `Warning: --reset will overwrite existing AGENTS.md and ${platformFolder} configuration. Continue?`,
55
- initialValue: false,
56
- });
57
- if (p.isCancel(confirmReset) || !confirmReset) {
58
- p.cancel('Reset cancelled.');
59
- process.exit(0);
60
- }
61
- // Force install if reset is confirmed
62
- const s = p.spinner();
63
- s.start('Re-installing kit files...');
64
- try {
65
- await installKit(targetDir, platform);
66
- s.stop('Kit files reset.');
67
- }
68
- catch (err) {
69
- s.stop('Reset failed.');
70
- console.error(err.message);
71
- process.exit(1);
76
+ const needsInstall = options.reset || !fs.existsSync(agentsPath);
77
+ if (needsInstall) {
78
+ if (!isHeadless) {
79
+ const shouldInstall = await p.confirm({
80
+ message: 'Kit not found in target repo. Install it first?',
81
+ initialValue: true,
82
+ });
83
+ if (p.isCancel(shouldInstall) || !shouldInstall) {
84
+ const result = formatter.fail({
85
+ code: 'CANCELLED',
86
+ message: 'Setup cancelled.',
87
+ exitCode: ExitCode.CANCELLED,
88
+ });
89
+ process.exit(OutputFormatter.getExitCode(result));
90
+ }
72
91
  }
73
- }
74
- else if (!fs.existsSync(agentsPath)) {
75
- const shouldInstall = await p.confirm({
76
- message: 'Kit not found in target repo. Install it first?',
77
- initialValue: true,
92
+ const installResult = await installKitApi({
93
+ targetDir,
94
+ platform,
95
+ force: options.reset,
96
+ dryRun: globalOpts.dryRun,
78
97
  });
79
- if (p.isCancel(shouldInstall) || !shouldInstall) {
80
- p.cancel('Setup cancelled.');
81
- process.exit(0);
98
+ if (!installResult.success) {
99
+ process.exit(OutputFormatter.getExitCode(installResult));
82
100
  }
83
- const s = p.spinner();
84
- s.start('Installing kit files...');
85
- try {
86
- await installKit(targetDir, platform);
87
- s.stop('Kit installed successfully.');
101
+ if (globalOpts.json || globalOpts.quiet) {
102
+ // Continue silently in headless mode
88
103
  }
89
- catch (err) {
90
- s.stop('Installation failed.');
91
- console.error(err.message);
92
- process.exit(1);
104
+ else {
105
+ console.log(' Kit installed');
93
106
  }
94
107
  }
95
- // Run project triage BEFORE asking any questions
108
+ // Run project triage
96
109
  let triageResult = null;
97
110
  let detectedType = null;
98
111
  let detectedTech = [];
99
- // Try to find triage script in multiple locations
112
+ let detectedPackageManager = 'npm/pnpm';
100
113
  const triageScriptPaths = [
101
114
  path.join(targetDir, platformFolder, 'skills/wp-project-triage/scripts/detect_wp_project.mjs'),
102
- path.join(process.cwd(), platformFolder, 'skills/wp-project-triage/scripts/detect_wp_project.mjs'),
103
- path.resolve(PACKAGE_ROOT, 'vendor/wp-agent-skills/skills/wp-project-triage/scripts/detect_wp_project.mjs'),
115
+ path.join(PACKAGE_ROOT, 'vendor/wp-agent-skills/skills/wp-project-triage/scripts/detect_wp_project.mjs'),
104
116
  ];
105
- const triageScriptPath = triageScriptPaths.find(p => fs.existsSync(p));
117
+ const triageScriptPath = triageScriptPaths.find((p) => fs.existsSync(p));
106
118
  if (triageScriptPath) {
107
- const s = p.spinner();
108
- s.start('Analyzing project structure...');
109
- try {
110
- const result = spawnSync('node', [triageScriptPath], {
111
- cwd: targetDir,
112
- encoding: 'utf-8',
113
- });
114
- if (result.status === 0 && result.stdout) {
115
- triageResult = JSON.parse(result.stdout.trim());
116
- detectedType = mapProjectType(triageResult.project?.primary);
117
- detectedTech = mapTechStack(triageResult);
118
- }
119
- s.stop('Project analyzed.');
120
- }
121
- catch (err) {
122
- s.stop('Auto-detection unavailable.');
123
- p.log.warn('Could not run project triage. Proceeding with manual setup.');
124
- }
125
- }
126
- else {
127
- p.log.info('Project triage not available yet. Using manual setup.');
128
- }
129
- let detectedPackageManager = 'npm/pnpm';
130
- if (triageResult && triageResult.tooling?.node?.packageManager) {
131
- detectedPackageManager = triageResult.tooling.node.packageManager;
132
- }
133
- let useDetectedValues = false;
134
- if (triageResult && hasConfidentDetection(detectedType, detectedTech)) {
135
- p.note(formatDetectionResults(detectedType, detectedTech, triageResult), 'Auto-Detection Results');
136
- const confirmDetection = await p.confirm({
137
- message: 'Use these detected values?',
138
- initialValue: true,
119
+ const result = spawnSync('node', [triageScriptPath], {
120
+ cwd: targetDir,
121
+ encoding: 'utf-8',
139
122
  });
140
- if (p.isCancel(confirmDetection)) {
141
- p.cancel('Setup cancelled.');
142
- process.exit(0);
123
+ if (result.status === 0 && result.stdout) {
124
+ triageResult = JSON.parse(result.stdout.trim());
125
+ detectedType = mapProjectType(triageResult.project?.primary ?? '');
126
+ detectedTech = mapTechStack(triageResult);
127
+ if (triageResult.tooling?.node?.packageManager) {
128
+ detectedPackageManager = triageResult.tooling.node.packageManager;
129
+ }
143
130
  }
144
- useDetectedValues = confirmDetection;
145
131
  }
146
- else if (triageResult) {
147
- if (detectedType || detectedTech.length > 0) {
148
- p.note(formatDetectionResults(detectedType, detectedTech, triageResult), 'Partial Detection (will be used as defaults)');
132
+ // Determine project config
133
+ let projectConfig;
134
+ if (options.auto && triageResult) {
135
+ // Auto mode: use detected values
136
+ if (!detectedType || detectedType === 'other') {
137
+ const result = formatter.fail({
138
+ code: 'AUTO_DETECTION_FAILED',
139
+ message: 'Auto-detection could not determine project type confidently. Use --project-type explicitly.',
140
+ exitCode: ExitCode.VALIDATION_ERROR,
141
+ });
142
+ process.exit(OutputFormatter.getExitCode(result));
149
143
  }
150
- }
151
- let projectInfo;
152
- if (useDetectedValues) {
153
- projectInfo = {
144
+ projectConfig = {
154
145
  projectType: detectedType,
155
146
  techStack: detectedTech,
147
+ packageManager: detectedPackageManager,
156
148
  };
157
- p.log.success('Using auto-detected configuration.');
149
+ if (!globalOpts.json && !globalOpts.quiet) {
150
+ console.log(`Auto-detected: ${detectedType} (${detectedTech.join(', ')})`);
151
+ }
152
+ }
153
+ else if (options.projectType) {
154
+ // Explicit project type provided (headless)
155
+ if (!isValidProjectType(options.projectType)) {
156
+ const result = formatter.fail({
157
+ code: 'INVALID_PROJECT_TYPE',
158
+ message: `Invalid project type: ${options.projectType}. Valid: ${VALID_PROJECT_TYPES.join(', ')}`,
159
+ exitCode: ExitCode.INVALID_ARGS,
160
+ });
161
+ process.exit(OutputFormatter.getExitCode(result));
162
+ }
163
+ projectConfig = {
164
+ projectType: options.projectType,
165
+ techStack: options.techStack
166
+ ? options.techStack.split(',').map((s) => s.trim())
167
+ : detectedTech,
168
+ packageManager: options.packageManager || detectedPackageManager,
169
+ };
170
+ }
171
+ else if (isHeadless) {
172
+ // Headless without enough info
173
+ const result = formatter.fail({
174
+ code: 'MISSING_CONFIG',
175
+ message: 'Headless mode requires --project-type (and optionally --tech-stack). Use --auto for auto-detection.',
176
+ exitCode: ExitCode.INVALID_ARGS,
177
+ });
178
+ process.exit(OutputFormatter.getExitCode(result));
158
179
  }
159
180
  else {
160
- projectInfo = await p.group({
161
- projectType: () => p.select({
181
+ // Interactive mode - prompt user
182
+ if (!globalOpts.json && !globalOpts.quiet) {
183
+ console.clear();
184
+ p.intro('WordPress Agent Kit Setup');
185
+ console.log(`Setting up kit in: ${targetDir} (platform: ${platform})`);
186
+ }
187
+ let useDetected = false;
188
+ if (triageResult && hasConfidentDetection(detectedType)) {
189
+ if (!globalOpts.json && !globalOpts.quiet) {
190
+ p.note(formatDetectionResults(detectedType, detectedTech), 'Auto-Detection Results');
191
+ const confirm = await p.confirm({
192
+ message: 'Use these detected values?',
193
+ initialValue: true,
194
+ });
195
+ if (p.isCancel(confirm))
196
+ process.exit(ExitCode.CANCELLED);
197
+ useDetected = confirm;
198
+ }
199
+ }
200
+ else if (triageResult && (detectedType || detectedTech.length > 0)) {
201
+ if (!globalOpts.json && !globalOpts.quiet) {
202
+ p.note(formatDetectionResults(detectedType, detectedTech), 'Partial Detection (used as defaults)');
203
+ }
204
+ }
205
+ if (useDetected) {
206
+ projectConfig = {
207
+ projectType: detectedType,
208
+ techStack: detectedTech,
209
+ packageManager: detectedPackageManager,
210
+ };
211
+ }
212
+ else {
213
+ // Interactive prompts
214
+ const projectTypePrompt = p.select({
162
215
  message: 'What type of WordPress project is this?',
163
216
  options: [
164
217
  { value: 'plugin', label: 'Plugin' },
@@ -170,11 +223,15 @@ export const setupCommand = new Command('setup')
170
223
  { value: 'unsure', label: "I'm not sure" },
171
224
  ],
172
225
  initialValue: detectedType || undefined,
173
- }),
174
- techStack: () => p.multiselect({
226
+ });
227
+ const techStackPrompt = p.multiselect({
175
228
  message: 'Select technologies (or skip if unsure):',
176
229
  options: [
177
- { value: 'gutenberg', label: 'Gutenberg Blocks', hint: 'block.json, @wordpress/blocks' },
230
+ {
231
+ value: 'gutenberg',
232
+ label: 'Gutenberg Blocks',
233
+ hint: 'block.json, @wordpress/blocks',
234
+ },
178
235
  { value: 'interactivity', label: 'Interactivity API', hint: 'data-wp-* directives' },
179
236
  { value: 'rest-api', label: 'REST API', hint: 'Custom endpoints' },
180
237
  { value: 'wpcli', label: 'WP-CLI', hint: 'Custom commands' },
@@ -185,53 +242,55 @@ export const setupCommand = new Command('setup')
185
242
  ],
186
243
  initialValues: detectedTech.length > 0 ? detectedTech : undefined,
187
244
  required: false,
188
- }),
189
- }, {
190
- onCancel: () => {
191
- p.cancel('Setup cancelled.');
192
- process.exit(0);
193
- },
194
- });
195
- if (projectInfo.projectType === 'unsure') {
196
- projectInfo.projectType = 'other';
197
- p.log.info('Using "other" as project type. You can adjust AGENTS.md later.');
245
+ });
246
+ const { projectType, techStack } = await p.group({ projectType: () => projectTypePrompt, techStack: () => techStackPrompt }, { onCancel: () => process.exit(ExitCode.CANCELLED) });
247
+ projectConfig = {
248
+ projectType: (projectType === 'unsure' ? 'other' : projectType),
249
+ techStack,
250
+ packageManager: detectedPackageManager,
251
+ };
198
252
  }
199
253
  }
200
- const customizeAgents = await p.confirm({
201
- message: 'Customize AGENTS.md with project details?',
202
- initialValue: true,
254
+ // Configure AGENTS.md
255
+ const configureResult = await configureAgentsMdApi({
256
+ targetDir,
257
+ platform,
258
+ config: projectConfig,
259
+ dryRun: globalOpts.dryRun,
203
260
  });
204
- if (p.isCancel(customizeAgents)) {
205
- p.cancel('Setup cancelled.');
206
- process.exit(0);
261
+ if (!configureResult.success) {
262
+ process.exit(OutputFormatter.getExitCode(configureResult));
207
263
  }
208
- if (customizeAgents) {
209
- try {
210
- let agentsContent = fs.readFileSync(agentsPath, 'utf-8');
211
- agentsContent = agentsContent.replace(/\*\*Tooling\*\*: .*/, `**Tooling**: ${projectInfo.techStack.includes('composer') ? 'Composer for PHP' : ''}${projectInfo.techStack.includes('npm') ? `, ${detectedPackageManager} for JS` : ''}.`);
212
- fs.writeFileSync(agentsPath, agentsContent, 'utf-8');
213
- p.log.success('Updated AGENTS.md');
264
+ // Success output
265
+ if (globalOpts.json || globalOpts.quiet) {
266
+ let modified = [];
267
+ let skipped = [];
268
+ if (isRegularResult(configureResult)) {
269
+ modified = configureResult.data.modified;
270
+ skipped = configureResult.data.skipped;
214
271
  }
215
- catch (err) {
216
- p.log.warn(`Could not update AGENTS.md: ${err.message}`);
272
+ else if (isDryRunResult(configureResult)) {
273
+ // Dry-run
274
+ modified = configureResult.data.summary.modified;
275
+ skipped = configureResult.data.summary.skipped;
217
276
  }
218
- }
219
- // Workflow instructions
220
- if (fs.existsSync(platformInstructionsPath)) {
221
- const customizeWorkflow = await p.confirm({
222
- message: 'Open workflow instructions for manual editing?',
223
- initialValue: false,
277
+ const result = formatter.success({
278
+ targetDir,
279
+ platform,
280
+ config: projectConfig,
281
+ modified,
282
+ skipped,
224
283
  });
225
- if (!p.isCancel(customizeWorkflow) && customizeWorkflow) {
226
- p.note(`Edit: ${platformInstructionsPath}\n\nAdd your project-specific:\n- Coding standards\n- Git workflow\n- Testing procedures\n- Deployment steps`, 'Workflow Instructions');
227
- }
284
+ process.exit(OutputFormatter.getExitCode(result));
228
285
  }
229
- const promptsFolder = path.join(targetDir, platformFolder, 'prompts');
230
- p.note(`✓ Kit installed and configured\n\n` +
231
- `Next steps:\n` +
232
- `1. Review ${agentsPath}\n` +
233
- `2. Customize ${promptsFolder}/ for your domain\n` +
234
- `3. Test with: ${projectInfo.projectType === 'plugin' ? '"Create a new settings page"' : '"Generate a block variation"'}\n` +
235
- `4. Adjust skills loading in AGENTS.md as needed`, 'Setup Complete');
236
- p.outro('Your WordPress project is now AI-ready!');
286
+ // Human output
287
+ console.log('✓ Setup complete');
288
+ console.log(` Project: ${projectConfig.projectType}`);
289
+ console.log(` Tech stack: ${projectConfig.techStack.join(', ') || 'none'}`);
290
+ console.log(` Package manager: ${projectConfig.packageManager}`);
291
+ console.log('\nNext steps:');
292
+ console.log(` 1. Review ${path.join(targetDir, 'AGENTS.md')}`);
293
+ console.log(` 2. Customize ${path.join(targetDir, platformFolder, 'prompts/')}`);
294
+ console.log(` 3. Run triage: node ${path.join(targetDir, platformFolder, 'skills/wp-project-triage/scripts/detect_wp_project.mjs')}`);
295
+ process.exit(0);
237
296
  });
@@ -1,70 +1,43 @@
1
1
  import { Command } from 'commander';
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import { run } from '../utils/run.js';
5
- const OFFICIAL_SKILLS_REPO_URL = 'https://github.com/WordPress/agent-skills.git';
6
- const DEFAULT_SKILLS_REF = 'trunk';
2
+ import { syncSkillsApi } from '../lib/api.js';
3
+ import { OutputFormatter } from '../utils/output.js';
4
+ function isDryRunResult(result) {
5
+ return result.success && 'wouldExecute' in (result.data || {});
6
+ }
7
+ function isRegularResult(result) {
8
+ return result.success && !('wouldExecute' in (result.data || {}));
9
+ }
7
10
  /**
8
11
  * Command to sync skills from the official WordPress agent-skills repository.
9
- * Can checkout a specific branch or tag.
12
+ * Supports --json, --ndjson, --dry-run flags.
10
13
  */
11
14
  export const syncSkillsCommand = new Command('sync-skills')
12
- .description('Sync skills from the official WordPress agent-skills repository')
13
- .argument('[ref]', 'Branch or tag to checkout', DEFAULT_SKILLS_REF)
14
- .action((refPassed) => {
15
- // Check for custom URL args - simplistic check
16
- if (/^(https?:\/\/|git@)/i.test(refPassed)) {
17
- console.error('Custom skills repository URLs are not supported. This script syncs from the official WordPress/agent-skills repo only.');
18
- process.exit(2);
15
+ .description('Sync skills from the official WordPress/agent-skills repository')
16
+ .argument('[ref]', 'Branch or tag to checkout', 'trunk')
17
+ .option('--ref <ref>', 'Branch or tag to checkout (alias for argument)')
18
+ .action(async (refArg, options, command) => {
19
+ const globalOpts = command.parent?.opts() || {};
20
+ const ref = options.ref || refArg;
21
+ const result = await syncSkillsApi({
22
+ targetDir: process.cwd(),
23
+ ref,
24
+ dryRun: globalOpts.dryRun,
25
+ });
26
+ if (globalOpts.json || globalOpts.ndjson || globalOpts.quiet) {
27
+ process.exit(OutputFormatter.getExitCode(result));
19
28
  }
20
- // Check if refPassed might contain URL (same check as above) or empty string
21
- const ref = refPassed || DEFAULT_SKILLS_REF;
22
- const repoRoot = process.cwd();
23
- const submodulePath = path.join('vendor', 'wp-agent-skills');
24
- const vendorSkillsDir = path.join(repoRoot, submodulePath);
25
- const submoduleGitDir = path.join(vendorSkillsDir, '.git');
26
- // Clone or update the skills repo (not as a submodule since it's gitignored)
27
- if (!fs.existsSync(submoduleGitDir)) {
28
- fs.mkdirSync(path.join(repoRoot, 'vendor'), { recursive: true });
29
- console.log(`Cloning ${OFFICIAL_SKILLS_REPO_URL} into ${submodulePath}...`);
30
- run('git', ['clone', OFFICIAL_SKILLS_REPO_URL, submodulePath], repoRoot);
29
+ if (isRegularResult(result)) {
30
+ const data = result.data;
31
+ console.log(`✓ Synced ${data.skillsSynced} skills from WordPress/agent-skills@${ref}`);
32
+ console.log(` Method: ${data.method}`);
33
+ console.log(` Duration: ${data.durationMs}ms`);
31
34
  }
32
- else {
33
- console.log(`Updating existing skills repo at ${submodulePath}...`);
34
- run('git', ['fetch', '--all', '--tags'], vendorSkillsDir);
35
- }
36
- // Checkout the specified ref
37
- if (ref) {
38
- run('git', ['checkout', ref], vendorSkillsDir);
39
- run('git', ['pull', 'origin', ref], vendorSkillsDir);
40
- }
41
- const targetSkills = path.join(repoRoot, '.github', 'skills');
42
- const upstreamBuildScript = path.join(vendorSkillsDir, 'shared', 'scripts', 'skillpack-build.mjs');
43
- const upstreamInstallScript = path.join(vendorSkillsDir, 'shared', 'scripts', 'skillpack-install.mjs');
44
- if (fs.existsSync(upstreamBuildScript) && fs.existsSync(upstreamInstallScript)) {
45
- if (fs.existsSync(targetSkills)) {
46
- fs.rmSync(targetSkills, { recursive: true, force: true });
47
- }
48
- fs.mkdirSync(path.join(repoRoot, '.github'), { recursive: true });
49
- run('node', ['shared/scripts/skillpack-build.mjs', '--clean', '--targets=vscode'], vendorSkillsDir);
50
- run('node', ['shared/scripts/skillpack-install.mjs', `--dest=${repoRoot}`, '--targets=vscode', '--from=dist', '--mode=replace'], vendorSkillsDir);
51
- console.log(`Synced skills into: ${targetSkills}`);
52
- console.log(`Skills source: ${OFFICIAL_SKILLS_REPO_URL}${ref ? ` @ ${ref}` : ''}`);
53
- console.log('Sync method: upstream skillpack-build.mjs + skillpack-install.mjs');
54
- // No process.exit here, let commander handle exit or fallthrough unless explicit stop
55
- return;
35
+ else if (isDryRunResult(result)) {
36
+ // Dry-run result
37
+ console.log(`✓ Dry-run: ${result.data.summary.skillsSynced} skills would be synced`);
56
38
  }
57
- const sourceSkills = path.join(vendorSkillsDir, '.github', 'skills');
58
- if (!fs.existsSync(sourceSkills)) {
59
- console.error(`Could not find upstream skills directory at: ${sourceSkills}`);
60
- process.exit(1);
61
- }
62
- if (fs.existsSync(targetSkills)) {
63
- fs.rmSync(targetSkills, { recursive: true, force: true });
39
+ else {
40
+ console.error(`✗ Sync failed: ${result.error?.message}`);
64
41
  }
65
- fs.mkdirSync(path.join(repoRoot, '.github'), { recursive: true });
66
- fs.cpSync(sourceSkills, targetSkills, { recursive: true });
67
- console.log(`Synced skills into: ${targetSkills}`);
68
- console.log(`Skills source: ${OFFICIAL_SKILLS_REPO_URL}${ref ? ` @ ${ref}` : ''}`);
69
- console.log('Sync method: fallback direct copy from .github/skills');
42
+ process.exit(result.success ? 0 : 1);
70
43
  });