bailian-cli 1.13.0 → 1.14.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 CHANGED
@@ -24,7 +24,7 @@ _Built for AI Agents. Every command works as a structured tool call._
24
24
 
25
25
  Equip your AI Agent out-of-the-box with these capabilities, composable across complex tasks:
26
26
 
27
- - **Text chat** — Qwen3.7-max: major gains in agentic coding, frontend coding, and vibe coding
27
+ - **Text chat** — Qwen3.8-max: major gains in agentic coding, frontend coding, and vibe coding
28
28
  - **Multimodal (Omni)** — Full omni-modal support across text + image + audio + video
29
29
  - **Image generation & editing** — Qwen-Image 2.0: pro text rendering, photorealism, strong semantic adherence, multi-image composition
30
30
  - **Video generation & editing** — happyhorse-1.1 series: text-/image-/reference-to-video and natural-language video editing (up to 9-image reference)
@@ -77,11 +77,17 @@ No timeline scrubbing. No frame-by-frame editing. Just one sentence → one vide
77
77
  ## Installation
78
78
 
79
79
  ```bash
80
+ # Recommended — no Node required
81
+ curl -fsSL https://bailian.aliyun.com/cli/install.sh | bash
82
+
83
+ # Windows (PowerShell)
84
+ irm https://bailian.aliyun.com/cli/install.ps1 | iex
85
+
86
+ # Node users / developers (Node.js >= 18.17)
80
87
  npm install -g bailian-cli
81
- npx skills add modelstudioai/cli --all -g
82
88
  ```
83
89
 
84
- > Requires Node.js >= 18.17.
90
+ > Binary install does not require Node.js. `npm install -g` remains fully supported.
85
91
 
86
92
  ## Quick Start
87
93
 
@@ -210,8 +216,9 @@ bl config set --key base_url --value https://dashscope-us.aliyuncs.com
210
216
  bl config set --key default_text_model --value qwen-turbo
211
217
  bl config set --key timeout --value 600
212
218
 
213
- # Self-update to latest version
219
+ # Self-update to latest or a specific version
214
220
  bl update
221
+ bl update --to 0.1.14
215
222
  ```
216
223
 
217
224
  Config file location: `~/.bailian/config.json`
package/README.zh.md CHANGED
@@ -24,7 +24,7 @@ _专为 AI Agent 打造,每个命令均可作为结构化工具调用。_
24
24
 
25
25
  让您的 AI Agent 开箱即具备以下能力,并可在复杂任务中自动组合调用:
26
26
 
27
- - **文本对话** — Qwen3.7-max:Agentic coding、前端编程、Vibe coding 等能力显著增强
27
+ - **文本对话** — Qwen3.8-max:Agentic coding、前端编程、Vibe coding 等能力显著增强
28
28
  - **全模态对话** — 文本 + 图像 + 音频 + 视频全模态支持
29
29
  - **图像生成与编辑** — Qwen-Image 2.0:专业文字渲染、真实质感、强语义遵循、多图合成
30
30
  - **视频生成与编辑** — happyhorse-1.1 系列,支持文生 / 图生 / 参考生(最多 9 张图参考)/ 自然语言视频编辑
@@ -75,11 +75,17 @@ _专为 AI Agent 打造,每个命令均可作为结构化工具调用。_
75
75
  ## 安装
76
76
 
77
77
  ```bash
78
+ # 推荐 — 无需本机 Node.js
79
+ curl -fsSL https://bailian.aliyun.com/cli/install.sh | bash
80
+
81
+ # Windows(PowerShell)
82
+ irm https://bailian.aliyun.com/cli/install.ps1 | iex
83
+
84
+ # Node 用户 / 开发者(需要 Node.js >= 18.17)
78
85
  npm install -g bailian-cli
79
- npx skills add modelstudioai/cli --all -g
80
86
  ```
81
87
 
82
- > 需要预先安装 Node.js >= 18.17。
88
+ > 二进制安装不依赖 Node.js。`npm install -g` 长期保留。
83
89
 
84
90
  ## 快速开始
85
91
 
@@ -210,6 +216,9 @@ bl config set --key timeout --value 600
210
216
 
211
217
  # 自更新到最新版本
212
218
  bl update
219
+
220
+ # 安装指定版本
221
+ bl update --to 0.1.14
213
222
  ```
