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 +50 -31
- package/bin/openclawmp.js +3 -0
- package/lib/api.js +2 -29
- package/lib/archive.js +1 -1
- package/lib/cli-text.js +34 -0
- package/lib/commands/help.js +3 -0
- package/lib/commands/info.js +25 -153
- package/lib/commands/install.js +39 -16
- package/lib/commands/list.js +3 -2
- package/lib/commands/login.js +6 -3
- package/lib/commands/oauth.js +82 -0
- package/lib/commands/publish.js +35 -454
- package/lib/commands/search.js +6 -5
- package/lib/commands/uninstall.js +4 -3
- package/lib/credentials.js +29 -1
- package/lib/help.js +26 -18
- package/lib/metadata.js +64 -82
- package/lib/publish-flow.js +6 -23
- package/package.json +1 -1
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
|
|
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-
|
|
40
|
+
openclawmp publish ./my-asset --yes
|
|
38
41
|
```
|
|
39
42
|
|
|
40
43
|
## 功能概览
|
|
41
44
|
|
|
42
45
|
- `search`:搜索市场资产,按类型、作者、安装量、标签和简介格式化输出。
|
|
43
|
-
- `
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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 --
|
|
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
|
-
-
|
|
177
|
-
- `.metadata.json`
|
|
178
|
-
- `
|
|
179
|
-
-
|
|
180
|
-
-
|
|
181
|
-
-
|
|
182
|
-
- `
|
|
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
|
-
-
|
|
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": "
|
|
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
|
-
|
|
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(), "
|
|
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);
|
package/lib/cli-text.js
ADDED
|
@@ -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
|
+
};
|
package/lib/commands/help.js
CHANGED
package/lib/commands/info.js
CHANGED
|
@@ -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
|
|
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("
|
|
19
|
+
console.log(CLI_NAME + " info");
|
|
19
20
|
console.log("");
|
|
20
21
|
console.log("用法:");
|
|
21
|
-
console.log("
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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 (
|
|
50
|
-
|
|
51
|
-
kind: "assetId",
|
|
52
|
-
assetId: text
|
|
53
|
-
};
|
|
48
|
+
if (args.length !== 1) {
|
|
49
|
+
throw new Error("info 命令只支持 <assetId> 用法,例如: " + usageExample);
|
|
54
50
|
}
|
|
55
51
|
|
|
56
|
-
|
|
57
|
-
if (
|
|
58
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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
|
|
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(
|
|
216
|
+
printAssetInfo(asset, asset, semvers, Boolean(parsed.options.verbose));
|
|
345
217
|
|
|
346
218
|
return {
|
|
347
|
-
asset
|
|
219
|
+
asset,
|
|
348
220
|
semvers,
|
|
349
|
-
summary
|
|
221
|
+
summary: asset
|
|
350
222
|
};
|
|
351
223
|
}
|
|
352
224
|
|
package/lib/commands/install.js
CHANGED
|
@@ -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("
|
|
26
|
+
console.log(CLI_NAME + " install");
|
|
26
27
|
console.log("");
|
|
27
28
|
console.log("用法:");
|
|
28
|
-
console.log("
|
|
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("
|
|
32
|
-
console.log("
|
|
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
|
-
|
|
257
|
-
|
|
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: " +
|
|
291
|
+
console.log("资产ID: " + resolvedAssetId);
|
|
271
292
|
console.log("下载版本: " + semver.semver);
|
|
272
|
-
|
|
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,
|
package/lib/commands/list.js
CHANGED
|
@@ -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("
|
|
24
|
+
console.log(CLI_NAME + " list");
|
|
24
25
|
console.log("");
|
|
25
26
|
console.log("用法:");
|
|
26
|
-
console.log("
|
|
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");
|
package/lib/commands/login.js
CHANGED
|
@@ -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("
|
|
9
|
+
console.log(CLI_NAME + " login");
|
|
9
10
|
console.log("");
|
|
10
11
|
console.log("用法:");
|
|
11
|
-
console.log("
|
|
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("
|
|
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,
|