prizmkit 1.0.127 → 1.0.129

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.
@@ -1,11 +1,14 @@
1
1
  /**
2
2
  * External Skills Installer
3
3
  *
4
- * Delegates to `npx skills add <repo> --skill <name> --yes` to fetch the skill.
5
- * When run non-interactively (stdio: 'pipe'), npx skills only writes the canonical
6
- * .agents/skills/<name>/SKILL.md without creating any platform symlinks.
7
- * We read that file, copy it to the selected platform dir(s), then clean up
8
- * .agents/ and skills-lock.json.
4
+ * Delegates to `npx skills add <repo> [--skill <name>] --yes` to fetch skills.
5
+ * Supports two modes:
6
+ * - Single skill: `--skill <name>` selects one skill from a repo
7
+ * - Install all: installs all skills from a repo (e.g. pbakaus/impeccable)
8
+ *
9
+ * When run non-interactively (stdio: 'pipe'), npx skills writes canonical
10
+ * .agents/skills/<name>/SKILL.md files. We copy those to the selected
11
+ * platform dir(s), then clean up .agents/ and skills-lock.json.
9
12
  */
10
13
 
11
14
  import { execSync } from 'node:child_process';
@@ -14,8 +17,9 @@ import fs from 'fs-extra';
14
17
 
