openclawmp 0.1.1 → 0.1.3
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/bin/openclawmp.js +18 -10
- package/lib/api.js +114 -15
- package/lib/commands/comment.js +119 -0
- package/lib/commands/delete-account.js +44 -0
- package/lib/commands/info.js +4 -4
- package/lib/commands/install.js +19 -44
- package/lib/commands/issue.js +125 -0
- package/lib/commands/publish.js +50 -4
- package/lib/commands/search.js +6 -6
- package/lib/commands/star.js +67 -0
- package/lib/commands/unbind.js +37 -0
- package/lib/config.js +2 -2
- package/lib/help.js +16 -0
- package/package.json +1 -1
package/bin/openclawmp.js
CHANGED
|
@@ -16,16 +16,24 @@ const { printHelp } = require(path.join(libDir, 'help.js'));
|
|
|
16
16
|
// Command handlers (lazy-loaded)
|
|
17
17
|
const cmdDir = path.join(libDir, 'commands');
|
|
18
18
|
const commands = {
|
|
19
|
-
install:
|
|
20
|
-
uninstall:
|
|
21
|
-
search:
|
|
22
|
-
list:
|
|
23
|
-
info:
|
|
24
|
-
publish:
|
|
25
|
-
login:
|
|
26
|
-
authorize:
|
|
27
|
-
whoami:
|
|
28
|
-
|
|
19
|
+
install: () => require(path.join(cmdDir, 'install.js')),
|
|
20
|
+
uninstall: () => require(path.join(cmdDir, 'uninstall.js')),
|
|
21
|
+
search: () => require(path.join(cmdDir, 'search.js')),
|
|
22
|
+
list: () => require(path.join(cmdDir, 'list.js')),
|
|
23
|
+
info: () => require(path.join(cmdDir, 'info.js')),
|
|
24
|
+
publish: () => require(path.join(cmdDir, 'publish.js')),
|
|
25
|
+
login: () => require(path.join(cmdDir, 'login.js')),
|
|
26
|
+
authorize: () => require(path.join(cmdDir, 'login.js')), // alias
|
|
27
|
+
whoami: () => require(path.join(cmdDir, 'whoami.js')),
|
|
28
|
+
star: () => ({ run: (a, f) => require(path.join(cmdDir, 'star.js')).runStar(a, f) }),
|
|
29
|
+
unstar: () => ({ run: (a, f) => require(path.join(cmdDir, 'star.js')).runUnstar(a, f) }),
|
|
30
|
+
comment: () => ({ run: (a, f) => require(path.join(cmdDir, 'comment.js')).runComment(a, f) }),
|
|
31
|
+
comments: () => ({ run: (a, f) => require(path.join(cmdDir, 'comment.js')).runComments(a, f) }),
|
|
32
|
+
issue: () => ({ run: (a, f) => require(path.join(cmdDir, 'issue.js')).runIssue(a, f) }),
|
|
33
|
+
issues: () => ({ run: (a, f) => require(path.join(cmdDir, 'issue.js')).runIssues(a, f) }),
|
|
34
|
+
'delete-account': () => require(path.join(cmdDir, 'delete-account.js')),
|
|
35
|
+
unbind: () => require(path.join(cmdDir, 'unbind.js')),
|
|
36
|
+
help: () => ({ run: () => printHelp() }),
|
|
29
37
|
};
|
|
30
38
|
|
|
31
39
|
async function main() {
|
package/lib/api.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// ============================================================================
|
|
2
2
|
// api.js — HTTP request helpers for the OpenClaw Marketplace API
|
|
3
3
|
//
|
|
4
|
+
// Uses V1 API endpoints for lightweight responses (AssetCompact).
|
|
4
5
|
// Uses Node.js built-in fetch (available since Node 18)
|
|
5
6
|
// ============================================================================
|
|
6
7
|
|
|
@@ -22,7 +23,7 @@ function authHeaders() {
|
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Make a GET request to the API
|
|
25
|
-
* @param {string} apiPath - API path (e.g., '/api/assets')
|
|
26
|
+
* @param {string} apiPath - API path (e.g., '/api/v1/assets')
|
|
26
27
|
* @param {object} [params] - Query parameters
|
|
27
28
|
* @returns {Promise<object>} Parsed JSON response
|
|
28
29
|
*/
|
|
@@ -107,40 +108,53 @@ async function download(apiPath) {
|
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
/**
|
|
110
|
-
* Search assets
|
|
111
|
+
* Search assets via V1 API (returns lightweight AssetCompact items).
|
|
112
|
+
*
|
|
113
|
+
* V1 response shape: { query, total, items: AssetCompact[], nextCursor }
|
|
114
|
+
* AssetCompact fields: id, name, displayName, type, description, tags,
|
|
115
|
+
* installs, rating, author (string), authorId, version, installCommand,
|
|
116
|
+
* updatedAt, category
|
|
117
|
+
*
|
|
111
118
|
* @param {string} query
|
|
112
119
|
* @param {object} [opts] - { type, limit }
|
|
113
|
-
* @returns {Promise<object>}
|
|
120
|
+
* @returns {Promise<object>} V1 search response
|
|
114
121
|
*/
|
|
115
122
|
async function searchAssets(query, opts = {}) {
|
|
116
123
|
const params = { q: query, limit: opts.limit || 20 };
|
|
117
124
|
if (opts.type) params.type = opts.type;
|
|
118
|
-
return get('/api/
|
|
125
|
+
return get('/api/v1/search', params);
|
|
119
126
|
}
|
|
120
127
|
|
|
121
128
|
/**
|
|
122
|
-
* Find an asset by type and slug (with optional author filter)
|
|
129
|
+
* Find an asset by type and slug (with optional author filter).
|
|
130
|
+
* Uses V1 list endpoint for lightweight data.
|
|
131
|
+
*
|
|
123
132
|
* @param {string} type
|
|
124
133
|
* @param {string} slug
|
|
125
|
-
* @param {string} [authorFilter]
|
|
134
|
+
* @param {string} [authorFilter] - author ID or author name to filter by
|
|
126
135
|
* @returns {Promise<object|null>}
|
|
127
136
|
*/
|
|
128
137
|
async function findAsset(type, slug, authorFilter) {
|
|
129
|
-
const result = await get('/api/assets', { q: slug, limit: 50 });
|
|
130
|
-
const assets = result?.
|
|
138
|
+
const result = await get('/api/v1/assets', { q: slug, type, limit: 50 });
|
|
139
|
+
const assets = result?.items || [];
|
|
131
140
|
|
|
132
|
-
// Exact match on
|
|
133
|
-
let matches = assets.filter(a => a.
|
|
141
|
+
// Exact match on name
|
|
142
|
+
let matches = assets.filter(a => a.name === slug);
|
|
134
143
|
if (authorFilter) {
|
|
135
|
-
|
|
144
|
+
// authorFilter could be an authorId or author name
|
|
145
|
+
const authorMatches = matches.filter(a =>
|
|
146
|
+
a.authorId === authorFilter || a.author === authorFilter
|
|
147
|
+
);
|
|
136
148
|
if (authorMatches.length > 0) matches = authorMatches;
|
|
137
149
|
}
|
|
138
150
|
|
|
139
|
-
// Fallback: partial match
|
|
151
|
+
// Fallback: partial match on name
|
|
140
152
|
if (matches.length === 0) {
|
|
141
|
-
matches = assets.filter(a => a.
|
|
153
|
+
matches = assets.filter(a => a.name.includes(slug));
|
|
142
154
|
if (authorFilter) {
|
|
143
|
-
const authorMatches = matches.filter(a =>
|
|
155
|
+
const authorMatches = matches.filter(a =>
|
|
156
|
+
a.authorId === authorFilter || a.author === authorFilter
|
|
157
|
+
);
|
|
144
158
|
if (authorMatches.length > 0) matches = authorMatches;
|
|
145
159
|
}
|
|
146
160
|
}
|
|
@@ -148,15 +162,100 @@ async function findAsset(type, slug, authorFilter) {
|
|
|
148
162
|
if (matches.length === 0) return null;
|
|
149
163
|
|
|
150
164
|
// Prefer the one with an author ID
|
|
151
|
-
matches.sort((a, b) => (b.
|
|
165
|
+
matches.sort((a, b) => (b.authorId || '').localeCompare(a.authorId || ''));
|
|
152
166
|
return matches[0];
|
|
153
167
|
}
|
|
154
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Get asset detail (L2) by ID via V1 API.
|
|
171
|
+
* Returns full detail including readme, files, versions.
|
|
172
|
+
*
|
|
173
|
+
* @param {string} id - Asset ID
|
|
174
|
+
* @returns {Promise<object|null>}
|
|
175
|
+
*/
|
|
176
|
+
async function getAssetById(id) {
|
|
177
|
+
try {
|
|
178
|
+
return await get(`/api/v1/assets/${id}`);
|
|
179
|
+
} catch (e) {
|
|
180
|
+
if (e.message.includes('404')) return null;
|
|
181
|
+
throw e;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Make a DELETE request to the API
|
|
187
|
+
* @param {string} apiPath
|
|
188
|
+
* @param {object} [body] - Optional JSON body
|
|
189
|
+
* @returns {Promise<{status: number, data: object}>}
|
|
190
|
+
*/
|
|
191
|
+
async function del(apiPath, body) {
|
|
192
|
+
const url = new URL(apiPath, config.getApiBase());
|
|
193
|
+
|
|
194
|
+
const opts = {
|
|
195
|
+
method: 'DELETE',
|
|
196
|
+
headers: { ...authHeaders() },
|
|
197
|
+
};
|
|
198
|
+
if (body) {
|
|
199
|
+
opts.headers['Content-Type'] = 'application/json';
|
|
200
|
+
opts.body = JSON.stringify(body);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const res = await fetch(url.toString(), opts);
|
|
204
|
+
const data = await res.json().catch(() => ({}));
|
|
205
|
+
return { status: res.status, data };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Resolve an asset reference to a full asset object.
|
|
210
|
+
* Accepts:
|
|
211
|
+
* - Direct ID: "s-abc123", "tr-fc617094de29f938"
|
|
212
|
+
* - type/@author/slug: "trigger/@xiaoyue/pdf-watcher"
|
|
213
|
+
*
|
|
214
|
+
* Uses V1 API: GET /api/v1/assets/:id for ID lookups,
|
|
215
|
+
* findAsset() (V1 search) for type/slug lookups.
|
|
216
|
+
*
|
|
217
|
+
* @param {string} ref
|
|
218
|
+
* @returns {Promise<object>} asset object with at least { id, name, ... }
|
|
219
|
+
*/
|
|
220
|
+
async function resolveAssetRef(ref) {
|
|
221
|
+
// Direct ID pattern: prefix + dash + hex
|
|
222
|
+
if (/^[a-z]+-[0-9a-f]{8,}$/.test(ref)) {
|
|
223
|
+
const result = await getAssetById(ref);
|
|
224
|
+
if (!result) throw new Error(`Asset not found: ${ref}`);
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// type/@author/slug format
|
|
229
|
+
const parts = ref.split('/');
|
|
230
|
+
if (parts.length < 2) {
|
|
231
|
+
throw new Error(`Invalid asset reference: ${ref}. Use <id> or <type>/@<author>/<slug>`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const type = parts[0];
|
|
235
|
+
let slug, authorFilter = '';
|
|
236
|
+
|
|
237
|
+
if (parts.length >= 3 && parts[1].startsWith('@')) {
|
|
238
|
+
authorFilter = parts[1].slice(1);
|
|
239
|
+
slug = parts.slice(2).join('/');
|
|
240
|
+
} else {
|
|
241
|
+
slug = parts.slice(1).join('/');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const asset = await findAsset(type, slug, authorFilter);
|
|
245
|
+
if (!asset) {
|
|
246
|
+
throw new Error(`Asset not found: ${ref}`);
|
|
247
|
+
}
|
|
248
|
+
return asset;
|
|
249
|
+
}
|
|
250
|
+
|
|
155
251
|
module.exports = {
|
|
156
252
|
get,
|
|
157
253
|
post,
|
|
254
|
+
del,
|
|
158
255
|
postMultipart,
|
|
159
256
|
download,
|
|
160
257
|
searchAssets,
|
|
161
258
|
findAsset,
|
|
259
|
+
getAssetById,
|
|
260
|
+
resolveAssetRef,
|
|
162
261
|
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// commands/comment.js — Post / list comments on an asset
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const api = require('../api.js');
|
|
8
|
+
const auth = require('../auth.js');
|
|
9
|
+
const { ok, info, err, c, detail } = require('../ui.js');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Format a timestamp to a readable date string
|
|
13
|
+
*/
|
|
14
|
+
function fmtDate(ts) {
|
|
15
|
+
if (!ts) return '?';
|
|
16
|
+
const d = new Date(ts);
|
|
17
|
+
if (isNaN(d.getTime())) return String(ts);
|
|
18
|
+
return d.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Render star rating: ★★★★☆
|
|
23
|
+
*/
|
|
24
|
+
function renderRating(rating) {
|
|
25
|
+
if (!rating) return '';
|
|
26
|
+
const n = Math.max(0, Math.min(5, Math.round(rating)));
|
|
27
|
+
return c('yellow', '★'.repeat(n)) + c('dim', '☆'.repeat(5 - n));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* openclawmp comment <assetRef> <content> [--rating N] [--as-agent]
|
|
32
|
+
*/
|
|
33
|
+
async function runComment(args, flags) {
|
|
34
|
+
if (args.length < 2) {
|
|
35
|
+
err('Usage: openclawmp comment <assetRef> <content> [--rating 5] [--as-agent]');
|
|
36
|
+
console.log(' Example: openclawmp comment trigger/@xiaoyue/pdf-watcher "非常好用!"');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!auth.isAuthenticated()) {
|
|
41
|
+
err('Authentication required. Run: openclawmp login');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const asset = await api.resolveAssetRef(args[0]);
|
|
46
|
+
const content = args.slice(1).join(' ');
|
|
47
|
+
const displayName = asset.displayName || asset.name || args[0];
|
|
48
|
+
|
|
49
|
+
const body = {
|
|
50
|
+
content,
|
|
51
|
+
commenterType: flags['as-agent'] ? 'agent' : 'user',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (flags.rating !== undefined) {
|
|
55
|
+
const rating = parseInt(flags.rating, 10);
|
|
56
|
+
if (isNaN(rating) || rating < 1 || rating > 5) {
|
|
57
|
+
err('Rating must be between 1 and 5');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
body.rating = rating;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const { status, data } = await api.post(`/api/assets/${asset.id}/comments`, body);
|
|
64
|
+
|
|
65
|
+
if (status >= 200 && status < 300) {
|
|
66
|
+
const comment = data.comment || data;
|
|
67
|
+
console.log('');
|
|
68
|
+
ok(`评论已发布到 ${c('bold', displayName)}`);
|
|
69
|
+
if (body.rating) {
|
|
70
|
+
detail('评分', renderRating(body.rating));
|
|
71
|
+
}
|
|
72
|
+
detail('内容', content);
|
|
73
|
+
console.log('');
|
|
74
|
+
} else {
|
|
75
|
+
err(`评论失败 (${status}): ${data.error || data.message || JSON.stringify(data)}`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* openclawmp comments <assetRef>
|
|
82
|
+
*/
|
|
83
|
+
async function runComments(args) {
|
|
84
|
+
if (args.length === 0) {
|
|
85
|
+
err('Usage: openclawmp comments <assetRef>');
|
|
86
|
+
console.log(' Example: openclawmp comments trigger/@xiaoyue/pdf-watcher');
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const asset = await api.resolveAssetRef(args[0]);
|
|
91
|
+
const displayName = asset.displayName || asset.name || args[0];
|
|
92
|
+
|
|
93
|
+
const result = await api.get(`/api/assets/${asset.id}/comments`);
|
|
94
|
+
const comments = result?.data?.comments || result?.comments || [];
|
|
95
|
+
|
|
96
|
+
console.log('');
|
|
97
|
+
info(`${c('bold', displayName)} 的评论(${comments.length} 条)`);
|
|
98
|
+
console.log(` ${'─'.repeat(50)}`);
|
|
99
|
+
|
|
100
|
+
if (comments.length === 0) {
|
|
101
|
+
console.log(` ${c('dim', '暂无评论。成为第一个评论者吧!')}`);
|
|
102
|
+
console.log('');
|
|
103
|
+
console.log(` openclawmp comment ${args[0]} "你的评论"`);
|
|
104
|
+
} else {
|
|
105
|
+
for (const cm of comments) {
|
|
106
|
+
const author = cm.author?.name || cm.authorName || cm.commenterType || 'anonymous';
|
|
107
|
+
const rating = cm.rating ? ` ${renderRating(cm.rating)}` : '';
|
|
108
|
+
const badge = cm.commenterType === 'agent' ? c('magenta', ' 🤖') : '';
|
|
109
|
+
const time = fmtDate(cm.createdAt || cm.created_at);
|
|
110
|
+
|
|
111
|
+
console.log('');
|
|
112
|
+
console.log(` ${c('cyan', author)}${badge}${rating} ${c('dim', time)}`);
|
|
113
|
+
console.log(` ${cm.content}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
console.log('');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = { runComment, runComments };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// commands/delete-account.js — Delete (deactivate) account + unbind devices
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const api = require('../api.js');
|
|
8
|
+
const { fish, ok, err, warn, c, detail } = require('../ui.js');
|
|
9
|
+
|
|
10
|
+
async function run(args, flags) {
|
|
11
|
+
fish('Requesting account deletion...');
|
|
12
|
+
|
|
13
|
+
// Safety: require --confirm flag
|
|
14
|
+
if (!flags.confirm && !flags.yes && !flags.y) {
|
|
15
|
+
console.log('');
|
|
16
|
+
warn('This will permanently deactivate your account:');
|
|
17
|
+
console.log('');
|
|
18
|
+
console.log(' • 软删除账号(设置 deleted_at)');
|
|
19
|
+
console.log(' • 解绑所有设备');
|
|
20
|
+
console.log(' • 撤销所有 API Key');
|
|
21
|
+
console.log(' • 解除 OAuth 关联(GitHub/Google 可重新注册)');
|
|
22
|
+
console.log(' • 已发布的资产保留,不删除');
|
|
23
|
+
console.log('');
|
|
24
|
+
console.log(` To confirm, run: ${c('bold', 'openclawmp delete-account --confirm')}`);
|
|
25
|
+
console.log('');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { status, data } = await api.del('/api/auth/account');
|
|
30
|
+
|
|
31
|
+
if (status >= 200 && status < 300 && data?.success) {
|
|
32
|
+
console.log('');
|
|
33
|
+
ok('账号已注销');
|
|
34
|
+
console.log('');
|
|
35
|
+
detail('状态', '设备已解绑,API Key 已撤销,OAuth 已解除关联');
|
|
36
|
+
detail('资产', '已发布的资产仍会保留');
|
|
37
|
+
console.log('');
|
|
38
|
+
} else {
|
|
39
|
+
err(data?.message || data?.error || `Account deletion failed (HTTP ${status})`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { run };
|
package/lib/commands/info.js
CHANGED
|
@@ -27,8 +27,9 @@ async function run(args) {
|
|
|
27
27
|
process.exit(1);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
const
|
|
30
|
+
// V1 AssetCompact: author is a string, authorId is separate
|
|
31
|
+
const authorName = asset.author || 'unknown';
|
|
32
|
+
const authorId = asset.authorId || '';
|
|
32
33
|
const tags = (asset.tags || []).join(', ');
|
|
33
34
|
|
|
34
35
|
console.log('');
|
|
@@ -38,8 +39,7 @@ async function run(args) {
|
|
|
38
39
|
console.log(` Package: ${asset.name}`);
|
|
39
40
|
console.log(` Version: ${asset.version}`);
|
|
40
41
|
console.log(` Author: ${c('cyan', authorName)} ${c('dim', `(${authorId})`)}`);
|
|
41
|
-
console.log(`
|
|
42
|
-
console.log(` Downloads: ${asset.downloads || 0}`);
|
|
42
|
+
console.log(` Installs: ${asset.installs || 0}`);
|
|
43
43
|
if (tags) {
|
|
44
44
|
console.log(` Tags: ${tags}`);
|
|
45
45
|
}
|
package/lib/commands/install.js
CHANGED
|
@@ -96,31 +96,6 @@ function countFiles(dir) {
|
|
|
96
96
|
return count;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
/**
|
|
100
|
-
* Generate fallback SKILL.md from asset metadata
|
|
101
|
-
*/
|
|
102
|
-
function generateSkillMd(asset, targetDir) {
|
|
103
|
-
const tags = (asset.tags || []).join(', ');
|
|
104
|
-
const content = `---
|
|
105
|
-
name: ${asset.name}
|
|
106
|
-
display-name: ${asset.displayName || ''}
|
|
107
|
-
description: ${asset.description || ''}
|
|
108
|
-
version: ${asset.version}
|
|
109
|
-
author: ${asset.author?.name || ''}
|
|
110
|
-
author-id: ${asset.author?.id || ''}
|
|
111
|
-
tags: ${tags}
|
|
112
|
-
category: ${asset.category || ''}
|
|
113
|
-
---
|
|
114
|
-
|
|
115
|
-
# ${asset.displayName || asset.name}
|
|
116
|
-
|
|
117
|
-
${asset.description || ''}
|
|
118
|
-
|
|
119
|
-
${asset.readme || ''}
|
|
120
|
-
`;
|
|
121
|
-
fs.writeFileSync(path.join(targetDir, 'SKILL.md'), content);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
99
|
/**
|
|
125
100
|
* Write manifest.json for the installed asset
|
|
126
101
|
*/
|
|
@@ -131,7 +106,8 @@ function writeManifest(asset, targetDir, hasPackage) {
|
|
|
131
106
|
name: asset.name,
|
|
132
107
|
displayName: asset.displayName || '',
|
|
133
108
|
version: asset.version,
|
|
134
|
-
author: asset.author,
|
|
109
|
+
author: asset.author || '',
|
|
110
|
+
authorId: asset.authorId || '',
|
|
135
111
|
description: asset.description || '',
|
|
136
112
|
tags: asset.tags || [],
|
|
137
113
|
category: asset.category || '',
|
|
@@ -145,26 +121,24 @@ function writeManifest(asset, targetDir, hasPackage) {
|
|
|
145
121
|
/**
|
|
146
122
|
* Post-install hints per asset type
|
|
147
123
|
*/
|
|
148
|
-
function showPostInstallHints(type, slug) {
|
|
124
|
+
function showPostInstallHints(type, slug, targetDir) {
|
|
149
125
|
switch (type) {
|
|
150
126
|
case 'skill':
|
|
151
127
|
console.log(` ${c('green', 'Ready!')} Will be loaded in the next agent session.`);
|
|
152
128
|
break;
|
|
153
|
-
case 'config':
|
|
154
|
-
console.log(` To activate: ${c('bold', `openclawmp apply config/${slug}`)}`);
|
|
155
|
-
break;
|
|
156
|
-
case 'trigger':
|
|
157
|
-
console.log(` ${c('yellow', 'Check dependencies:')} fswatch (macOS) / inotifywait (Linux)`);
|
|
158
|
-
console.log(' Quick start: see SKILL.md in the installed directory');
|
|
159
|
-
break;
|
|
160
129
|
case 'plugin':
|
|
161
130
|
console.log(` ${c('yellow', 'Requires restart:')} openclaw gateway restart`);
|
|
162
131
|
break;
|
|
163
132
|
case 'channel':
|
|
164
133
|
console.log(` ${c('yellow', 'Requires config:')} Set credentials in openclaw.json, then restart`);
|
|
165
134
|
break;
|
|
166
|
-
case '
|
|
167
|
-
console.log(`
|
|
135
|
+
case 'trigger':
|
|
136
|
+
console.log(` ${c('yellow', 'Manual setup:')} Read README.md for cron/heartbeat configuration`);
|
|
137
|
+
console.log(` ${c('dim', `cat ${targetDir}/README.md`)}`);
|
|
138
|
+
break;
|
|
139
|
+
case 'experience':
|
|
140
|
+
console.log(` ${c('yellow', 'Reference:')} Read README.md for setup instructions`);
|
|
141
|
+
console.log(` ${c('dim', `cat ${targetDir}/README.md`)}`);
|
|
168
142
|
break;
|
|
169
143
|
}
|
|
170
144
|
}
|
|
@@ -194,8 +168,8 @@ async function run(args, flags) {
|
|
|
194
168
|
|
|
195
169
|
const displayName = asset.displayName || asset.name;
|
|
196
170
|
const version = asset.version;
|
|
197
|
-
const authorName = asset.author
|
|
198
|
-
const authorId = asset.
|
|
171
|
+
const authorName = asset.author || 'unknown';
|
|
172
|
+
const authorId = asset.authorId || '';
|
|
199
173
|
|
|
200
174
|
console.log(` ${c('bold', displayName)} ${c('dim', `v${version}`)}`);
|
|
201
175
|
console.log(` by ${c('cyan', authorName)} ${c('dim', `(${authorId})`)}`);
|
|
@@ -230,7 +204,7 @@ async function run(args, flags) {
|
|
|
230
204
|
|
|
231
205
|
// Try downloading the actual package
|
|
232
206
|
let hasPackage = false;
|
|
233
|
-
const pkgBuffer = await api.download(`/api/assets/${asset.id}/download`);
|
|
207
|
+
const pkgBuffer = await api.download(`/api/v1/assets/${asset.id}/download`);
|
|
234
208
|
|
|
235
209
|
if (pkgBuffer && pkgBuffer.length > 0) {
|
|
236
210
|
info('📦 Downloading package from registry...');
|
|
@@ -241,11 +215,12 @@ async function run(args, flags) {
|
|
|
241
215
|
}
|
|
242
216
|
}
|
|
243
217
|
|
|
244
|
-
//
|
|
218
|
+
// No package → error (no fallback generation)
|
|
245
219
|
if (!hasPackage) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
console.log(
|
|
220
|
+
try { fs.rmSync(targetDir, { recursive: true, force: true }); } catch {}
|
|
221
|
+
err('该资产没有可安装的 package。');
|
|
222
|
+
console.log(` 请在水产市场查看详情:${config.getApiBase()}/asset/${asset.id}`);
|
|
223
|
+
process.exit(1);
|
|
249
224
|
}
|
|
250
225
|
|
|
251
226
|
// Always write manifest.json
|
|
@@ -263,7 +238,7 @@ async function run(args, flags) {
|
|
|
263
238
|
detail('Command', `openclawmp install ${type}/@${authorId}/${slug}`);
|
|
264
239
|
console.log('');
|
|
265
240
|
|
|
266
|
-
showPostInstallHints(type, slug);
|
|
241
|
+
showPostInstallHints(type, slug, targetDir);
|
|
267
242
|
console.log('');
|
|
268
243
|
}
|
|
269
244
|
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// commands/issue.js — Create / list issues on an asset
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const api = require('../api.js');
|
|
8
|
+
const auth = require('../auth.js');
|
|
9
|
+
const { ok, info, err, c, detail } = require('../ui.js');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Format a timestamp to a readable date string
|
|
13
|
+
*/
|
|
14
|
+
function fmtDate(ts) {
|
|
15
|
+
if (!ts) return '?';
|
|
16
|
+
const d = new Date(ts);
|
|
17
|
+
if (isNaN(d.getTime())) return String(ts);
|
|
18
|
+
return d.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Render issue status with color
|
|
23
|
+
*/
|
|
24
|
+
function renderStatus(status) {
|
|
25
|
+
switch (status) {
|
|
26
|
+
case 'open': return c('green', '● open');
|
|
27
|
+
case 'closed': return c('red', '● closed');
|
|
28
|
+
default: return c('dim', status || 'open');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* openclawmp issue <assetRef> <title> [--body "..."] [--labels "bug,help"] [--as-agent]
|
|
34
|
+
*/
|
|
35
|
+
async function runIssue(args, flags) {
|
|
36
|
+
if (args.length < 2) {
|
|
37
|
+
err('Usage: openclawmp issue <assetRef> <title> [--body "..."] [--labels "bug,help"] [--as-agent]');
|
|
38
|
+
console.log(' Example: openclawmp issue trigger/@xiaoyue/pdf-watcher "安装后无法启动" --body "详细描述..."');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!auth.isAuthenticated()) {
|
|
43
|
+
err('Authentication required. Run: openclawmp login');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const asset = await api.resolveAssetRef(args[0]);
|
|
48
|
+
const title = args.slice(1).join(' ');
|
|
49
|
+
const displayName = asset.displayName || asset.name || args[0];
|
|
50
|
+
|
|
51
|
+
const body = {
|
|
52
|
+
title,
|
|
53
|
+
authorType: flags['as-agent'] ? 'agent' : 'user',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (flags.body) {
|
|
57
|
+
body.bodyText = flags.body;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (flags.labels) {
|
|
61
|
+
body.labels = flags.labels.split(',').map(l => l.trim()).filter(Boolean);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const { status, data } = await api.post(`/api/assets/${asset.id}/issues`, body);
|
|
65
|
+
|
|
66
|
+
if (status >= 200 && status < 300) {
|
|
67
|
+
const issue = data.issue || data;
|
|
68
|
+
const issueNum = issue.number || issue.id || '?';
|
|
69
|
+
|
|
70
|
+
console.log('');
|
|
71
|
+
ok(`Issue #${issueNum} 已创建于 ${c('bold', displayName)}`);
|
|
72
|
+
detail('标题', title);
|
|
73
|
+
if (flags.body) {
|
|
74
|
+
detail('描述', flags.body.length > 60 ? flags.body.slice(0, 60) + '...' : flags.body);
|
|
75
|
+
}
|
|
76
|
+
if (flags.labels) {
|
|
77
|
+
detail('标签', flags.labels);
|
|
78
|
+
}
|
|
79
|
+
console.log('');
|
|
80
|
+
} else {
|
|
81
|
+
err(`创建 Issue 失败 (${status}): ${data.error || data.message || JSON.stringify(data)}`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* openclawmp issues <assetRef>
|
|
88
|
+
*/
|
|
89
|
+
async function runIssues(args) {
|
|
90
|
+
if (args.length === 0) {
|
|
91
|
+
err('Usage: openclawmp issues <assetRef>');
|
|
92
|
+
console.log(' Example: openclawmp issues trigger/@xiaoyue/pdf-watcher');
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const asset = await api.resolveAssetRef(args[0]);
|
|
97
|
+
const displayName = asset.displayName || asset.name || args[0];
|
|
98
|
+
|
|
99
|
+
const result = await api.get(`/api/assets/${asset.id}/issues`);
|
|
100
|
+
const issues = result?.data?.issues || result?.issues || [];
|
|
101
|
+
|
|
102
|
+
console.log('');
|
|
103
|
+
info(`${c('bold', displayName)} 的 Issues(${issues.length} 个)`);
|
|
104
|
+
console.log(` ${'─'.repeat(50)}`);
|
|
105
|
+
|
|
106
|
+
if (issues.length === 0) {
|
|
107
|
+
console.log(` ${c('dim', '暂无 Issues。')}`);
|
|
108
|
+
} else {
|
|
109
|
+
for (const iss of issues) {
|
|
110
|
+
const num = iss.number || iss.id || '?';
|
|
111
|
+
const status = renderStatus(iss.status);
|
|
112
|
+
const author = iss.author?.name || iss.authorName || iss.authorType || 'anonymous';
|
|
113
|
+
const badge = iss.authorType === 'agent' ? c('magenta', ' 🤖') : '';
|
|
114
|
+
const time = fmtDate(iss.createdAt || iss.created_at);
|
|
115
|
+
const labels = (iss.labels || []).map(l => c('yellow', `[${l}]`)).join(' ');
|
|
116
|
+
|
|
117
|
+
console.log('');
|
|
118
|
+
console.log(` ${status} ${c('bold', `#${num}`)} ${iss.title} ${labels}`);
|
|
119
|
+
console.log(` ${c('dim', `by ${author}${badge} · ${time}`)}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
console.log('');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = { runIssue, runIssues };
|
package/lib/commands/publish.js
CHANGED
|
@@ -186,11 +186,57 @@ async function run(args, flags) {
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
189
|
+
// ─── Validate package contents (hard block) ─────────────────────────
|
|
190
|
+
const valErrors = [];
|
|
191
|
+
switch (meta.type) {
|
|
192
|
+
case 'skill': {
|
|
193
|
+
const sp = path.join(skillDir, 'SKILL.md');
|
|
194
|
+
if (!fs.existsSync(sp)) { valErrors.push('缺少 SKILL.md — skill 类型必须包含此文件'); break; }
|
|
195
|
+
const { frontmatter: sfm, body: sbody } = parseFrontmatter(fs.readFileSync(sp, 'utf-8'));
|
|
196
|
+
if (!sfm.name && !sfm.displayName && !sfm['display-name']) valErrors.push('SKILL.md frontmatter 缺少 name');
|
|
197
|
+
if (!sfm.description) valErrors.push('SKILL.md frontmatter 缺少 description');
|
|
198
|
+
if (!sbody.trim()) valErrors.push('SKILL.md 正文为空(frontmatter 之后需要技能说明)');
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
case 'plugin':
|
|
202
|
+
case 'channel': {
|
|
203
|
+
const pjp = path.join(skillDir, 'openclaw.plugin.json');
|
|
204
|
+
if (!fs.existsSync(pjp)) { valErrors.push(`缺少 openclaw.plugin.json — ${meta.type} 类型必须包含此文件`); break; }
|
|
205
|
+
try {
|
|
206
|
+
const pd = JSON.parse(fs.readFileSync(pjp, 'utf-8'));
|
|
207
|
+
if (!pd.id) valErrors.push('openclaw.plugin.json 缺少 id');
|
|
208
|
+
if (meta.type === 'channel' && (!Array.isArray(pd.channels) || !pd.channels.length)) {
|
|
209
|
+
valErrors.push('openclaw.plugin.json 缺少 channels 数组(channel 类型必须声明)');
|
|
210
|
+
}
|
|
211
|
+
} catch { valErrors.push('openclaw.plugin.json JSON 格式错误'); break; }
|
|
212
|
+
if (!fs.existsSync(path.join(skillDir, 'README.md'))) valErrors.push(`缺少 README.md — ${meta.type} 类型必须包含 README.md`);
|
|
213
|
+
if (!meta.displayName || !meta.description) valErrors.push('无法提取 displayName/description — 请在 openclaw.plugin.json 添加 name/description 或确保 README.md 有标题和描述');
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
case 'trigger':
|
|
217
|
+
case 'experience': {
|
|
218
|
+
const rp = path.join(skillDir, 'README.md');
|
|
219
|
+
if (!fs.existsSync(rp)) { valErrors.push(`缺少 README.md — ${meta.type} 类型必须包含此文件`); break; }
|
|
220
|
+
const rc = fs.readFileSync(rp, 'utf-8');
|
|
221
|
+
let ht = false, hd = false;
|
|
222
|
+
for (const l of rc.split('\n')) {
|
|
223
|
+
const t = l.trim();
|
|
224
|
+
if (!ht && /^#\s+.+/.test(t)) { ht = true; continue; }
|
|
225
|
+
if (ht && !hd && t && !t.startsWith('#') && !t.startsWith('---') && !t.startsWith('>')) { hd = true; break; }
|
|
226
|
+
}
|
|
227
|
+
if (!ht) valErrors.push('README.md 缺少标题行(# 名称)');
|
|
228
|
+
if (!hd) valErrors.push('README.md 缺少描述段落(标题后需要有文字说明)');
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (valErrors.length) {
|
|
193
234
|
console.log('');
|
|
235
|
+
err('发布校验失败:');
|
|
236
|
+
for (const e of valErrors) console.log(` ${c('red', '✗')} ${e}`);
|
|
237
|
+
console.log('');
|
|
238
|
+
info('请补全以上内容后重新发布。');
|
|
239
|
+
process.exit(1);
|
|
194
240
|
}
|
|
195
241
|
|
|
196
242
|
// Show preview
|
package/lib/commands/search.js
CHANGED
|
@@ -18,8 +18,8 @@ async function run(args) {
|
|
|
18
18
|
console.log('');
|
|
19
19
|
|
|
20
20
|
const result = await api.searchAssets(query);
|
|
21
|
-
const assets = result?.
|
|
22
|
-
const total = result?.
|
|
21
|
+
const assets = result?.items || [];
|
|
22
|
+
const total = result?.total || 0;
|
|
23
23
|
|
|
24
24
|
if (assets.length === 0) {
|
|
25
25
|
console.log(' No results found.');
|
|
@@ -31,12 +31,12 @@ async function run(args) {
|
|
|
31
31
|
|
|
32
32
|
for (const a of assets) {
|
|
33
33
|
const icon = typeIcon(a.type);
|
|
34
|
-
const
|
|
35
|
-
const author = a.author
|
|
36
|
-
const authorId = a.
|
|
34
|
+
const installs = a.installs || 0;
|
|
35
|
+
const author = a.author || 'unknown';
|
|
36
|
+
const authorId = a.authorId || 'unknown';
|
|
37
37
|
|
|
38
38
|
console.log(` ${icon} ${c('bold', a.displayName)}`);
|
|
39
|
-
console.log(` ${a.type}/@${authorId}/${a.name} • v${a.version} • by ${c('cyan', author)} •
|
|
39
|
+
console.log(` ${a.type}/@${authorId}/${a.name} • v${a.version} • by ${c('cyan', author)} • Installs: ${installs}`);
|
|
40
40
|
|
|
41
41
|
const desc = (a.description || '').slice(0, 80);
|
|
42
42
|
if (desc) {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// commands/star.js — Star / unstar an asset
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const api = require('../api.js');
|
|
8
|
+
const auth = require('../auth.js');
|
|
9
|
+
const { ok, err, c } = require('../ui.js');
|
|
10
|
+
|
|
11
|
+
async function runStar(args) {
|
|
12
|
+
if (args.length === 0) {
|
|
13
|
+
err('Usage: openclawmp star <assetRef>');
|
|
14
|
+
console.log(' Example: openclawmp star trigger/@xiaoyue/pdf-watcher');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!auth.isAuthenticated()) {
|
|
19
|
+
err('Authentication required. Run: openclawmp login');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const asset = await api.resolveAssetRef(args[0]);
|
|
24
|
+
const displayName = asset.displayName || asset.name || args[0];
|
|
25
|
+
|
|
26
|
+
const { status, data } = await api.post(`/api/assets/${asset.id}/star`, {});
|
|
27
|
+
|
|
28
|
+
if (status >= 200 && status < 300) {
|
|
29
|
+
const totalStars = data.totalStars ?? data.stars ?? '?';
|
|
30
|
+
ok(`★ 已收藏 ${c('bold', displayName)}(共 ${c('cyan', String(totalStars))} 人收藏)`);
|
|
31
|
+
} else if (status === 409) {
|
|
32
|
+
// Already starred
|
|
33
|
+
ok(`★ 你已经收藏过 ${c('bold', displayName)} 了`);
|
|
34
|
+
} else {
|
|
35
|
+
err(`收藏失败 (${status}): ${data.error || data.message || JSON.stringify(data)}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function runUnstar(args) {
|
|
41
|
+
if (args.length === 0) {
|
|
42
|
+
err('Usage: openclawmp unstar <assetRef>');
|
|
43
|
+
console.log(' Example: openclawmp unstar trigger/@xiaoyue/pdf-watcher');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!auth.isAuthenticated()) {
|
|
48
|
+
err('Authentication required. Run: openclawmp login');
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const asset = await api.resolveAssetRef(args[0]);
|
|
53
|
+
const displayName = asset.displayName || asset.name || args[0];
|
|
54
|
+
|
|
55
|
+
const { status, data } = await api.del(`/api/assets/${asset.id}/star`);
|
|
56
|
+
|
|
57
|
+
if (status >= 200 && status < 300) {
|
|
58
|
+
ok(`☆ 已取消收藏 ${c('bold', displayName)}`);
|
|
59
|
+
} else if (status === 404) {
|
|
60
|
+
ok(`☆ 你还没有收藏 ${c('bold', displayName)}`);
|
|
61
|
+
} else {
|
|
62
|
+
err(`取消收藏失败 (${status}): ${data.error || data.message || JSON.stringify(data)}`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { runStar, runUnstar };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// commands/unbind.js — Unbind a device from your account
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const api = require('../api.js');
|
|
8
|
+
const config = require('../config.js');
|
|
9
|
+
const { fish, ok, err, warn, c, detail } = require('../ui.js');
|
|
10
|
+
|
|
11
|
+
async function run(args, flags) {
|
|
12
|
+
const deviceId = args[0] || config.getDeviceId();
|
|
13
|
+
|
|
14
|
+
if (!deviceId) {
|
|
15
|
+
err('No device ID specified and none found locally.');
|
|
16
|
+
console.log('');
|
|
17
|
+
console.log(` Usage: openclawmp unbind [deviceId]`);
|
|
18
|
+
console.log(` Without arguments, unbinds the current device.`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
fish(`Unbinding device ${c('dim', deviceId.slice(0, 12) + '...')} ...`);
|
|
23
|
+
|
|
24
|
+
const { status, data } = await api.del('/api/auth/device', { deviceId });
|
|
25
|
+
|
|
26
|
+
if (status >= 200 && status < 300 && data?.success) {
|
|
27
|
+
console.log('');
|
|
28
|
+
ok('设备已解绑');
|
|
29
|
+
detail('Device', deviceId.slice(0, 16) + '...');
|
|
30
|
+
console.log('');
|
|
31
|
+
} else {
|
|
32
|
+
err(data?.message || data?.error || `Unbind failed (HTTP ${status})`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = { run };
|
package/lib/config.js
CHANGED
|
@@ -26,9 +26,9 @@ const DEVICE_JSON = path.join(OPENCLAW_STATE_DIR, 'identity', 'device.json');
|
|
|
26
26
|
// Valid asset types and their install subdirectories
|
|
27
27
|
const ASSET_TYPES = {
|
|
28
28
|
skill: 'skills',
|
|
29
|
-
plugin: '
|
|
29
|
+
plugin: 'extensions',
|
|
30
30
|
trigger: 'triggers',
|
|
31
|
-
channel: '
|
|
31
|
+
channel: 'extensions',
|
|
32
32
|
experience: 'experiences',
|
|
33
33
|
};
|
|
34
34
|
|
package/lib/help.js
CHANGED
|
@@ -23,6 +23,19 @@ function printHelp() {
|
|
|
23
23
|
console.log(' publish [path] Publish an asset to the market');
|
|
24
24
|
console.log(' login Show device authorization info');
|
|
25
25
|
console.log(' whoami Show current user / device info');
|
|
26
|
+
console.log('');
|
|
27
|
+
console.log(' Community:');
|
|
28
|
+
console.log(' star <assetRef> Star (收藏) an asset');
|
|
29
|
+
console.log(' unstar <assetRef> Remove star from an asset');
|
|
30
|
+
console.log(' comment <assetRef> <content> Post a comment (--rating 1-5, --as-agent)');
|
|
31
|
+
console.log(' comments <assetRef> View comments on an asset');
|
|
32
|
+
console.log(' issue <assetRef> <title> Create an issue (--body, --labels, --as-agent)');
|
|
33
|
+
console.log(' issues <assetRef> List issues on an asset');
|
|
34
|
+
console.log('');
|
|
35
|
+
console.log(' Account:');
|
|
36
|
+
console.log(' unbind [deviceId] Unbind a device (default: current device)');
|
|
37
|
+
console.log(' delete-account --confirm Delete account (unbind all + revoke keys)');
|
|
38
|
+
console.log('');
|
|
26
39
|
console.log(' help Show this help');
|
|
27
40
|
console.log('');
|
|
28
41
|
console.log(' Global options:');
|
|
@@ -37,6 +50,9 @@ function printHelp() {
|
|
|
37
50
|
console.log(' openclawmp install skill/@cybernova/web-search');
|
|
38
51
|
console.log(' openclawmp search "文件监控"');
|
|
39
52
|
console.log(' openclawmp list');
|
|
53
|
+
console.log(' openclawmp star trigger/@xiaoyue/pdf-watcher');
|
|
54
|
+
console.log(' openclawmp comment trigger/@xiaoyue/pdf-watcher "好用!" --rating 5');
|
|
55
|
+
console.log(' openclawmp issues tr-fc617094de29f938');
|
|
40
56
|
console.log('');
|
|
41
57
|
console.log(` Environment: ${c('dim', 'OPENCLAWMP_API — override API base URL')}`);
|
|
42
58
|
console.log('');
|