@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.
- package/README.md +19 -0
- package/bin/cli.js +92 -1
- 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;
|