openclawmp 1.0.0-alpha.0 → 1.0.0-alpha.2

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
@@ -15,11 +15,14 @@ npm install -g openclawmp
15
15
  ## 快速开始
16
16
 
17
17
  ```bash
18
+ # 写入本地 token
19
+ openclawmp oauth <your-token>
20
+
18
21
  # 搜索市场资产
19
22
  openclawmp search 天气
20
23
 
21
- # 查看资产详情
22
- openclawmp info 7c19dc4c3244418096f1dcb59c93f795
24
+ # 根据 assetId 查看资产详情
25
+ openclawmp info 6c476a36955a4270a3fa303beeeed5ee
23
26
 
24
27
  # 安装资产
25
28
  openclawmp install skill/7c19dc4c3244418096f1dcb59c93f795
@@ -34,13 +37,14 @@ openclawmp list
34
37
  openclawmp uninstall skill/demo-skill
35
38
 
36
39
  # 发布本地资产
37
- openclawmp publish ./my-skill --type skill --version 1.0.0 --yes
40
+ openclawmp publish ./my-asset --yes
38
41
  ```
39
42
 
40
43
  ## 功能概览
41
44
 
42
45
  - `search`:搜索市场资产,按类型、作者、安装量、标签和简介格式化输出。
43
- - `info`:查看资产详情与版本信息。
46
+ - `oauth`:将 token 写入本地凭证文件。
47
+ - `info`:根据 `assetId` 查看资产详情与版本信息。
44
48
  - `install`:根据 `assetId` 和可选 `semver` 下载归档并安装到本地 OpenClaw 目录。
45
49
  - `uninstall`:删除本地安装目录,并清理对应锁文件记录。
46
50
  - `list`:扫描本地已安装资产,结合 lockfile 和安装元数据展示结果。
@@ -74,6 +78,12 @@ openclawmp publish ./my-skill --type skill --version 1.0.0 --yes
74
78
  }
75
79
  ```
76
80
 
81
+ 也可以直接通过命令写入:
82
+
83
+ ```bash
84
+ openclawmp oauth <your-token>
85
+ ```
86
+
77
87
  请求头约定:
78
88
 
79
89
  - `Authorization: Bearer <credential token>`:受保护接口必需;`search` 可匿名调用
@@ -84,24 +94,41 @@ openclawmp publish ./my-skill --type skill --version 1.0.0 --yes
84
94
 
85
95
  ```bash
86
96
  npm install -g openclawmp
97
+ openclawmp oauth <your-token>
87
98
  openclawmp publish /path/to/asset
88
99
  ```
89
100
 
90
101
  示例:
91
102
 
92
103
  ```bash
93
- openclawmp publish ./my-skill \
94
- --type skill \
95
- --category productivity \
96
- --tags agent,automation \
97
- --version 1.0.0 \
98
- --yes
104
+ cat .metadata.json
105
+ ```
106
+
107
+ ```json
108
+ {
109
+ "assetType": "skill",
110
+ "name": "my-skill",
111
+ "displayName": "My Skill",
112
+ "semver": "1.0.0",
113
+ "category": "productivity",
114
+ "tags": ["agent", "automation"]
115
+ }
116
+ ```
117
+
118
+ ```bash
119
+ openclawmp publish ./my-skill --yes
120
+ ```
121
+
122
+ 查看资产详情:
123
+
124
+ ```bash
125
+ openclawmp info 6c476a36955a4270a3fa303beeeed5ee
99
126
  ```
100
127
 
101
128
  更新已有资产版本:
102
129
 
103
130
  ```bash