15
18
  /**
16
19
  * @param {Object} skill - Skill definition from external_skills.known
17
- * skill.repo {string} - GitHub URL
18
- * skill.name {string} - Skill name as listed in the repo
20
+ * skill.repo {string} - GitHub short ref (e.g. "pbakaus/impeccable") or full URL
21
+ * skill.name {string} - Skill name (used for display / single-skill mode)
22
+ * skill.installAll {boolean} - If true, install all skills from the repo
19
23
  * @param {string} platform - 'claude' | 'codebuddy' | 'both'
20
24
  * @param {string} projectRoot - Target project root
21
25
  * @param {boolean} dryRun
@@ -25,35 +29,60 @@ export async function installExternalSkill(skill, platform, projectRoot, dryRun)
25
29
  throw new Error(`Skill "${skill.name}" has no repo URL defined`);
26
30
  }
27
31
 
28
- const cmd = `npx skills add ${skill.repo} --skill ${skill.name} --yes`;
32
+ // installAll: install entire repo (all skills), otherwise install single skill by name
33
+ const cmd = skill.installAll
34
+ ? `npx skills add ${skill.repo} --yes`
35
+ : `npx skills add ${skill.repo} --skill ${skill.name} --yes`;
29
36
 
30
37
  if (dryRun) {
31
- const targets = getTargetDirs(platform, projectRoot)
32
- .map(d => path.relative(projectRoot, path.join(d, skill.name, 'SKILL.md')));
33
38
  console.log(` [dry-run] ${cmd}`);
34
- for (const t of targets) console.log(` [dry-run] → ${t}`);
39
+ if (skill.installAll) {
40
+ console.log(` [dry-run] → all skills from ${skill.repo}`);
41
+ } else {
42
+ const targets = getTargetDirs(platform, projectRoot)
43
+ .map(d => path.relative(projectRoot, path.join(d, skill.name, 'SKILL.md')));
44
+ for (const t of targets) console.log(` [dry-run] → ${t}`);
45
+ }
35
46
  return;
36
47
  }
37
48
 
38
- // stdio: 'pipe' → npx skills runs non-interactively, writes only .agents/skills/<name>/SKILL.md
39
- execSync(cmd, { cwd: projectRoot, stdio: 'pipe', timeout: 60000 });
49
+ // stdio: 'pipe' → npx skills runs non-interactively
50
+ execSync(cmd, { cwd: projectRoot, stdio: 'pipe', timeout: 120000 });
40
51
 
41
- const canonicalSkillMd = path.join(projectRoot, '.agents', 'skills', skill.name, 'SKILL.md');
42
- if (!await fs.pathExists(canonicalSkillMd)) {
43
- throw new Error(`npx skills did not produce .agents/skills/${skill.name}/SKILL.md`);
44
- }
52
+ const agentsSkillsDir = path.join(projectRoot, '.agents', 'skills');
53
+
54
+ if (skill.installAll) {
55
+ // Repo-level install: copy ALL skill dirs from .agents/skills/ to platform dirs
56
+ if (!await fs.pathExists(agentsSkillsDir)) {
57
+ throw new Error(`npx skills did not produce .agents/skills/ for repo ${skill.repo}`);
58
+ }
59
+ const skillDirs = (await fs.readdir(agentsSkillsDir, { withFileTypes: true }))
60
+ .filter(d => d.isDirectory())
61
+ .map(d => d.name);
62
+
63
+ for (const targetDir of getTargetDirs(platform, projectRoot)) {
64
+ for (const skillName of skillDirs) {
65
+ const srcDir = path.join(agentsSkillsDir, skillName);
66
+ const destDir = path.join(targetDir, skillName);
67
+ await fs.remove(destDir).catch(() => {});
68
+ await fs.copy(srcDir, destDir);
69
+ }
70
+ }
71
+ } else {
72
+ // Single-skill install
73
+ const canonicalSkillMd = path.join(agentsSkillsDir, skill.name, 'SKILL.md');
74
+ if (!await fs.pathExists(canonicalSkillMd)) {
75
+ throw new Error(`npx skills did not produce .agents/skills/${skill.name}/SKILL.md`);
76
+ }
45
77
 
46
- const content = await fs.readFile(canonicalSkillMd, 'utf8');
47
-
48
- // Copy to selected platform dir(s)
49
- // npx skills may have created symlinks (e.g. .claude/skills/<name> -> ../../.agents/skills/<name>)
50
- // We must remove those symlinks first so we can write a real directory with the actual content.
51
- for (const targetDir of getTargetDirs(platform, projectRoot)) {
52
- const skillDir = path.join(targetDir, skill.name);
53
- // Remove any existing symlink or directory at this path before creating a real dir
54
- await fs.remove(skillDir).catch(() => {});
55
- await fs.ensureDir(skillDir);
56
- await fs.writeFile(path.join(skillDir, 'SKILL.md'), content);
78
+ const content = await fs.readFile(canonicalSkillMd, 'utf8');
79
+
80
+ for (const targetDir of getTargetDirs(platform, projectRoot)) {
81
+ const skillDir = path.join(targetDir, skill.name);
82
+ await fs.remove(skillDir).catch(() => {});
83
+ await fs.ensureDir(skillDir);
84
+ await fs.writeFile(path.join(skillDir, 'SKILL.md'), content);
85
+ }
57
86
  }
58
87
 
59
88
  // Clean up npx skills artifacts after copying
@@ -30,6 +30,12 @@ export function generateGitignore(options = {}) {
30
30
  );
31
31
  }
32
32
 
33
+ lines.push(
34
+ '# Playwright CLI 快照和截图',
35
+ '.playwright-cli/',
36
+ '',
37
+ );
38
+
33
39
  lines.push(
34
40
  '# ===========================',
35
41
  '# Common',
package/src/index.js CHANGED
@@ -67,6 +67,7 @@ export async function runScaffold(directory, options) {
67
67
  skills: options.skills || 'full',
68
68
  team: options.team !== false,
69
69
  pipeline: options.pipeline !== false,
70
+ playwrightCli: options.playwrightCli !== false,
70
71
  rules: options.rules || 'recommended',
71
72
  aiCli: options.aiCli || '',
72
73
  externalSkills: options.externalSkills ? options.externalSkills.split(',').map(s => s.trim()).filter(Boolean) : [],
@@ -202,6 +203,12 @@ export async function runScaffold(directory, options) {
202
203
  default: true,
203
204
  });
204
205
 
206
+ // 4.5. Playwright CLI (browser interaction support)
207
+ const playwrightCli = await confirm({
208
+ message: '安装浏览器交互工具 (playwright-cli)? 支持 UI 自动验证',
209
+ default: true,
210
+ });
211
+
205
212
  // 5. Rules 预设
206
213
  const rulesPreset = await select({
207
214
  message: '安装 AI 行为规则 (rules):',
@@ -227,6 +234,7 @@ export async function runScaffold(directory, options) {
227
234
  console.log(` 技能: ${chalk.cyan(suiteLabel)}`);
228
235
  console.log(` 团队模式: ${team ? chalk.green('启用') : chalk.gray('禁用')}`);
229
236
  console.log(` 流水线: ${pipeline ? chalk.green('启用') : chalk.gray('禁用')}`);
237
+ console.log(` 浏览器工具: ${playwrightCli ? chalk.green('启用') : chalk.gray('禁用')}`);
230
238
  console.log(` 规则: ${chalk.cyan(rulesPreset)}`);
231
239
  if (aiCli) console.log(` AI CLI: ${chalk.cyan(aiCli)}`);
232
240
  if (selectedExternalSkills.length > 0) console.log(` 外部技能: ${chalk.cyan(selectedExternalSkills.join(', '))}`);
@@ -249,6 +257,7 @@ export async function runScaffold(directory, options) {
249
257
  skills: skillSuite,
250
258
  team,
251
259
  pipeline,
260
+ playwrightCli,
252
261
  rules: rulesPreset,
253
262
  aiCli: aiCli || '',
254
263
  externalSkills: selectedExternalSkills,
package/src/scaffold.js CHANGED
@@ -10,9 +10,10 @@
10
10
  import chalk from 'chalk';
11
11
  import fs from 'fs-extra';
12
12
  import path from 'path';
13
- import { readFileSync } from 'fs';
13
+ import { readFileSync, existsSync } from 'fs';
14
14
  import { fileURLToPath } from 'url';
15
15
  import { dirname, join } from 'path';
16
+ import { execSync } from 'child_process';
16
17
  import {
17
18
  loadMetadata,
18
19
  getSkillsDir,
@@ -691,6 +692,46 @@ export async function installGitignore(projectRoot, options, dryRun) {
691
692
  }
692
693
  }
693
694
 
695
+ // ============================================================
696
+ // Playwright CLI 安装
697
+ // ============================================================
698
+
699
+ /**
700
+ * 安装 playwright-cli(全局 npm 包 + workspace 初始化)
701
+ * 用于浏览器交互验证(browser_interaction 功能)
702
+ */
703
+ export async function installPlaywrightCli(projectRoot, dryRun) {
704
+ if (dryRun) {
705
+ console.log(chalk.gray(' [dry-run] @playwright/cli (global install + workspace init)'));
706
+ return;
707
+ }
708
+
709
+ // Check if already installed
710
+ try {
711
+ execSync('playwright-cli --version', { stdio: 'pipe' });
712
+ console.log(chalk.green(' ✓ playwright-cli (already installed)'));
713
+ } catch {
714
+ // Not installed, install it
715
+ try {
716
+ console.log(chalk.blue(' ⏳ Installing @playwright/cli globally...'));
717
+ execSync('npm install -g @playwright/cli@latest', { stdio: 'pipe', timeout: 120000 });
718
+ console.log(chalk.green(' ✓ @playwright/cli installed globally'));
719
+ } catch (e) {
720
+ console.log(chalk.yellow(` ⚠ @playwright/cli install failed: ${e.message}`));
721
+ console.log(chalk.yellow(' ⚠ Run manually: npm install -g @playwright/cli@latest'));
722
+ return;
723
+ }
724
+ }
725
+
726
+ // Initialize workspace
727
+ try {
728
+ execSync('playwright-cli install', { cwd: projectRoot, stdio: 'pipe', timeout: 60000 });
729
+ console.log(chalk.green(' ✓ playwright-cli workspace initialized'));
730
+ } catch (e) {
731
+ console.log(chalk.yellow(` ⚠ playwright-cli workspace init skipped: ${e.message}`));
732
+ }
733
+ }
734
+
694
735
  // ============================================================
