feat: llm wiki sync

This commit is contained in:
故璃
2026-07-23 15:31:57 +08:00
parent d08edf0cd8
commit 90a44d7140
3 changed files with 277 additions and 50 deletions
+12 -20
View File
@@ -5,12 +5,11 @@
* 保证用户首次使用 `bl advisor recommend` 时数据已就位。
*
* 流程:
* 1. fetch FC 签名函数 → 拿 OSS 预签名 URL
* 2. 下载 manifest.json + wiki-doc-full.tar.br~2.15MB
* 3. 校验 sha256
* 4. Node 原生 brotli 解压 + tar-stream 解包到同盘临时目录
* 5. renameSync 原子替换到 ~/.bailian/skills/bailian-docs-llm-wiki/
* 6. 写 ~/.bailian/wiki-sync-state.json
* 1. 从公共读 OSS 直链下载 manifest.json + wiki-doc-full.tar.br~2.15MB
* 2. 校验 sha256
* 3. Node 原生 brotli 解压 + tar-stream 解包到同盘临时目录
* 4. renameSync 原子替换到 ~/.bailian/skills/bailian-docs-llm-wiki/
* 5. 写 ~/.bailian/wiki-sync-state.json
*
* 设计约束:
* - 无条件覆盖:每次 install 都全量替换,不比对已有版本
@@ -34,7 +33,7 @@ import { pipeline } from "node:stream/promises";
import { createBrotliDecompress } from "node:zlib";
import tar from "tar-stream";
const FC_SIGN_URL = "https://signature-gmqkxxozrl.cn-hangzhou.fcapp.run";
const OSS_BASE_URL = "https://bailian-wiki.oss-cn-hangzhou.aliyuncs.com/bailian-docs-llm-wiki";
const CONFIG_DIR_NAME = ".bailian";
const SKILL_DIR_NAME = "skills/bailian-docs-llm-wiki";
const STATE_FILE_NAME = "wiki-sync-state.json";
@@ -106,25 +105,18 @@ function atomicSwap(tmpDir, catalogDir) {
}
async function main() {
// 1. 拿全部签名 URL
const signResp = await fetchJson(FC_SIGN_URL, MANIFEST_TIMEOUT_MS);
const urls = signResp?.urls;
if (!urls?.[MANIFEST_KEY] || !urls?.[ASSET_KEY]) {
throw new Error("签名函数未返回所需 URL");
}
// 2. 下载 manifest
const manifest = await fetchJson(urls[MANIFEST_KEY], MANIFEST_TIMEOUT_MS);
// 1. 下载 manifest
const manifest = await fetchJson(`${OSS_BASE_URL}/${MANIFEST_KEY}`, MANIFEST_TIMEOUT_MS);
if (!manifest?.version) throw new Error("manifest 无 version");
// 3. 下载 tar.br + 校验
const tarBuf = await downloadBuffer(urls[ASSET_KEY]);
// 2. 下载 tar.br + 校验
const tarBuf = await downloadBuffer(`${OSS_BASE_URL}/${ASSET_KEY}`);
const sha256 = createHash("sha256").update(tarBuf).digest("hex");
if (manifest.asset?.sha256 && sha256 !== manifest.asset.sha256) {
throw new Error("sha256 校验失败");
}
// 4. 解包到同盘临时目录 + 原子替换
// 3. 解包到同盘临时目录 + 原子替换
const catalogDir = getCatalogDir();
const tmpDir = `${catalogDir}.tmp-${process.pid}-${Date.now()}`;
try {
@@ -136,7 +128,7 @@ async function main() {
throw err;
}
// 5. 写 state
// 4. 写 state
try {
writeFileSync(
getStatePath(),
+14 -30
View File
@@ -3,7 +3,7 @@
*
* `bl advisor recommend` 执行时调用 `maybeSyncWikiData()`
* 1. 12h throttle距上次检查不足 12h 直接跳过
* 2. 拉 FC 签名 URL → 下载 manifest.json 比对版本
* 2. 从公共读 OSS 下载 manifest.json 比对版本
* 3. 版本相同 → 仅刷新 lastChecked
* 4. 版本不同 → 下载 tar.br → 校验 sha256 → brotli 解压 + tar-stream 解包
* 到同盘临时目录 → renameSync 原子替换 → 写 state
@@ -30,8 +30,8 @@ import { createBrotliDecompress } from "node:zlib";
import tar from "tar-stream";
import { getConfigDir } from "../config/paths.ts";
/** FC 匿名签名函数入口,返回 OSS 预签名 URL(硬编码,不走 env。 */
const FC_SIGN_URL = "https://signature-gmqkxxozrl.cn-hangzhou.fcapp.run";
/** 公共读 OSS 目录,直链下载(硬编码,不走 env。 */
const OSS_BASE_URL = "https://bailian-wiki.oss-cn-hangzhou.aliyuncs.com/bailian-docs-llm-wiki";
const SKILL_DIR_NAME = "skills/bailian-docs-llm-wiki";
const STATE_FILE_NAME = "wiki-sync-state.json";
const MODELS_FILE = "models.jsonl";
@@ -93,20 +93,6 @@ function writeState(state: SyncState): void {
}
}
/** 拉全部 OSS 预签名 URL`{ urls: { "manifest.json": "...", "wiki-doc-full.tar.br": "..." } }` */
async function fetchSignedUrls(): Promise<Record<string, string> | null> {
try {
const res = await fetch(FC_SIGN_URL, {
signal: AbortSignal.timeout(MANIFEST_TIMEOUT_MS),
});
if (!res.ok) return null;
const data = (await res.json()) as { urls?: Record<string, string> };
return data.urls ?? null;
} catch {
return null;
}
}
async function fetchManifest(url: string): Promise<Manifest | null> {
try {
const res = await fetch(url, { signal: AbortSignal.timeout(MANIFEST_TIMEOUT_MS) });
@@ -182,25 +168,23 @@ export async function maybeSyncWikiData(): Promise<boolean> {
return false;
}
// 2. 拿签名 URL + manifest
const urls = await fetchSignedUrls();
if (!urls?.[MANIFEST_KEY]) return false; // 失败不写 lastChecked下次重试
const manifest = await fetchManifest(urls[MANIFEST_KEY]);
if (!manifest?.version) return false;
// 2. manifest
const manifest = await fetchManifest(`${OSS_BASE_URL}/${MANIFEST_KEY}`);
if (!manifest?.version) return false; // 失败不写 lastChecked下次重试
// 3. 版本相同且本地数据存在:仅刷新 lastChecked无需重新下载。
// 若数据缺失,即便版本相同也要继续走下载补齐(落到第 4 步)。
if (state && manifest.version === state.version && catalogDataExists()) {
writeState({ lastChecked: now, version: state.version });
// 覆盖两种状态:(a) state.version === manifest.version → 直接命中;
// (b) state 缺失但数据完好(用户或意外只删了 state→ 用 manifest 版本
// 写回 state避免无谓的 2MB 下载+解压。
// 数据缺失或版本落后时落到第 4 步全量下载补齐。
const dataOk = catalogDataExists();
if (dataOk && (!state || state.version === manifest.version)) {
writeState({ lastChecked: now, version: manifest.version });
return false;
}
// 4. 版本不同:下载 + 校验 + 解包 + 原子替换
const assetUrl = urls[ASSET_KEY];
if (!assetUrl) return false;
const tarBuf = await downloadBuffer(assetUrl);
const tarBuf = await downloadBuffer(`${OSS_BASE_URL}/${ASSET_KEY}`);
if (!tarBuf) return false;
// sha256 校验
+251
View File
@@ -0,0 +1,251 @@
{
"version": 1,
"skills": {
"ask-matt": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/engineering/ask-matt/SKILL.md",
"computedHash": "0f843160e34a24f5bd12cdc7de7d40951e77fbdc05ce8f891b37ca32eac2c44d"
},
"batch-grill-me": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/in-progress/batch-grill-me/SKILL.md",
"computedHash": "f448831b8f04518b1527408f1d5024384ad5f543bdbaac6fabb7aa053ad60489"
},
"claude-handoff": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/in-progress/claude-handoff/SKILL.md",
"computedHash": "40a7f4ec80b9826ad7d0c47b0778c99686d6d9d662112b3e175a816f3aca4c39"
},
"code-review": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/engineering/code-review/SKILL.md",
"computedHash": "31d149a480eaa68c11e32f5ee77f0fd0b98a906834d531d881d502352edd0b8e"
},
"codebase-design": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/engineering/codebase-design/SKILL.md",
"computedHash": "6d9d51d8caa01633fd00cf87089c5618c982321f942ac69f62565dc001c6b22f"
},
"design-an-interface": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/deprecated/design-an-interface/SKILL.md",
"computedHash": "aef348a9af675b771635623b5aa7790da4d40adc1ee88b58b6fd116eda2047d8"
},
"diagnosing-bugs": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/engineering/diagnosing-bugs/SKILL.md",
"computedHash": "fd6c99466b7ba43be624e6e66ed6d7af2796ded7218f24c83820823977819e22"
},
"domain-modeling": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/engineering/domain-modeling/SKILL.md",
"computedHash": "363cb0f53b0b431e7c00086ad1f823500b7e1b70b5616ee969c979f0934e9e6e"
},
"edit-article": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/personal/edit-article/SKILL.md",
"computedHash": "cb2a13b2acb6c8a0706eb3a973a2681f30fd74c00784c74ceaa98e12eb2b2fc3"
},
"git-guardrails-claude-code": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/misc/git-guardrails-claude-code/SKILL.md",
"computedHash": "8c29552c621da0121427306afd92f8e02f47f446cdb0b55ceae01de6879fcb3a"
},
"grill-me": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/productivity/grill-me/SKILL.md",
"computedHash": "f361db4e15e6bfd562a9282b1dccda513910a50061f9e838ce017be9c69dde3f"
},
"grill-with-docs": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/engineering/grill-with-docs/SKILL.md",
"computedHash": "9c460cbd94fd3c63cdef967dbdb6e66ca687103cdc380cd37834e4d10b738f78"
},
"grilling": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/productivity/grilling/SKILL.md",
"computedHash": "368d3dd7251247c69f3656d93dc83c8f1577792eacac2111f1e7981db2ece49b"
},
"handoff": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/productivity/handoff/SKILL.md",
"computedHash": "ad03e8d4ea3cbbff66420eb7ba3cc375b5cbe1821a2449b53e863256cf5b5cde"
},
"implement": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/engineering/implement/SKILL.md",
"computedHash": "2139cfedf24791adbc839aaab6019cff158af1e28bfead020ec6e0ce01b3e74d"
},
"improve-codebase-architecture": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/engineering/improve-codebase-architecture/SKILL.md",
"computedHash": "66e8a50c83c3c724fcfe0769701b665c56cec220cc4f49cc1aee8bdfc07de94a"
},
"loop-me": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/in-progress/loop-me/SKILL.md",
"computedHash": "e1fcab9b531b338eb540c9d57ee089e2660e4bba1d7623ec566c6f32f77f2d26"
},
"migrate-to-shoehorn": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/misc/migrate-to-shoehorn/SKILL.md",
"computedHash": "6397731ced114f3657aa88b55ed13d1344a56d77ca449c568e3200c21740fa99"
},
"obsidian-vault": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/personal/obsidian-vault/SKILL.md",
"computedHash": "5c08eda96fd76a71793c0a8cb91bef41d518586654a14af4f2edf8f8fd0f96a7"
},
"prototype": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/engineering/prototype/SKILL.md",
"computedHash": "faba901c53a6ca245174c4ba5db3929d14253232b3050fdbd46539720adf1ab8"
},
"qa": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/deprecated/qa/SKILL.md",
"computedHash": "223de2b02aa9ab36cfa4fe93e80e527b56e912e7ae725f9d596f6d2457afab77"
},
"request-refactor-plan": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/deprecated/request-refactor-plan/SKILL.md",
"computedHash": "08c06e587d0f979b8731f6a00c67fb8d00e563614daf80bf648d41148e553473"
},
"research": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/engineering/research/SKILL.md",
"computedHash": "bd3e2c6826671d82c86ed0da3dac3370ebcf63b0fe847f91bb444e1fb7dac21b"
},
"resolving-merge-conflicts": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/engineering/resolving-merge-conflicts/SKILL.md",
"computedHash": "28aad6f8b1b7025abc8892fa8890f68fe1533499b28a565f416b159131d22fad"
},
"scaffold-exercises": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/misc/scaffold-exercises/SKILL.md",
"computedHash": "354c91f6dbc9b058632f30594aacb4edc6d25012596585abb00402af8d7ec5e9"
},
"setup-matt-pocock-skills": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/engineering/setup-matt-pocock-skills/SKILL.md",
"computedHash": "74e894a3509e2676d4cdb771c8eace087092430635e845e02a9cc2f757c552a4"
},
"setup-pre-commit": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/misc/setup-pre-commit/SKILL.md",
"computedHash": "1b140af948d0a6330c4cf52d46ca2e06a0e96fdc34d22ec8888d2b9751b99b62"
},
"setup-ts-deep-modules": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/in-progress/setup-ts-deep-modules/SKILL.md",
"computedHash": "bd5cbaa455454c2d6c27a1b734e0d820606da6c482bf3c79ec55fbc3f3e03ef3"
},
"tdd": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/engineering/tdd/SKILL.md",
"computedHash": "81eca2a5b53a63f481c0849be7a663a8cd43d5cf53f32b644ec0a2f50cf91aa2"
},
"teach": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/productivity/teach/SKILL.md",
"computedHash": "68999bb1a241384b2f921f3321d779b7d05eb6089077b70d88cee2aee3d75ccc"
},
"to-questionnaire": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/in-progress/to-questionnaire/SKILL.md",
"computedHash": "d8938509f3400d9977343e11e876fc6e1982b0b9c133a2f2e9d3d730a67a103e"
},
"to-spec": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/engineering/to-spec/SKILL.md",
"computedHash": "0f544cd0c099c06f0dd0b7b9ee98b4237218e7e95fd3d3c02e791efbaf74bacb"
},
"to-tickets": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/engineering/to-tickets/SKILL.md",
"computedHash": "5d79577541b5cf6dade61c69844432ea20ce7b12527cdda1dd4d6fe8d59a135d"
},
"triage": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/engineering/triage/SKILL.md",
"computedHash": "7c923b6a677cfe689500721f08307e2a4c46797ff169dc55ef6c34a431a0d533"
},
"ubiquitous-language": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/deprecated/ubiquitous-language/SKILL.md",
"computedHash": "0395170031b57ea63f2eb22561542894c03e3047430a2f1118a06b411054429b"
},
"wayfinder": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/engineering/wayfinder/SKILL.md",
"computedHash": "c9e18cefd77b6b5b0ee35f59ce2fd96359aa2d2f02e3479e12c873c5402d9d43"
},
"wizard": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/in-progress/wizard/SKILL.md",
"computedHash": "dab67fb9fdcc70f3a06cf2facecf58f405f478504812103b53d4c5a84765254d"
},
"writing-beats": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/in-progress/writing-beats/SKILL.md",
"computedHash": "7581e49a991e45e4e486b128c6391b82bd112c143cea3cc4426c23530dd545af"
},
"writing-fragments": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/in-progress/writing-fragments/SKILL.md",
"computedHash": "972ab831c7a39dab971439fa4718b668114d3063d347d8ba29b2a19765b3f911"
},
"writing-great-skills": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/productivity/writing-great-skills/SKILL.md",
"computedHash": "4deb21855fb4deeeb1b8217b041faff003fd0c915d24a22f636851c520bc9c5c"
},
"writing-shape": {
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/in-progress/writing-shape/SKILL.md",
"computedHash": "90899c7b3853a80da0d9ef634844b22871cc3ea6aa1bc9e4299f40484ac05392"
}
}
}