104
- openclawmp publish ./my-skill --version 1.0.1 --yes
131
+ openclawmp publish ./my-skill --asset-id s-123456 --yes
105
132
  ```
106
133
 
107
134
  如果目录根部已存在 `.assetid`,会自动把该值透传为 `assetId`;也可显式传入 `--asset-id <id>`。
@@ -137,25 +164,18 @@ openclawmp search 天气 --page-size 10
137
164
  入口采用懒加载:
138
165
 
139
166
  - `publish`
167
+ - `oauth`
140
168
  - `search`
169
+ - `info`
141
170
  - `install`
142
171
  - `uninstall`
143
172
  - `list`
144
173
 
145
- 其中当前可用的是 `publish`、`search`、`install`、`uninstall`、`list`。
174
+ 其中当前可用的是 `publish`、`oauth`、`search`、`info`、`install`、`uninstall`、`list`。
146
175
 
147
176
  ## 发布命令参数
148
177
 
149
- - `--type <skill|experience|plugin|trigger|channel>`
150
178
  - `--asset-id <id>`:未传时会尝试读取目录根部 `.assetid`
151
- - `--name <slug>`
152
- - `--display-name <name>`
153
- - `--description <text>`
154
- - `--version <semver>`
155
- - `--category <category>`
156
- - `--tags <a,b,c>`
157
- - `--tag <value>`:可重复
158
- - `--long-description <text>`
159
179
  - `--base-url <url>`
160
180
  - `--token-file <path>`
161
181
  - `--part-size-mb <number>`
@@ -171,18 +191,17 @@ openclawmp search 天气 --page-size 10
171
191
  - 根目录可通过 `.openclawmpignore` 补充忽略规则,也会读取根目录 `.gitignore` / `.npmignore` 的常见规则
172
192
  - 子路径遇到无权限读取的目录或文件时会自动跳过;使用 `--verbose` 可查看具体跳过项
173
193
 
174
- ## Metadata 提取规则
194
+ ## Publish Metadata
175
195
 
176
- - 优先级:CLI 显式参数 > `.metadata.json` > `README.md` > `package.json` > `openclaw.plugin.json` > `SKILL.md`
177
- - `.metadata.json` 存在时会优先使用,适合显式固定 `type`、`name`、`displayName`、`description`、`version`、`category`、`tags`、`longDescription`
178
- - `SKILL.md` `name`、`display-name`、`description`、`version`、`tags` 只从 frontmatter(`---` 到 `---`)提取,不会把正文内容混进 `description`
179
- - `README.md` 支持 YAML frontmatter 和前置 `key: value` 形式字段提取 `name`、`display-name`、`description`、`version`、`type`、`category`、`tags`
180
- - `trigger` / `experience` 会把 README 标题后的第一段作为描述兜底
181
- - 发布必填规则:`assetType`、`name`、`displayName`、`semver`
182
- - `version` 必须是严格的 `x.x.x` 形式,例如 `1.2.1`、`1.1.10`
183
- - `assetId` 为可选字段;有就透传,没有就省略,由后端判断是新建还是已有资产发新版
196
+ - `publish` 只读取发布目录或归档里的 `.metadata.json`
197
+ - `.metadata.json` 必须包含 `assetType`、`name`、`displayName`、`semver`
198
+ - `semver` 必须是严格的 `x.y.z` 纯数字格式,例如 `1.2.3`
199
+ - 可选字段:`assetId`、`category`、`tags`、`description`、`longDescription`
200
+ - 不再从 `README.md`、`package.json`、`openclaw.plugin.json`、`SKILL.md` 自动推断发布 metadata
201
+ - 不再通过 CLI 参数补录 `assetType`、`name`、`displayName`、`semver`
202
+ - `assetId` 可写在 `.metadata.json`,也可以通过 `--asset-id` 或目录根部 `.assetid` 透传
184
203
  - `objectId` 由上传完成后自动生成,登录态始终必需
185
- - 调用发布接口前会打印 `CreateAssetSemver` 请求预览;真正发网请求前会打印对应 request body
204
+ - 发布流程不再打印请求预览或请求头明细;终端仅显示操作类型与 request-id
186
205
 
187
206
  ## 默认配置来源
188
207
 
package/bin/openclawmp.js CHANGED
@@ -15,6 +15,9 @@ const COMMAND_LOADERS = {
15
15
  login: function loadLogin() {
16
16
  return require("../lib/commands/login");
17
17
  },
18
+ oauth: function loadOauth() {
19
+ return require("../lib/commands/oauth");
20
+ },
18
21
  publish: function loadPublish() {
19
22
  return require("../lib/commands/publish");
20
23
  },
package/lib/api.js CHANGED
@@ -79,30 +79,6 @@ function detectTokenPrefix(token) {
79
79
  return text;
80
80
  }
81
81
 
82
- function maskAuthorizationHeader(value) {
83
- const text = String(value || "");
84
- if (!text) {
85
- return text;
86
- }
87
-
88
- const match = text.match(/^Bearer\s+(.+)$/u);
89
- const token = match ? match[1] : text;
90
- if (token.length <= 8) {
91
- return "Bearer " + token[0] + "***" + token[token.length - 1];
92
- }
93
- return "Bearer " + token.slice(0, 4) + "..." + token.slice(-4);
94
- }
95
-
96
- function printOutgoingHeaders(methodName, headers) {
97
- const printableHeaders = Object.assign({}, headers);
98
- if (printableHeaders.Authorization) {
99
- printableHeaders.Authorization = maskAuthorizationHeader(printableHeaders.Authorization);
100
- }
101
-
102
- console.log("");
103
- console.log("即将发送请求头: " + methodName);
104
- console.log(JSON.stringify(printableHeaders, null, 2));
105
- }
106
82
 
107
83
  function createCatalogClient(options) {
108
84
  const defaultRequestId = normalizeRequestId(options.requestId) || generateRequestId();
@@ -115,7 +91,7 @@ function createCatalogClient(options) {
115
91
  "Connect-Protocol-Version": "1",
116
92
  "Content-Type": "application/json",
117
93
  "Accept": "application/json",
118
- "User-Agent": "mpdev/" + (options.cliVersion || "dev")
94
+ "User-Agent": "openclawmp/" + (options.cliVersion || "dev")
119
95
  };
120
96
 
121
97
  if (options.token) {
@@ -129,11 +105,8 @@ function createCatalogClient(options) {
129
105
  }
130
106
 
131
107
  printTerminalOperationNotice(operationName, [
132
- methodName,
133
- requestId ? "request-id: " + requestId : "",
134
- requestUrl
108
+ requestId ? "request-id: " + requestId : ""
135
109
  ]);
136
- printOutgoingHeaders(methodName, headers);
137
110
 
138
111
  let response;
139
112
  try {
package/lib/archive.js CHANGED
@@ -879,7 +879,7 @@ async function preparePackage(inputPath) {
879
879
  const inspected = await inspectPublishInput(inputPath);
880
880
 
881
881
  if (inspected.isDirectory) {
882
- const tempDir = await fsp.mkdtemp(path.join(os.tmpdir(), "mpdev-cli-"));
882
+ const tempDir = await fsp.mkdtemp(path.join(os.tmpdir(), "openclawmp-cli-"));
883
883
  const packagePath = path.join(tempDir, inspected.packageFileName);
884
884
  await createTarGzFromEntries(inspected.entries, packagePath);
885
885
  const packageStat = await fsp.stat(packagePath);
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+
3
+ const CLI_NAME = "openclawmp";
4
+
5
+ function buildInstallSpecifier(assetType, assetId, semver) {
6
+ const normalizedAssetType = String(assetType || "").trim();
7
+ const normalizedAssetId = String(assetId || "").trim();
8
+ const normalizedSemver = String(semver || "").trim();
9
+
10
+ if (!normalizedAssetType || !normalizedAssetId) {
11
+ return "";
12
+ }
13
+
14
+ return (
15
+ normalizedAssetType +
16
+ "/" +
17
+ normalizedAssetId +
18
+ (normalizedSemver ? "@" + normalizedSemver : "")
19
+ );
20
+ }
21
+
22
+ function buildInstallCommand(assetType, assetId, semver) {
23
+ const specifier = buildInstallSpecifier(assetType, assetId, semver);
24
+ if (!specifier) {
25
+ return "";
26
+ }
27
+ return CLI_NAME + " install " + specifier;
28
+ }
29
+
30
+ module.exports = {
31
+ CLI_NAME,
32
+ buildInstallSpecifier,
33
+ buildInstallCommand
34
+ };
@@ -10,6 +10,9 @@ const COMMAND_LOADERS = {
10
10
  login: function loadLogin() {
11
11
  return require("./login");
12
12
  },
13
+ oauth: function loadOauth() {
14
+ return require("./oauth");
15
+ },
13
16
  publish: function loadPublish() {
14
17
  return require("./publish");
15
18
  },
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
 
3
3
  const { parseOptions } = require("../cli-parser");
4
+ const { CLI_NAME } = require("../cli-text");
4
5
  const { createCatalogClient } = require("../api");
5
- const { fromCatalogAssetType, normalizeLocalAssetType, toCatalogAssetType } = require("../asset-types");
6
+ const { fromCatalogAssetType } = require("../asset-types");
6
7
  const { resolveConnectBaseUrl } = require("../config");
7
8
  const { generateRequestId, humanize } = require("../utils");
8
9
 
@@ -15,10 +16,13 @@ const TYPE_ICONS = {
15
16
  };
16
17
 
17
18
  function printHelp() {
18
- console.log("mpdev info");
19
+ console.log(CLI_NAME + " info");
19
20
  console.log("");
20
21
  console.log("用法:");
21
- console.log(" mpdev info <asset-id | type/name | type/@author/name | query> [options]");
22
+ console.log(" " + CLI_NAME + " info <assetId> [options]");
23
+ console.log("");
24
+ console.log("示例:");
25
+ console.log(" " + CLI_NAME + " info 6c476a36955a4270a3fa303beeeed5ee");
22
26
  console.log("");
23
27
  console.log("参数:");
24
28
  console.log(" --base-url <url> API 基地址");
@@ -33,57 +37,28 @@ function pickFirstText(values) {
33
37
  }) || "";
34
38
  }
35
39
 
36
- function normalizeName(input) {
37
- return String(input || "")
38
- .trim()
39
- .toLowerCase()
40
- .replace(/\s+/g, " ");
41
- }
40
+ function parseAssetId(positionals) {
41
+ const args = Array.isArray(positionals) ? positionals.filter(Boolean) : [];
42
+ const usageExample = CLI_NAME + " info 6c476a36955a4270a3fa303beeeed5ee";
42
43
 
43
- function parseAssetReference(raw) {
44
- const text = String(raw || "").trim();
45
- if (!text) {
46
- throw new Error("info 命令缺少资产标识");
44
+ if (args.length === 0) {
45
+ throw new Error("info 命令缺少 assetId");
47
46
  }
48
47
 
49
- if (/^(?:[a-z]{1,4}-[a-z0-9]+|[a-f0-9]{32})$/iu.test(text)) {
50
- return {
51
- kind: "assetId",
52
- assetId: text
53
- };
48
+ if (args.length !== 1) {
49
+ throw new Error("info 命令只支持 <assetId> 用法,例如: " + usageExample);
54
50
  }
55
51
 
56
- let match = text.match(/^([^/]+)\/@([^/]+)\/(.+)$/u);
57
- if (match) {
58
- const assetType = normalizeLocalAssetType(match[1]);
59
- if (!assetType) {
60
- throw new Error("未知资产类型: " + match[1]);
61
- }
62
- return {
63
- kind: "typedAuthorName",
64
- assetType,
65
- authorId: match[2],
66
- query: match[3].trim()
67
- };
52
+ const assetId = String(args[0] || "").trim();
53
+ if (!assetId) {
54
+ throw new Error("info 命令缺少 assetId");
68
55
  }
69
56
 
70
- match = text.match(/^([^/]+)\/(.+)$/u);
71
- if (match) {
72
- const assetType = normalizeLocalAssetType(match[1]);
73
- if (!assetType) {
74
- throw new Error("未知资产类型: " + match[1]);
75
- }
76
- return {
77
- kind: "typedName",
78
- assetType,
79
- query: match[2].trim()
80
- };
57
+ if (/\s/u.test(assetId) || assetId.includes("/")) {
58
+ throw new Error("info 命令只支持 <assetId> 用法,例如: " + usageExample);
81
59
  }
82
60
 
83
- return {
84
- kind: "query",
85
- query: text
86
- };
61
+ return assetId;
87
62
  }
88
63
 
89
64
  function formatTypeLabel(assetType) {
@@ -156,100 +131,6 @@ function normalizeTimestamp(value) {
156
131
  return "";
157
132
  }
158
133
 
159
- async function listAllUserAssets(client, authorId, assetType) {
160
- const items = [];
161
- let pageToken = "";
162
-
163
- do {
164
- const response = await client.listUserAssets({
165
- userId: authorId,
166
- assetType: toCatalogAssetType(assetType),
167
- pageSize: 100,
168
- pageToken
169
- });
170
-
171
- const pageItems = Array.isArray(response.data.items) ? response.data.items : [];
172
- items.push.apply(items, pageItems);
173
- pageToken = response.data.nextPageToken || "";
174
- } while (pageToken);
175
-
176
- return items;
177
- }
178
-
179
- function buildAmbiguousError(items) {
180
- return (
181
- "匹配到多个资产,请改用更精确的 assetId 或 type/@author/name。候选项: " +
182
- items
183
- .map(function mapItem(item) {
184
- return (
185
- (item.displayName || item.name || item.assetId) +
186
- " [" +
187
- (item.assetId || "") +
188
- "] @" +
189
- (item.ownerUserId || "unknown")
190
- );
191
- })
192
- .join(", ")
193
- );
194
- }
195
-
196
- function pickAssetFromItems(items, requestedName) {
197
- const normalizedRequested = normalizeName(requestedName);
198
- const exactMatches = items.filter(function filterItem(item) {
199
- return (
200
- item &&
201
- (normalizeName(item.name) === normalizedRequested ||
202
- normalizeName(item.displayName) === normalizedRequested)
203
- );
204
- });
205
-
206
- if (exactMatches.length === 1) {
207
- return exactMatches[0];
208
- }
209
- if (exactMatches.length > 1) {
210
- throw new Error(buildAmbiguousError(exactMatches));
211
- }
212
-
213
- const fuzzyMatches = items.filter(function filterItem(item) {
214
- return (
215
- item &&
216
- (normalizeName(item.name).includes(normalizedRequested) ||
217
- normalizeName(item.displayName).includes(normalizedRequested))
218
- );
219
- });
220
-
221
- if (fuzzyMatches.length === 1) {
222
- return fuzzyMatches[0];
223
- }
224
- if (fuzzyMatches.length > 1) {
225
- throw new Error(buildAmbiguousError(fuzzyMatches));
226
- }
227
-
228
- throw new Error("未找到资产: " + requestedName);
229
- }
230
-
231
- async function resolveAssetSummary(client, reference) {
232
- if (reference.kind === "assetId") {
233
- return {
234
- assetId: reference.assetId
235
- };
236
- }
237
-
238
- if (reference.kind === "typedAuthorName") {
239
- const items = await listAllUserAssets(client, reference.authorId, reference.assetType);
240
- return pickAssetFromItems(items, reference.query);
241
- }
242
-
243
- const response = await client.searchAssets({
244
- query: reference.query,
245
- assetType: reference.assetType ? toCatalogAssetType(reference.assetType) : 0,
246
- pageSize: 50,
247
- pageToken: ""
248
- });
249
- const items = Array.isArray(response.data.items) ? response.data.items : [];
250
- return pickAssetFromItems(items, reference.query);
251
- }
252
-
253
134
  function printAssetInfo(detail, summary, semvers, verbose) {
254
135
  const localAssetType = fromCatalogAssetType(detail.assetType);
255
136
  const title = pickFirstText([detail.displayName, detail.name, detail.assetId]) || "(未命名资产)";
@@ -309,10 +190,7 @@ async function run(context) {
309
190
  verbose: { type: "boolean" }
310
191
  });
311
192
 
312
- const rawReference = parsed.positionals.join(" ").trim();
313
- if (!rawReference) {
314
- throw new Error("info 命令缺少资产标识");
315
- }
193
+ const assetId = parseAssetId(parsed.positionals);
316
194
 
317
195
  const requestId = parsed.options["request-id"] || generateRequestId();
318
196
  const client = createCatalogClient({
@@ -321,13 +199,6 @@ async function run(context) {
321
199
  requestId
322
200
  });
323
201
 
324
- const reference = parseAssetReference(rawReference);
325
- const summary = await resolveAssetSummary(client, reference);
326
- const assetId = summary.assetId;
327
- if (!assetId) {
328
- throw new Error("未能解析 assetId: " + rawReference);
329
- }
330
-
331
202
  const assetResponse = await client.getAsset({
332
203
  assetId
333
204
  });
@@ -340,13 +211,14 @@ async function run(context) {
340
211
  const semvers = Array.isArray(semverResponse.data.items)
341
212
  ? semverResponse.data.items
342
213
  : [];
214
+ const asset = assetResponse.data || {};
343
215
 
344
- printAssetInfo(assetResponse.data || {}, summary, semvers, Boolean(parsed.options.verbose));
216
+ printAssetInfo(asset, asset, semvers, Boolean(parsed.options.verbose));
345
217
 
346
218
  return {
347
- asset: assetResponse.data || {},
219
+ asset,
348
220
  semvers,
349
- summary
221
+ summary: asset
350
222
  };
351
223
  }
352
224
 
@@ -4,8 +4,9 @@ const fs = require("node:fs/promises");
4
4
  const os = require("node:os");
5
5
  const path = require("node:path");
6
6
 
7
- const { fromCatalogAssetType, normalizeLocalAssetType } = require("../asset-types");
7
+ const { VALID_ASSET_TYPES, fromCatalogAssetType, normalizeLocalAssetType } = require("../asset-types");
8
8
  const { parseOptions } = require("../cli-parser");
9
+ const { CLI_NAME, buildInstallCommand } = require("../cli-text");
9
10
  const { createCatalogClient } = require("../api");
10
11
  const { resolveConnectBaseUrl, getInstallRoot } = require("../config");
11
12
  const { loadHubCredentials } = require("../credentials");
@@ -22,21 +23,32 @@ const {
22
23
  } = require("../utils");
23
24
 
24
25
  function printHelp() {
25
- console.log("mpdev install");
26
+ console.log(CLI_NAME + " install");
26
27
  console.log("");
27
28
  console.log("用法:");
28
- console.log(" mpdev install <type>/<assetId>[@<semver>] [options]");
29
+ console.log(" " + CLI_NAME + " install <type>/<assetId>[@<semver>] [options]");
30
+ console.log("");
31
+ console.log("说明:");
32
+ console.log(" type 支持: " + VALID_ASSET_TYPES.join(", "));
33
+ console.log(" install 需要本地 token,默认读取 ~/.openclaw/hub-credentials.json");
34
+ console.log(" 可通过 --token-file <path> 指定其他凭证文件");
35
+ console.log(" 默认安装目录:");
36
+ console.log(" skill -> ~/.openclaw/skills");
37
+ console.log(" plugin -> ~/.openclaw/extensions");
38
+ console.log(" channel -> ~/.openclaw/extensions");
39
+ console.log(" trigger -> ~/.openclaw/triggers");
40
+ console.log(" experience -> ~/.openclaw/experiences");
29
41
  console.log("");
30
42
  console.log("示例:");
31
- console.log(" mpdev install skill/7c19dc4c3244418096f1dcb59c93f795");
32
- console.log(" mpdev install skill/7c19dc4c3244418096f1dcb59c93f795@1.0.3");
43
+ console.log(" " + buildInstallCommand("skill", "7c19dc4c3244418096f1dcb59c93f795"));
44
+ console.log(" " + buildInstallCommand("skill", "7c19dc4c3244418096f1dcb59c93f795", "1.0.3"));
33
45
  console.log("");
34
46
  console.log("参数:");
35
47
  console.log(" --version <semver> 安装指定版本(也可写在 <assetId>@<semver>)");
36
48
  console.log(" --base-url <url> API 基地址");
37
49
  console.log(" --token-file <path> 本地命令行令牌文件");
38
50
  console.log(" --request-id <id> 手动指定 X-Request-ID");
39
- console.log(" --target-dir <path> 自定义安装目录");
51
+ console.log(" --target-dir <path> 自定义安装根目录,最终会安装到 <target-dir>/<asset-name>");
40
52
  console.log(" --verbose 输出更多上下文");
41
53
  console.log(" -h, --help 查看帮助");
42
54
  }
@@ -116,6 +128,18 @@ function resolveAssetTypeFromDetail(assetDetail) {
116
128
  );
117
129
  }
118
130
 
131
+ function buildReversionDownloadUrlRequest(assetId, semver) {
132
+ const request = {
133
+ assetId
134
+ };
135
+
136
+ if (semver && semver.reversionId) {
137
+ request.reversionId = semver.reversionId;
138
+ }
139
+
140
+ return request;
141
+ }
142
+
119
143
  async function downloadBuffer(url) {
120
144
  printTerminalOperationNotice("下载归档文件", [url]);
121
145
 
@@ -252,14 +276,11 @@ async function run(context) {
252
276
  assetId: assetDetail.assetId || specifier.assetId,
253
277
  latestSemver: assetDetail.latestSemver || ""
254
278
  }, requestedVersion);
279
+ const resolvedAssetId = assetDetail.assetId || specifier.assetId;
255
280
 
256
- if (!semver.reversionId) {
257
- throw new Error("版本缺少 reversionId,无法下载安装");
258
- }
259
-
260
- const downloadInfo = await client.getReversionDownloadUrl({
261
- reversionId: semver.reversionId
262
- });
281
+ const downloadInfo = await client.getReversionDownloadUrl(
282
+ buildReversionDownloadUrlRequest(resolvedAssetId, semver)
283
+ );
263
284
 
264
285
  const downloadUrl = downloadInfo.data.downloadUrl;
265
286
  if (!downloadUrl) {
@@ -267,9 +288,11 @@ async function run(context) {
267
288
  }
268
289
 
269
290
  if (parsed.options.verbose) {
270
- console.log("资产ID: " + (assetDetail.assetId || specifier.assetId));
291
+ console.log("资产ID: " + resolvedAssetId);
271
292
  console.log("下载版本: " + semver.semver);
272
- console.log("reversionId: " + semver.reversionId);
293
+ if (semver.reversionId) {
294
+ console.log("reversionId: " + semver.reversionId);
295
+ }
273
296
  console.log("downloadUrl: " + downloadUrl);
274
297
  }
275
298
 
@@ -288,7 +311,6 @@ async function run(context) {
288
311
  await installDownloadedArchive(downloaded.buffer, targetDir, fileNameHint);
289
312
  await writeInstallMetadata(targetDir, assetDetail);
290
313
 
291
- const resolvedAssetId = assetDetail.assetId || specifier.assetId;
292
314
  const lockKey = specifier.assetType + "/" + resolvedAssetId;
293
315
  await upsertInstalledAsset({
294
316
  key: lockKey,
@@ -311,6 +333,7 @@ async function run(context) {
311
333
  }
312
334
 
313
335
  module.exports = {
336
+ buildReversionDownloadUrlRequest,
314
337
  parseAssetSpecifier,
315
338
  printHelp,
316
339
  resolveRequestedVersion,
@@ -4,6 +4,7 @@ const fs = require("node:fs/promises");
4
4
  const path = require("node:path");
5
5
 
6
6
  const { parseOptions } = require("../cli-parser");
7
+ const { CLI_NAME } = require("../cli-text");
7
8
  const { VALID_ASSET_TYPES, normalizeLocalAssetType } = require("../asset-types");
8
9
  const { getInstallRoot } = require("../config");
9
10
  const { normalizeInstallMetadata, readInstallMetadata } = require("../install-metadata");
@@ -20,10 +21,10 @@ const TYPE_ICONS = {
20
21
  };
21
22
 
22
23
  function printHelp() {
23
- console.log("mpdev list");
24
+ console.log(CLI_NAME + " list");
24
25
  console.log("");
25
26
  console.log("用法:");
26
- console.log(" mpdev list [options]");
27
+ console.log(" " + CLI_NAME + " list [options]");
27
28
  console.log("");
28
29
  console.log("参数:");
29
30
  console.log(" --type <type> skill | experience | plugin | trigger | channel");
@@ -1,14 +1,15 @@
1
1
  "use strict";
2
2
 
3
+ const { CLI_NAME } = require("../cli-text");
3
4
  const { defaultCredentialsPath } = require("../config");
4
5
  const { loadHubCredentials } = require("../credentials");
5
6
  const { parseOptions } = require("../cli-parser");
6
7
 
7
8
  function printHelp() {
8
- console.log("mpdev login");
9
+ console.log(CLI_NAME + " login");
9
10
  console.log("");
10
11
  console.log("用法:");
11
- console.log(" mpdev login [options]");
12
+ console.log(" " + CLI_NAME + " login [options]");
12
13
  console.log("");
13
14
  console.log("参数:");
14
15
  console.log(" --token-file <path> 指定本地凭证文件路径");
@@ -50,7 +51,9 @@ async function run(context) {
50
51
  console.log("当前未检测到本地登录凭证。");
51
52
  console.log("- 凭证文件: " + tokenFile);
52
53
  console.log("- 网页入口: https://openclawmp.cc");
53
- console.log("请先在网页侧完成令牌授权,然后将 token 写入上述文件,例如:");
54
+ console.log("请先在网页侧完成令牌授权,然后通过以下命令写入 token");
55
+ console.log(CLI_NAME + " oauth <your-token>");
56
+ console.log("也可以手动写入 JSON 文件,例如:");
54
57
  console.log('{ "token": "<your-token>" }');
55
58
  return {
56
59
  loggedIn: false,