695
736
  // 主安装函数
696
737
  // ============================================================
@@ -705,11 +746,12 @@ export async function installGitignore(projectRoot, options, dryRun) {
705
746
  * @param {string} [config.rules] - Rules preset: 'recommended' | 'minimal' | 'none'
706
747
  * @param {string} [config.aiCli] - AI CLI 可执行命令(写入 .prizmkit/config.json)
707
748
  * @param {string[]} [config.externalSkills] - 要安装的外部 skill 名称列表
749
+ * @param {boolean} [config.playwrightCli] - 是否安装 playwright-cli
708
750
  * @param {string} config.projectRoot - 目标项目根目录
709
751
  * @param {boolean} config.dryRun - 是否为预览模式
710
752
  */
711
753
  export async function scaffold(config) {
712
- const { platform, skills, team, pipeline, rules, aiCli, externalSkills, projectRoot, dryRun } = config;
754
+ const { platform, skills, team, pipeline, rules, aiCli, externalSkills, playwrightCli, projectRoot, dryRun } = config;
713
755
  const platforms = platform === 'both' ? ['codebuddy', 'claude'] : [platform];
714
756
 
715
757
  if (dryRun) {
@@ -803,7 +845,14 @@ export async function scaffold(config) {
803
845
  }
804
846
  }
805
847
 
806
- // 10. Clean up stray .codebuddy/ directory left by third-party tools (e.g. npx skills)
848
+ // 10. Playwright CLI (optional)
849
+ if (playwrightCli) {
850
+ console.log(chalk.blue(' 浏览器交互工具:'));
851
+ await installPlaywrightCli(projectRoot, dryRun);
852
+ console.log('');
853
+ }
854
+
855
+ // 11. Clean up stray .codebuddy/ directory left by third-party tools (e.g. npx skills)
807
856
  // when installing for Claude Code only. CodeBuddy files should never appear in a
808
857
  // claude-only install.
809
858
  if (!dryRun && !platforms.includes('codebuddy')) {
@@ -890,6 +939,7 @@ export async function scaffold(config) {
890
939
  console.log(chalk.gray(` 平台: ${platforms.map(p => p === 'codebuddy' ? 'CodeBuddy' : 'Claude Code').join(' + ')}`));
891
940
  console.log(chalk.gray(` 团队: ${team ? '已启用' : '未启用'}`));
892
941
  console.log(chalk.gray(` 流水线: ${pipeline ? '已安装' : '未安装'}`));
942
+ console.log(chalk.gray(` 浏览器工具: ${playwrightCli ? '已安装 (playwright-cli)' : '未安装'}`));
893
943
  console.log(chalk.gray(` 规则: ${rules || 'recommended'}`));
894
944
  if (aiCli) console.log(chalk.gray(` AI CLI: ${aiCli}`));
895
945
  console.log('');