@heihei0299/matt-skills 1.1.2 → 1.2.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 (3) hide show
  1. package/README.md +19 -0
  2. package/bin/cli.js +92 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -131,6 +131,25 @@ node bin/cli.js install [选项] # 把技能复制到目标工
131
131
  - `--global`:安装到全局目录(`~/.codex/skills`、`~/.pi/agent/skills`、`~/.config/opencode/skills`、`~/.claude/skills`);`--project` 回到项目级
132
132
  - `--all`:安装全部技能(默认交互勾选);`--force`:覆盖已存在的技能
133
133
 
134
+
135
+ ### 上游同步(自动更新)
136
+
137
+ 本仓库的 `.agents/skills/` 中 **非独有技能** 来自 `mattpocock/skills` 上游。已实现双通道自动同步:
138
+
139
+ - **本地 CLI**:`matt-skills check`(只读比对)与 `matt-skills update`(一键覆盖本地 `.agents/skills/`,自动处理 `writing-great-skills → writing-for-agents` 重命名与增删)
140
+ - **GitHub Actions**:`.github/workflows/sync-upstream.yml` 每周一 02:00 UTC 自动 `check`,有差异则 `apply` 并提 PR(`upstream-sync/<short-sha>`),支持 `workflow_dispatch` 手动触发(`ref`/`dry_run` 参数)
141
+
142
+ ```sh
143
+ npx @heihei0299/matt-skills check # 只读检查,对比本地 vs 上游 HEAD(有差异 exit 1)
144
+ npx @heihei0299/matt-skills check --json # JSON 输出:{ head, counts, result: { added, updated, renamed, removed, same } }
145
+ npx @heihei0299/matt-skills update --dry-run # 演练,不写文件
146
+ npx @heihei0299/matt-skills update # 覆盖 .agents/skills 非独有技能
147
+ node scripts/sync-upstream.js --check # 等价底层脚本(CLI check/update 的实现)
148
+ node scripts/sync-upstream.js --apply --dry-run
149
+ ```
150
+
151
+ 实现细节:`scripts/sync-upstream.js` 为单一事实源(CLI 与 Actions 共用),以 `config/proprietary.json` 为独有白名单,上游通过 `git clone --depth 1 https://github.com/mattpocock/skills.git` 获取,比对 `SKILL.md` 的 sha256,自动处理新增/更新/重命名/删除;Actions 提 PR 后需人工合入,合入后按“发布”节打 `v*` 标签即发布(自动 patch 发版可在后续扩展为 PR 合入后自动 bump)。
152
+ 上游重命名映射:`RENAMES = { "writing-great-skills": "writing-for-agents" }`,Actions/CLI 均会删除旧目录并复制新目录。
134
153
  ## 发布
135
154
 
136
155
  推送 `v*` 标签自动发布到 npm(GitHub Actions,见 `.github/workflows/publish.yml`):
package/bin/cli.js CHANGED
@@ -20,6 +20,10 @@ Usage:
20
20
  matt-skills sync [options] Sync existing project to latest template + skills
21
21
  matt-skills list [--json] List available skills and their descriptions
22
22
  matt-skills install [options] Install skills (interactive by default)
23
+ matt-skills check [--json] [--upstream <url>] [--ref <ref>]
24
+ Check if upstream skills are up to date (read-only)
25
+ matt-skills update [--dry-run] [--force] [--upstream <url>] [--ref <ref>]
26
+ Update .agents/skills from upstream (auto handles renames)
23
27
  matt-skills --help Show this help
24
28
 
25
29
  Init options:
@@ -28,6 +32,15 @@ Init options:
28
32
  Sync options:
29
33
  --dest <path> Target directory (default: current directory)
30
34
  --force Overwrite existing files
