@topmindspace/topmind-writing-skills 0.7.1

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 (115) hide show
  1. package/CHANGELOG.md +931 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +124 -0
  4. package/README.md +124 -0
  5. package/bin/topmind-writing-skills.js +388 -0
  6. package/package.json +56 -0
  7. package/topmind-briefs/CHANGELOG.md +22 -0
  8. package/topmind-briefs/README.en.md +41 -0
  9. package/topmind-briefs/README.md +37 -0
  10. package/topmind-briefs/SKILL.md +101 -0
  11. package/topmind-briefs/dist/topmind-briefs.manifest.json +21 -0
  12. package/topmind-briefs/dist/topmind-briefs.zip +0 -0
  13. package/topmind-briefs/package-lock.json +13 -0
  14. package/topmind-briefs/package.json +1 -0
  15. package/topmind-briefs/references/examples.md +131 -0
  16. package/topmind-briefs/references/platform-specs.md +63 -0
  17. package/topmind-briefs/references/style-guide.md +60 -0
  18. package/topmind-briefs/scripts/audit_css.py +39 -0
  19. package/topmind-briefs/scripts/audit_docs.py +80 -0
  20. package/topmind-briefs/scripts/audit_skill.py +60 -0
  21. package/topmind-briefs/scripts/audit_styles.py +54 -0
  22. package/topmind-briefs/scripts/negative_tests.py +88 -0
  23. package/topmind-briefs/scripts/package_skill.py +188 -0
  24. package/topmind-cover/README.en.md +54 -0
  25. package/topmind-cover/README.md +54 -0
  26. package/topmind-cover/SKILL.md +134 -0
  27. package/topmind-cover/assets/examples/README.md +34 -0
  28. package/topmind-cover/assets/examples/bg-blur-alt.png +0 -0
  29. package/topmind-cover/assets/examples/bg-blur-wechat.png +0 -0
  30. package/topmind-cover/assets/examples/bg-blur.png +0 -0
  31. package/topmind-cover/assets/examples/big-type-wechat.png +0 -0
  32. package/topmind-cover/assets/examples/big-type.png +0 -0
  33. package/topmind-cover/assets/examples/brand-launch-wechat.png +0 -0
  34. package/topmind-cover/assets/examples/brand-launch.png +0 -0
  35. package/topmind-cover/assets/examples/gan-huo-wechat.png +0 -0
  36. package/topmind-cover/assets/examples/gan-huo.png +0 -0
  37. package/topmind-cover/assets/examples/ip-fun-wechat.png +0 -0
  38. package/topmind-cover/assets/examples/ip-fun.png +0 -0
  39. package/topmind-cover/assets/examples/magazine-wechat.png +0 -0
  40. package/topmind-cover/assets/examples/magazine.png +0 -0
  41. package/topmind-cover/assets/examples/minimal-wechat.png +0 -0
  42. package/topmind-cover/assets/examples/minimal.png +0 -0
  43. package/topmind-cover/assets/examples/news-flash-wechat.png +0 -0
  44. package/topmind-cover/assets/examples/news-flash.png +0 -0
  45. package/topmind-cover/assets/examples/overview.png +0 -0
  46. package/topmind-cover/assets/examples/paper-collage-alt.png +0 -0
  47. package/topmind-cover/assets/examples/paper-collage-wechat.png +0 -0
  48. package/topmind-cover/assets/examples/paper-collage.png +0 -0
  49. package/topmind-cover/assets/examples/tutorial-steps-wechat.png +0 -0
  50. package/topmind-cover/assets/examples/tutorial-steps.png +0 -0
  51. package/topmind-cover/assets/examples/white-clean-alt-wechat.png +0 -0
  52. package/topmind-cover/assets/examples/white-clean-alt.png +0 -0
  53. package/topmind-cover/assets/examples/white-clean-wechat.png +0 -0
  54. package/topmind-cover/assets/examples/white-clean.png +0 -0
  55. package/topmind-cover/package-lock.json +16 -0
  56. package/topmind-cover/package.json +16 -0
  57. package/topmind-cover/references/cover-prompts.md +436 -0
  58. package/topmind-cover/references/cover-styles.md +496 -0
  59. package/topmind-cover/references/impact-language.md +94 -0
  60. package/topmind-cover/references/ref-white-clean-1.png +0 -0
  61. package/topmind-cover/references/ref-white-clean-2.png +0 -0
  62. package/topmind-cover/scripts/audit_css.py +39 -0
  63. package/topmind-cover/scripts/audit_docs.py +80 -0
  64. package/topmind-cover/scripts/audit_skill.py +60 -0
  65. package/topmind-cover/scripts/audit_styles.py +54 -0
  66. package/topmind-cover/scripts/crop-cover.py +74 -0
  67. package/topmind-cover/scripts/negative_tests.py +71 -0
  68. package/topmind-cover/scripts/package_skill.py +188 -0
  69. package/topmind-wechat-post/README.en.md +111 -0
  70. package/topmind-wechat-post/README.md +111 -0
  71. package/topmind-wechat-post/SKILL.md +251 -0
  72. package/topmind-wechat-post/assets/themes/amber-review.json +35 -0
  73. package/topmind-wechat-post/assets/themes/graphite.json +35 -0
  74. package/topmind-wechat-post/assets/themes/minimal-ink.json +35 -0
  75. package/topmind-wechat-post/assets/themes/newsprint.json +35 -0
  76. package/topmind-wechat-post/assets/themes/tech-blue.json +35 -0
  77. package/topmind-wechat-post/package-lock.json +16 -0
  78. package/topmind-wechat-post/package.json +16 -0
  79. package/topmind-wechat-post/references/design.md +50 -0
  80. package/topmind-wechat-post/references/element-spec.md +163 -0
  81. package/topmind-wechat-post/references/known-pits.md +85 -0
  82. package/topmind-wechat-post/references/theme-map.md +139 -0
  83. package/topmind-wechat-post/references/typography-rules.md +62 -0
  84. package/topmind-wechat-post/references/wechat-constraints.md +111 -0
  85. package/topmind-wechat-post/references/workflow.md +102 -0
  86. package/topmind-wechat-post/references/writing-quality.md +105 -0
  87. package/topmind-wechat-post/scripts/audit_css.py +39 -0
  88. package/topmind-wechat-post/scripts/audit_docs.py +80 -0
  89. package/topmind-wechat-post/scripts/audit_skill.py +60 -0
  90. package/topmind-wechat-post/scripts/audit_styles.py +54 -0
  91. package/topmind-wechat-post/scripts/lint-wechat.py +446 -0
  92. package/topmind-wechat-post/scripts/md2wechat.py +1792 -0
  93. package/topmind-wechat-post/scripts/negative_tests.py +171 -0
  94. package/topmind-wechat-post/scripts/new-article.py +183 -0
  95. package/topmind-wechat-post/scripts/package_skill.py +188 -0
  96. package/topmind-wechat-post/scripts/push-to-topstream.py +226 -0
  97. package/topmind-wechat-post/scripts/scan_ai_flavor.py +464 -0
  98. package/topmind-wechat-post/scripts/sync-mapping.py +220 -0
  99. package/topmind-wechat-post/scripts/sync-status.py +187 -0
  100. package/topmind-x-article/README.en.md +109 -0
  101. package/topmind-x-article/README.md +104 -0
  102. package/topmind-x-article/SKILL.md +156 -0
  103. package/topmind-x-article/package-lock.json +16 -0
  104. package/topmind-x-article/package.json +16 -0
  105. package/topmind-x-article/references/publish-checklist.md +31 -0
  106. package/topmind-x-article/references/x-format.md +27 -0
  107. package/topmind-x-article/references/x-html-format.md +85 -0
  108. package/topmind-x-article/scripts/audit_css.py +39 -0
  109. package/topmind-x-article/scripts/audit_docs.py +80 -0
  110. package/topmind-x-article/scripts/audit_skill.py +60 -0
  111. package/topmind-x-article/scripts/audit_styles.py +54 -0
  112. package/topmind-x-article/scripts/md2x-html.py +522 -0
  113. package/topmind-x-article/scripts/md2x.py +323 -0
  114. package/topmind-x-article/scripts/negative_tests.py +91 -0
  115. package/topmind-x-article/scripts/package_skill.py +188 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TopMindspace
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md ADDED
@@ -0,0 +1,124 @@
1
+ # topmind-writing-skills
2
+
3
+ **[中文](./README.md)** | English
4
+
5
+ > Language note: **Chinese is the default** (`README.md`). This file is the full English parallel.
6
+
7
+ [![Release](https://img.shields.io/github/v/release/topmindspace/topmind-writing-skills?style=flat-square&color=blue)](https://github.com/topmindspace/topmind-writing-skills/releases)
8
+ [![npm](https://img.shields.io/npm/v/@topmindspace/topmind-writing-skills?style=flat-square)](https://www.npmjs.com/package/@topmindspace/topmind-writing-skills)
9
+ [![CI](https://img.shields.io/github/actions/workflow/status/topmindspace/topmind-writing-skills/ci.yml?style=flat-square&label=CI)](https://github.com/topmindspace/topmind-writing-skills/actions/workflows/ci.yml)
10
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?style=flat-square)](LICENSE)
11
+
12
+ **TopMindSpace writing-skills collection** — WeChat articles, X long-form posts, short-form briefs, cover art: four skills, one repo.
13
+
14
+ <p align="center">
15
+ <img src="docs/assets/writing-cover-white.png" alt="topmind-writing-skills · writing skills collection" width="960" />
16
+ </p>
17
+
18
+ > The formal business presentation skill now lives on its own: [`topmind-presentation`](https://github.com/topmindspace/topmind-presentation) (repo `topmind-presentation`; the repo is the skill — clone and copy the root into your skills directory).
19
+
20
+ ### topmind-wechat-post · WeChat article authoring
21
+
22
+ Full lifecycle for WeChat articles: delivery package, review & rewrite, three quality gates (facts / logic / de-AI-flavor), WeChat inline typography (images must be embedded), publish checklist and status sync. Python stdlib only — zero dependencies.
23
+
24
+ ```bash
25
+ npx @topmindspace/topmind-writing-skills install topmind-wechat-post
26
+ ```
27
+
28
+ ### topmind-x-article · One-click X long-form publishing
29
+
30
+ Markdown draft → paste-ready plain text (`md2x.py` adapts to the X Article editor: images → `[图N]`, tables → "item: value" lists) + cover art + publish checklist. Publishing is done by hand-pasting; fetch the text back for verification after posting.
31
+
32
+ ```bash
33
+ npx @topmindspace/topmind-writing-skills install topmind-x-article
34
+ ```
35
+
36
+ ### topmind-briefs · Short-form briefs
37
+
38
+ Short-form briefs for WeChat + X: one topic, one chart (data rankings / paper one-liners / product alerts / mechanism explainers). One X post or short thread, WeChat ~300–800 words, 1 key visual. One-click-copy HTML for both, draft only — never auto-post.
39
+
40
+ ```bash
41
+ npx @topmindspace/topmind-writing-skills install topmind-briefs
42
+ ```
43
+
44
+ ### topmind-cover · Cover art generation
45
+
46
+ Shared cover art for X long-form and WeChat: striking, eye-catching, theme-focused. **Pick a style → check the example → compose the prompt from the recipe**, then one-click crop to both platform sizes with `crop-cover.py` (X 1200×675, WeChat 900×383). 11-style index and recipes: [`references/cover-styles.md`](./topmind-cover/references/cover-styles.md).
47
+
48
+ ```bash
49
+ npx @topmindspace/topmind-writing-skills install topmind-cover
50
+ ```
51
+
52
+ <p align="center">
53
+ <img src="https://github.com/topmindspace/topmind-writing-skills/raw/main/topmind-cover/assets/examples/overview.png" alt="topmind-cover · 11 cover-style overview" width="960" />
54
+ </p>
55
+
56
+ > Let ideas fly — make good thinking visible.
57
+
58
+ > **Rename note**: this repo was renamed from `tms-skills` to `topmind-writing-skills`;
59
+ > the npm package is `@topmindspace/topmind-writing-skills` and the old package is no longer updated.
60
+ > Also do not install the old package's `^2` (2.0.0–2.1.1 deprecated).
61
+
62
+ ## Skills
63
+
64
+ | Skill | Version | What it does |
65
+ |-------|---------|--------------|
66
+ | [`topmind-wechat-post`](./topmind-wechat-post/) | **0.2.0** | WeChat article lifecycle: package, review, 3 quality gates, inline typography & publish checklist |
67
+ | [`topmind-x-article`](./topmind-x-article/) | **0.4.0** | X long-form one-click publish: Markdown → one-click-copy HTML (images/prompt copy, click-to-zoom images) / plain-text fallback + cover + checklist |
68
+ | [`topmind-cover`](./topmind-cover/) | **0.3.0** | Cover art for X / WeChat (X master 1500×600 / 5:2): striking, theme-focused; 11-style cover style library + 26 example images (incl. 3 alt samples) + 1 style overview |
69
+ | [`topmind-briefs`](./topmind-briefs/) | **0.2.1** | Short-form briefs (WeChat + X): one topic, one chart; data rankings / paper one-liners / product alerts / mechanism explainers; one-click-copy HTML for both |
70
+
71
+ Installer [`@topmindspace/topmind-writing-skills`](https://www.npmjs.com/package/@topmindspace/topmind-writing-skills) is **0.7.0** (whole-repo same-tag releases).
72
+
73
+ ## Install
74
+
75
+ **npm = pinned snapshot**; **GitHub = track repo HEAD**.
76
+
77
+ ```bash
78
+ npx @topmindspace/topmind-writing-skills list
79
+ npx @topmindspace/topmind-writing-skills install topmind-briefs
80
+ npx @topmindspace/topmind-writing-skills install topmind-briefs --to ./.claude/skills
81
+ npx @topmindspace/topmind-writing-skills@0.7.1 install topmind-briefs # pin
82
+ npx @topmindspace/topmind-writing-skills uninstall topmind-briefs --to ./.claude/skills
83
+ ```
84
+
85
+ ```bash
86
+ # track HEAD
87
+ npx github:topmindspace/topmind-writing-skills install topmind-briefs
88
+ ```
89
+
90
+ Default probe order: `./.agents` → `./.claude` → `./.cursor` → `./.codex` → `./.mimocode`, then user-level `~/.claude`, etc. Or pass `--to`. When `--to` is omitted, the install summary explicitly prints the auto-detected target directory. Do not `npm install topmind-briefs` (skill id is not a standalone package).
91
+
92
+ `install` details: if a same-named skill already exists, the installed version is reported and overwriting is refused — pass `--force` to replace (or `uninstall` first). On success, a three-line summary is printed: where it was installed / installed skill version and installer version / next steps.
93
+
94
+ ## Repo layout
95
+
96
+ ```
97
+ topmind-writing-skills/
98
+ ├─ topmind-briefs/ # skill (SKILL.md + assets + references + scripts)
99
+ ├─ topmind-cover/ # skill
100
+ ├─ topmind-wechat-post/ # skill
101
+ ├─ topmind-x-article/ # skill
102
+ ├─ bin/topmind-writing-skills.js # CLI: list / install / uninstall
103
+ ├─ docs/ # publishing guide · CI notes (history archived in docs/archive)
104
+ ├─ scripts/ # repo gates / privacy scan
105
+ ├─ package.json # @topmindspace/topmind-writing-skills
106
+ └─ LICENSE · CHANGELOG.md · README.md · README.en.md
107
+ ```
108
+
109
+ ## Release & CI
110
+
111
+ | Action | GitHub | npm |
112
+ |--------|:------:|:---:|
113
+ | push main | live | **unchanged** |
114
+ | tag `vX.Y.Z` | Release + zip | **auto publish** |
115
+
116
+ ```bash
117
+ npm run check && npm run audit && npm run privacy
118
+ ```
119
+
120
+ See [docs/PUBLISHING.md](./docs/PUBLISHING.md) · [docs/ci.md](./docs/ci.md).
121
+
122
+ ## License
123
+
124
+ MIT © TopMindspace — [LICENSE](./LICENSE)
package/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # topmind-writing-skills
2
+
3
+ [English](./README.en.md) | 中文
4
+
5
+ [![Release](https://img.shields.io/github/v/release/topmindspace/topmind-writing-skills?style=flat-square&color=blue)](https://github.com/topmindspace/topmind-writing-skills/releases)
6
+ [![npm](https://img.shields.io/npm/v/@topmindspace/topmind-writing-skills?style=flat-square)](https://www.npmjs.com/package/@topmindspace/topmind-writing-skills)
7
+ [![CI](https://img.shields.io/github/actions/workflow/status/topmindspace/topmind-writing-skills/ci.yml?style=flat-square&label=CI)](https://github.com/topmindspace/topmind-writing-skills/actions/workflows/ci.yml)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?style=flat-square)](LICENSE)
9
+
10
+ **TopMindSpace 写作技能合集** —— 公众号文章、X 长文、干货短文、封面配图:四个技能,一个仓库。
11
+
12
+ <p align="center">
13
+ <img src="docs/assets/writing-cover-white.png" alt="topmind-writing-skills · 写作技能合集" width="960" />
14
+ </p>
15
+
16
+ > 正式商务演示技能已独立出去:[`topmind-presentation`](https://github.com/topmindspace/topmind-presentation)(仓库 `topmind-presentation`,仓库即技能,clone 后复制根目录到技能目录即可)。
17
+
18
+ ### topmind-wechat-post · 公众号创作
19
+
20
+ 公众号文章全生命周期:交付包、审校改写、质量三关(事实 / 逻辑 / 去 AI 味)、微信内联排版(图片必内嵌)、发布清单与状态同步。脚本纯 Python 标准库,零依赖。
21
+
22
+ ```bash
23
+ npx @topmindspace/topmind-writing-skills install topmind-wechat-post
24
+ ```
25
+
26
+ ### topmind-x-article · X 长文一键发布
27
+
28
+ Markdown 原稿 → 可直接粘贴的纯文本(`md2x.py` 按 X Article 编辑器支持转制:图片→`[图N]`、表格→"项:值"列表)+ 封面图 + 发布清单。发布走人工粘贴,发布后抓回核对。
29
+
30
+ ```bash
31
+ npx @topmindspace/topmind-writing-skills install topmind-x-article
32
+ ```
33
+
34
+ ### topmind-briefs · 干货短文
35
+
36
+ 公众号 + X 双平台干货短文:一件事讲透(数据榜单 / 论文一句话解读 / 新品速递 / 机制讲解),X 一帖或短 thread、公众号约 300–800 字,1 张核心图。双版一键复制 HTML,只起草不代发。
37
+
38
+ ```bash
39
+ npx @topmindspace/topmind-writing-skills install topmind-briefs
40
+ ```
41
+
42
+ ### topmind-cover · 封面配图生成
43
+
44
+ X 长文与公众号共用的封面图:震撼、醒目、主题突出。**选风格 → 看示例 → 按配方组 prompt** 三步出图,`crop-cover.py` 一键裁出双平台尺寸(X 1200×675、公众号 900×383)。11 种风格索引与配方见 [`references/cover-styles.md`](./topmind-cover/references/cover-styles.md)。
45
+
46
+ ```bash
47
+ npx @topmindspace/topmind-writing-skills install topmind-cover
48
+ ```
49
+
50
+ <p align="center">
51
+ <img src="https://github.com/topmindspace/topmind-writing-skills/raw/main/topmind-cover/assets/examples/overview.png" alt="topmind-cover · 11 封面风格总览" width="960" />
52
+ </p>
53
+
54
+ > 让 idea 飞,好想法被看见。
55
+
56
+ > **改名说明**:本仓由 `tms-skills` 改名为 `topmind-writing-skills`,
57
+ > npm 包为 `@topmindspace/topmind-writing-skills`,旧包不再更新。
58
+ > 也不要安装旧包的 `^2`(2.0.0–2.1.1 已弃用)。
59
+
60
+ ## 技能一览
61
+
62
+ | 技能 | 版本 | 做什么 |
63
+ |------|------|--------|
64
+ | [`topmind-wechat-post`](./topmind-wechat-post/) | **0.2.0** | 公众号文章全生命周期:交付包、审校改写、质量三关、微信内联排版与发布清单 |
65
+ | [`topmind-x-article`](./topmind-x-article/) | **0.4.0** | X 长文一键发布:Markdown 原稿 → 一键复制 HTML(含配图/提示词复制、图片点击放大)/ 纯文本兜底 + 封面图 + 发布清单 |
66
+ | [`topmind-cover`](./topmind-cover/) | **0.3.0** | 文章封面配图(X / 公众号共用,X 主尺寸 1500×600 / 5:2):震撼醒目主题突出;11 风格封面风格库 + 26 张示例图(含 3 张 alt 样张) + 1 张风格总览图 |
67
+ | [`topmind-briefs`](./topmind-briefs/) | **0.2.1** | 干货短文(公众号 + X 双平台):一件事讲透,数据榜单/论文解读/新品速递;双版一键复制 HTML |
68
+
69
+ 安装器 [`@topmindspace/topmind-writing-skills`](https://www.npmjs.com/package/@topmindspace/topmind-writing-skills) 为 **0.7.0**(整仓同 tag 发版)。
70
+
71
+ > SKILL.md frontmatter 除标准 `name`/`description` 外,本仓库扩展了 `action_category` / `triggers` / `triggers_cn` / `updated` 字段供安装器与路由使用。
72
+
73
+ ## 安装
74
+
75
+ **npm = 钉版本快照**;**GitHub = 跟仓库 HEAD**。
76
+
77
+ ```bash
78
+ npx @topmindspace/topmind-writing-skills list
79
+ npx @topmindspace/topmind-writing-skills install topmind-briefs
80
+ npx @topmindspace/topmind-writing-skills install topmind-briefs --to ./.claude/skills
81
+ npx @topmindspace/topmind-writing-skills@0.7.1 install topmind-briefs # 钉版本
82
+ npx @topmindspace/topmind-writing-skills uninstall topmind-briefs --to ./.claude/skills
83
+ ```
84
+
85
+ ```bash
86
+ # 跟 HEAD
87
+ npx github:topmindspace/topmind-writing-skills install topmind-briefs
88
+ ```
89
+
90
+ 默认探测:`./.agents` → `./.claude` → `./.cursor` → `./.codex` → `./.mimocode`,再用户级 `~/.claude` 等;也可用 `--to`。未给 `--to` 时安装摘要会明确打印探测到的目标目录。不要 `npm install topmind-briefs`(技能 id 不是独立包)。
91
+
92
+ `install` 细节:若同名技能已存在,会报出已装版本并拒绝覆盖,需加 `--force` 才替换(或先 `uninstall` 再装)。安装成功后打印三行摘要:装到哪里 / 装的技能版本与安装器版本 / 下一步。
93
+
94
+ ## 仓库结构
95
+
96
+ ```
97
+ topmind-writing-skills/
98
+ ├─ topmind-briefs/ # 技能(SKILL.md + assets + references + scripts)
99
+ ├─ topmind-cover/ # 技能
100
+ ├─ topmind-wechat-post/ # 技能
101
+ ├─ topmind-x-article/ # 技能
102
+ ├─ bin/topmind-writing-skills.js # CLI:list / install / uninstall
103
+ ├─ docs/ # 发布规范 · CI 说明(历史审计归档在 docs/archive)
104
+ ├─ scripts/ # 仓库级门禁 / 隐私扫描
105
+ ├─ package.json # @topmindspace/topmind-writing-skills
106
+ └─ LICENSE · CHANGELOG.md · README.md · README.en.md
107
+ ```
108
+
109
+ ## 发布与 CI
110
+
111
+ | 动作 | GitHub | npm |
112
+ |------|:------:|:---:|
113
+ | push main | 立即可见 | **不变** |
114
+ | tag `vX.Y.Z` | Release + zip | **自动 publish** |
115
+
116
+ ```bash
117
+ npm run check && npm run audit && npm run privacy
118
+ ```
119
+
120
+ 详见 [docs/PUBLISHING.md](./docs/PUBLISHING.md) · [docs/ci.md](./docs/ci.md)。
121
+
122
+ ## 许可证
123
+
124
+ MIT © TopMindspace — [LICENSE](./LICENSE)
@@ -0,0 +1,388 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * topmind-writing-skills — TopMindspace agent skills installer
4
+ *
5
+ * Recommended channel (when published): npm @topmindspace/topmind-writing-skills
6
+ * GitHub npx remains available for HEAD / offline clone.
7
+ *
8
+ * Usage:
9
+ * npx @topmindspace/topmind-writing-skills list
10
+ * npx @topmindspace/topmind-writing-skills install topmind-briefs
11
+ * npx @topmindspace/topmind-writing-skills install topmind-briefs --to ./skills-out
12
+ * npx @topmindspace/topmind-writing-skills install topmind-briefs --force
13
+ * npx @topmindspace/topmind-writing-skills uninstall topmind-briefs --to ./skills-out
14
+ * npx github:topmindspace/topmind-writing-skills install topmind-briefs # follow repo HEAD
15
+ */
16
+ 'use strict';
17
+
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+
21
+ const ROOT = path.resolve(__dirname, '..');
22
+ // Skills live at repo root (one directory per skill with SKILL.md).
23
+ const INFRA_DIRS = new Set(['bin', 'docs', 'scripts', 'node_modules', 'dist', 'release-assets']);
24
+ const SKILL_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
25
+
26
+ function die(msg, code = 1) {
27
+ console.error(msg);
28
+ process.exit(code);
29
+ }
30
+
31
+ function skillDir(skillId) {
32
+ return path.join(ROOT, skillId);
33
+ }
34
+
35
+ function assertSkillId(skillId) {
36
+ if (!skillId || typeof skillId !== 'string') {
37
+ die('Missing skill id.\n\n' + usageText());
38
+ }
39
+ if (
40
+ skillId.includes('..') ||
41
+ skillId.includes('/') ||
42
+ skillId.includes('\\') ||
43
+ path.isAbsolute(skillId) ||
44
+ !SKILL_ID_RE.test(skillId)
45
+ ) {
46
+ die(
47
+ `Invalid skill id: ${JSON.stringify(skillId)}\n` +
48
+ 'Skill ids must match ^[a-z0-9]+(?:-[a-z0-9]+)*$ (no paths, dots, or slashes).'
49
+ );
50
+ }
51
+ }
52
+
53
+ function listSkillIds() {
54
+ return fs
55
+ .readdirSync(ROOT, { withFileTypes: true })
56
+ .filter((d) => d.isDirectory() && !INFRA_DIRS.has(d.name) && !d.name.startsWith('.'))
57
+ .filter((d) => fs.existsSync(path.join(ROOT, d.name, 'SKILL.md')))
58
+ .map((d) => d.name)
59
+ .sort();
60
+ }
61
+
62
+ function readSkillMeta(skillId) {
63
+ const skillPath = skillDir(skillId);
64
+ const skillMd = path.join(skillPath, 'SKILL.md');
65
+ if (!fs.existsSync(skillMd)) return { id: skillId, description: '' };
66
+ const text = fs.readFileSync(skillMd, 'utf8');
67
+ const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
68
+ if (!m) return { id: skillId, description: '' };
69
+ const fm = m[1];
70
+ let desc = '';
71
+ // 块标量:description: >- / > / | / |- 后跟缩进行
72
+ const block = fm.match(/^description:\s*[>|][-+]?\s*$/m);
73
+ if (block) {
74
+ const lines = [];
75
+ // slice 起点是 description 行尾,split 后首元素恒为空,直接丢掉
76
+ const rest = fm.slice(block.index + block[0].length).split(/\r?\n/).slice(1);
77
+ for (const ln of rest) {
78
+ if (/^[ \t]+\S/.test(ln)) lines.push(ln.trim()); // 缩进的内容行
79
+ else break; // 空行或顶格行 → 块结束
80
+ }
81
+ const folded = /^description:\s*>/.test(block[0]);
82
+ desc = folded ? lines.join(' ') : lines.join('\n');
83
+ } else {
84
+ desc = ((fm.match(/^description:\s*"(.*)"\s*$/m) || fm.match(/^description:\s*(.+)\s*$/m) || [])[1] || '');
85
+ }
86
+ return { id: skillId, description: String(desc).slice(0, 120) };
87
+ }
88
+
89
+ /** 读技能目录下 SKILL.md frontmatter 的 version 字段(读不到返回 '',调用方兜底)。 */
90
+ function readSkillVersion(skillPath) {
91
+ try {
92
+ const p = path.join(skillPath, 'SKILL.md');
93
+ if (!fs.existsSync(p)) return '';
94
+ const text = fs.readFileSync(p, 'utf8');
95
+ const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
96
+ if (!m) return '';
97
+ const v = m[1].match(/^version:\s*(.+?)\s*$/m);
98
+ return v ? v[1] : '';
99
+ } catch {
100
+ return '';
101
+ }
102
+ }
103
+
104
+ function installerVersion() {
105
+ try {
106
+ const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
107
+ return pkg.version || 'unknown';
108
+ } catch {
109
+ return 'unknown';
110
+ }
111
+ }
112
+
113
+ function resolveDefaultTarget() {
114
+ const home = process.env.HOME || process.env.USERPROFILE || '';
115
+ const candidates = [
116
+ path.join(process.cwd(), '.agents', 'skills'),
117
+ path.join(process.cwd(), '.claude', 'skills'),
118
+ path.join(process.cwd(), '.cursor', 'skills'),
119
+ path.join(process.cwd(), '.codex', 'skills'),
120
+ path.join(process.cwd(), '.mimocode', 'skills'),
121
+ path.join(home, '.claude', 'skills'),
122
+ path.join(home, '.agents', 'skills'),
123
+ path.join(home, '.cursor', 'skills'),
124
+ path.join(home, '.codex', 'skills'),
125
+ ];
126
+ for (const dir of candidates) {
127
+ if (dir && fs.existsSync(path.dirname(dir))) {
128
+ return dir;
129
+ }
130
+ }
131
+ return path.join(process.cwd(), '.agents', 'skills');
132
+ }
133
+
134
+ function copyDir(src, dest) {
135
+ fs.mkdirSync(dest, { recursive: true });
136
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
137
+ const s = path.join(src, entry.name);
138
+ const d = path.join(dest, entry.name);
139
+ if (entry.isDirectory()) {
140
+ if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === '__pycache__') continue;
141
+ copyDir(s, d);
142
+ } else if (entry.isFile()) {
143
+ if (entry.name.endsWith('.pyc')) continue;
144
+ fs.copyFileSync(s, d);
145
+ } else {
146
+ console.warn(`warning: skipping non-regular file: ${s}`);
147
+ }
148
+ }
149
+ }
150
+
151
+ function normPath(p) {
152
+ const s = path.resolve(p);
153
+ return process.platform === 'win32' ? s.toLowerCase() : s;
154
+ }
155
+
156
+ /** realpath 能解析就解析(跟符号链接),失败回退 path.resolve。
157
+ * dest 可能还不存在(install 流程):往上找最深存在的祖先解析后再拼回。
158
+ * 否则 --to 经符号链接指向包内时,词法比对会被绕过(rmSync 会跟链接删掉源)。 */
159
+ function realOrResolved(p) {
160
+ let cur = path.resolve(p);
161
+ const tail = [];
162
+ for (;;) {
163
+ try {
164
+ return path.join(fs.realpathSync(cur), ...tail);
165
+ } catch {
166
+ const parent = path.dirname(cur);
167
+ if (parent === cur) return path.join(cur, ...tail);
168
+ tail.unshift(path.basename(cur));
169
+ cur = parent;
170
+ }
171
+ }
172
+ }
173
+
174
+ /** 安装目标守卫(审计 严重-2):
175
+ * dest == 技能源目录 → --force 会先 rmSync 删掉源目录,再"成功"装出空目录(数据丢失 + 虚假成功);
176
+ * dest 在源目录内部 → copyDir 无限递归复制直至 ENAMETOOLONG,并留下垃圾目录树。
177
+ * realpath 归一化后比对(dest === src,或 dest 以 src + 路径分隔符开头),
178
+ * 防止 --to 经符号链接指向包内绕过词法比对;
179
+ * Windows 下额外做大小写归一。无论是否 --force,一律拒绝。
180
+ */
181
+ function assertDestOutsideSource(src, dest) {
182
+ const absSrc = normPath(realOrResolved(src));
183
+ const absDest = normPath(realOrResolved(dest));
184
+ if (absDest === absSrc || absDest.startsWith(absSrc + path.sep)) {
185
+ die(
186
+ `Refusing to install into the skill's own directory tree.\n` +
187
+ ` skill source: ${absSrc}\n` +
188
+ ` destination: ${absDest}\n` +
189
+ `Choose a --to directory outside the skill source ` +
190
+ `(installing into it would delete or recurse into the source).`
191
+ );
192
+ }
193
+ }
194
+
195
+ function install(skillId, targetRoot, { force = false, defaultTarget = false } = {}) {
196
+ assertSkillId(skillId);
197
+ const src = skillDir(skillId);
198
+ if (!fs.existsSync(path.join(src, 'SKILL.md'))) {
199
+ die(`Skill not found: ${skillId}\nAvailable: ${listSkillIds().join(', ') || '(none)'}`);
200
+ }
201
+ const dest = path.join(targetRoot, skillId);
202
+ assertDestOutsideSource(src, dest);
203
+ try {
204
+ fs.mkdirSync(targetRoot, { recursive: true });
205
+ } catch (e) {
206
+ die(
207
+ `Cannot create install directory: ${targetRoot}\n` +
208
+ ` reason: ${e.message}\n` +
209
+ 'Choose a --to directory that can be created (not a file, not unwritable).'
210
+ );
211
+ }
212
+ if (fs.existsSync(dest)) {
213
+ if (!force) {
214
+ // 已装过:报出已装版本,要求 --force 才覆盖(不静默覆盖)。
215
+ const installedVer = readSkillVersion(dest) || 'unknown';
216
+ const packageVer = readSkillVersion(src) || 'unknown';
217
+ die(
218
+ `Already installed: ${dest}\n` +
219
+ ` installed version: ${installedVer} (this package ships ${packageVer})\n` +
220
+ 'Refusing to overwrite. Pass --force (or -f) to replace, or remove it first:\n' +
221
+ ` topmind-writing-skills uninstall ${skillId} --to ${targetRoot}`
222
+ );
223
+ }
224
+ fs.rmSync(dest, { recursive: true, force: true });
225
+ }
226
+ copyDir(src, dest);
227
+ // 安装摘要三行:装到哪里 / 装了什么版本 / 下一步。
228
+ const toNote = defaultTarget ? ' (default, auto-detected — no --to given)' : '';
229
+ const skillVer = readSkillVersion(dest) || 'unknown';
230
+ console.log(`Installed ${skillId}`);
231
+ console.log(` to: ${dest}${toNote}`);
232
+ console.log(` version: skill ${skillVer} (installer ${installerVersion()})`);
233
+ console.log(` next: ${nextStep(skillId, dest)}`);
234
+ }
235
+
236
+ function nextStep(skillId, dest) {
237
+ return 'restart your agent so it picks up the skill';
238
+ }
239
+
240
+ /** 列出目标根目录下已安装的技能(有 SKILL.md 的子目录)。 */
241
+ function listInstalledIds(targetRoot) {
242
+ try {
243
+ return fs
244
+ .readdirSync(targetRoot, { withFileTypes: true })
245
+ .filter((d) => d.isDirectory() && fs.existsSync(path.join(targetRoot, d.name, 'SKILL.md')))
246
+ .map((d) => d.name)
247
+ .sort();
248
+ } catch {
249
+ return [];
250
+ }
251
+ }
252
+
253
+ function uninstall(skillId, targetRoot) {
254
+ assertSkillId(skillId);
255
+ const src = skillDir(skillId);
256
+ const dest = path.join(targetRoot, skillId);
257
+ // 守卫:--to 指到包内时,dest 会命中安装器自带的技能源目录 → 一律拒绝,
258
+ // 防止把仓库自身的技能目录删掉(同 install 的 assertDestOutsideSource)。
259
+ if (fs.existsSync(path.join(src, 'SKILL.md'))) {
260
+ assertDestOutsideSource(src, dest);
261
+ }
262
+ if (!fs.existsSync(dest)) {
263
+ const installed = listInstalledIds(targetRoot);
264
+ die(
265
+ `Nothing to uninstall: ${dest}\n` +
266
+ ` skill '${skillId}' is not installed under ${targetRoot}.` +
267
+ (installed.length ? `\n installed here: ${installed.join(', ')}` : '')
268
+ );
269
+ }
270
+ if (!fs.existsSync(path.join(dest, 'SKILL.md'))) {
271
+ die(
272
+ `Refusing to uninstall: ${dest}\n` +
273
+ ` not a skill directory (missing SKILL.md) — will not delete arbitrary folders.`
274
+ );
275
+ }
276
+ fs.rmSync(dest, { recursive: true, force: true });
277
+ console.log(`Uninstalled ${skillId}`);
278
+ console.log(` from ${dest}`);
279
+ }
280
+
281
+ function usageText() {
282
+ return `topmind-writing-skills — install TopMindspace agent skills
283
+
284
+ Usage:
285
+ topmind-writing-skills list|ls
286
+ topmind-writing-skills install <skill-id> [--to|-t <dir>] [--force|-f]
287
+ topmind-writing-skills uninstall <skill-id> [--to|-t <dir>]
288
+ topmind-writing-skills help
289
+
290
+ Behavior:
291
+ install copies <skill-id>/ into <dir>/<skill-id>/. If the skill is already
292
+ installed, the installed version is reported and --force is required
293
+ to overwrite. Prints where/what-version/next-steps when done.
294
+ uninstall removes <dir>/<skill-id>/ (refuses: not installed, not a skill
295
+ directory, or the package's own skill source).
296
+ No --to: the target directory is auto-detected (probe order below) and
297
+ printed explicitly in the install summary.
298
+
299
+ Target probe order (no --to): ./.agents/skills, ./.claude/skills,
300
+ ./.cursor/skills, ./.codex/skills, ./.mimocode/skills, then
301
+ ~/.claude/skills, ~/.agents/skills, ~/.cursor/skills, ~/.codex/skills.
302
+
303
+ Examples (npm recommended when published):
304
+ npx @topmindspace/topmind-writing-skills list
305
+ npx @topmindspace/topmind-writing-skills install topmind-briefs
306
+ npx @topmindspace/topmind-writing-skills install topmind-briefs --to ./.agents/skills
307
+ npx @topmindspace/topmind-writing-skills install topmind-briefs --force
308
+ npx @topmindspace/topmind-writing-skills uninstall topmind-briefs --to ./.agents/skills
309
+
310
+ # Follow repo HEAD:
311
+ npx github:topmindspace/topmind-writing-skills install topmind-briefs
312
+
313
+ Privacy: the installer is fully local — it never collects or uploads anything.
314
+ `;
315
+ }
316
+
317
+ function usage() {
318
+ console.log(usageText());
319
+ }
320
+
321
+ function main(argv) {
322
+ const args = argv.slice(2);
323
+ const cmd = args[0];
324
+
325
+ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
326
+ usage();
327
+ return;
328
+ }
329
+
330
+ if (cmd === 'list' || cmd === 'ls') {
331
+ const ids = listSkillIds();
332
+ if (!ids.length) {
333
+ console.log('No skills found in package.');
334
+ return;
335
+ }
336
+ for (const id of ids) {
337
+ const meta = readSkillMeta(id);
338
+ console.log(`${id}`);
339
+ if (meta.description) console.log(` ${meta.description}…`);
340
+ }
341
+ return;
342
+ }
343
+
344
+ if (cmd === 'install') {
345
+ const skillId = args[1];
346
+ if (!skillId || skillId.startsWith('-')) {
347
+ die('Missing skill id.\n\n' + usageText());
348
+ }
349
+ let to = null;
350
+ let force = false;
351
+ for (let i = 2; i < args.length; i++) {
352
+ if (args[i] === '--to' || args[i] === '-t') {
353
+ to = args[++i];
354
+ if (!to) die('Option --to requires a directory path.');
355
+ } else if (args[i] === '--force' || args[i] === '-f') {
356
+ force = true;
357
+ } else {
358
+ die(`Unknown option: ${args[i]}\n\n` + usageText());
359
+ }
360
+ }
361
+ const targetRoot = to ? path.resolve(to) : resolveDefaultTarget();
362
+ install(skillId, targetRoot, { force, defaultTarget: !to });
363
+ return;
364
+ }
365
+
366
+ if (cmd === 'uninstall') {
367
+ const skillId = args[1];
368
+ if (!skillId || skillId.startsWith('-')) {
369
+ die('Missing skill id.\n\n' + usageText());
370
+ }
371
+ let to = null;
372
+ for (let i = 2; i < args.length; i++) {
373
+ if (args[i] === '--to' || args[i] === '-t') {
374
+ to = args[++i];
375
+ if (!to) die('Option --to requires a directory path.');
376
+ } else {
377
+ die(`Unknown option: ${args[i]}\n\n` + usageText());
378
+ }
379
+ }
380
+ const targetRoot = to ? path.resolve(to) : resolveDefaultTarget();
381
+ uninstall(skillId, targetRoot);
382
+ return;
383
+ }
384
+
385
+ die(`Unknown command: ${cmd}\n\n` + usageText());
386
+ }
387
+
388
+ main(process.argv);