214
223
 
215
224
  配置文件位置:`~/.bailian/config.json`
package/dist/bailian.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import{createCli as e}from"bailian-cli-runtime";import{advisorRecommend as t,appCall as n,appList as r,authGenerateAccessToken as i,authLogin as a,authLogout as o,authStatus as s,configAgent as c,configList as l,configSet as u,configShow as d,configUi as f,configUse as p,consoleCall as m,datasetDelete as h,datasetGet as g,datasetList as _,datasetUpload as v,datasetValidate as y,deployAudioCreate as b,deployDelete as x,deployGet as S,deployImageCreate as C,deployList as w,deployModels as T,deployScale as E,deployTextCreate as D,deployUpdate as O,fileUpload as k,finetuneAudioCreate as A,finetuneCancel as j,finetuneCapability as M,finetuneCheckpoints as N,finetuneDelete as P,finetuneExport as F,finetuneGet as I,finetuneImageCreate as L,finetuneList as R,finetuneLogs as z,finetuneTextCreate as B,finetuneWatch as V,imageEdit as H,imageGenerate as U,knowledgeChat as W,knowledgeRetrieve as G,knowledgeSearch as K,managedAgentApply as q,managedAgentDestroy as J,managedAgentInit as Y,managedAgentPlan as X,managedAgentSessionCreate as Z,managedAgentSessionDelete as Q,managedAgentSessionEvents as $,managedAgentSessionGet as ee,managedAgentSessionList as te,managedAgentSessionRun as ne,managedAgentSessionSend as re,managedAgentSkillList as ie,managedAgentStateImport as ae,managedAgentStateList as oe,managedAgentStateRm as se,managedAgentStateShow as ce,managedAgentValidate as le,mcpCall as ue,mcpList as de,mcpTools as fe,memoryAdd as pe,memoryDelete as me,memoryList as he,memoryProfileCreate as ge,memoryProfileGet as _e,memorySearch as ve,memoryUpdate as ye,modelList as be,pipelineRun as xe,pipelineValidate as Se,pluginInstall as Ce,pluginLink as we,pluginList as Te,pluginRemove as Ee,quotaCheck as De,quotaHistory as Oe,quotaList as ke,quotaRequest as Ae,searchWeb as je,speechRecognize as Me,speechSynthesize as Ne,textChat as Pe,textOmni as Fe,tokenPlanAddMember as Ie,tokenPlanAssignSeats as Le,tokenPlanCreateKey as Re,tokenPlanListSeats as ze,update as Be,usageFree as Ve,usageFreetier as He,usageStats as Ue,usageSummary as We,videoDownload as Ge,videoEdit as Ke,videoGenerate as qe,videoRef as Je,videoTaskGet as Ye,visionDescribe as Xe,workspaceInit as Ze,workspaceList as Qe}from"bailian-cli-commands";e({"auth login":a,"auth status":s,"auth logout":o,"auth generate-access-token":i,"text chat":Pe,omni:Fe,"image generate":U,"image edit":H,"video generate":qe,"video edit":Ke,"video ref":Je,"video task get":Ye,"video download":Ge,"vision describe":Xe,"config show":d,"config set":u,"config list":l,"config use":p,"config ui":f,"config agent":c,update:Be,"app call":n,"app list":r,"memory add":pe,"memory search":ve,"memory list":he,"memory update":ye,"memory delete":me,"memory profile create":ge,"memory profile get":_e,"knowledge retrieve":G,"knowledge search":K,"knowledge chat":W,"mcp call":ue,"mcp list":de,"mcp tools":fe,"search web":je,"speech synthesize":Ne,"speech recognize":Me,"file upload":k,"console call":m,"usage free":Ve,"usage freetier":He,"usage stats":Ue,"usage summary":We,"pipeline run":xe,"pipeline validate":Se,"advisor recommend":t,"model list":be,"workspace list":Qe,"quota list":ke,"quota request":Ae,"quota history":Oe,"quota check":De,"dataset upload":v,"dataset list":_,"dataset get":g,"dataset delete":h,"dataset validate":y,"finetune text create":B,"finetune audio create":A,"finetune image create":L,"finetune list":R,"finetune get":I,"finetune cancel":j,"finetune delete":P,"finetune logs":z,"finetune checkpoints":N,"finetune export":F,"finetune watch":V,"finetune capability":M,"deploy text create":D,"deploy audio create":b,"deploy image create":C,"deploy list":w,"deploy get":S,"deploy models":T,"deploy scale":E,"deploy update":O,"deploy delete":x,"token-plan list-seats":ze,"token-plan create-key":Re,"token-plan assign-seats":Le,"token-plan add-member":Ie,"workspace init":Ze,"plugin install":Ce,"plugin link":we,"plugin list":Te,"plugin remove":Ee,"managed-agent init":Y,"managed-agent validate":le,"managed-agent plan":X,"managed-agent apply":q,"managed-agent destroy":J,"managed-agent state list":oe,"managed-agent state show":ce,"managed-agent state rm":se,"managed-agent state import":ae,"managed-agent session create":Z,"managed-agent session list":te,"managed-agent session get":ee,"managed-agent session delete":Q,"managed-agent session run":ne,"managed-agent session send":re,"managed-agent session events":$,"managed-agent skill-list":ie},{binName:`bl`,version:`1.13.0`,clientName:`bailian-cli`,npmPackage:`bailian-cli`,quickStartTasks:[`Help me generate a set of Amazon e-commerce main images for baseball caps (white background + lifestyle shots + model wear shots)`,`Help me generate a 3-minute humorous crosstalk audio clip`,`Help me generate a Little Red Riding Hood picture-book PDF (with illustrations)`,`Help me analyze this video and write a Xiaohongshu-style post`],commandPacks:{supported:{"@ali/bailian-plugin-agent":{commandPrefixes:[`agent`],credentialAccess:[`apiKey`]},"@ali/bailian-plugin-inner-console-call":{commandPrefixes:[`inner-console`]}}}}).run();export{};
3
+ import{createCli as e}from"bailian-cli-runtime";import{advisorRecommend as t,appCall as n,appList as r,authGenerateAccessToken as i,authLogin as a,authLogout as o,authStatus as s,configAgent as c,configList as l,configSet as u,configShow as d,configUi as f,configUse as p,consoleCall as m,datasetDelete as h,datasetGet as g,datasetList as _,datasetUpload as v,datasetValidate as y,deployAudioCreate as b,deployDelete as x,deployGet as S,deployImageCreate as C,deployList as w,deployModels as T,deployScale as E,deployTextCreate as D,deployUpdate as O,fileUpload as k,finetuneAudioCreate as A,finetuneCancel as j,finetuneCapability as M,finetuneCheckpoints as N,finetuneDelete as P,finetuneExport as F,finetuneGet as I,finetuneImageCreate as L,finetuneList as R,finetuneLogs as z,finetuneTextCreate as B,finetuneWatch as V,imageEdit as H,imageGenerate as U,knowledgeChat as W,knowledgeRetrieve as G,knowledgeSearch as K,managedAgentApply as q,managedAgentDestroy as J,managedAgentInit as Y,managedAgentPlan as X,managedAgentSessionCreate as Z,managedAgentSessionDelete as Q,managedAgentSessionEvents as $,managedAgentSessionGet as ee,managedAgentSessionList as te,managedAgentSessionRun as ne,managedAgentSessionSend as re,managedAgentSkillList as ie,managedAgentStateImport as ae,managedAgentStateList as oe,managedAgentStateRm as se,managedAgentStateShow as ce,managedAgentValidate as le,mcpCall as ue,mcpList as de,mcpTools as fe,memoryAdd as pe,memoryDelete as me,memoryList as he,memoryProfileCreate as ge,memoryProfileGet as _e,memorySearch as ve,memoryUpdate as ye,modelList as be,pipelineRun as xe,pipelineValidate as Se,pluginInstall as Ce,pluginLink as we,pluginList as Te,pluginRemove as Ee,quotaCheck as De,quotaHistory as Oe,quotaList as ke,quotaRequest as Ae,searchWeb as je,skillAdd as Me,skillList as Ne,skillRemove as Pe,skillUpdate as Fe,speechRecognize as Ie,speechSynthesize as Le,textChat as Re,textOmni as ze,tokenPlanAddMember as Be,tokenPlanAssignSeats as Ve,tokenPlanCreateKey as He,tokenPlanListSeats as Ue,update as We,usageFree as Ge,usageFreetier as Ke,usageStats as qe,usageSummary as Je,videoDownload as Ye,videoEdit as Xe,videoGenerate as Ze,videoRef as Qe,videoTaskGet as $e,visionDescribe as et,workspaceInit as tt,workspaceList as nt}from"bailian-cli-commands";e({"auth login":a,"auth status":s,"auth logout":o,"auth generate-access-token":i,"text chat":Re,omni:ze,"image generate":U,"image edit":H,"video generate":Ze,"video edit":Xe,"video ref":Qe,"video task get":$e,"video download":Ye,"vision describe":et,"config show":d,"config set":u,"config list":l,"config use":p,"config ui":f,"config agent":c,update:We,"app call":n,"app list":r,"memory add":pe,"memory search":ve,"memory list":he,"memory update":ye,"memory delete":me,"memory profile create":ge,"memory profile get":_e,"knowledge retrieve":G,"knowledge search":K,"knowledge chat":W,"mcp call":ue,"mcp list":de,"mcp tools":fe,"search web":je,"speech synthesize":Le,"speech recognize":Ie,"file upload":k,"console call":m,"usage free":Ge,"usage freetier":Ke,"usage stats":qe,"usage summary":Je,"pipeline run":xe,"pipeline validate":Se,"advisor recommend":t,"model list":be,"workspace list":nt,"quota list":ke,"quota request":Ae,"quota history":Oe,"quota check":De,"dataset upload":v,"dataset list":_,"dataset get":g,"dataset delete":h,"dataset validate":y,"finetune text create":B,"finetune audio create":A,"finetune image create":L,"finetune list":R,"finetune get":I,"finetune cancel":j,"finetune delete":P,"finetune logs":z,"finetune checkpoints":N,"finetune export":F,"finetune watch":V,"finetune capability":M,"deploy text create":D,"deploy audio create":b,"deploy image create":C,"deploy list":w,"deploy get":S,"deploy models":T,"deploy scale":E,"deploy update":O,"deploy delete":x,"token-plan list-seats":Ue,"token-plan create-key":He,"token-plan assign-seats":Ve,"token-plan add-member":Be,"workspace init":tt,"plugin install":Ce,"plugin link":we,"plugin list":Te,"plugin remove":Ee,"skill add":Me,"skill update":Fe,"skill remove":Pe,"skill list":Ne,"managed-agent init":Y,"managed-agent validate":le,"managed-agent plan":X,"managed-agent apply":q,"managed-agent destroy":J,"managed-agent state list":oe,"managed-agent state show":ce,"managed-agent state rm":se,"managed-agent state import":ae,"managed-agent session create":Z,"managed-agent session list":te,"managed-agent session get":ee,"managed-agent session delete":Q,"managed-agent session run":ne,"managed-agent session send":re,"managed-agent session events":$,"managed-agent skill-list":ie},{binName:`bl`,version:`1.14.0`,clientName:`bailian-cli`,npmPackage:`bailian-cli`,quickStartTasks:[`Help me generate a set of Amazon e-commerce main images for baseball caps (white background + lifestyle shots + model wear shots)`,`Help me generate a 3-minute humorous crosstalk audio clip`,`Help me generate a Little Red Riding Hood picture-book PDF (with illustrations)`,`Help me analyze this video and write a Xiaohongshu-style post`],commandPacks:{supported:{"@ali/bailian-plugin-agent":{commandPrefixes:[`agent`],credentialAccess:[`apiKey`]},"@ali/bailian-plugin-inner-console-call":{commandPrefixes:[`inner-console`]}}}}).run();export{};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bailian-cli",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "description": "CLI for Aliyun Model Studio (DashScope) AI Platform.",
5
5
  "keywords": [
6
6
  "agent",
@@ -25,7 +25,8 @@
25
25
  },
