@topmindspace/topmind-skills 4.15.8 → 4.15.10

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/CHANGELOG.md CHANGED
@@ -4,6 +4,17 @@ topmind-skills 的版本记录。GitHub 只保留最近 2 个 Release,本文
4
4
 
5
5
  版本真源:`topmind-pack.json`。`package.json`、每个 `SKILL.md` 的 `metadata.version`、`evals/evals.json` 与之一致。
6
6
 
7
+ ## 4.15.10 — 2026-10-31
8
+
9
+ - 命令面与引擎对齐为 9 域 / 42 条:补上 `list-category-files`、待办的更新、截止日和删除,以及只读 `creator`(inspect、snapshot、matchLengthReport)。`creator` 不是第九引擎,也不写工作区。
10
+ - 授权与建议改为方向优先:用工作区里已有的笔记和结论判断,缺少的知识先研究。耐久经验只在用户明确要求或接受建议后写入。入口不再复制第二套阶梯。
11
+
12
+ ## 4.15.9 — 2026-10-28
13
+
14
+ - 写作路由按用户意图直达独立平台技能;通用工作区稿件才走 `topmind-write`,发布动作由对应连接器处理。
15
+ - 安装升级固定在指定项目、全局或 `--dest` 目录;单目录回执不再误判为多宿主安装,多宿主复制安装升级后仍保持复制方式。
16
+ - 技能包先暂存再替换,失败恢复旧文件;拒绝无关同名技能或链接、非法清单路径、自安装与冲突的共享资源;在写入前检查多宿主目录,避免链接别名造成自引用或半完成安装。
17
+
7
18
  ## 4.15.8 — 2026-10-26
8
19
 
9
20
  - 明确写作包独立发布、Desktop/Obsidian 默认内置,其他 Agent 按需安装;发现宿主能力后路由,不凭核心目录误判。
