cbs-4px-merchant-cli 0.0.2 → 0.0.4

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.
@@ -1,184 +1,54 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Postinstall script: automatically install SKILL.md to all detected AI coding tools.
4
+ * Postinstall hook — a best-effort convenience wrapper, nothing more.
5
5
  *
6
- * For each tool whose root dir exists, writes SKILL.md + reference/ to
7
- * <root>/skills/cbs-merchant-cli/.
6
+ * All skill logic lives in `src/services/skill-installer.ts` and is reachable
7
+ * as `<bin> skill install`; this hook only spawns that command so users on npm
8
+ * versions that still run install scripts get the skill without a second step.
8
9
  *
9
- * Inspired by bailian-cli's postinstall approach.
10
+ * It deliberately owns no logic of its own, because it cannot be relied upon:
11
+ * npm 11 blocks dependency install scripts by default and *still exits 0*, so
12
+ * anything that must happen cannot live here. When the hook is skipped, the
13
+ * install docs' second step (`<bin> skill install`) covers it.
14
+ *
15
+ * Never fails the install: any problem exits 0 with a short hint on stderr.
10
16
  */
11
17
 
12
- import {
13
- existsSync,
14
- mkdirSync,
15
- copyFileSync,
16
- cpSync,
17
- openSync,
18
- writeSync,
19
- writeFileSync,
20
- closeSync,
21
- readFileSync,
22
- } from 'fs';
18
+ import { existsSync, readFileSync } from 'fs';
23
19
  import { join, dirname } from 'path';
24
- import { homedir } from 'os';
25
20
  import { fileURLToPath } from 'url';
26
- import { execSync } from 'child_process';
21
+ import { spawnSync } from 'child_process';
27
22
 
28
23
  const __dirname = dirname(fileURLToPath(import.meta.url));
