aisoulhub 1.0.7 → 1.0.11
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 +35 -1
- package/package.json +2 -2
- package/src/commands/install.js +100 -27
- package/src/index.js +3 -11
- package/src/utils/check.js +1 -45
- package/src/utils/download.js +4 -35
- package/src/utils/meta.js +4 -16
- package/src/utils/openclaw.js +48 -16
- package/src/commands/update.js +0 -76
package/README.md
CHANGED
|
@@ -1,2 +1,36 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AISoulHub CLI
|
|
2
2
|
|
|
3
|
+
A command-line interface tool to easily install, update, and manage AI Soul configurations and skills for OpenClaw agents. It seamlessly integrates with [AISoulHub](https://aisoulhub.com) to provide a streamlined experience.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
Before using this tool, ensure you have the following installed:
|
|
8
|
+
|
|
9
|
+
- [Node.js](https://nodejs.org/) (v16 or higher recommended)
|
|
10
|
+
- [OpenClaw](https://openclaw.ai/) CLI (`openclaw` command must be available in your PATH)
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
You can run `aisoulhub` directly via `npx` without needing to install it globally.
|
|
15
|
+
|
|
16
|
+
### 1. Install an AI Soul
|
|
17
|
+
|
|
18
|
+
Downloads and installs an AI Soul package into a new or existing OpenClaw Agent.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx aisoulhub install <ai_soul_id>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### 2. Update an AI Soul
|
|
25
|
+
|
|
26
|
+
Updates an existing OpenClaw Agent with the latest AI Soul package configuration and skills.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx aisoulhub update
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
<br />
|
|
33
|
+
|
|
34
|
+
## License
|
|
35
|
+
|
|
36
|
+
ISC
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aisoulhub",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"type": "module",
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@inquirer/prompts": "^8.3.2",
|
|
26
|
-
"axios": "^1.13.6",
|
|
27
26
|
"chalk": "^5.6.2",
|
|
27
|
+
"clawhub": "^0.9.0",
|
|
28
28
|
"commander": "^14.0.3",
|
|
29
29
|
"ora": "^9.3.0",
|
|
30
30
|
"shelljs": "^0.10.0",
|
package/src/commands/install.js
CHANGED
|
@@ -1,61 +1,120 @@
|
|
|
1
|
-
import { checkOpenclaw
|
|
2
|
-
import { getAgents, addAgent } from '../utils/openclaw.js';
|
|
3
|
-
import {
|
|
1
|
+
import { checkOpenclaw } from '../utils/check.js';
|
|
2
|
+
import { getAgents, addAgent, setAgentIdentity } from '../utils/openclaw.js';
|
|
3
|
+
import { extractLocalZip } from '../utils/download.js';
|
|
4
4
|
import { processMetaJson } from '../utils/meta.js';
|
|
5
|
-
import { select, input } from '@inquirer/prompts';
|
|
5
|
+
import { select, input, confirm } from '@inquirer/prompts';
|
|
6
6
|
import chalk from 'chalk';
|
|
7
|
+
import fs from 'fs';
|
|
7
8
|
import path from 'path';
|
|
8
9
|
import os from 'os';
|
|
10
|
+
import unzipper from 'unzipper';
|
|
9
11
|
|
|
10
|
-
export async function installCommand(
|
|
11
|
-
if (!
|
|
12
|
-
console.error(chalk.red('❌
|
|
12
|
+
export async function installCommand(zipPath) {
|
|
13
|
+
if (!zipPath) {
|
|
14
|
+
console.error(chalk.red('❌ Error: zip path is required, e.g.: aisoulhub install <path_to_zip>'));
|
|
13
15
|
process.exit(1);
|
|
14
16
|
}
|
|
15
17
|
|
|
18
|
+
// 检查是否为本地 zip 文件
|
|
19
|
+
if (!zipPath.endsWith('.zip') || !fs.existsSync(path.resolve(zipPath))) {
|
|
20
|
+
console.error(chalk.red(`❌ Error: Invalid or missing local zip file at [${zipPath}]`));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const localZipPath = path.resolve(zipPath);
|
|
24
|
+
|
|
16
25
|
// 1. 前置检查
|
|
17
26
|
checkOpenclaw();
|
|
18
|
-
await checkLogin();
|
|
19
27
|
|
|
20
28
|
// 2. 获取当前的 agents 列表
|
|
21
29
|
let agents = [];
|
|
22
30
|
try {
|
|
23
31
|
agents = getAgents();
|
|
24
32
|
} catch (err) {
|
|
25
|
-
console.error(chalk.yellow(`⚠️
|
|
33
|
+
console.error(chalk.yellow(`⚠️ Failed to get agents list: ${err.message}`));
|
|
26
34
|
}
|
|
27
35
|
|
|
28
36
|
// 3. 提示用户选择或创建 Agent
|
|
29
37
|
const choices = [
|
|
30
|
-
{ name: '✨
|
|
31
|
-
...agents.map(a => ({ name: `📁 ${a.name} (${a.path})`, value: a.path }))
|
|
38
|
+
{ name: '✨ Create new Agent', value: { name: 'create_new', path: null } },
|
|
39
|
+
...agents.map(a => ({ name: `📁 ${a.name} (${a.path})`, value: { name: a.name, path: a.path } }))
|
|
32
40
|
];
|
|
33
41
|
|
|
34
42
|
const agentChoice = await select({
|
|
35
|
-
message: '
|
|
43
|
+
message: 'Please select the Agent to install:',
|
|
36
44
|
choices
|
|
37
45
|
});
|
|
38
46
|
|
|
39
|
-
|
|
47
|
+
if (agentChoice.name === 'main') {
|
|
48
|
+
const isConfirmed = await confirm({
|
|
49
|
+
message: chalk.yellow('⚠️ Warning: Overwriting the "main" agent might cause unexpected issues. Are you sure you want to continue?'),
|
|
50
|
+
default: false
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (!isConfirmed) {
|
|
54
|
+
console.log(chalk.blue('ℹ️ Operation cancelled by user.'));
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let targetPath = agentChoice.path;
|
|
60
|
+
let finalAgentName = agentChoice.name;
|
|
61
|
+
|
|
62
|
+
if (agentChoice.name === 'create_new') {
|
|
63
|
+
let defaultAgentName = '';
|
|
64
|
+
let identityName = '';
|
|
65
|
+
|
|
66
|
+
// 尝试从 zip 文件中的 aisoul-meta.json 获取默认 name 和 slug
|
|
67
|
+
try {
|
|
68
|
+
const directory = await unzipper.Open.file(localZipPath);
|
|
69
|
+
const metaFile = directory.files.find(d => d.path === 'aisoul-meta.json');
|
|
70
|
+
if (metaFile) {
|
|
71
|
+
const content = await metaFile.buffer();
|
|
72
|
+
const meta = JSON.parse(content.toString('utf-8'));
|
|
73
|
+
if (meta.slug) {
|
|
74
|
+
defaultAgentName = meta.slug;
|
|
75
|
+
}
|
|
76
|
+
if (meta.name) {
|
|
77
|
+
identityName = meta.name;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch (err) {
|
|
81
|
+
// 忽略读取 zip 出错的情况
|
|
82
|
+
}
|
|
40
83
|
|
|
41
|
-
if (agentChoice === 'create_new') {
|
|
42
84
|
const newAgentName = await input({
|
|
43
|
-
message: '
|
|
44
|
-
|
|
85
|
+
message: 'Please enter the name of the new Agent:',
|
|
86
|
+
default: defaultAgentName,
|
|
87
|
+
validate: (val) => {
|
|
88
|
+
if (val.trim().length === 0) return 'Agent name cannot be empty';
|
|
89
|
+
if (val.trim().toLowerCase() === 'main') return '"main" is reserved. Choose another name.';
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
45
92
|
});
|
|
93
|
+
|
|
94
|
+
finalAgentName = newAgentName;
|
|
46
95
|
|
|
47
96
|
const defaultWorkspace = path.join(os.homedir(), '.openclaw', `workspace-${newAgentName}`);
|
|
48
97
|
|
|
49
98
|
const newWorkspacePath = await input({
|
|
50
|
-
message: '
|
|
99
|
+
message: 'Please enter the workspace directory for the new Agent:',
|
|
51
100
|
default: defaultWorkspace,
|
|
52
|
-
validate: (val) => val.trim().length > 0 ? true : '
|
|
101
|
+
validate: (val) => val.trim().length > 0 ? true : 'Workspace directory cannot be empty'
|
|
53
102
|
});
|
|
54
103
|
|
|
55
|
-
console.log(chalk.cyan(`🔄
|
|
104
|
+
console.log(chalk.cyan(`🔄 Creating Agent [${newAgentName}]...`));
|
|
56
105
|
try {
|
|
57
106
|
addAgent(newAgentName, newWorkspacePath);
|
|
58
|
-
console.log(chalk.green(`✅ Agent [${newAgentName}]
|
|
107
|
+
console.log(chalk.green(`✅ Agent [${newAgentName}] created successfully.`));
|
|
108
|
+
|
|
109
|
+
// 设置 Agent 的显示名称
|
|
110
|
+
if (identityName) {
|
|
111
|
+
try {
|
|
112
|
+
setAgentIdentity(newAgentName, identityName);
|
|
113
|
+
console.log(chalk.green(`✅ Agent identity name set to [${identityName}].`));
|
|
114
|
+
} catch (identityErr) {
|
|
115
|
+
console.error(chalk.yellow(`⚠️ Warning: Failed to set identity name: ${identityErr.message}`));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
59
118
|
|
|
60
119
|
// 重新获取列表以获取新 agent 的路径
|
|
61
120
|
const updatedAgents = getAgents();
|
|
@@ -72,20 +131,34 @@ export async function installCommand(aiSoulId) {
|
|
|
72
131
|
}
|
|
73
132
|
|
|
74
133
|
if (!targetPath) {
|
|
75
|
-
console.error(chalk.red('❌
|
|
134
|
+
console.error(chalk.red('❌ Error: Unable to determine the workspace directory path for the Agent.'));
|
|
76
135
|
process.exit(1);
|
|
77
136
|
}
|
|
78
137
|
|
|
79
|
-
console.log(chalk.blue(`ℹ️
|
|
138
|
+
console.log(chalk.blue(`ℹ️ Target workspace directory: ${targetPath}`));
|
|
139
|
+
|
|
140
|
+
// 4. 解压本地 ZIP 文件
|
|
141
|
+
const extractSuccess = await extractLocalZip(localZipPath, targetPath);
|
|
80
142
|
|
|
81
|
-
|
|
82
|
-
const downloadSuccess = await downloadAndExtract(aiSoulId, targetPath);
|
|
83
|
-
if (!downloadSuccess) {
|
|
143
|
+
if (!extractSuccess) {
|
|
84
144
|
process.exit(1);
|
|
85
145
|
}
|
|
86
146
|
|
|
87
|
-
// 5. 解析 meta.json 并安装技能
|
|
147
|
+
// 5. 解析 aisoul-meta.json 并安装技能
|
|
88
148
|
await processMetaJson(targetPath);
|
|
89
149
|
|
|
90
|
-
console.log(chalk.green(`🎉
|
|
150
|
+
console.log(chalk.green(`🎉 Successfully installed local package to the Agent.`));
|
|
151
|
+
|
|
152
|
+
// 直接唤醒 Agent
|
|
153
|
+
console.log(chalk.cyan(`\n👉 Waking up the agent...`));
|
|
154
|
+
const { spawn } = await import('child_process');
|
|
155
|
+
|
|
156
|
+
const child = spawn('openclaw', ['agent', '--agent', finalAgentName, '--message', 'hi'], {
|
|
157
|
+
stdio: 'inherit',
|
|
158
|
+
shell: true
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
child.on('error', (error) => {
|
|
162
|
+
console.error(chalk.red(`❌ Failed to start the agent: ${error.message}`));
|
|
163
|
+
});
|
|
91
164
|
}
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { installCommand } from './commands/install.js';
|
|
3
|
-
import { updateCommand } from './commands/update.js';
|
|
4
3
|
import fs from 'fs';
|
|
5
4
|
import path from 'path';
|
|
6
5
|
import { fileURLToPath } from 'url';
|
|
@@ -16,23 +15,16 @@ export async function main() {
|
|
|
16
15
|
|
|
17
16
|
program
|
|
18
17
|
.name('aisoulhub')
|
|
19
|
-
.description('AISoulHub CLI
|
|
18
|
+
.description('AISoulHub CLI tool for installing Agent skills and configurations')
|
|
20
19
|
.version(pkg.version);
|
|
21
20
|
|
|
22
21
|
program
|
|
23
22
|
.command('install')
|
|
24
|
-
.description('
|
|
25
|
-
.argument('<
|
|
23
|
+
.description('Install new Agent configuration and skills from a local zip file')
|
|
24
|
+
.argument('<path_to_zip>', 'Specify the local path to the zip file')
|
|
26
25
|
.action(async (aiSoulId) => {
|
|
27
26
|
await installCommand(aiSoulId);
|
|
28
27
|
});
|
|
29
28
|
|
|
30
|
-
program
|
|
31
|
-
.command('update')
|
|
32
|
-
.description('更新已存在的 Agent 配置及技能')
|
|
33
|
-
.action(async () => {
|
|
34
|
-
await updateCommand();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
29
|
program.parseAsync(process.argv);
|
|
38
30
|
}
|
package/src/utils/check.js
CHANGED
|
@@ -1,53 +1,9 @@
|
|
|
1
1
|
import shell from 'shelljs';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import os from 'os';
|
|
5
2
|
import chalk from 'chalk';
|
|
6
|
-
import { input } from '@inquirer/prompts';
|
|
7
3
|
|
|
8
4
|
export function checkOpenclaw() {
|
|
9
5
|
if (!shell.which('openclaw')) {
|
|
10
|
-
console.error(chalk.red('❌
|
|
6
|
+
console.error(chalk.red('❌ Error: openclaw command not found. Please follow the instructions at https://openclaw.ai to install openclaw, then try again.'));
|
|
11
7
|
process.exit(1);
|
|
12
8
|
}
|
|
13
9
|
}
|
|
14
|
-
|
|
15
|
-
const CONFIG_PATH = path.join(os.homedir(), '.aisoulhub.json');
|
|
16
|
-
|
|
17
|
-
export async function checkLogin() {
|
|
18
|
-
let config = {};
|
|
19
|
-
if (fs.existsSync(CONFIG_PATH)) {
|
|
20
|
-
try {
|
|
21
|
-
const content = fs.readFileSync(CONFIG_PATH, 'utf-8');
|
|
22
|
-
config = JSON.parse(content);
|
|
23
|
-
} catch (e) {
|
|
24
|
-
// ignore parse error
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (!config.email) {
|
|
29
|
-
console.log(chalk.yellow('ℹ️ 未检测到登录信息,请输入您的邮箱进行登录:'));
|
|
30
|
-
const email = await input({
|
|
31
|
-
message: '请输入您的邮箱地址:',
|
|
32
|
-
validate: (value) => {
|
|
33
|
-
const pass = value.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
|
|
34
|
-
if (pass) {
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
return '请输入有效的邮箱地址';
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
config.email = email;
|
|
42
|
-
try {
|
|
43
|
-
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
|
|
44
|
-
console.log(chalk.green(`✅ 登录成功!邮箱: ${email} 已保存。`));
|
|
45
|
-
} catch (e) {
|
|
46
|
-
console.log(chalk.yellow(`⚠️ 登录成功,但无法保存到 ${CONFIG_PATH}: ${e.message}`));
|
|
47
|
-
}
|
|
48
|
-
} else {
|
|
49
|
-
console.log(chalk.blue(`ℹ️ 当前登录邮箱: ${config.email}`));
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return config.email;
|
|
53
|
-
}
|
package/src/utils/download.js
CHANGED
|
@@ -1,37 +1,12 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
1
|
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import os from 'os';
|
|
5
2
|
import unzipper from 'unzipper';
|
|
6
3
|
import ora from 'ora';
|
|
7
4
|
import chalk from 'chalk';
|
|
8
5
|
|
|
9
|
-
export async function
|
|
10
|
-
const
|
|
11
|
-
const tempDir = os.tmpdir();
|
|
12
|
-
const zipPath = path.join(tempDir, `${aiSoulId}.zip`);
|
|
13
|
-
|
|
14
|
-
const spinner = ora(`正在下载包 ${aiSoulId}...`).start();
|
|
6
|
+
export async function extractLocalZip(zipPath, targetPath) {
|
|
7
|
+
const spinner = ora(`Extracting local package from ${zipPath}...`).start();
|
|
15
8
|
|
|
16
9
|
try {
|
|
17
|
-
const response = await axios({
|
|
18
|
-
method: 'GET',
|
|
19
|
-
url,
|
|
20
|
-
responseType: 'stream',
|
|
21
|
-
// Provide a mock user-agent just in case
|
|
22
|
-
headers: { 'User-Agent': 'aisoulhub-cli/1.0.0' }
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const writer = fs.createWriteStream(zipPath);
|
|
26
|
-
response.data.pipe(writer);
|
|
27
|
-
|
|
28
|
-
await new Promise((resolve, reject) => {
|
|
29
|
-
writer.on('finish', resolve);
|
|
30
|
-
writer.on('error', reject);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
spinner.text = `下载完成,正在解压...`;
|
|
34
|
-
|
|
35
10
|
// Ensure target path exists
|
|
36
11
|
if (!fs.existsSync(targetPath)) {
|
|
37
12
|
fs.mkdirSync(targetPath, { recursive: true });
|
|
@@ -42,16 +17,10 @@ export async function downloadAndExtract(aiSoulId, targetPath) {
|
|
|
42
17
|
.pipe(unzipper.Extract({ path: targetPath }))
|
|
43
18
|
.promise();
|
|
44
19
|
|
|
45
|
-
spinner.succeed(chalk.green(
|
|
46
|
-
|
|
47
|
-
// Cleanup temp zip
|
|
48
|
-
fs.unlinkSync(zipPath);
|
|
20
|
+
spinner.succeed(chalk.green(`Local package extracted successfully.`));
|
|
49
21
|
return true;
|
|
50
22
|
} catch (err) {
|
|
51
|
-
spinner.fail(chalk.red(
|
|
52
|
-
if (fs.existsSync(zipPath)) {
|
|
53
|
-
fs.unlinkSync(zipPath);
|
|
54
|
-
}
|
|
23
|
+
spinner.fail(chalk.red(`Error: Failed to extract the local package: ${err.message}`));
|
|
55
24
|
return false;
|
|
56
25
|
}
|
|
57
26
|
}
|
package/src/utils/meta.js
CHANGED
|
@@ -4,9 +4,9 @@ import chalk from 'chalk';
|
|
|
4
4
|
import { installSkill } from './openclaw.js';
|
|
5
5
|
|
|
6
6
|
export async function processMetaJson(workspacePath) {
|
|
7
|
-
const metaPath = path.join(workspacePath, 'meta.json');
|
|
7
|
+
const metaPath = path.join(workspacePath, 'aisoul-meta.json');
|
|
8
8
|
if (!fs.existsSync(metaPath)) {
|
|
9
|
-
console.log(chalk.yellow(`ℹ️
|
|
9
|
+
console.log(chalk.yellow(`ℹ️ aisoul-meta.json not found, skipping skill installation. (${metaPath})`));
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -16,22 +16,10 @@ export async function processMetaJson(workspacePath) {
|
|
|
16
16
|
const skills = meta.skills || [];
|
|
17
17
|
|
|
18
18
|
if (skills.length === 0) {
|
|
19
|
-
console.log(chalk.blue('ℹ️ meta.json
|
|
19
|
+
console.log(chalk.blue('ℹ️ No skills defined to install in aisoul-meta.json.'));
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
console.log(chalk.cyan(`🔄 开始安装 ${skills.length} 个技能...`));
|
|
24
|
-
for (const skill of skills) {
|
|
25
|
-
console.log(chalk.blue(`⏳ 正在安装技能: ${skill}...`));
|
|
26
|
-
const success = installSkill(workspacePath, skill);
|
|
27
|
-
if (success) {
|
|
28
|
-
console.log(chalk.green(`✅ 技能 ${skill} 安装成功。`));
|
|
29
|
-
} else {
|
|
30
|
-
console.log(chalk.red(`❌ 技能 ${skill} 安装失败。`));
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
console.log(chalk.green('🎉 所有技能安装流程结束。'));
|
|
34
22
|
} catch (err) {
|
|
35
|
-
console.error(chalk.red(`❌
|
|
23
|
+
console.error(chalk.red(`❌ Failed to read or parse aisoul-meta.json: ${err.message}`));
|
|
36
24
|
}
|
|
37
25
|
}
|
package/src/utils/openclaw.js
CHANGED
|
@@ -3,17 +3,31 @@ import shell from 'shelljs';
|
|
|
3
3
|
export function getAgents() {
|
|
4
4
|
const result = shell.exec('openclaw agents list --json', { silent: true });
|
|
5
5
|
if (result.code !== 0) {
|
|
6
|
-
throw new Error('
|
|
6
|
+
throw new Error('Failed to get agents list: ' + result.stderr);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
// Try to parse JSON first
|
|
10
10
|
try {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
// 尝试提取标准 JSON 数组结构(截取第一个 '[' 到匹配的 ']' 的内容)
|
|
12
|
+
// 为了防止截取到后面的无关内容,我们查找从 '[' 开始,以 ']' 结束,并且紧跟换行或结尾的部分
|
|
13
|
+
const jsonMatch = result.stdout.match(/\[[\s\S]*?\n\]/);
|
|
14
|
+
if (jsonMatch) {
|
|
15
|
+
const list = JSON.parse(jsonMatch[0]);
|
|
16
|
+
if (Array.isArray(list)) {
|
|
17
|
+
return list.map(item => ({
|
|
18
|
+
name: item.id || item.name,
|
|
19
|
+
path: item.workspace || item.path || item.agentDir
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
// 如果没有匹配到数组结构,则尝试直接解析
|
|
24
|
+
const list = JSON.parse(result.stdout);
|
|
25
|
+
if (Array.isArray(list)) {
|
|
26
|
+
return list.map(item => ({
|
|
27
|
+
name: item.id || item.name,
|
|
28
|
+
path: item.workspace || item.path || item.agentDir
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
17
31
|
}
|
|
18
32
|
} catch (err) {
|
|
19
33
|
// Not JSON, parse text
|
|
@@ -45,21 +59,39 @@ export function addAgent(name, workspacePath) {
|
|
|
45
59
|
}
|
|
46
60
|
const result = shell.exec(cmd, { silent: true });
|
|
47
61
|
if (result.code !== 0) {
|
|
48
|
-
throw new Error(
|
|
62
|
+
throw new Error(`Failed to create agent [${name}]: ` + result.stderr);
|
|
63
|
+
}
|
|
64
|
+
return result.stdout;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function setAgentIdentity(agentName, identityName) {
|
|
68
|
+
const cmd = `openclaw agents set-identity --agent "${agentName}" --name "${identityName}"`;
|
|
69
|
+
const result = shell.exec(cmd, { silent: true });
|
|
70
|
+
if (result.code !== 0) {
|
|
71
|
+
throw new Error(`Failed to set agent identity for [${agentName}]: ` + result.stderr);
|
|
49
72
|
}
|
|
50
73
|
return result.stdout;
|
|
51
74
|
}
|
|
52
75
|
|
|
53
|
-
export function installSkill(workspacePath, skillStr) {
|
|
54
|
-
|
|
55
|
-
const result = shell.exec(`
|
|
76
|
+
export async function installSkill(workspacePath, skillStr, retryCount = 0) {
|
|
77
|
+
const maxRetries = 3;
|
|
78
|
+
const result = shell.exec(`npx clawhub install ${skillStr} --workdir "${workspacePath}"`, { silent: true });
|
|
79
|
+
|
|
56
80
|
if (result.code !== 0) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
81
|
+
const errorOutput = result.stderr || result.stdout;
|
|
82
|
+
if (errorOutput.includes('Rate limit exceeded')) {
|
|
83
|
+
if (retryCount < maxRetries) {
|
|
84
|
+
console.log(`⚠️ Rate limit exceeded. Waiting 5 seconds before retrying (${retryCount + 1}/${maxRetries})...`);
|
|
85
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
86
|
+
return installSkill(workspacePath, skillStr, retryCount + 1);
|
|
87
|
+
} else {
|
|
88
|
+
console.error(`❌ Failed to install skill ${skillStr} after ${maxRetries} retries due to rate limit.`);
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
62
91
|
}
|
|
92
|
+
|
|
93
|
+
console.error(`❌ Failed to install skill ${skillStr}: `, errorOutput);
|
|
94
|
+
return false;
|
|
63
95
|
}
|
|
64
96
|
return true;
|
|
65
97
|
}
|
package/src/commands/update.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { checkOpenclaw, checkLogin } from '../utils/check.js';
|
|
2
|
-
import { getAgents } from '../utils/openclaw.js';
|
|
3
|
-
import { downloadAndExtract } from '../utils/download.js';
|
|
4
|
-
import { processMetaJson } from '../utils/meta.js';
|
|
5
|
-
import { select, input } from '@inquirer/prompts';
|
|
6
|
-
import fs from 'fs';
|
|
7
|
-
import path from 'path';
|
|
8
|
-
import chalk from 'chalk';
|
|
9
|
-
|
|
10
|
-
export async function updateCommand() {
|
|
11
|
-
// 1. 前置检查
|
|
12
|
-
checkOpenclaw();
|
|
13
|
-
await checkLogin();
|
|
14
|
-
|
|
15
|
-
// 2. 获取当前的 agents 列表
|
|
16
|
-
let agents = [];
|
|
17
|
-
try {
|
|
18
|
-
agents = getAgents();
|
|
19
|
-
} catch (err) {
|
|
20
|
-
console.error(chalk.red(`❌ 获取 agents 列表失败: ${err.message}`));
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (agents.length === 0) {
|
|
25
|
-
console.error(chalk.yellow('⚠️ 当前没有可更新的 Agent。请先使用 install 命令安装。'));
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// 3. 提示用户选择 Agent
|
|
30
|
-
const choices = agents.map(a => ({ name: `📁 ${a.name} (${a.path})`, value: a.path }));
|
|
31
|
-
|
|
32
|
-
const targetPath = await select({
|
|
33
|
-
message: '请选择要更新的 Agent:',
|
|
34
|
-
choices
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
if (!targetPath || !fs.existsSync(targetPath)) {
|
|
38
|
-
console.error(chalk.red('❌ 错误: 无效的 Agent 工作目录。'));
|
|
39
|
-
process.exit(1);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// 4. 确定 ai_soul_id
|
|
43
|
-
let aiSoulId = null;
|
|
44
|
-
const metaPath = path.join(targetPath, 'meta.json');
|
|
45
|
-
if (fs.existsSync(metaPath)) {
|
|
46
|
-
try {
|
|
47
|
-
const content = fs.readFileSync(metaPath, 'utf-8');
|
|
48
|
-
const meta = JSON.parse(content);
|
|
49
|
-
if (meta.ai_soul_id) {
|
|
50
|
-
aiSoulId = meta.ai_soul_id;
|
|
51
|
-
console.log(chalk.blue(`ℹ️ 从 meta.json 中检测到 ai_soul_id: ${aiSoulId}`));
|
|
52
|
-
}
|
|
53
|
-
} catch (e) {
|
|
54
|
-
console.warn(chalk.yellow('⚠️ 无法解析 meta.json。'));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (!aiSoulId) {
|
|
59
|
-
console.log(chalk.yellow('ℹ️ 未能从当前 Agent 的 meta.json 中获取 ai_soul_id。'));
|
|
60
|
-
aiSoulId = await input({
|
|
61
|
-
message: '请输入需要更新的 ai_soul_id:',
|
|
62
|
-
validate: (val) => val.trim().length > 0 ? true : 'ai_soul_id 不能为空'
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// 5. 下载并解压 ZIP 文件 (覆盖)
|
|
67
|
-
const downloadSuccess = await downloadAndExtract(aiSoulId, targetPath);
|
|
68
|
-
if (!downloadSuccess) {
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// 6. 解析 meta.json 并更新技能
|
|
73
|
-
await processMetaJson(targetPath);
|
|
74
|
-
|
|
75
|
-
console.log(chalk.green(`🎉 成功更新 Agent [${aiSoulId}]。`));
|
|
76
|
-
}
|