package/INSTALL.md CHANGED
@@ -14,9 +14,12 @@ npm:[`@topmindspace/topmind-skills`](https://www.npmjs.com/package/@topmindspa
14
14
  # 推荐:装到全局,自动给检测到的助手建链接
15
15
  npx @topmindspace/topmind-skills add topmindspace/topmind-skills -g
16
16
 
17
- # 升级
17
+ # 升级全局安装
18
18
  npx @topmindspace/topmind-skills update -g
19
19
 
20
+ # 升级当前项目安装
21
+ npx @topmindspace/topmind-skills update -p
22
+
20
23
  # 从 GitHub Release 安装(固定版本)
21
24
  npx @topmindspace/topmind-skills add release:latest -g
22
25
  ```
@@ -70,7 +73,7 @@ npx @topmindspace/topmind-skills paths -g
70
73
  npx @topmindspace/topmind-skills doctor --repair
71
74
  ```
72
75
 
73
- 升级规则:包里版本更新时直接覆盖,不留备份,打印 `Upgraded <技能> <旧> -> <新>`;版本相同跳过,打印 `Already up to date`(加 `--force` 重装);已装版本比包里新时不降级,加 `--force` 才降级。
76
+ 升级规则:`update -g` 只读取全局安装记录,`update -p` 只读取当前项目的安装记录;用 `--dest <目录>` 安装的,请用 `update --dest <同一目录>`。安装器沿用记录中的来源、目标助手与复制/链接方式,不会猜测另一个目录。新版本先完整暂存再替换;失败时恢复旧文件。版本相同跳过并提示 `Already up to date`(加 `--force` 重装);已装版本比包里新时不降级,加 `--force` 才降级。
74
77
 
75
78
  私有仓库:先 `export GH_TOKEN=…`(或 `GITHUB_TOKEN`)再执行 `add`。
76
79
 
package/README.en.md CHANGED
@@ -16,7 +16,8 @@ Then say "topmind" or just ask for what you need. To upgrade:
16
16
  npx @topmindspace/topmind-skills update -g
17
17
  ```
18
18
 
19
- - A newer packaged skill overwrites the installed copy and prints `Upgraded <skill> <old> -> <new>`; the same version prints `Already up to date`; a newer installed version is kept unless you pass `--force`.
19
+ - Use `update -p` for a project install, or `update --dest <same-directory>` for a custom directory. Updates stay within the selected installation scope.
20
+ - A new version is staged before replacement; a failed upgrade restores the prior files. The same version prints `Already up to date`; a newer installed version is kept unless you pass `--force`.
20
21
  - To install for some assistants only, add `-a claude-code -a codex` to `add`.
21
22
  - The community CLI `npx skills add topmindspace/topmind-skills -g -y` also works, but it leaves out the `shared/` folder, so some rules cannot be read. See [INSTALL.md](./INSTALL.md#社区-cli-npx-skills) for the extra step.
22
23
  - Pinned or offline installs, project scope and per-assistant folders are covered in [INSTALL.md](./INSTALL.md).
@@ -43,7 +44,7 @@ The pack has 10 skills: the `topmind` entry, 6 actions, 2 optional connectors an
43
44
 
44
45
  ### Companion skills (installed separately)
45
46
 
46
- These are not part of this pack. When installed, `topmind` hands matching requests to them; otherwise it falls back to `topmind-write` or explains in the receipt.
47
+ These are not part of this pack. Explicit WeChat, X article/post, cover, or long-image requests go straight to an installed specialist skill. Without one, the assistant completes the feasible general draft and explains any missing delivery capability.
47
48
 
48
49
  | Need | Skill | Repository |
49
50
  |---|---|---|
@@ -79,7 +80,7 @@ topmind-workspace/
79
80
  - **Save settings**: `writeback_mode: auto | confirm`. auto writes and returns a receipt; confirm also saves content edits directly and queues deletion/archive for review. Explicitly requested topic or memory changes reuse your authorization; inferred suggestions need acceptance; see [`shared/auto-suggest.md`](./shared/auto-suggest.md).
80
81
  - **Files are the only source of truth**: assistant session state must not become topmind content truth.
81
82
  - **Desktop is not required, UTR is optional**: without topmind Desktop or the UTR CLI, the assistant uses its own file tools under the same conventions.
82
- - **Nothing is posted for you**: skills only draft; you publish.
83
+ - **Drafting and posting are separate**: writing skills only prepare drafts. An available connector handles posting when you explicitly request it, subject to its permissions.
83
84
 
84
85
  ## For maintainers
85
86
 
package/README.md CHANGED
@@ -16,7 +16,8 @@ npx @topmindspace/topmind-skills add topmindspace/topmind-skills -g
16
16
  npx @topmindspace/topmind-skills update -g
17
17
  ```
18
18
 
19
- - 包里版本更新时直接覆盖,打印 `Upgraded <技能> <旧> -> <新>`;版本相同打印 `Already up to date`;已装版本更新时不降级,要降级加 `--force`。
19
+ - 项目安装用 `update -p`,指定目录安装用 `update --dest <同一目录>`;升级只读对应安装记录,不跳到别的工作区。
20
+ - 新版本先暂存再替换,失败恢复旧文件;版本相同提示 `Already up to date`,已装版本更新时不降级,要降级加 `--force`。
20
21
  - 只装给部分助手:在 `add` 后面加 `-a claude-code -a codex`。
21
22
  - 社区 CLI `npx skills add topmindspace/topmind-skills -g -y` 也能装,但不带 `shared/` 目录,部分规则会读不到,装完要补一步,见 [INSTALL.md](./INSTALL.md#社区-cli-npx-skills)。
22
23
  - 固定版本或离线安装、项目级安装、各助手的目录位置,都在 [INSTALL.md](./INSTALL.md)。
@@ -43,7 +44,7 @@ npx @topmindspace/topmind-skills update -g
43
44
 
44
45
  ### 配套技能(单独安装)
45
46
 
46
- 下面这些不随本包发布,装了以后 `topmind` 会把对应请求转过去,没装时退回 `topmind-write` 或在回执里说明。
47
+ 下面这些不随本包发布。明确的公众号、X 长文/短帖、封面和长图需求会直达已安装的专用技能;没装时完成可行的通用稿,说明缺少的交付能力。
47
48
 
48
49
  | 需求 | 技能 | 仓库 |
49
50
  |---|---|---|
@@ -79,7 +80,7 @@ topmind-workspace/
79
80
  - **保存设置**:`writeback_mode: auto | confirm`。auto 直接写并给回执;confirm 内容编辑也直接写,仅删除/归档进入审阅。明确要求的建专题、记忆更新直接执行;推测的建议需接受,规则见 [`shared/auto-suggest.md`](./shared/auto-suggest.md)。
80
81
  - **文件是唯一的数据来源**:助手自己的会话状态不得成为 topmind 内容真源。
81
82
  - **不要求 Desktop,UTR 可选**:topmind Desktop 和 UTR 命令行都不是必需的,没有时用助手自带的文件工具,遵守同一套约定。
82
- - **不代发**:任何技能都只起草,发布由你操作。
83
+ - **写稿与发布分开**:写作技能只出稿;你明确要求发布时,由实际可用的连接器按其权限执行。
83
84
 
84
85
  ## 给维护者
85
86
 
@@ -191,6 +191,15 @@ function samePath(a, b) {
191
191
  return path.resolve(a) === path.resolve(b);
192
192
  }
193
193
 
194
+ async function sameExistingDirectory(a, b) {
195
+ if (samePath(a, b)) return true;
196
+ try {
197
+ return (await fs.realpath(a)) === (await fs.realpath(b));
198
+ } catch {
199
+ return false;
200
+ }
201
+ }
202
+
194
203
  function detectedAgentIds() {
195
204
  return Object.values(agentCatalog())
196
205
  .filter((a) => a.detect())
@@ -636,6 +645,8 @@ async function listInstallEntries(skillsRoot, skillsFilter) {
636
645
  version = pack.version || version;
637
646
  name = pack.name || name;
638
647
  ids = (pack.skills || []).map((s) => s.id || s.path).filter(Boolean);
648
+ const invalid = ids.find((id) => typeof id !== "string" || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(id));
649
+ if (invalid) fail(`invalid skill id in pack: ${JSON.stringify(invalid)}`);
639
650
  } else {
640
651
  // bare Agent Skills layout (external pack, e.g. topmind-writing-skills)
641
652
  try {
@@ -735,10 +746,17 @@ function compareVersions(a, b) {
735
746
  return 0;
736
747
  }
737
748
 
738
- /** Existing non-symlink dst is a previous install of this skill (same SKILL.md name) or an installer-owned file. */
749
+ /** Match a previous skill by identity, or a link already pointing at this entry. */
739
750
  async function isPreviousInstall(src, dst) {
740
751
  const st = await fs.lstat(dst).catch(() => null);
741
752
  if (!st) return false;
753
+ if (st.isSymbolicLink()) {
754
+ const link = await fs.readlink(dst);
755
+ if (samePath(path.resolve(path.dirname(dst), link), src)) return true;
756
+ const a = await readSkillMeta(src);
757
+ const b = await readSkillMeta(dst);
758
+ return !!(a && b && a.name && a.name === b.name);
759
+ }
742
760
  const sst = await fs.lstat(src).catch(() => null);
743
761
  if (sst && sst.isFile() && st.isFile()) return true;
744
762
  if (!st.isDirectory()) return false;
@@ -747,17 +765,49 @@ async function isPreviousInstall(src, dst) {
747
765
  return !!(a && b && a.name && a.name === b.name);
748
766
  }
749
767
 
750
- async function installCopy(skillsRoot, dest, entries, locale, { force = false } = {}) {
768
+ async function assertSharedNoConflict(sourceDir, targetDir, relative = "") {
769
+ for (const entry of await fs.readdir(sourceDir, { withFileTypes: true })) {
770
+ const rel = path.join(relative, entry.name);
771
+ const src = path.join(sourceDir, entry.name);
772
+ const dst = path.join(targetDir, entry.name);
773
+ const existing = await fs.lstat(dst).catch(() => null);
774
+ if (entry.isDirectory()) {
775
+ if (existing && !existing.isDirectory()) fail(`shared resource conflict: ${rel} is not a directory`);
776
+ if (existing) await assertSharedNoConflict(src, dst, rel);
777
+ } else if (!entry.isFile() || (existing && (!existing.isFile() || !(await fs.readFile(src)).equals(await fs.readFile(dst))))) {
778
+ fail(`shared resource conflict: ${rel} differs from an installed pack; pass --force to replace it`);
779
+ }
780
+ }
781
+ }
782
+
783
+ async function installCopy(skillsRoot, dest, entries, locale, { force = false, topmindPack = false } = {}) {
751
784
  await fs.mkdir(dest, { recursive: true });
752
- // Pre-pass: never silently downgrade a skill that is newer than the packaged one.
785
+ // Validate the whole plan before touching an installed entry.
753
786
  const plan = new Map();
754
787
  const newer = [];
788
+ const destReal = await fs.realpath(dest);
755
789
  for (const name of entries) {
756
790
  const src = path.join(skillsRoot, name);
757
791
  const dst = path.join(dest, name);
792
+ if (!(await pathExists(src))) continue;
793
+ const srcReal = await fs.realpath(src);
794
+ const target = path.join(destReal, name);
795
+ if (target === srcReal || target.startsWith(srcReal + path.sep)) {
796
+ fail(`refusing to install ${name} into its own source tree: ${target}`);
797
+ }
758
798
  const sm = await readSkillMeta(src);
799
+ if (name === "shared" && !force) {
800
+ const installed = await fs.lstat(dst).catch(() => null);
801
+ if (installed && !installed.isDirectory()) {
802
+ fail(`${dst} is not a directory owned by this installation; pass --force to replace it`);
803
+ }
804
+ }
759
805
  if (!sm) continue;
760
- const dm = (await fs.lstat(dst).catch(() => null))?.isSymbolicLink() ? null : await readSkillMeta(dst);
806
+ const existing = await fs.lstat(dst).catch(() => null);
807
+ const dm = existing ? await readSkillMeta(dst) : null;
808
+ if (existing && (!dm || dm.name !== sm.name) && !force) {
809
+ fail(`${dst} is not an installed ${sm.name} skill — pass --force to replace it`);
810
+ }
761
811
  if (!dm) continue;
762
812
  const cmp = compareVersions(sm.version, dm.version);
763
813
  plan.set(name, { cmp, old: dm.version || "unknown", now: sm.version || "unknown" });
@@ -766,50 +816,100 @@ async function installCopy(skillsRoot, dest, entries, locale, { force = false }
766
816
  if (newer.length && !force) {
767
817
  fail(`installed version is newer than packaged (${newer.join(", ")}); not downgrading — pass --force to downgrade`);
768
818
  }
769
- for (const name of entries) {
770
- const src = path.join(skillsRoot, name);
771
- const dst = path.join(dest, name);
772
- if (!(await pathExists(src))) continue;
773
- const pv = plan.get(name);
774
- if (pv && pv.cmp === 0 && !force) {
775
- log(` = Already up to date: ${name} ${pv.now}`);
776
- continue;
777
- }
778
- const st = await fs.lstat(src);
779
- if (await pathExists(dst)) await fs.rm(dst, { recursive: true, force: true });
780
- if (st.isDirectory()) {
781
- await fs.cp(src, dst, { recursive: true });
782
- // Apply locale overlay if exists
783
- if (locale) {
784
- const overlayDir = path.join(src, "locales", locale);
785
- if (await pathExists(overlayDir)) {
786
- await applyLocaleOverlay(overlayDir, dst);
787
- log(` locale: ${locale}`);
819
+ const staging = await fs.mkdtemp(path.join(dest, ".topmind-skills-stage-"));
820
+ const changes = [];
821
+ let keepStaging = false;
822
+ try {
823
+ // Stage every changed entry first. Preserve sibling packs' shared resources.
824
+ for (const name of entries) {
825
+ const src = path.join(skillsRoot, name);
826
+ if (!(await pathExists(src))) continue;
827
+ const pv = plan.get(name);
828
+ if (pv && pv.cmp === 0 && !force) continue;
829
+ const dst = path.join(dest, name);
830
+ const next = path.join(staging, "next", name);
831
+ const backup = path.join(staging, "previous", name);
832
+ await fs.mkdir(path.dirname(next), { recursive: true });
833
+ const st = await fs.lstat(src);
834
+ if (st.isDirectory()) {
835
+ if (name === "shared" && (await fs.lstat(dst).catch(() => null))?.isDirectory()) {
836
+ if (!force && !topmindPack) await assertSharedNoConflict(src, dst);
837
+ await fs.cp(dst, next, { recursive: true });
838
+ await fs.cp(src, next, { recursive: true, force: true });
839
+ } else {
840
+ await fs.cp(src, next, { recursive: true });
788
841
  }
789
- // Clean up locales/ directory from installed copy (not needed at runtime)
790
- const localesInstalledDir = path.join(dst, "locales");
791
- if (await pathExists(localesInstalledDir)) {
792
- await fs.rm(localesInstalledDir, { recursive: true, force: true });
842
+ if (locale) {
843
+ const overlayDir = path.join(src, "locales", locale);
844
+ if (await pathExists(overlayDir)) await applyLocaleOverlay(overlayDir, next);
845
+ await fs.rm(path.join(next, "locales"), { recursive: true, force: true });
793
846
  }
847
+ } else {
848
+ await fs.copyFile(src, next);
794
849
  }
795
- } else {
796
- await fs.copyFile(src, dst);
850
+ changes.push({ name, dst, next, backup, pv, backedUp: false, installed: false });
851
+ }
852
+
853
+ for (const item of changes) {
854
+ if (await fs.lstat(item.dst).catch(() => null)) {
855
+ await fs.mkdir(path.dirname(item.backup), { recursive: true });
856
+ await fs.rename(item.dst, item.backup);
857
+ item.backedUp = true;
858
+ }
859
+ await fs.rename(item.next, item.dst);
860
+ item.installed = true;
861
+ }
862
+ } catch (error) {
863
+ let rollbackError = null;
864
+ for (const item of [...changes].reverse()) {
865
+ try {
866
+ if (item.installed) await fs.rm(item.dst, { recursive: true, force: true });
867
+ if (item.backedUp) await fs.rename(item.backup, item.dst);
868
+ } catch (restoreError) {
869
+ rollbackError = restoreError;
870
+ keepStaging = true;
871
+ }
872
+ }
873
+ if (rollbackError) {
874
+ throw new Error(`install failed and rollback needs attention; previous files remain at ${staging}: ${rollbackError.message}`, { cause: error });
875
+ }
876
+ throw error;
877
+ } finally {
878
+ if (!keepStaging) {
879
+ await fs.rm(staging, { recursive: true, force: true }).catch((error) => {
880
+ log(`warning: temporary install files remain at ${staging}: ${error.message}`);
881
+ });
882
+ }
883
+ }
884
+ for (const name of entries) {
885
+ const pv = plan.get(name);
886
+ if (pv && pv.cmp === 0 && !force) log(` = Already up to date: ${name} ${pv.now}`);
887
+ else if (changes.some((item) => item.name === name)) {
888
+ if (pv && pv.cmp > 0) log(` ↑ Upgraded ${name} ${pv.old} -> ${pv.now}`);
889
+ else if (pv) log(` + Reinstalled ${name} ${pv.now} (--force)`);
890
+ else log(` + ${name}`);
797
891
  }
798
- if (pv && pv.cmp > 0) log(` ↑ Upgraded ${name} ${pv.old} -> ${pv.now}`);
799
- else if (pv) log(` + Reinstalled ${name} ${pv.now} (--force)`);
800
- else log(` + ${name}`);
801
892
  }
802
893
  }
803
894
 
804
895
  async function installSymlink(skillsRoot, dest, entries, { force }) {
805
896
  await fs.mkdir(dest, { recursive: true });
897
+ const destReal = await fs.realpath(dest);
898
+ for (const name of entries) {
899
+ const src = path.join(skillsRoot, name);
900
+ if (!(await pathExists(src))) continue;
901
+ const srcReal = await fs.realpath(src);
902
+ const target = path.join(destReal, name);
903
+ if (target === srcReal || target.startsWith(srcReal + path.sep)) {
904
+ fail(`refusing to install ${name} into its own source tree: ${target}`);
905
+ }
906
+ }
806
907
  for (const name of entries) {
807
908
  const src = path.join(skillsRoot, name);
808
909
  const dst = path.join(dest, name);
809
910
  if (!(await pathExists(src))) continue;
810
- if (await pathExists(dst)) {
811
- const st = await fs.lstat(dst);
812
- if (st.isSymbolicLink() || force || (await isPreviousInstall(src, dst))) await fs.rm(dst, { recursive: true, force: true });
911
+ if (await fs.lstat(dst).catch(() => null)) {
912
+ if (force || (await isPreviousInstall(src, dst))) await fs.rm(dst, { recursive: true, force: true });
813
913
  else fail(`${dst} is an unrelated folder (no matching SKILL.md name) — pass --force to replace it`);
814
914
  }
815
915
  await fs.symlink(src, dst, "junction");
@@ -834,6 +934,7 @@ async function writeInstallReceipt(dest, meta) {
834
934
  source_raw: meta.sourceRaw,
835
935
  repo_path: meta.repoPath,
836
936
  mode: meta.mode,
937
+ install_kind: meta.installKind || "single",
837
938
  host: meta.host || null,
838
939
  scope: meta.scope || null,
839
940
  agents: meta.agents || null,
@@ -856,6 +957,9 @@ async function writeInstallReceipt(dest, meta) {
856
957
  /** Relative-symlink each pack entry from an agent root back to the canonical body. */
857
958
  async function linkPackEntries(canonicalRoot, agentRoot, entryNames, { force }) {
858
959
  await fs.mkdir(agentRoot, { recursive: true });
960
+ // A host's entire skills directory may alias the canonical directory.
961
+ // Never replace an entry through that alias with a link to itself.
962
+ if (await sameExistingDirectory(agentRoot, canonicalRoot)) return;
859
963
  for (const name of entryNames) {
860
964
  const src = path.join(canonicalRoot, name);
861
965
  const dst = path.join(agentRoot, name);
@@ -863,11 +967,9 @@ async function linkPackEntries(canonicalRoot, agentRoot, entryNames, { force })
863
967
  if (samePath(agentRoot, canonicalRoot)) continue;
864
968
  if ((await pathExists(dst)) || (await isBrokenSymlink(dst))) {
865
969
  const st = await fs.lstat(dst).catch(() => null);
866
- if (st && st.isSymbolicLink()) await fs.rm(dst, { recursive: true, force: true });
867
- else if (force || (await isPreviousInstall(src, dst))) await fs.rm(dst, { recursive: true, force: true });
970
+ if (st && (force || (await isPreviousInstall(src, dst)))) await fs.rm(dst, { recursive: true, force: true });
868
971
  else {
869
- log(` ! ${dst} is an unrelated folder (no matching SKILL.md name) — skipped; pass --force to replace it`);
870
- continue;
972
+ fail(`${dst} changed during install or is an unrelated entry — pass --force to replace it`);
871
973
  }
872
974
  }
873
975
  const rel = path.relative(agentRoot, src);
@@ -914,8 +1016,37 @@ async function installViaMatrix(skillsRoot, plan, opts) {
914
1016
  return { canonical, targets, plan };
915
1017
  }
916
1018
 
1019
+ await fs.mkdir(canonical, { recursive: true });
1020
+ for (const t of targets) {
1021
+ if (!samePath(t.root, canonical)) {
1022
+ try {
1023
+ await fs.mkdir(t.root, { recursive: true });
1024
+ const stat = await fs.stat(t.root);
1025
+ if (!stat.isDirectory()) fail(`${t.root} is not a skills directory`);
1026
+ } catch (error) {
1027
+ fail(`cannot use agent skills directory ${t.root}: ${error.message}`);
1028
+ }
1029
+ }
1030
+ t.sharesCanonical = await sameExistingDirectory(t.root, canonical);
1031
+ }
1032
+
1033
+ if (linkMode === "symlink" && !opts.force) {
1034
+ for (const t of targets) {
1035
+ if (t.sharesCanonical) continue;
1036
+ for (const name of plan.entries) {
1037
+ const dst = path.join(t.root, name);
1038
+ if (!(await fs.lstat(dst).catch(() => null))) continue;
1039
+ const sourceEntry = path.join(skillsRoot, name);
1040
+ const canonicalEntry = path.join(canonical, name);
1041
+ if (!(await isPreviousInstall(sourceEntry, dst)) && !(await isPreviousInstall(canonicalEntry, dst))) {
1042
+ fail(`${dst} is an unrelated entry — pass --force to replace it`);
1043
+ }
1044
+ }
1045
+ }
1046
+ }
1047
+
917
1048
  // 1) Full pack body → canonical
918
- await installCopy(skillsRoot, canonical, plan.entries, opts.locale, opts);
1049
+ await installCopy(skillsRoot, canonical, plan.entries, opts.locale, { ...opts, topmindPack: plan.topmindPack });
919
1050
 
920
1051
  // 2) Private agent dirs: symlink entries (default) or copy
921
1052
  for (const t of targets) {
@@ -924,7 +1055,7 @@ async function installViaMatrix(skillsRoot, plan, opts) {
924
1055
  continue;
925
1056
  }
926
1057
  if (linkMode === "copy") {
927
- await installCopy(skillsRoot, t.root, plan.entries, opts.locale, opts);
1058
+ await installCopy(skillsRoot, t.root, plan.entries, opts.locale, { ...opts, topmindPack: plan.topmindPack });
928
1059
  } else {
929
1060
  await linkPackEntries(canonical, t.root, plan.entries, opts);
930
1061
  }
@@ -937,6 +1068,7 @@ async function installViaMatrix(skillsRoot, plan, opts) {
937
1068
  sourceRaw: opts.source,
938
1069
  repoPath: opts.repoPath,
939
1070
  mode: linkMode,
1071
+ installKind: "matrix",
940
1072
  host: null,
941
1073
  scope: opts.scope,
942
1074
  agents: targets.map((t) => t.agent.id),
@@ -948,7 +1080,7 @@ async function installViaMatrix(skillsRoot, plan, opts) {
948
1080
  log(`done → ${canonical}`);
949
1081
  if (plan.topmindPack) {
950
1082
  log(`daily entry: ${path.join(canonical, "topmind", "SKILL.md")}`);
951
- log(`update later: node bin/install-skills.mjs update -g`);
1083
+ log(`update later: npx @topmindspace/topmind-skills update ${global ? "-g" : "-p"}`);
952
1084
  } else {
953
1085
  log(`update later: re-run add ${opts.source || "<source>"} (update only follows the topmind pack receipt)`);
954
1086
  }
@@ -1084,7 +1216,7 @@ async function cmdAdd(opts, sourceOverride) {
1084
1216
  }
1085
1217
  await installSymlink(skillsRoot, dest, plan.entries, opts);
1086
1218
  } else {
1087
- await installCopy(skillsRoot, dest, plan.entries, opts.locale, opts);
1219
+ await installCopy(skillsRoot, dest, plan.entries, opts.locale, { ...opts, topmindPack: plan.topmindPack });
1088
1220
  }
1089
1221
 
1090
1222
  await writeInstallReceipt(dest, {
@@ -1096,7 +1228,8 @@ async function cmdAdd(opts, sourceOverride) {
1096
1228
  mode: opts.mode,
1097
1229
  host: opts.host,
1098
1230
  scope: opts.scope,
1099
- agents: opts.agents,
1231
+ agents: null,
1232
+ installKind: "single",
1100
1233
  skillIds: plan.skillIds,
1101
1234
  locale: opts.locale,
1102
1235
  topmindPack: plan.topmindPack,
@@ -1115,10 +1248,8 @@ async function cmdAdd(opts, sourceOverride) {
1115
1248
  }
1116
1249
 
1117
1250
  async function cmdUpdate(opts) {
1118
- // Prefer receipt at matrix canonical; fall back to explicit --dest receipt.
1119
- const destCandidates = opts.dest
1120
- ? [opts.dest]
1121
- : [canonicalBase(opts.global), canonicalBase(!opts.global), HOST_DEFAULTS["claude-code"]()];
1251
+ // A scope is an instruction, not a search hint: never update another scope.
1252
+ const destCandidates = [opts.dest || canonicalBase(opts.global)];
1122
1253
  let dest = null;
1123
1254
  let receipt = null;
1124
1255
  for (const d of destCandidates) {
@@ -1131,8 +1262,8 @@ async function cmdUpdate(opts) {
1131
1262
  if (!receipt) {
1132
1263
  fail(
1133
1264
  `no ${RECEIPT_NAME} in ${destCandidates.join(", ")}.\n` +
1134
- ` First install: node bin/install-skills.mjs add ${DEFAULT_OWNER_REPO} -g\n` +
1135
- ` Or community: npx skills update -g -y`,
1265
+ ` First install: npx @topmindspace/topmind-skills add ${DEFAULT_OWNER_REPO} ${opts.dest ? "--dest <same directory>" : opts.global ? "-g" : "-p"}\n` +
1266
+ ` To update another scope, pass -g, -p or --dest explicitly.`,
1136
1267
  );
1137
1268
  }
1138
1269
  log(`update from receipt: ${receipt.source} @ ${receipt.version} → ${dest}`);
@@ -1142,15 +1273,17 @@ async function cmdUpdate(opts) {
1142
1273
  source: receipt.source_raw || receipt.update?.source || DEFAULT_OWNER_REPO,
1143
1274
  repoPath: receipt.repo_path || receipt.update?.path || DEFAULT_PATH,
1144
1275
  mode: receipt.mode === "symlink" ? "symlink" : "copy",
1145
- linkMode: receipt.agents ? "symlink" : opts.linkMode,
1276
+ linkMode: receipt.mode === "copy" ? "copy" : "symlink",
1146
1277
  host: receipt.host || opts.host,
1147
1278
  scope: receipt.scope || opts.scope,
1148
- agents: receipt.agents || opts.agents,
1279
+ agents: Array.isArray(receipt.agents) ? receipt.agents : opts.agents,
1149
1280
  locale: receipt.locale || opts.locale,
1150
1281
  };
1151
- // Keep matrix mode when the receipt was a matrix install (no single dest host).
1152
- if (receipt.agents) next.dest = null;
1153
- else if (!next.dest) next.dest = dest;
1282
+ // Legacy matrix receipts used source="matrix"; old single-dir receipts also
1283
+ // contained agents: [], so array presence alone cannot identify the mode.
1284
+ const matrix = receipt.install_kind === "matrix" ||
1285
+ (!receipt.install_kind && receipt.source === "matrix");
1286
+ next.dest = matrix ? null : dest;
1154
1287
 
1155
1288
  if (next.mode === "symlink" && next.dest) {
1156
1289
  const local = parseSource(next.source, next.repoPath);
package/evals/evals.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "skill_name": "topmind",
3
3
  "daily_entry": "topmind",
4
- "version": "4.15.8",
4
+ "version": "4.15.10",
5
5
  "evals": [
6
6
  {
7
7
  "id": 1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topmindspace/topmind-skills",
3
- "version": "4.15.8",
3
+ "version": "4.15.10",
4
4
  "description": "topmind Agent Skills pack: one daily entry (topmind) routing to capture, organize, write, memory, maintain and loop, plus optional WeRead, X and ledger skills.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,5 +1,9 @@
1
1
  # 请求授权与主动建议
2
2
 
3
+ **方向大于约束**:引导、目标、已有经验与知识经验优先于固定操作剧本;规约只保留底线。用户概念仍 ≤5(记一下 · 动态 · 专题 · 我的情况 · 交付),文件仍是内容真源,不把「沉淀」教成产品 UI 里的第六个动词。硬闸仍在:写回分级、路径围栏、外部发布确认,以及禁止只做口头改写。
4
+
5
+ 按用户目标行动。用工作区里已有的笔记、记忆和结论做判断;缺的知识先研究分析再决定。耐久的知识经验只在用户明确要求或接受建议后写入。推测出的分类、记忆、专题仍只提案。明确请求不重复询问。
6
+
3
7
  先完成用户请求,再按价值决定是否建议延伸。用户不需要学习模块名,不为分类、待办、记忆弹一组选择题。
4
8
 
5
9
  ## 三种情况
@@ -42,14 +42,15 @@ Level 3(最低): 仅对话
42
42
 
43
43
  | 需要 | 命令 |
44
44
  |------|------|
45
- | 类别 / 专题 | `list-categories`(WorkspaceModel Descriptor)· `list-topics` · `inspect-topic` · `list-topic-files` · `list-inbox` |
45
+ | 类别 / 专题 / 大类根稿 | `list-categories` · `list-category-files` · `list-topics` · `inspect-topic` · `list-topic-files` · `list-inbox` |
46
46
  | 捕获 / 创建 | `create-topic` · `capture-note` · `save-output` |
47
- | 待办 | `memory.list-todos` · `memory.add-todo` · `memory.toggle-todo` |
48
- | 记忆 | `memory.append-profile` · `memory.append-topic` · `memory.promote` · `memory.digest` · `memory.retire-profile` · `memory.update-profile` · `memory.compact-history` · `memory.restore-profile` |
49
- | Inbox 路由 | `plan-inbox-routing`(primary;整理 inbox 前规划) |
47
+ | 待办 | `memory.list-todos` · `memory.add-todo` · `memory.toggle-todo` · `memory.update-todo` · `memory.set-todo-due` · `memory.delete-todo` |
48
+ | 记忆 | `memory.promote` · `memory.digest` · `memory.append-profile` · `memory.append-topic` · `memory.retire-profile` · `memory.update-profile` · `memory.compact-history` · `memory.restore-profile` |
49
+ | 创作只读 | `creator.inspect` · `creator.snapshot` · `creator.matchLengthReport` |
50
+ | Inbox 路由 | `plan-inbox-routing` |
50
51
  | 检查 / 维护 | `doctor-workspace` · `archive-topic` · `archive-stream-year` · `restore-safety-receipt` · `contract.validate` · `contract.reseed` |
51
52
 
52
- 扩展面见 `TOOLS.md` §Current Command Surface。输入用独立字段 `category` + `topic`。
53
+ 上表覆盖注册表里的全部 primary,含 `list-category-files` 与 `creator`。`creator` 是只读投影,不是引擎,也不写工作区。advanced 与 MCP 默认数量不在这里另抄,以引擎仓 `TOOLS.md` 与 `utr/core/contract-registry.mjs` 为准。输入用独立字段 `category` + `topic`。
53
54
 
54
55
  ## Level 3 — 对话 only
55
56
 
@@ -8,7 +8,7 @@
8
8
  > - 独立技能:`topmind-research`(对外检索与研究报告)、`topmind-presentation`(HTML / PPTX 演示稿)、`topmind-handoff`(跨工具交接包)
9
9
  > - 第三方:`last30days`、`TopStream每日精选`
10
10
  >
11
- > 任何技能都不代用户发布:写稿技能只出稿;要发出去走对应连接器(如 `topmind-x`),并且每次都须用户确认。
11
+ > 写稿技能只出稿;明确要发出去时走对应连接器(如 `topmind-x`),遵守其发布前确认与实际权限,不能把出稿说成已发布。
12
12
 
13
13
  ## 碰撞词裁决
14
14
 
@@ -26,7 +26,7 @@
26
26
  | 整理 | organize | 「整理 inbox」→ organize + `plan-inbox-routing`。其它 → organize |
27
27
  | 复盘 | organize vs loop | 「复盘」→ loop。「复盘要点成笔记」→ organize |
28
28
  | 归档 | capture vs maintain | 「归档材料」→ capture。「归档专题 / archive topic」→ maintain |
29
- | 笔记 / note | capture vs write | 未限定 → capture。「写作 / 起草 / 续写」→ write |
29
+ | 笔记 / note | capture vs write | 未限定 → capture。「写作 / 起草 / 续写」→ write;「新建笔记」创建后打开,交给宿主编辑器 |
30
30
  | 记账 / 记一笔 / 花了 / 存入 | ledger vs capture | 「记账 / 记一笔 / 花了 / 存入 / 查看账单 / 账户余额」→ `topmind-ledger`(记忆平面账本)。「记一下」仍 → capture |
31
31
  | 研究 / 分析 / 对比 | organize vs topmind-research(外部可选) | 对象是工作区里已存的笔记、专题内材料 → `topmind-organize`。需要对外检索一手资料(论文、技术报告、官方公告、模型或产品横向对比)、AI 前沿深挖 → `topmind-research`(analyze);未安装 → `topmind-organize`「研究分析」 |
32
32
  | AI 动态 / 大厂动态 / AI 周报 / 最近有什么新的 | topmind-research vs last30days vs TopStream每日精选(均外部可选) | 官方公告、技术报告、论文的周度汇总 → `topmind-research`(collect)。社区讨论与口碑(X、Reddit、HN 等)→ `last30days`。每日精选例行 → `TopStream每日精选`。对应技能未安装 → `topmind-organize`(只用工作区已存材料,回执注明)。「整理本周 / 理顺本周动态」指工作区动态类别,仍 → organize「整理本周」 |
@@ -66,7 +66,8 @@
66
66
  - 修复 / 清理 / 重建 → maintain
67
67
  - 巡检 / 整体 / 断点 → loop
68
68
  - 整理 / 分析 / 总结 → organize
69
- - 写 / 发布 / 出稿 → write
69
+ - 写 / 出稿 / 通用稿件 → write;明确平台稿优先对应独立写作技能
70
+ - 发布 / 发出去 → 对应连接器;写作技能只准备稿件
70
71
  - 记忆 / 更新我的情况 / 周期反思 → memory
71
72
  - 仅类别名 → capture 到该类别根
72
73
  - 全不像 → capture 到 Inbox(buffer)
package/topmind/SKILL.md CHANGED
@@ -7,14 +7,14 @@ compatibility: >-
7
7
  topmind workspace with {NN-Name}/ categories. Host file tools primary; UTR optional.
8
8
  Install with shared/ sibling for progressive disclosure links.
9
9
  metadata:
10
- version: "4.15.8"
10
+ version: "4.15.10"
11
11
  action_category: "router"
12
12
  entrypoint: "true"
13
13
  triggers: "topmind, 知识库, 工作区, 待办, 有什么要做的, 要做的事, todo, todo list"
14
14
  tags: "router, entrypoint, topmind"
15
15
  author: "TopMindSpace"
16
16
  homepage: "https://github.com/topmindspace/topmind-skills"
17
- updated: "2026-10-26"
17
+ updated: "2026-10-28"
18
18
  degradation: "../shared/capability-degradation.md"
19
19
  ---
20
20
 
@@ -31,15 +31,16 @@ metadata:
31
31
  | 记链接、摘录、想法、记一下要做某事 | `topmind-capture`;动态周期本 / 专题 / Inbox,显式待办进 `memory/todo.md` |
32
32
  | 待办有哪些、完成/更新某条待办 | 直接读写 `memory/todo.md`,不启动整理流程 |
33
33
  | 整理本周、总结/对比已有材料、开/并专题 | `topmind-organize`;临时问答按需回复,持久整理写回 |
34
- | 起草、润色、成稿、交付 | `topmind-write` |
34
+ | 用工作区素材起草、润色、保存通用稿件 | `topmind-write` |
35
+ | 明确公众号、X 长文/短帖、封面或长图 | 直接使用已安装的独立写作技能;需保存到工作区时沿用 `topmind-write` 的落盘约定 |
35
36
  | 记住我、更新我的情况、周期反思 | `topmind-memory` |
36
37
  | 结构体检、修复、恢复 | `topmind-maintain` |
37
38
  | 全工作区语义巡检、长任务断点恢复 | `topmind-loop` |
38
39
  | 微信读书 / X / 记账 | 可选 `topmind-weread` / `topmind-x` / `topmind-ledger` |
39
40
 
40
- 外部检索研究、公众号、X 长文/短帖、封面/海报、幻灯片、交接的可选技能与容易混淆的请求,按需读 [路由消歧](../shared/trigger-disambiguation.md)。仅使用宿主实际可用的技能和工具;未安装专用技能时完成通用可行部分并说明限制。
41
+ 外部检索研究、平台写作、封面/海报、幻灯片、交接的可选技能与容易混淆的请求,按需读 [路由消歧](../shared/trigger-disambiguation.md)。仅使用宿主实际可用的技能和工具;未安装专用技能时完成通用可行部分并说明限制。不让用户先选择内部技能。
41
42
 
42
- 用户一次要求多步,按依赖连续完成到最终交付;不因切换技能重复确认。用户未请求的延伸只给建议,见 [多意图](references/multi-intent.md) 与 [授权及建议](../shared/auto-suggest.md)。
43
+ 用户一次要求多步,按依赖连续完成到最终交付;不因切换技能重复确认。怎么判断、何时用工作区经验、何时研究、何时写入耐久经验,只见 [授权及建议](../shared/auto-suggest.md)(方向大于约束)。用户未请求的延伸只给建议。本入口不复制第二套阶梯。多意图边界见 [多意图](references/multi-intent.md)。
43
44
 
44
45
  ## 产品特有约定
45
46
 
@@ -5,7 +5,7 @@ description: >-
5
5
  license: MIT
6
6
  compatibility: topmind workspace. Host file tools or optional UTR capture-note.
7
7
  metadata:
8
- version: "4.15.8"
8
+ version: "4.15.10"
9
9
  action_category: "capture"
10
10
  entrypoint: "false"
11
11
  triggers: "收集, 收进, 保存, 剪藏, 记一下, 临时存放, 随手记, 想法, 灵感, 暂存沉淀, 记录, 备忘, 存一下, 暂存, 笔记, 抄下来, 摘录, 记一下要做, 别忘了, 要做, capture, note, save, Note it"
@@ -5,7 +5,7 @@ description: >-
5
5
  license: MIT
6
6
  compatibility: Host file tools or Kernel writeback. Desktop optional mini-app. Feishu/lark-cli is not required.
7
7
  metadata:
8
- version: "4.15.8"
8
+ version: "4.15.10"
9
9
  action_category: "memory"
10
10
  entrypoint: "false"
11
11
  triggers: "记账, 记一笔, 花了, 存入, 查看账单, 账户余额, bookkeeping, log expense"
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## Why Loop Exists
6
6
 
7
- UTR 提供 **8 域 / 38** 条确定性命令(以 topmind 仓库 `TOOLS.md` 为准);`doctor-workspace` 做规则健康检查。Loop 在之上增加**语义层**:阅读 `topic.md` 与笔记、对照 skill 契约、再调用 UTR 修复。
7
+ UTR 提供 **9 域 / 42** 条确定性命令(以 topmind 仓库 `TOOLS.md` 为准);`doctor-workspace` 做规则健康检查。Loop 在之上增加**语义层**:阅读 `topic.md` 与笔记、对照 skill 契约、再调用 UTR 修复。
8
8
 
9
9
  ## Design Choice — Skill + Agent + State File, Not CLI
10
10
 
@@ -5,7 +5,7 @@ description: >-
5
5
  license: MIT
6
6
  compatibility: topmind workspace. Progress under .topmind/loop/*. Host LLM runs the walk; UTR optional.
7
7
  metadata:
8
- version: "4.15.8"
8
+ version: "4.15.10"
9
9
  action_category: "loop"
10
10
  entrypoint: "false"
11
11
  triggers: "跑一遍 loop, loop 一下, 整体体检, 工作区复盘, 把工作区跑一遍, 巡检, 继续 loop, 从断点继续, 全面检查, 复盘, review, audit, walk"
@@ -5,7 +5,7 @@ description: >-
5
5
  license: MIT
6
6
  compatibility: topmind engine and/or workspace. Prefer read-only diagnostics first.
7
7
  metadata:
8
- version: "4.15.8"
8
+ version: "4.15.10"
9
9
  action_category: "maintain"
10
10
  entrypoint: "false"
11
11
  triggers: "快速体检, 体检, 诊断, doctor, 健康检查, 清理, 修复, 快速检查, 检查, 状态, workspace check, scan, fix, maintain"
@@ -5,7 +5,7 @@ description: >-
5
5
  license: MIT
6
6
  compatibility: workspace core profile (memory/profile.md) + periodic reflections.
7
7
  metadata:
8
- version: "4.15.8"
8
+ version: "4.15.10"
9
9
  action_category: "memory"
10
10
  entrypoint: "false"
11
11
  triggers: "写入专题记忆, 更新我的情况, stable memory, 追加确认结论, 记住这个, 记住我, 加到专题记忆, 沉淀结论, 提炼到记忆, update profile, memory"
@@ -5,7 +5,7 @@ description: >-
5
5
  license: MIT
6
6
  compatibility: topmind workspace with stream period notes, topics, or Inbox.
7
7
  metadata:
8
- version: "4.15.8"
8
+ version: "4.15.10"
9
9
  action_category: "organize"
10
10
  entrypoint: "false"
11
11
  triggers: "整理本周, 理顺本周, 周复盘, 整理, 组织, 研究, 分析, 对比, 提取, 梳理, 归纳, 证据, 实体, 质量审查, 总结, 概括, 汇总, 提纲, 分类整理, 找关联, 总结成笔记要点, organize, summarize, analyze, weekly review"
package/topmind-pack.json CHANGED
@@ -4,8 +4,8 @@
4
4
  "label": "topmind",
5
5
  "description": "topmind skill pack: single daily entry topmind + progressive disclosure (shared/ + references/), Agent Skills spec-valid frontmatter (custom fields under metadata, checked by agentskills validate), capture-first template-driven categories, optional connectors topmind-weread and topmind-x, optional topmind-ledger (记账). None of the optional skills is an extra user concept. WeChat (公众号) and other writing skills live in the external topmind-writing-skills pack (topmind-wechat-post etc.) and are routed only when installed. File-system-first content truth; UTR optional.",
6
6
  "daily_entry": "topmind",
7
- "version": "4.15.8",
8
- "updated": "2026-10-26",
7
+ "version": "4.15.10",
8
+ "updated": "2026-10-28",
9
9
  "license": "MIT",
10
10
  "locales": {
11
11
  "default": "zh-CN",
@@ -334,14 +334,16 @@
334
334
  "workspace-transform",
335
335
  "workspace-maintain",
336
336
  "contract",
337
+ "creator",
337
338
  "memory",
338
339
  "lifecycle",
339
340
  "derived"
340
341
  ],
341
- "kind_naming_legacy": "Domain names (workspace-read / workspace-write / workspace-transform / workspace-maintain / contract / memory / lifecycle / derived) are stable identifiers. Command names follow the topic-and-category vocabulary.",
342
+ "kind_naming_legacy": "Domain names (workspace-read / workspace-write / workspace-transform / workspace-maintain / contract / creator / memory / lifecycle / derived) are stable identifiers. Command names follow the topic-and-category vocabulary.",
342
343
  "command_vocabulary": {
343
344
  "workspace-read": [
344
345
  "list-categories",
346
+ "list-category-files",
345
347
  "list-topics",
346
348
  "inspect-topic",
347
349
  "list-topic-files",
@@ -372,6 +374,11 @@
372
374
  "ensure",
373
375
  "reseed"
374
376
  ],
377
+ "creator": [
378
+ "inspect",
379
+ "snapshot",
380
+ "matchLengthReport"
381
+ ],
375
382
  "memory": [
376
383
  "promote",
377
384
  "digest",
@@ -395,11 +402,12 @@
395
402
  "rebuild"
396
403
  ]
397
404
  },
398
- "command_count": 38,
399
- "mcp_default_count": 29,
405
+ "command_count": 42,
406
+ "mcp_default_count": 33,
400
407
  "command_exposure": {
401
408
  "primary": [
402
409
  "workspace-read.list-categories",
410
+ "workspace-read.list-category-files",
403
411
  "workspace-read.list-topics",
404
412
  "workspace-read.inspect-topic",
405
413
  "workspace-read.list-topic-files",
@@ -410,6 +418,9 @@
410
418
  "workspace-transform.plan-inbox-routing",
411
419
  "workspace-maintain.doctor-workspace",
412
420
  "contract.validate",
421
+ "creator.inspect",
422
+ "creator.snapshot",
423
+ "creator.matchLengthReport",
413
424
  "memory.promote",
414
425
  "memory.digest",
415
426
  "memory.append-profile",
@@ -5,7 +5,7 @@ description: >-
5
5
  license: MIT
6
6
  compatibility: Requires WeRead Skill API key (wrk-*). Desktop WereadService preferred; host may call gateway manually.
7
7
  metadata:
8
- version: "4.15.8"
8
+ version: "4.15.10"
9
9
  action_category: "connector"
10
10
  entrypoint: "false"
11
11
  triggers: "微信读书, weread, 划线同步, 读书笔记, 阅读统计, 书架, weread sync, sync highlights"
@@ -1,18 +1,18 @@
1
1
  ---
2
2
  name: topmind-write
3
3
  description: >-
4
- 根据 topmind 素材起草、续写、改稿或交付。Use when 写成稿、润色、write、draft、revise。Do NOT use for capture-only, profile-only, or external posting requests.
4
+ 根据 topmind 工作区素材起草、续写、改稿或保存通用稿件。Use when 写成稿、润色、write、draft、revise。明确平台稿优先已安装的独立写作技能。Do NOT use for capture-only, profile-only, or external posting requests.
5
5
  license: MIT
6
6
  compatibility: topmind workspace. Delivery role category (often 88-交付 / 88-Delivery).
7
7
  metadata:
8
- version: "4.15.8"
8
+ version: "4.15.10"
9
9
  action_category: "write"
10
10
  entrypoint: "false"
11
- triggers: "写, 写作, 起草, 续写, 修订, 润色, 交付, 改写, de-AI, 输出, 出稿, 定稿, 发布, 导出, 排版, write, draft, deliver, publish, export"
11
+ triggers: "写作, 起草, 续写, 修订, 润色, 交付, 改写, de-AI, 出稿, 定稿, 导出, write, draft, deliver, export"
12
12
  tags: "write, draft, revise, polish, deliver"
13
13
  author: "TopMindSpace"
14
14
  homepage: "https://github.com/topmindspace/topmind-skills"
15
- updated: "2026-10-26"
15
+ updated: "2026-10-28"
16
16
  degradation: "../shared/capability-degradation.md"
17
17
  ---
18
18
 
@@ -36,7 +36,7 @@ metadata:
36
36
 
37
37
  ## 可选专用能力
38
38
 
39
- 写作技能保持独立仓库;Desktop 与 Obsidian 默认内置受验证版本,其他 Agent 按需安装。改稿加载 `topmind-revise`,只改排版加载 `topmind-format`;宿主有 `render_article` 时用它生成平台 HTML/文本,无需猜测或执行 Python 命令。生成后按用户意图落盘,预览/复制不等于发布。
39
+ 写作技能保持独立仓库;Desktop 与 Obsidian 默认内置受验证版本,其他 Agent 按需安装。明确指定平台或只改排版时,优先直接使用相应独立技能:改稿 `topmind-revise`,只排版 `topmind-format`;本技能仅补工作区素材读取和落盘,不再要求用户选一次技能。宿主有 `render_article` 时用它生成平台 HTML/文本,无需猜测或执行 Python 命令。生成后按用户意图落盘,预览/复制不等于发布。
40
40
 
41
41
  公众号 → `topmind-wechat-post`;X 长文 → `topmind-x-article`;干货短文 / 引流短帖 → `topmind-briefs` / `topmind-viral-posts`;幻灯片 → `topmind-presentation`。已安装且匹配时使用,未安装则完成通用稿并说明专用格式限制,不能伪装已生成 HTML/PPTX。消歧详见 [trigger-disambiguation](../shared/trigger-disambiguation.md)。写稿不构成外部发布授权。
42
42
 
@@ -5,7 +5,7 @@ description: >-
5
5
  license: MIT
6
6
  compatibility: Desktop needs Bearer (read) and/or xurl (post). Agent hosts use official X MCP + xurl bridge.
7
7
  metadata:
8
- version: "4.15.8"
8
+ version: "4.15.10"
9
9
  action_category: "connector"
10
10
  entrypoint: "false"
11
11
  triggers: "发推, 推特, twitter, x.com, 发帖, 推文, post tweet, x platform"