26
26
  "files": [
27
27
  "dist",
28
- "README.zh.md"
28
+ "README.zh.md",
29
+ "postinstall.js"
29
30
  ],
30
31
  "type": "module",
31
32
  "exports": {
@@ -36,9 +37,10 @@
36
37
  "registry": "https://registry.npmjs.org/"
37
38
  },
38
39
  "dependencies": {
39
- "bailian-cli-commands": "1.13.0",
40
- "bailian-cli-core": "1.13.0",
41
- "bailian-cli-runtime": "1.13.0"
40
+ "tar-stream": "^3.2.0",
41
+ "bailian-cli-commands": "1.14.0",
42
+ "bailian-cli-runtime": "1.14.0",
43
+ "bailian-cli-core": "1.14.0"
42
44
  },
43
45
  "devDependencies": {
44
46
  "@clack/prompts": "^0.7.0",
@@ -62,6 +64,7 @@
62
64
  "build": "vp pack",
63
65
  "dev": "tsx src/main.ts",
64
66
  "test": "vp test",
65
- "check": "vp check"
67
+ "check": "vp check",
68
+ "postinstall": "node postinstall.js"
66
69
  }
67
70
  }
package/postinstall.js ADDED
@@ -0,0 +1,213 @@
1
+ /**
2
+ * postinstall.js — Wiki data sync (layer 1: triggered by npm install)
3
+ *
4
+ * Runs automatically after npm/pnpm installs bailian-cli: unconditionally downloads the full Wiki data
5
+ * package and overwrites the local directory, ensuring data is in place the first time the user runs
6
+ * `bl advisor recommend`.
7
+ *
8
+ * Flow (unified skill publishing protocol: skills/index.json + one content-addressed object per skill):
9
+ * 1. Download skills/index.json from public-read OSS, get the bailian-docs-llm-wiki entry
10
+ * 2. Download skills/bailian-docs-llm-wiki/<entry.object> (sha256-<hex>.tar.br, brotli q6, ~2.3MB);
11
+ * legacy fallback to skill.tar.br when the entry has no valid object field
12
+ * 3. Node built-in brotli decompress + tar-stream extract (per-entry path safety check) to same-volume temp dir
13
+ * 4. renameSync atomic swap into ~/.bailian/skills/bailian-docs-llm-wiki/
14
+ * 5. Write ~/.bailian/wiki-sync-state.json
15
+ * 6. Write ~/.bailian/skills/skill-lock.json record (same ledger as bl skill)
16
+ *
17
+ * Design constraints:
18
+ * - Unconditional overwrite: every install fully replaces, no version comparison
19
+ * - Silent failure: any step failure → console.warn → process.exit(0), never blocks install
20
+ * - Standalone implementation: does not import bailian-cli-core, avoiding ESM path issues after bundling
21
+ * - Depends on Node built-in modules + tar-stream (consistent with sync.ts / publisher skills-publish.mjs)
22
+ */
23
+ import {
24
+ createWriteStream,
25
+ existsSync,
26
+ mkdirSync,
27
+ readFileSync,
28
+ renameSync,
29
+ rmSync,
30
+ writeFileSync,
31
+ } from "node:fs";
32
+ import { homedir } from "node:os";
33
+ import { dirname, join } from "node:path";
34
+ import { Readable } from "node:stream";
35
+ import { pipeline } from "node:stream/promises";
36
+ import { createBrotliDecompress } from "node:zlib";
37
+ import tar from "tar-stream";
38
+
39
+ const REGISTRY_BASE_URL = "https://bailian-wiki.oss-cn-hangzhou.aliyuncs.com/skills";
40
+ const WIKI_SKILL_NAME = "bailian-docs-llm-wiki";
41
+ const CONFIG_DIR_NAME = ".bailian";
42
+ const SKILL_DIR_NAME = "skills/bailian-docs-llm-wiki";
43
+ const STATE_FILE_NAME = "wiki-sync-state.json";
44
+ const INDEX_KEY = "index.json";
45
+ /** Legacy fixed asset key (entries without a valid content-addressed object field) */
46
+ const LEGACY_ASSET_NAME = "skill.tar.br";
47
+ /** Same strict shape check as core registry.ts: only a valid object name may enter the URL */
48
+ const OBJECT_FILE_RE = /^sha256-[0-9a-f]{64}\.tar\.br$/;
49
+
50
+ const INDEX_TIMEOUT_MS = 3000;
51
+ const DOWNLOAD_TIMEOUT_MS = 30000;
52
+
53
+ function getConfigDir() {
54
+ if (process.env.BAILIAN_CONFIG_DIR) return process.env.BAILIAN_CONFIG_DIR;
55
+ return join(homedir(), CONFIG_DIR_NAME);
56
+ }
57
+
58
+ function getCatalogDir() {
59
+ return join(getConfigDir(), SKILL_DIR_NAME);
60
+ }
61
+
62
+ function getStatePath() {
63
+ return join(getConfigDir(), STATE_FILE_NAME);
64
+ }
65
+
66
+ function getSkillLockPath() {
67
+ return join(getConfigDir(), "skills", "skill-lock.json");
68
+ }
69
+
70
+ /**
71
+ * Record this sync in skill-lock.json (same ledger as bl skill; list shows installed).
72
+ * Semantics aligned with upsertSkillLockEntry in core/src/skills/lock.ts: shallow-merge with the existing
73
+ * entry, preserving fields like links written by bl skill add; rebuild as empty table if lock is corrupted/unrecognized.
74
+ * best-effort: failure does not affect data sync results.
75
+ */
76
+ function upsertSkillLock(name, entry) {
77
+ try {
78
+ let lock = { version: 1, skills: {} };
79
+ try {
80
+ const parsed = JSON.parse(readFileSync(getSkillLockPath(), "utf-8"));
81
+ if (parsed?.version === 1 && parsed.skills && typeof parsed.skills === "object") {
82
+ lock = parsed;
83
+ }
84
+ } catch {
85
+ /* absent/corrupted → empty table */
86
+ }
87
+ lock.skills[name] = { ...lock.skills[name], ...entry };
88
+ mkdirSync(dirname(getSkillLockPath()), { recursive: true });
89
+ writeFileSync(getSkillLockPath(), JSON.stringify(lock, null, 2) + "\n");
90
+ } catch {
91
+ /* Bookkeeping failure does not block install; advisor-side sync will backfill */
92
+ }
93
+ }
94
+
95
+ async function fetchJson(url, timeoutMs) {
96
+ const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
97
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
98
+ return res.json();
99
+ }
100
+
101
+ async function downloadBuffer(url) {
102
+ const res = await fetch(url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });
103
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
104
+ return Buffer.from(await res.arrayBuffer());
105
+ }
106
+
107
+ /** tar 条目路径必须是相对路径且不含 ..,防止 tar-slip 逃逸解包目录 */
108
+ function isSafeEntryName(name) {
109
+ if (name.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(name)) return false;
110
+ return !name.split("/").includes("..");
111
+ }
112
+
113
+ /** Brotli decompress + tar-stream extract into destDir (symmetric with publisher tar.pack()). */
114
+ async function extractTarBr(tarBrBuffer, destDir) {
115
+ const extract = tar.extract();
116
+
117
+ extract.on("entry", (header, stream, next) => {
118
+ if (!isSafeEntryName(header.name)) {
119
+ // Same semantics as core skills/extract.ts: destroy so the pipeline rejects with this
120
+ // error; silence the entry stream to avoid its companion error becoming unhandled
121
+ stream.on("error", () => {});
122
+ stream.resume();
123
+ extract.destroy(new Error(`unsafe tar entry: ${header.name}`));
124
+ return;
125
+ }
126
+ const filePath = join(destDir, header.name);
127
+ if (header.type === "directory") {
128
+ mkdirSync(filePath, { recursive: true });
129
+ stream.resume();
130
+ stream.on("end", next);
131
+ return;
132
+ }
133
+ mkdirSync(dirname(filePath), { recursive: true });
134
+ const ws = createWriteStream(filePath);
135
+ stream.pipe(ws);
136
+ ws.on("finish", next);
137
+ ws.on("error", next);
138
+ });
139
+
140
+ await pipeline(Readable.from(tarBrBuffer), createBrotliDecompress(), extract);
141
+ }
142
+
143
+ /** Atomic swap: tmpDir (same volume) → catalogDir. */
144
+ function atomicSwap(tmpDir, catalogDir) {
145
+ mkdirSync(dirname(catalogDir), { recursive: true });
146
+ const backup = `${catalogDir}.old-${Date.now()}`;
147
+ if (existsSync(catalogDir)) renameSync(catalogDir, backup);
148
+ try {
149
+ renameSync(tmpDir, catalogDir);
150
+ } catch (err) {
151
+ if (existsSync(backup) && !existsSync(catalogDir)) renameSync(backup, catalogDir);
152
+ throw err;
153
+ }
154
+ if (existsSync(backup)) rmSync(backup, { recursive: true, force: true });
155
+ }
156
+
157
+ async function main() {
158
+ // 1. Download skills/index.json and get the wiki entry
159
+ const index = await fetchJson(`${REGISTRY_BASE_URL}/${INDEX_KEY}`, INDEX_TIMEOUT_MS);
160
+ const entry = index?.skills?.[WIKI_SKILL_NAME];
161
+ if (!entry?.contentHash)
162
+ throw new Error("no bailian-docs-llm-wiki entry (or contentHash) in index.json");
163
+
164
+ // 2. Download the skill archive: content-addressed object first, legacy fixed key as fallback
165
+ const assetName =
166
+ entry.object && OBJECT_FILE_RE.test(entry.object) ? entry.object : LEGACY_ASSET_NAME;
167
+ const tarBuf = await downloadBuffer(`${REGISTRY_BASE_URL}/${WIKI_SKILL_NAME}/${assetName}`);
168
+
169
+ // 3. Extract to same-volume temp dir + atomic swap
170
+ const catalogDir = getCatalogDir();
171
+ const tmpDir = `${catalogDir}.tmp-${process.pid}-${Date.now()}`;
172
+ try {
173
+ mkdirSync(tmpDir, { recursive: true });
174
+ await extractTarBr(tarBuf, tmpDir);
175
+ atomicSwap(tmpDir, catalogDir);
176
+ } catch (err) {
177
+ if (existsSync(tmpDir)) rmSync(tmpDir, { recursive: true, force: true });
178
+ throw err;
179
+ }
180
+
181
+ // 4. Write state
182
+ try {
183
+ writeFileSync(
184
+ getStatePath(),
185
+ JSON.stringify({ lastChecked: Date.now(), contentHash: entry.contentHash }),
186
+ );
187
+ } catch {
188
+ /* state write failure has no impact: first recommend will re-check */
189
+ }
190
+
191
+ // 5. skill-lock.json record: wiki shares the same ledger as bl skill
192
+ upsertSkillLock(WIKI_SKILL_NAME, {
193
+ contentHash: entry.contentHash,
194
+ ...(entry.publishedAt ? { publishedAt: entry.publishedAt } : {}),
195
+ installedAt: new Date().toISOString(),
196
+ sourceType: "oss",
197
+ ...(entry.description ? { description: entry.description } : {}),
198
+ });
199
+
200
+ process.stdout.write(`bailian-cli: wiki data ready (${entry.publishedAt ?? "latest"})\n`);
201
+ }
202
+
203
+ main().catch((err) => {
204
+ // Unconditional pass-through: install-time network/permission issues should not block npm install;
205
+ // sync.ts will fall back to syncing on the first `bl advisor recommend`.
206
+ const msg = err instanceof Error ? err.message : String(err);
207
+ process.stderr.write(
208
+ `bailian-cli: wiki data pre-download skipped (${msg}); will sync automatically on first use.\n`,
209
+ );
210
+ // Force a success exit code so a download failure never fails `npm install`.
211
+ // eslint-disable-next-line unicorn/no-process-exit
212
+ process.exit(0);
213
+ });