35
+ Check options:
36
+ --json Output as JSON
37
+ --upstream <url> Upstream repo URL (default: https://github.com/mattpocock/skills.git)
38
+ --ref <ref> Upstream ref (default: HEAD)
39
+ Update options:
40
+ --dry-run Show what would change without writing files
41
+ --force Force overwrite (default is to overwrite; kept for compatibility)
42
+ --upstream <url> Upstream repo URL
43
+ --ref <ref> Upstream ref
31
44
 
32
45
  Install options:
33
46
  --tools <a,b> Install for the given tools (codex, pi, opencode, claude); skips tool selection
@@ -291,6 +304,77 @@ function parseInstallArgs(args) {
291
304
  return { dest, all, force, global, tools };
292
305
  }
293
306
 
307
+ async function checkCommand(args) {
308
+ const { compare } = await import('../scripts/sync-upstream.js');
309
+ const json = args.includes('--json');
310
+ const upstreamIdx = args.indexOf('--upstream');
311
+ const upstreamUrl = upstreamIdx !== -1 ? args[upstreamIdx + 1] : undefined;
312
+ const refIdx = args.indexOf('--ref');
313
+ const ref = refIdx !== -1 ? args[refIdx + 1] : undefined;
314
+ const cmp = await compare({ upstreamUrl, ref });
315
+ if (json) {
316
+ process.stdout.write(JSON.stringify({ head: cmp.head, counts: cmp.counts, result: cmp.result }, null, 2) + '\n');
317
+ } else {
318
+ const lines = [];
319
+ lines.push(`上游 HEAD: ${cmp.head}`);
320
+ lines.push(`本地非独有: ${cmp.counts.local} 上游: ${cmp.counts.upstream}`);
321
+ lines.push('');
322
+ const totalDiff = cmp.result.added.length + cmp.result.updated.length + cmp.result.removed.length + cmp.result.renamed.length;
323
+ if (totalDiff === 0) {
324
+ lines.push('✅ 已是最新,无差异');
325
+ } else {
326
+ if (cmp.result.added.length) lines.push(`新增 (${cmp.result.added.length}): ${cmp.result.added.join(', ')}`);
327
+ if (cmp.result.updated.length) lines.push(`更新 (${cmp.result.updated.length}): ${cmp.result.updated.join(', ')}`);
328
+ if (cmp.result.renamed.length) lines.push(`重命名 (${cmp.result.renamed.length}): ${cmp.result.renamed.map((r) => `${r.from}→${r.to}`).join(', ')}`);
329
+ if (cmp.result.removed.length) lines.push(`删除 (${cmp.result.removed.length}): ${cmp.result.removed.join(', ')}`);
330
+ if (cmp.result.same.length) lines.push(`一致 (${cmp.result.same.length}): ${cmp.result.same.join(', ')}`);
331
+ }
332
+ process.stdout.write(lines.join('\n') + '\n');
333
+ }
334
+ const { rm } = await import('node:fs/promises');
335
+ await rm(cmp.dest, { recursive: true, force: true });
336
+ const hasDiff = cmp.result.added.length + cmp.result.updated.length + cmp.result.removed.length + cmp.result.renamed.length > 0;
337
+ if (hasDiff) process.exitCode = 1;
338
+ }
339
+
340
+ async function updateCommand(args) {
341
+ const { applySync } = await import('../scripts/sync-upstream.js');
342
+ const dryRun = args.includes('--dry-run');
343
+ const json = args.includes('--json');
344
+ const upstreamIdx = args.indexOf('--upstream');
345
+ const upstreamUrl = upstreamIdx !== -1 ? args[upstreamIdx + 1] : undefined;
346
+ const refIdx = args.indexOf('--ref');
347
+ const ref = refIdx !== -1 ? args[refIdx + 1] : undefined;
348
+ const res = await applySync({ upstreamUrl, ref, dryRun });
349
+ if (json) {
350
+ process.stdout.write(JSON.stringify({ head: res.head, result: res.result, actions: res.actions, dryRun }, null, 2) + '\n');
351
+ } else {
352
+ const lines = [];
353
+ lines.push(`上游 HEAD: ${res.head}`);
354
+ lines.push(`本地非独有: ${res.counts.local} 上游: ${res.counts.upstream}`);
355
+ lines.push('');
356
+ const totalDiff = res.result.added.length + res.result.updated.length + res.result.removed.length + res.result.renamed.length;
357
+ if (totalDiff === 0) {
358
+ lines.push('✅ 已是最新,无差异');
359
+ } else {
360
+ if (res.result.added.length) lines.push(`新增 (${res.result.added.length}): ${res.result.added.join(', ')}`);
361
+ if (res.result.updated.length) lines.push(`更新 (${res.result.updated.length}): ${res.result.updated.join(', ')}`);
362
+ if (res.result.renamed.length) lines.push(`重命名 (${res.result.renamed.length}): ${res.result.renamed.map((r) => `${r.from}→${r.to}`).join(', ')}`);
363
+ if (res.result.removed.length) lines.push(`删除 (${res.result.removed.length}): ${res.result.removed.join(', ')}`);
364
+ if (res.result.same.length) lines.push(`一致 (${res.result.same.length}): ${res.result.same.join(', ')}`);
365
+ }
366
+ process.stdout.write(lines.join('\n') + '\n');
367
+ if (res.actions.length) {
368
+ process.stdout.write(`\n已执行 ${res.actions.length} 项:\n`);
369
+ for (const a of res.actions) process.stdout.write(` - ${a}\n`);
370
+ }
371
+ if (dryRun) process.stdout.write('\n(dry-run,未写文件)\n');
372
+ }
373
+ if (!dryRun && res.actions.length) {
374
+ process.stdout.write('\n提示:请运行 npm test 验证,并按需执行 npm run build:template 更新模板镜像\n');
375
+ }
376
+ }
377
+
294
378
  async function main() {
295
379
  const args = process.argv.slice(2);
296
380
  if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
@@ -321,10 +405,17 @@ async function main() {
321
405
  await installCommand(parseInstallArgs(rest));
322
406
  return;
323
407
  }
408
+ if (command === 'check') {
409
+ await checkCommand(rest);
410
+ return;
411
+ }
412
+ if (command === 'update') {
413
+ await updateCommand(rest);
414
+ return;
415
+ }
324
416
  process.stderr.write(HELP);
325
417
  process.exitCode = 1;
326
418
  }
327
-
328
419
  main().catch((error) => {
329
420
  process.stderr.write(`error: ${error.message}\n`);
330
421
  process.exitCode = 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heihei0299/matt-skills",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "Agent skills + 项目配置模板:一条命令初始化 opencode / pi-agent 项目(含 mattpocock/skills 上游技能)",
5
5
  "type": "module",
6
6
  "bin": {