29
- const skillSource = join(__dirname, '..', 'skill', 'SKILL.md');
30
- const referenceDirSource = join(__dirname, '..', 'skill', 'reference');
31
-
32
- if (!existsSync(skillSource)) {
33
- process.exit(0);
34
- }
35
-
36
- // ── Site metadata (generated at build time by scripts/build.js) ──
37
- // Falls back to safe defaults so a source checkout without a build still runs.
38
- const siteMeta = (() => {
39
- const defaults = {
40
- siteId: '4px',
41
- env: 'prod',
42
- baseUrl: 'https://b.4px.com',
43
- akUrl: 'https://b.4px.com/site/ak-manage',
44
- packageName: 'cbs-merchant-cli',
45
- binName: 'mc',
46
- };
47
- try {
48
- const metaPath = join(__dirname, '..', 'site-meta.json');
49
- const parsed = JSON.parse(readFileSync(metaPath, 'utf-8'));
50
- return { ...defaults, ...parsed };
51
- } catch {
52
- return defaults;
53
- }
54
- })();
55
-
56
- // Skill install dir name derived from package name (e.g. cbs-4px-merchant-cli).
57
- const skillDirName = siteMeta.packageName.replace(/^@[^/]+\//, '');
58
-
59
- // Site-specific CLI command name (e.g. fmc / cmc); falls back to mc.
60
- const bin = siteMeta.binName || 'mc';
61
-
62
- const home = homedir();
24
+ const ROOT = join(__dirname, '..');
25
+ const ENTRY = join(ROOT, 'dist', 'index.js');
63
26
 
64
- const targets = [
65
- { root: join(home, '.qoder'), label: 'Qoder' },
66
- { root: join(home, '.claude'), label: 'Claude Code' },
67
- { root: join(home, '.cline'), label: 'Cline' },
68
- { root: join(home, '.qwen'), label: 'QwenCode' },
69
- { root: join(home, '.cursor'), label: 'Cursor' },
70
- { root: join(home, '.qoderwork'), label: 'QoderWork' },
71
- { root: join(home, '.codex'), label: 'Codex' },
72
- { root: join(home, '.kilo'), label: 'Kilo Code' },
73
- { root: join(home, '.openclaw'), label: 'OpenCLaw' },
74
- { root: join(home, '.config', 'opencode'), label: 'OpenCode' },
75
- ];
76
-
77
- const available = targets.filter(({ root }) => existsSync(root));
78
-
79
- if (available.length === 0) {
27
+ // Source checkout that has not been built yet — nothing to install.
28
+ if (!existsSync(ENTRY) || !existsSync(join(ROOT, 'skill', 'SKILL.md'))) {
80
29
  process.exit(0);
81
30
  }
82
31
 
83
- // Write to /dev/tty to bypass npm output suppression
84
- let ttyFd;
85
- try {
86
- ttyFd = openSync('/dev/tty', 'w');
87
- } catch {
88
- ttyFd = null;
89
- }
90
-
91
- function ttyPrint(msg) {
92
- if (ttyFd != null) {
93
- writeSync(ttyFd, msg + '\n');
94
- } else {
95
- // Fallback for environments without /dev/tty (e.g. Claude Code, CI agents)
96
- console.log(msg);
97
- }
98
- }
99
-
100
- ttyPrint(
101
- `\n🚀 Installing BPortal Merchant CLI skill to ${available.length} AI coding tool(s):`,
102
- );
32
+ const result = spawnSync(process.execPath, [ENTRY, 'skill', 'install'], {
33
+ stdio: ['ignore', 'ignore', 'inherit'],
34
+ });
103
35
 
104
- for (const { root, label } of available) {
105
- try {
106
- const targetDir = join(root, 'skills', skillDirName);
107
- mkdirSync(targetDir, { recursive: true });
108
- copyFileSync(skillSource, join(targetDir, 'SKILL.md'));
109
- if (existsSync(referenceDirSource)) {
110
- cpSync(referenceDirSource, join(targetDir, 'reference'), {
111
- recursive: true,
112
- });
113
- }
114
- // Write version marker for drift detection at CLI runtime
115
- const pkgJsonPath = join(__dirname, '..', 'package.json');
116
- let skillVersion = 'unknown';
117
- try {
118
- const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
119
- if (typeof pkg.version === 'string') skillVersion = pkg.version;
120
- } catch { /* fallback to unknown */ }
121
- writeFileSync(join(targetDir, '.skill-version'), skillVersion);
122
- ttyPrint(` ✓ ${label} (v${skillVersion})`);
123
- } catch {
124
- // Silent fail — don't block npm install
125
- }
36
+ if (result.status !== 0) {
37
+ const bin = resolveBinName();
38
+ process.stderr.write(
39
+ `\n⚠ skill 自动注册未完成,请手动执行: ${bin} skill install\n\n`,
40
+ );
126
41
  }
127
42
 
128
- // ── API Key detection ──
129
- const configPath = join(home, '.bportal', 'config.json');
130
- let hasApiKey = false;
43
+ process.exit(0);
131
44
 
132
- if (process.env.BPORTAL_API_KEY) {
133
- hasApiKey = true;
134
- }
135
- if (!hasApiKey && existsSync(configPath)) {
45
+ /** Site-specific command name (fmc / cmc); falls back to the base name. */
46
+ function resolveBinName() {
136
47
  try {
137
- const cfg = JSON.parse(readFileSync(configPath, 'utf-8'));
138
- if (typeof cfg.apiKey === 'string' && cfg.apiKey.length > 0) hasApiKey = true;
48
+ // site-meta.json is written by scripts/build.js; absent in an unbuilt checkout.
49
+ const meta = JSON.parse(readFileSync(join(ROOT, 'site-meta.json'), 'utf-8'));
50
+ return meta.binName || 'mc';
139
51
  } catch {
140
- /* ignore */
52
+ return 'mc';
141
53
  }
142
54
  }
143
-
144
- if (hasApiKey) {
145
- ttyPrint('');
146
- ttyPrint('🎯 Try these commands:');
147
- ttyPrint('');
148
- ttyPrint(` ${bin} track get <orderNo> 查询物流轨迹`);
149
- ttyPrint(` ${bin} label get <orderNo> 获取面单 PDF`);
150
- ttyPrint(` ${bin} order get <orderNo> 查看订单详情`);
151
- ttyPrint(` ${bin} order get <orderNo> --output json`);
152
- ttyPrint('');
153
- } else {
154
- ttyPrint('');
155
- ttyPrint('💡 Set up your API Key to get started:');
156
- ttyPrint('');
157
- ttyPrint(' 1️⃣ Get an API Key:');
158
- ttyPrint(` ${siteMeta.akUrl}`);
159
- ttyPrint('');
160
- ttyPrint(' 2️⃣ Sign in:');
161
- ttyPrint(` ${bin} auth login --api-key SK_xxxxx`);
162
- ttyPrint('');
163
- ttyPrint(' 3️⃣ Then try:');
164
- ttyPrint(` ${bin} track get <orderNo>`);
165
- ttyPrint(` ${bin} order get <orderNo>`);
166
- ttyPrint('');
167
- }
168
-
169
- // ── PATH check ──
170
- try {
171
- execSync(`which ${bin}`, { stdio: 'ignore' });
172
- } catch {
173
- ttyPrint(`⚠️ Command "${bin}" not found in PATH.`);
174
- ttyPrint(' Your npm global bin directory may not be on PATH.');
175
- ttyPrint(' Run `npm config get prefix` and add its `bin/` to your PATH,');
176
- ttyPrint(' or reinstall with:');
177
- ttyPrint('');
178
- ttyPrint(` npm install -g ${siteMeta.packageName}`);
179
- ttyPrint('');
180
- }
181
-
182
- if (ttyFd != null) {
183
- closeSync(ttyFd);
184
- }
package/skill/SKILL.md CHANGED
@@ -37,7 +37,7 @@ description: >-
37
37
  | 地址簿 | [`reference/order.md`](reference/order.md) | 确认参数格式 |
38
38
  | 国家查询 | [`reference/order.md`](reference/order.md) | 确认参数格式 |
39
39
  | 产品推荐 | [`reference/product.md`](reference/product.md) | 参数多(国家/重量/电池/邮编等),必须确认 |
40
- | 理赔查询 | [`reference/claim.md`](reference/claim.md) | 参数多(单号/时间/状态/分页),必须确认 |
40
+ | 理赔查询 | [`reference/service.md`](reference/service.md) | 参数多(单号/时间/状态/分页),必须确认 |
41
41
  | 异常件管理 | [`reference/problem.md`](reference/problem.md) | 确认子命令和参数格式 |
42
42
  | 服务中心 | [`reference/service.md`](reference/service.md) | 确认子命令和参数格式 |
43
43
  | 财务管理 | [`reference/finance.md`](reference/finance.md) | 确认子命令和参数格式 |
@@ -61,7 +61,7 @@ description: >-
61
61
  | `label` | 面单打印:获取面单 PDF 链接 | [`reference/label.md`](reference/label.md) |
62
62
  | `order` | 订单管理:查看订单详情、订单列表、地址簿、目的国搜索 | [`reference/order.md`](reference/order.md) |
63
63
  | `product` | 产品推荐:推荐产品、产品列表、产品详情、时效查询、搜索产品 | [`reference/product.md`](reference/product.md) |
64
- | `claim` | 理赔查询:按单号/时间/状态查询理赔列表 | [`reference/claim.md`](reference/claim.md) |
64
+ | `claim` | 理赔查询:按单号/时间/状态查询理赔列表(挂在 `service` 下,即 `fmc service claim ...`) | [`reference/service.md`](reference/service.md) |
65
65
  | `problem` | 异常件管理:问题件列表、AI处理建议、通知件、尾程异常、欠费件、拦截件 | [`reference/problem.md`](reference/problem.md) |
66
66
  | `service` | 服务中心:售后工单、查件、投诉、服务单详情、处理记录、理赔详情 | [`reference/service.md`](reference/service.md) |
67
67
  | `finance` | 财务管理:账户余额、账单、每日费用、未出账汇总及明细、充值记录(在线及线下)、计费明细 | [`reference/finance.md`](reference/finance.md) |
@@ -86,7 +86,7 @@ description: >-
86
86
  用户提到"试算运费/运费计算" → `fmc product recommend`
87
87
  用户提到"时效/预估时效" → `fmc product eta`
88
88
  用户提到"搜索产品/找产品" → `fmc product search`
89
- 用户提到"理赔/索赔/赔偿/丢件赔付" → `fmc claim list`
89
+ 用户提到"理赔/索赔/赔偿/丢件赔付" → `fmc service claim list`
90
90
  用户提到"异常件/问题件/异常单/异常订单" → `fmc problem list`
91
91
  用户提到"处理建议/异常建议/AI建议" → `fmc problem suggest`
92
92
  用户提到"通知件" → `fmc problem notify`
@@ -116,10 +116,10 @@ description: >-
116
116
  |-----------|---------|--------|------|
117
117
  | "查一下这个单号" | `track get`(默认) | — | 无额外上下文时默认查轨迹;若用户要费用/收件人则走 `order get` |
118
118
  | "这个包裹的详情" | `order get` | `track get` | "详情"含收件人/费用等结构化信息 |
119
- | "我的包裹丢了" | `claim list` | `track get` | 用户意图是理赔而非查轨迹 |
119
+ | "我的包裹丢了" | `service claim list` | `track get` | 用户意图是理赔而非查轨迹 |
120
120
  | "寄快递/发货选什么" | `product recommend` | — | 发货前选品,需要国家参数 |
121
121
  | "运费多少" | `product recommend` | `order get` | 发货前询价用 product;已发货查费用用 order |
122
- | "查理赔" | `claim list` | `service claim-detail` | 前者查列表,后者按理赔单号查详情 |
122
+ | "查理赔" | `service claim list` | `service claim detail` | 前者查列表,后者按理赔单号查详情 |
123
123
  | "我的订单" | `order list` | `order get` | 前者是列表,后者按单号查详情 |
124
124
  | "运费/多少钱" | `product recommend`(发货前询价) | `order get`(已发货查费用) | 发货前用 product recommend 试算;已发货查费用用 order get |
125
125
 
@@ -129,10 +129,10 @@ description: >-
129
129
 
130
130
  ```bash
131
131
  # 配置 API Key(持久化到 ~/.bportal/config.json)
132
- fmc auth login --api-key SK_xxxxx
132
+ fmc auth login --api-key sk-xxxxx
133
133
 
134
134
  # 或通过环境变量
135
- export BPORTAL_API_KEY=SK_xxxxx
135
+ export BPORTAL_API_KEY=sk-xxxxx
136
136
  ```
137
137
 
138
138
  获取 API Key: https://b.4px.com/site/ak-manage
@@ -172,7 +172,7 @@ fmc order list --status IN_TRANSIT
172
172
  fmc product recommend --country US
173
173
 
174
174
  # 查询理赔列表(按单号)
175
- fmc claim list --biz-no FPX12345678
175
+ fmc service claim list --biz-no FPX12345678
176
176
 
177
177
  # 查询异常件
178
178
  fmc problem list
@@ -217,9 +217,8 @@ fmc config set baseUrl https://b.4px.com
217
217
  - [`reference/label.md`](reference/label.md) — 面单打印详细参数
218
218
  - [`reference/order.md`](reference/order.md) — 订单管理详细参数
219
219
  - [`reference/product.md`](reference/product.md) — 产品推荐与查询详细参数
220
- - [`reference/claim.md`](reference/claim.md) — 理赔查询详细参数
221
220
  - [`reference/problem.md`](reference/problem.md) — 异常件管理详细参数
222
- - [`reference/service.md`](reference/service.md) — 服务中心详细参数
221
+ - [`reference/service.md`](reference/service.md) — 服务中心与理赔查询详细参数
223
222
  - [`reference/finance.md`](reference/finance.md) — 财务管理详细参数
224
223
  - [`reference/pickup.md`](reference/pickup.md) — 揽收管理详细参数
225
224
  - [`reference/notice.md`](reference/notice.md) — 系统公告详细参数
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "cbs-4px-merchant-cli",
3
+ "version": "0.0.4",
4
+ "description": "BPortal Merchant CLI (fmc) skill — BPortal",
5
+ "files": [
6
+ "SKILL.md",
7
+ "package.json",
8
+ "reference/auth.md",
9
+ "reference/config.md",
10
+ "reference/customs.md",
11
+ "reference/finance.md",
12
+ "reference/index.md",
13
+ "reference/label.md",
14
+ "reference/notice.md",
15
+ "reference/order.md",
16
+ "reference/pickup.md",
17
+ "reference/problem.md",
18
+ "reference/product.md",
19
+ "reference/service.md",
20
+ "reference/track.md",
21
+ "reference/update.md"
22
+ ],
23
+ "installSource": "cbs-4px-merchant-cli",
24
+ "installMethod": "cli"
25
+ }
@@ -21,14 +21,18 @@
21
21
 
22
22
  | Flag | Type | Required | Description |
23
23
  | --- | --- | --- | --- |
24
- | `--api-key <key>` | string | yes | API Key(格式:SK_xxx) |
24
+ | `--api-key <key>` | string | yes | API Key(控制台生成的原串,常见前缀 `SK_` 或 `sk-`) |
25
25
 
26
26
  获取 API Key: https://b.4px.com/site/ak-manage
27
27
 
28
+ > **不要校验或改写用户给的 Key。** 后端同时接受 `SK_xxx` 与 `sk-xxx` 两种前缀,CLI 也不做格式校验。
29
+ > 原样传入即可 —— 因为“看起来格式不对”而拒用、或自行转成大写,只会把一个有效的 Key 弄坏。
30
+ > 有效性以调用结果为准:返回 401 才是无效。
31
+
28
32
  #### Examples
29
33
 
30
34
  ```bash
31
- fmc auth login --api-key SK_xxxxx
35
+ fmc auth login --api-key sk-xxxxx
32
36
  ```
33
37
 
34
38
  ### `fmc auth status`
@@ -57,6 +61,6 @@ fmc auth status --output json
57
61
  Authentication Status:
58
62
  Status: ✔ 已认证
59
63
  Source: ~/.bportal/config.json
60
- Key: SK_1***3456
64
+ Key: sk-1***3456
61
65
  Base URL: https://b.4px.com
62
66
  ```
@@ -29,7 +29,7 @@
29
29
 
30
30
  ```bash
31
31
  fmc config set baseUrl https://b.4px.com
32
- fmc config set apiKey SK_xxxxx
32
+ fmc config set apiKey sk-xxxxx
33
33
  ```
34
34
 
35
35
  ### `fmc config list`
@@ -63,6 +63,9 @@ Command **details** are in sibling `<group>.md` files in this directory.
63
63
  | `fmc customs hscode search` | 查询海关编码 | [customs.md](customs.md) |
64
64
  | `fmc customs registration info` | 查询客户备案信息 | [customs.md](customs.md) |
65
65
  | `fmc update` | 升级 CLI 及对应 skill 到最新版本 | [update.md](update.md) |
66
+ | `fmc skill install` | 把当前版本的 skill 同步到本机 AI 工具 | [update.md](update.md) |
67
+ | `fmc skill status` | 查看各 AI 工具的 skill 安装与版本状态 | [update.md](update.md) |
68
+ | `fmc skill uninstall` | 移除本 CLI 安装的 skill | [update.md](update.md) |
66
69
 
67
70
  ## By group
68
71
 
@@ -81,6 +84,7 @@ Command **details** are in sibling `<group>.md` files in this directory.
81
84
  | `notice` | `list`, `detail`, `message list`, `message detail`, `message unread` | [notice.md](notice.md) |
82
85
  | `customs` | `cargo list`, `commodity list`, `commodity detail`, `hscode search`, `registration info` | [customs.md](customs.md) |
83
86
  | `update` | (no subcommand) | [update.md](update.md) |
87
+ | `skill` | `install`, `status`, `uninstall` | [update.md](update.md) |
84
88
 
85
89
  ## Global flags
86
90
 
@@ -95,7 +99,9 @@ Command **details** are in sibling `<group>.md` files in this directory.
95
99
  ## Notes
96
100
 
97
101
  - `track get` 命令无需认证即可使用。
98
- - 其余大部分命令需要先配置 API Key:`fmc auth login --api-key <SK_xxx>`。
102
+ - 其余大部分命令需要先配置 API Key:`fmc auth login --api-key <key>`。Key 是控制台生成的原串(常见前缀 `SK_` 或 `sk-`),原样传入,不要校验或改写。
99
103
  - 新增的 `problem`、`service`、`finance`、`pickup`、`notice`、`customs` 模块均需要 API Key 认证。`order` 模块下的 `address-book`、`country` 子命令也需要认证。
100
104
  - 配置文件位于 `~/.bportal/config.json`;升级状态缓存位于 `~/.bportal/update-state.json`。
101
105
  - 当检测到新版本可用时,stderr 会输出 `{"_notice":{"type":"update",...}}`;设置环境变量 `BPORTAL_NO_UPDATE_NOTIFIER=1` 可关闭。
106
+ - 当已安装的 skill 版本与 CLI 不一致时,stderr 会输出 `{"_notice":{"type":"skills",...}}`;执行 `fmc skill install` 即可同步。
107
+ - `npm install -g cbs-4px-merchant-cli` **不保证**会自动注册 skill(npm 11 默认拦截安装期脚本,且仍退出 0),安装后应显式执行 `fmc skill install`。
@@ -1,23 +1,31 @@
1
- # `fmc update`
1
+ # `fmc update` / `fmc skill`
2
2
 
3
- 升级 CLI 到最新版本,并自动同步对应的 AI skill。
3
+ 升级 CLI 本体,以及把当前版本的 skill 同步到本机 AI 工具。
4
4
 
5
5
  ## Synopsis
6
6
 
7
7
  ```bash
8
8
  fmc update [--check] [--tag <tag>] [--force] [-y] [--output <format>]
9
+ fmc skill install|status|uninstall [--agent <ids>] [--force] [--dry-run] [--output <format>]
9
10
  ```
10
11
 
11
12
  ## Description
12
13
 
13
- `fmc update` 通过 npm 全局安装机制,将 CLI 升级到 npm registry 上的最新版本。
14
- 升级后由 npm postinstall 自动把新版 SKILL.md 同步到所有已检测到的 AI 工具目录
15
- (`~/.qoder/skills/`, `~/.claude/skills/`, `~/.cursor/skills/` 等)。
14
+ `fmc update` 通过 npm 全局安装机制,将 CLI 升级到 npm registry 上的最新版本,
15
+ 完成后自动调用一次 `fmc skill install`,把新版 SKILL.md 同步到所有已检测到的
16
+ AI 工具目录(`~/.qoder/skills/`、`~/.claude/skills/`、`~/.cursor/skills/` 等)。
16
17
 
17
- **升级目标**:当前 site 的 npm 包 `cbs-4px-merchant-cli` 在 registry `{{ "https://registry.anpm.alibaba-inc.com" }}` 上。
18
+ > **为何不靠 npm postinstall:**npm 11 默认拦截依赖包的安装期脚本,**且跳过时仍然退出 0**。
19
+ > 所以 skill 铺设不能依赖生命周期钩子,`fmc skill install` 才是唯一可靠的入口;
20
+ > postinstall 仅作为“能跑就顺便跑”的便利钩子保留。
21
+
22
+ **升级目标**:当前 site 的 npm 包 `cbs-4px-merchant-cli`。registry 以本机 `npm config get registry` 为准,
23
+ 可用 `BPORTAL_REGISTRY` 临时覆盖。
18
24
 
19
25
  ## Options
20
26
 
27
+ ### `update`
28
+
21
29
  | Flag | Description |
22
30
  | --- | --- |
23
31
  | `--check` | 只检查是否有新版本,不执行升级 |
@@ -26,6 +34,15 @@ fmc update [--check] [--tag <tag>] [--force] [-y] [--output <format>]
26
34
  | `-y, --yes` | 跳过交互式确认 |
27
35
  | `--output <format>` | 输出格式: `text` \| `json`(TTY 默认 text,管道默认 json) |
28
36
 
37
+ ### `skill install` / `skill status` / `skill uninstall`
38
+
39
+ | Flag | Description |
40
+ | --- | --- |
41
+ | `--agent <ids>` | 只处理指定 AI 工具(逗号分隔,可重复);非法值会报错并列出全部合法 id |
42
+ | `--force` | 覆盖 / 删除非本 CLI 安装的目录(默认拒绝) |
43
+ | `--dry-run` | 只打印将要执行的操作,不写入任何文件 |
44
+ | `--output <format>` | 输出格式: `text` \| `json` |
45
+
29
46
  ## Environment variables
30
47
 
31
48
  | Variable | Description |
@@ -53,22 +70,40 @@ fmc update --force -y
53
70
 
54
71
  # 临时切换 registry 后升级(不修改 ~/.npmrc)
55
72
  BPORTAL_REGISTRY=https://registry.npmjs.org fmc update --check
73
+
74
+ # 把 skill 重新铺到本机所有 AI 工具(安装后的第二步,可反复执行)
75
+ fmc skill install
76
+
77
+ # 先看将要发生什么,不写入任何文件
78
+ fmc skill install --dry-run --output json
79
+
80
+ # 只铺到指定工具
81
+ fmc skill install --agent claude-code,qoder
82
+
83
+ # 查看各工具的安装与版本状态
84
+ fmc skill status --output json
85
+
86
+ # 移除本 CLI 安装的 skill
87
+ fmc skill uninstall
56
88
  ```
57
89
 
58
90
  ## JSON envelope (`--output json`)
59
91
 
92
+ ### `update`
93
+
60
94
  ```json
61
95
  {
62
96
  "ok": true,
63
97
  "action": "updated",
64
98
  "package": "cbs-4px-merchant-cli",
65
- "registry": "https://registry.anpm.alibaba-inc.com",
99
+ "registry": "https://registry.npmjs.org",
66
100
  "tag": "latest",
67
- "previous_version": "1.0.10",
68
- "current_version": "1.0.11",
69
- "latest_version": "1.0.11",
101
+ "previous_version": "0.0.2",
102
+ "current_version": "0.0.3",
103
+ "latest_version": "0.0.3",
70
104
  "install_method": "npm",
71
- "message": "已升级到 v1.0.11(skill 已通过 postinstall 自动同步)。重新打开终端使生效。"
105
+ "skill_sync": "ok",
106
+ "message": "已升级到 v0.0.3,skill 已同步。重新打开终端使生效。"
72
107
  }
73
108
  ```
74
109
 
@@ -82,16 +117,63 @@ BPORTAL_REGISTRY=https://registry.npmjs.org fmc update --check
82
117
  | `manual_required` | CLI 非 npm 全局安装,需手动升级 |
83
118
  | `error` | 失败(详见 `error.code`:`NETWORK` / `NPM_NOT_FOUND` / `NPM_INSTALL_FAILED` / `EACCES`) |
84
119
 
85
- ## Passive update notifier
120
+ `skill_sync` 取值 `ok` \| `failed`。注意:**skill 同步失败不会把 `ok` 置为 false**,
121
+ 因为二进制确实升级成功了;此时 `hint` 会提示手动执行 `fmc skill install`。
122
+
123
+ ### `skill install` / `skill status` / `skill uninstall`
124
+
125
+ ```json
126
+ {
127
+ "ok": true,
128
+ "action": "installed",
129
+ "package": "cbs-4px-merchant-cli",
130
+ "skill": { "name": "cbs-4px-merchant-cli", "version": "0.0.3" },
131
+ "api_key_configured": false,
132
+ "targets": [
133
+ {
134
+ "agent": "claude-code",
135
+ "label": "Claude Code",
136
+ "dir": "/Users/me/.claude/skills/cbs-4px-merchant-cli",
137
+ "outcome": "updated",
138
+ "previous_version": "0.0.2",
139
+ "removed": ["reference/claim.md"]
140
+ }
141
+ ],
142
+ "summary": { "total": 1, "installed": 0, "updated": 1, "skipped": 0, "failed": 0 },
143
+ "hint": "尚未配置 API Key。申请地址: https://b.4px.com/site/ak-manage,然后执行 fmc auth login --api-key <key>"
144
+ }
145
+ ```
146
+
147
+ `outcome` 取值:
148
+
149
+ | Value | Meaning |
150
+ | --- | --- |
151
+ | `installed` | 全新安装(目录原本不存在或为空) |
152
+ | `updated` | 覆盖升级,`removed` 列出被清理的孤儿文件 |
153
+ | `skipped` | 目录归属不明或属于其他安装器,未触碰(`reason` 说明原因,`--force` 可覆盖) |
154
+ | `failed` | 写入失败(`reason` 为错误信息) |
155
+
156
+ `api_key_configured` 只是存在性布尔值,**不会回显 Key 本身**。
157
+ `fmc skill status` 的 `targets` 则携带 `state` / `installed_version` / `installed_source`,
158
+ 用于分辨“我装的”和“别人装的”。
159
+
160
+ ## Passive notifiers
86
161
 
87
162
  普通命令运行后(如 `fmc track get ...`),若本地缓存(`~/.bportal/update-state.json`,
88
163
  TTL 24 小时)发现 registry 上有新版本,stderr 会输出一条 JSON 通知:
89
164
 
90
165
  ```json
91
- {"_notice":{"type":"update","package":"cbs-4px-merchant-cli","current":"1.0.10","latest":"1.0.11","hint":"Run: fmc update to upgrade"}}
166
+ {"_notice":{"type":"update","package":"cbs-4px-merchant-cli","current":"0.0.2","latest":"0.0.3","hint":"Run: fmc update to upgrade"}}
167
+ ```
168
+
169
+ 若本机已安装的 skill 版本与 CLI 不一致,还会额外输出一条漂移通知:
170
+
171
+ ```json
172
+ {"_notice":{"type":"skills","current":"0.0.2","expected":"0.0.3","hint":"Run: fmc skill install to sync skills with CLI v0.0.3"}}
92
173
  ```
93
174
 
94
- AI Agent 可据此提示用户升级。设置 `BPORTAL_NO_UPDATE_NOTIFIER=1` 即可关闭。
175
+ 漂移检测会扫描**所有**已安装目录;多个工具版本不一致时 `current` 为数组。
176
+ AI Agent 可据此提示用户升级或同步。设置 `BPORTAL_NO_UPDATE_NOTIFIER=1` 即可关闭升级提示。
95
177
 
96
178
  ## Failure handling
97
179
 
@@ -100,3 +182,6 @@ AI Agent 可据此提示用户升级。设置 `BPORTAL_NO_UPDATE_NOTIFIER=1` 即
100
182
  或使用 `sudo`。
101
183
  - **网络/registry 不可达**:检查 VPN,或用 `BPORTAL_REGISTRY` 临时切换。
102
184
  - **非 npm 安装**:`action=manual_required`,envelope 内 `upgrade_command` 字段给出确切的手动升级命令。
185
+ - **skill 同步失败**:`skill_sync=failed`,二进制已升级,手动执行 `fmc skill install` 即可。
186
+ - **目录被跳过**:`outcome=skipped` 说明该目录不是本 CLI 装的(比如技能市场装的)。
187
+ 先用 `fmc skill status` 看 `installed_source` 确认归属,确实该覆盖再加 `--force`。