@openclaw-cn/cli 1.1.8 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/claw.js +36 -1
- package/lib/commands/admin.js +166 -0
- package/lib/commands/auth.js +65 -3
- package/lib/commands/forum.js +11 -1
- package/lib/commands/profile.js +5 -0
- package/lib/commands/skill.js +42 -2
- package/lib/config.js +4 -0
- package/package.json +1 -1
package/bin/claw.js
CHANGED
|
@@ -4,11 +4,30 @@ import { program } from 'commander';
|
|
|
4
4
|
import { readFileSync } from 'fs';
|
|
5
5
|
import { join, dirname } from 'path';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
|
+
import { get } from 'https';
|
|
7
8
|
|
|
8
9
|
// Load package.json
|
|
9
10
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
|
|
11
12
|
|
|
13
|
+
// Async version check (non-blocking, 3s timeout)
|
|
14
|
+
const versionCheck = new Promise((resolve) => {
|
|
15
|
+
const timer = setTimeout(() => resolve(null), 3000);
|
|
16
|
+
const req = get('https://registry.npmjs.org/@openclaw-cn/cli/latest', { timeout: 3000 }, (res) => {
|
|
17
|
+
let data = '';
|
|
18
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
19
|
+
res.on('end', () => {
|
|
20
|
+
clearTimeout(timer);
|
|
21
|
+
try {
|
|
22
|
+
const { version } = JSON.parse(data);
|
|
23
|
+
resolve(version !== pkg.version ? version : null);
|
|
24
|
+
} catch { resolve(null); }
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
req.on('error', () => { clearTimeout(timer); resolve(null); });
|
|
28
|
+
req.on('timeout', () => { req.destroy(); });
|
|
29
|
+
});
|
|
30
|
+
|
|
12
31
|
import auth from '../lib/commands/auth.js';
|
|
13
32
|
import skill from '../lib/commands/skill.js';
|
|
14
33
|
import forum from '../lib/commands/forum.js';
|
|
@@ -31,4 +50,20 @@ profile(program);
|
|
|
31
50
|
inbox(program);
|
|
32
51
|
admin(program);
|
|
33
52
|
|
|
34
|
-
program.
|
|
53
|
+
await program.parseAsync();
|
|
54
|
+
|
|
55
|
+
// Show update notification after command completes
|
|
56
|
+
const latestVersion = await versionCheck;
|
|
57
|
+
if (latestVersion) {
|
|
58
|
+
const { default: chalk } = await import('chalk');
|
|
59
|
+
console.log();
|
|
60
|
+
console.log(chalk.yellow(`───────────────────────────────────────────────`));
|
|
61
|
+
console.log(chalk.yellow(` ⚠️ 新版本可用: ${pkg.version} → ${chalk.green(latestVersion)}`));
|
|
62
|
+
console.log();
|
|
63
|
+
console.log(` 更新命令:`);
|
|
64
|
+
console.log(chalk.cyan(` npm install -g @openclaw-cn/cli`));
|
|
65
|
+
console.log();
|
|
66
|
+
console.log(chalk.gray(` 中国大陆用户建议使用淘宝镜像加速:`));
|
|
67
|
+
console.log(chalk.cyan(` npm install -g @openclaw-cn/cli --registry=https://registry.npmmirror.com`));
|
|
68
|
+
console.log(chalk.yellow(`───────────────────────────────────────────────`));
|
|
69
|
+
}
|
package/lib/commands/admin.js
CHANGED
|
@@ -187,6 +187,141 @@ export default function(program) {
|
|
|
187
187
|
}
|
|
188
188
|
});
|
|
189
189
|
|
|
190
|
+
skill
|
|
191
|
+
.command('view <id>')
|
|
192
|
+
.description('View detailed info of a skill (including pending)')
|
|
193
|
+
.action(async (id) => {
|
|
194
|
+
const spinner = ora(`Fetching skill ${id}...`).start();
|
|
195
|
+
try {
|
|
196
|
+
const client = getClient();
|
|
197
|
+
const res = await client.get(`/skills/${encodeURIComponent(id)}`);
|
|
198
|
+
spinner.stop();
|
|
199
|
+
|
|
200
|
+
const s = res.data;
|
|
201
|
+
console.log(chalk.bold(`\n${'='.repeat(60)}`));
|
|
202
|
+
console.log(chalk.bold(`${s.icon || '📦'} ${s.name}`));
|
|
203
|
+
console.log(chalk.bold(`${'='.repeat(60)}`));
|
|
204
|
+
console.log(`${chalk.cyan('ID:')} ${s.id}`);
|
|
205
|
+
console.log(`${chalk.cyan('Version:')} ${s.version}`);
|
|
206
|
+
console.log(`${chalk.cyan('Status:')} ${s.status === 'approved' ? chalk.green(s.status) : s.status === 'pending' ? chalk.yellow(s.status) : chalk.red(s.status)}`);
|
|
207
|
+
console.log(`${chalk.cyan('Author:')} ${s.owner_name} (${s.owner_id})`);
|
|
208
|
+
console.log(`${chalk.cyan('Description:')} ${s.description}`);
|
|
209
|
+
console.log(`${chalk.cyan('Created:')} ${new Date(s.created_at).toLocaleString()}`);
|
|
210
|
+
console.log(`${chalk.cyan('Updated:')} ${new Date(s.updated_at).toLocaleString()}`);
|
|
211
|
+
|
|
212
|
+
// Show files list
|
|
213
|
+
if (s.files) {
|
|
214
|
+
let filesMap = {};
|
|
215
|
+
try {
|
|
216
|
+
filesMap = typeof s.files === 'string' ? JSON.parse(s.files) : s.files;
|
|
217
|
+
} catch (e) {}
|
|
218
|
+
const fileNames = Object.keys(filesMap);
|
|
219
|
+
if (fileNames.length > 0) {
|
|
220
|
+
console.log(`${chalk.cyan('Files:')} ${fileNames.join(', ')}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Show metadata
|
|
225
|
+
if (s.metadata) {
|
|
226
|
+
let meta = {};
|
|
227
|
+
try {
|
|
228
|
+
meta = typeof s.metadata === 'string' ? JSON.parse(s.metadata) : s.metadata;
|
|
229
|
+
} catch (e) {}
|
|
230
|
+
if (Object.keys(meta).length > 0) {
|
|
231
|
+
console.log(`${chalk.cyan('Metadata:')} ${JSON.stringify(meta, null, 2)}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
console.log(chalk.bold(`\n${'─'.repeat(60)}`));
|
|
236
|
+
console.log(chalk.cyan('README:'));
|
|
237
|
+
console.log(chalk.bold(`${'─'.repeat(60)}\n`));
|
|
238
|
+
console.log(s.readme || '(No readme)');
|
|
239
|
+
console.log();
|
|
240
|
+
} catch (err) {
|
|
241
|
+
spinner.fail(chalk.red(formatError(err)));
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
skill
|
|
246
|
+
.command('install <id>')
|
|
247
|
+
.description('Install a skill to local for testing (admin can install pending skills)')
|
|
248
|
+
.action(async (id) => {
|
|
249
|
+
const spinner = ora(`Installing ${id} for testing...`).start();
|
|
250
|
+
try {
|
|
251
|
+
const client = getClient();
|
|
252
|
+
const res = await client.get(`/skills/${encodeURIComponent(id)}`);
|
|
253
|
+
const s = res.data;
|
|
254
|
+
|
|
255
|
+
if (s.status !== 'approved') {
|
|
256
|
+
spinner.info(chalk.yellow(`Note: This skill is in "${s.status}" status.`));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Determine install path
|
|
260
|
+
const os = await import('os');
|
|
261
|
+
const fs = await import('fs');
|
|
262
|
+
const path = await import('path');
|
|
263
|
+
const matter = (await import('gray-matter')).default;
|
|
264
|
+
|
|
265
|
+
const baseDir = process.env.OPENCLAW_INSTALL_DIR ||
|
|
266
|
+
(process.env.OPENCLAW_HOME ? path.default.join(process.env.OPENCLAW_HOME, '.openclaw') : path.default.join(os.default.homedir(), '.openclaw'));
|
|
267
|
+
const folderName = s.id.replace('/', '__');
|
|
268
|
+
const installDir = path.default.join(baseDir, 'skills', folderName);
|
|
269
|
+
|
|
270
|
+
if (fs.default.existsSync(installDir)) {
|
|
271
|
+
fs.default.rmSync(installDir, { recursive: true });
|
|
272
|
+
}
|
|
273
|
+
fs.default.mkdirSync(installDir, { recursive: true });
|
|
274
|
+
|
|
275
|
+
// Restore files
|
|
276
|
+
if (s.files) {
|
|
277
|
+
let filesMap = {};
|
|
278
|
+
try {
|
|
279
|
+
filesMap = typeof s.files === 'string' ? JSON.parse(s.files) : s.files;
|
|
280
|
+
} catch (e) {}
|
|
281
|
+
|
|
282
|
+
for (const [relPath, content] of Object.entries(filesMap)) {
|
|
283
|
+
if (relPath.includes('..')) continue;
|
|
284
|
+
const targetPath = path.default.join(installDir, relPath);
|
|
285
|
+
const targetDir = path.default.dirname(targetPath);
|
|
286
|
+
if (!fs.default.existsSync(targetDir)) {
|
|
287
|
+
fs.default.mkdirSync(targetDir, { recursive: true });
|
|
288
|
+
}
|
|
289
|
+
fs.default.writeFileSync(targetPath, content);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Write SKILL.md
|
|
294
|
+
let metadata = {};
|
|
295
|
+
if (s.metadata) {
|
|
296
|
+
try { metadata = JSON.parse(s.metadata); } catch (e) {}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const frontmatterData = {
|
|
300
|
+
id: s.id,
|
|
301
|
+
owner_id: s.owner_id,
|
|
302
|
+
name: s.name,
|
|
303
|
+
description: s.description,
|
|
304
|
+
version: s.version,
|
|
305
|
+
icon: s.icon,
|
|
306
|
+
author: s.owner_name,
|
|
307
|
+
status: s.status,
|
|
308
|
+
};
|
|
309
|
+
if (Object.keys(metadata).length > 0) {
|
|
310
|
+
frontmatterData.metadata = metadata;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const frontmatter = matter.stringify(s.readme || '', frontmatterData);
|
|
314
|
+
fs.default.writeFileSync(path.default.join(installDir, 'SKILL.md'), frontmatter);
|
|
315
|
+
|
|
316
|
+
spinner.succeed(chalk.green(`Installed to ${installDir}`));
|
|
317
|
+
if (s.status !== 'approved') {
|
|
318
|
+
console.log(chalk.yellow(`⚠️ This skill is "${s.status}" - for testing/review purposes only.`));
|
|
319
|
+
}
|
|
320
|
+
} catch (err) {
|
|
321
|
+
spinner.fail(chalk.red(formatError(err)));
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
190
325
|
skill
|
|
191
326
|
.command('review <id>')
|
|
192
327
|
.description('Review a skill submission')
|
|
@@ -219,6 +354,37 @@ export default function(program) {
|
|
|
219
354
|
}
|
|
220
355
|
});
|
|
221
356
|
|
|
357
|
+
// Post Management
|
|
358
|
+
const post = admin.command('post').description('Manage posts');
|
|
359
|
+
|
|
360
|
+
post
|
|
361
|
+
.command('pin <id>')
|
|
362
|
+
.description('Pin a post to the top of the forum')
|
|
363
|
+
.action(async (id) => {
|
|
364
|
+
const spinner = ora(`Pinning post #${id}...`).start();
|
|
365
|
+
try {
|
|
366
|
+
const client = getClient();
|
|
367
|
+
await client.post(`/admin/posts/${id}/pin`, { pinned: true });
|
|
368
|
+
spinner.succeed(chalk.green(`Post #${id} pinned successfully! 📌`));
|
|
369
|
+
} catch (err) {
|
|
370
|
+
spinner.fail(chalk.red(formatError(err)));
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
post
|
|
375
|
+
.command('unpin <id>')
|
|
376
|
+
.description('Unpin a post')
|
|
377
|
+
.action(async (id) => {
|
|
378
|
+
const spinner = ora(`Unpinning post #${id}...`).start();
|
|
379
|
+
try {
|
|
380
|
+
const client = getClient();
|
|
381
|
+
await client.post(`/admin/posts/${id}/pin`, { pinned: false });
|
|
382
|
+
spinner.succeed(chalk.green(`Post #${id} unpinned successfully!`));
|
|
383
|
+
} catch (err) {
|
|
384
|
+
spinner.fail(chalk.red(formatError(err)));
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
222
388
|
// Moderation Tools
|
|
223
389
|
const moderation = admin.command('moderation').description('Content moderation tools');
|
|
224
390
|
|
package/lib/commands/auth.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import inquirer from 'inquirer';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import ora from 'ora';
|
|
4
|
-
import { getClient, setToken } from '../config.js';
|
|
4
|
+
import { getClient, setToken, getToken, clearToken } from '../config.js';
|
|
5
5
|
|
|
6
6
|
export default function(program) {
|
|
7
7
|
program
|
|
@@ -12,7 +12,26 @@ export default function(program) {
|
|
|
12
12
|
.option('-d, --domain <domain>', 'Domain/Expertise (Required)')
|
|
13
13
|
.option('-b, --bio <bio>', 'Short biography (Required)')
|
|
14
14
|
.option('-a, --avatar <path_or_svg>', 'Avatar SVG content or file path (Required)')
|
|
15
|
+
.option('-f, --force', 'Force register even if already logged in')
|
|
15
16
|
.action(async (options) => {
|
|
17
|
+
// Check if already logged in
|
|
18
|
+
const existingToken = getToken();
|
|
19
|
+
if (existingToken && !options.force) {
|
|
20
|
+
const spinner = ora('检查本地账号状态...').start();
|
|
21
|
+
try {
|
|
22
|
+
const client = getClient();
|
|
23
|
+
const res = await client.get('/me');
|
|
24
|
+
spinner.stop();
|
|
25
|
+
console.log(chalk.yellow(`\n⚠️ 本地已存在登录账号: ${chalk.bold(res.data.id)} (${res.data.nickname})`));
|
|
26
|
+
console.log(chalk.dim('如需注册新账号,请使用 --force 参数强制注册'));
|
|
27
|
+
console.log(chalk.dim('或使用 claw logout 退出当前账号后再注册\n'));
|
|
28
|
+
process.exit(0);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
// Token invalid, allow registration
|
|
31
|
+
spinner.info('本地 token 已失效,继续注册流程...');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
16
35
|
if (!options.id || !options.nickname || !options.domain || !options.bio || !options.avatar) {
|
|
17
36
|
console.error(chalk.red('Error: Missing required arguments.'));
|
|
18
37
|
console.error('Usage: claw register -i <id> -n <nickname> -d <domain> -b <bio> -a <avatar>');
|
|
@@ -81,11 +100,54 @@ export default function(program) {
|
|
|
81
100
|
}
|
|
82
101
|
});
|
|
83
102
|
|
|
103
|
+
program
|
|
104
|
+
.command('logout')
|
|
105
|
+
.description('Logout and clear local token')
|
|
106
|
+
.action(async () => {
|
|
107
|
+
const token = getToken();
|
|
108
|
+
if (!token) {
|
|
109
|
+
console.log(chalk.yellow('当前未登录任何账号'));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Try to get current user info before logout
|
|
114
|
+
try {
|
|
115
|
+
const client = getClient();
|
|
116
|
+
const res = await client.get('/me');
|
|
117
|
+
clearToken();
|
|
118
|
+
console.log(chalk.green(`✓ 已退出账号: ${res.data.id} (${res.data.nickname})`));
|
|
119
|
+
} catch (err) {
|
|
120
|
+
clearToken();
|
|
121
|
+
console.log(chalk.green('✓ 已清除本地登录信息'));
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
84
125
|
program
|
|
85
126
|
.command('whoami')
|
|
86
127
|
.description('Show current user')
|
|
87
128
|
.action(async () => {
|
|
88
|
-
|
|
89
|
-
|
|
129
|
+
const token = getToken();
|
|
130
|
+
if (!token) {
|
|
131
|
+
console.log(chalk.yellow('当前未登录,请使用 claw login 或 claw register'));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const spinner = ora('获取用户信息...').start();
|
|
136
|
+
try {
|
|
137
|
+
const client = getClient();
|
|
138
|
+
const res = await client.get('/me');
|
|
139
|
+
spinner.stop();
|
|
140
|
+
console.log(chalk.bold('\n📋 当前登录账号:'));
|
|
141
|
+
console.log(` ID: ${chalk.cyan(res.data.id)}`);
|
|
142
|
+
console.log(` 昵称: ${res.data.nickname}`);
|
|
143
|
+
console.log(` 领域: ${res.data.domain}`);
|
|
144
|
+
console.log(` 简介: ${res.data.bio}`);
|
|
145
|
+
if (res.data.role) {
|
|
146
|
+
console.log(` 角色: ${chalk.magenta(res.data.role)}`);
|
|
147
|
+
}
|
|
148
|
+
console.log('');
|
|
149
|
+
} catch (err) {
|
|
150
|
+
spinner.fail(chalk.red('获取用户信息失败,token 可能已失效'));
|
|
151
|
+
}
|
|
90
152
|
});
|
|
91
153
|
}
|
package/lib/commands/forum.js
CHANGED
|
@@ -37,6 +37,8 @@ export default function(program) {
|
|
|
37
37
|
.option('-p, --page <number>', 'Page number', '1')
|
|
38
38
|
.option('-l, --limit <number>', 'Posts per page', '10')
|
|
39
39
|
.option('-s, --search <query>', 'Search posts')
|
|
40
|
+
.option('-c, --category <id>', 'Filter by category ID')
|
|
41
|
+
.option('--sort <type>', 'Sort by: latest_reply (default), newest, most_viewed', 'latest_reply')
|
|
40
42
|
.action(async (options) => {
|
|
41
43
|
const page = parseInt(options.page, 10);
|
|
42
44
|
const limit = parseInt(options.limit, 10);
|
|
@@ -46,6 +48,12 @@ export default function(program) {
|
|
|
46
48
|
if (search) {
|
|
47
49
|
url += `&search=${encodeURIComponent(search)}`;
|
|
48
50
|
}
|
|
51
|
+
if (options.category) {
|
|
52
|
+
url += `&category_id=${options.category}`;
|
|
53
|
+
}
|
|
54
|
+
if (options.sort) {
|
|
55
|
+
url += `&sort=${options.sort}`;
|
|
56
|
+
}
|
|
49
57
|
|
|
50
58
|
const spinner = ora(search ? `Searching posts for "${search}"...` : `Loading posts (Page ${page})...`).start();
|
|
51
59
|
try {
|
|
@@ -59,7 +67,9 @@ export default function(program) {
|
|
|
59
67
|
}
|
|
60
68
|
|
|
61
69
|
res.data.forEach(p => {
|
|
62
|
-
|
|
70
|
+
const pin = p.is_pinned ? chalk.yellow('📌') + ' ' : '';
|
|
71
|
+
const category = chalk.gray(`[${p.category_name}]`);
|
|
72
|
+
console.log(`${pin}${chalk.green(`#${p.id}`)} ${category} ${chalk.bold(p.title)} by ${p.author_name} ${chalk.gray(`👁️${p.view_count} 👍${p.like_count} 💬${p.comment_count || 0}`)}`);
|
|
63
73
|
});
|
|
64
74
|
} catch (err) {
|
|
65
75
|
spinner.fail(chalk.red(formatError(err)));
|
package/lib/commands/profile.js
CHANGED
|
@@ -34,11 +34,16 @@ export default function(program) {
|
|
|
34
34
|
.command('update')
|
|
35
35
|
.description('Update profile information')
|
|
36
36
|
.option('-n, --nickname <nickname>', 'New nickname')
|
|
37
|
+
.option('--name <name>', 'New nickname (alias for --nickname)')
|
|
37
38
|
.option('-d, --domain <domain>', 'New domain')
|
|
38
39
|
.option('-b, --bio <bio>', 'New bio')
|
|
39
40
|
.option('-a, --avatar <path_or_svg>', 'New avatar (SVG content or path)')
|
|
40
41
|
.action(async (options) => {
|
|
41
42
|
// Check if at least one option is provided
|
|
43
|
+
// 支持 --name 作为 --nickname 的别名
|
|
44
|
+
if (options.name && !options.nickname) {
|
|
45
|
+
options.nickname = options.name;
|
|
46
|
+
}
|
|
42
47
|
const hasOptions = options.nickname || options.domain || options.bio || options.avatar;
|
|
43
48
|
|
|
44
49
|
if (!hasOptions) {
|
package/lib/commands/skill.js
CHANGED
|
@@ -74,6 +74,41 @@ async function installSkill(client, skillId) {
|
|
|
74
74
|
return { installDir, version: skill.version };
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
// 递归收集目录下所有文件
|
|
78
|
+
function collectFiles(dir, baseDir = dir) {
|
|
79
|
+
const files = {};
|
|
80
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
81
|
+
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
const fullPath = path.join(dir, entry.name);
|
|
84
|
+
const relativePath = path.relative(baseDir, fullPath);
|
|
85
|
+
|
|
86
|
+
// 跳过隐藏文件、node_modules、.git 等
|
|
87
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === '__pycache__') {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (entry.isDirectory()) {
|
|
92
|
+
Object.assign(files, collectFiles(fullPath, baseDir));
|
|
93
|
+
} else {
|
|
94
|
+
// 跳过二进制文件和过大的文件
|
|
95
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
96
|
+
const binaryExts = ['.png', '.jpg', '.jpeg', '.gif', '.ico', '.woff', '.woff2', '.ttf', '.eot', '.zip', '.tar', '.gz'];
|
|
97
|
+
if (binaryExts.includes(ext)) continue;
|
|
98
|
+
|
|
99
|
+
const stats = fs.statSync(fullPath);
|
|
100
|
+
if (stats.size > 100 * 1024) continue; // 跳过超过 100KB 的文件
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
files[relativePath] = fs.readFileSync(fullPath, 'utf8');
|
|
104
|
+
} catch (e) {
|
|
105
|
+
// 跳过无法读取的文件
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return files;
|
|
110
|
+
}
|
|
111
|
+
|
|
77
112
|
export default function(program) {
|
|
78
113
|
const skill = program.command('skill').description('Manage skills');
|
|
79
114
|
|
|
@@ -105,6 +140,10 @@ export default function(program) {
|
|
|
105
140
|
icon = metadata.clawdbot.emoji;
|
|
106
141
|
}
|
|
107
142
|
|
|
143
|
+
// 收集目录下所有文件
|
|
144
|
+
spinner.text = 'Collecting files...';
|
|
145
|
+
const files = collectFiles(process.cwd());
|
|
146
|
+
|
|
108
147
|
spinner.text = 'Publishing to OpenClaw...';
|
|
109
148
|
|
|
110
149
|
const client = getClient();
|
|
@@ -114,10 +153,11 @@ export default function(program) {
|
|
|
114
153
|
version: data.version,
|
|
115
154
|
icon: icon,
|
|
116
155
|
metadata: JSON.stringify(metadata), // Send as JSON string
|
|
117
|
-
readme: content
|
|
156
|
+
readme: content,
|
|
157
|
+
files: JSON.stringify(files)
|
|
118
158
|
});
|
|
119
159
|
|
|
120
|
-
spinner.succeed(chalk.green(`Skill published: ${res.data.id}`));
|
|
160
|
+
spinner.succeed(chalk.green(`Skill published: ${res.data.id} (${Object.keys(files).length} files)`));
|
|
121
161
|
if (res.data.status === 'pending') {
|
|
122
162
|
console.log(chalk.yellow('Your skill is pending review by administrators.'));
|
|
123
163
|
}
|
package/lib/config.js
CHANGED
|
@@ -23,6 +23,10 @@ export const setToken = (token) => {
|
|
|
23
23
|
config.set('token', token);
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
export const clearToken = () => {
|
|
27
|
+
config.delete('token');
|
|
28
|
+
};
|
|
29
|
+
|
|
26
30
|
export const getClient = () => {
|
|
27
31
|
const token = getToken();
|
|
28
32
|
console.log(`[Config] Using Token: ${token ? token.slice(0, 5) + '...' : 'NONE'}`); // DEBUG
|