aisoulhub 1.0.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.
@@ -0,0 +1,9 @@
1
+ # Checklist
2
+ - [x] 运行 `npx aisoulhub` 可正确唤起 CLI 帮助信息。
3
+ - [x] 若未安装 `openclaw`,执行命令时能正确提示并退出。
4
+ - [x] 若未登录,执行命令时能正确提示输入邮箱,并保存至 `~/.aisoulhub.json`。
5
+ - [x] 执行 `install` 命令时,能正确提供"创建新 agent"或"选择已有 agent"的交互选项。
6
+ - [x] `install` 命令能够成功从远端下载指定 zip 包,并正确解压到目标 workspace 目录中(包含 `SOUL.md`、`AGENTS.md`、`IDENTITY.md`、`meta.json`)。
7
+ - [x] `install` 命令能够正确读取 `meta.json` 并触发 skill 安装流程。
8
+ - [x] 执行 `update` 命令时,能正确展示已有的 agent 列表供用户选择。
9
+ - [x] `update` 命令能够正确完成下载、解压覆盖及 skill 更新流程。
@@ -0,0 +1,49 @@
1
+ # aisoulhub CLI Spec
2
+
3
+ ## Why
4
+ 需要一个名为 `aisoulhub` 的 npx 包(CLI 工具),用于便捷地从 aisoulhub.com 下载、安装和更新 AI Soul 配置文件及配套的 skills,并无缝集成到 openclaw agent 的工作流中。
5
+
6
+ ## What Changes
7
+ - 初始化 Node.js CLI 项目,包名为 `aisoulhub`。
8
+ - 实现环境依赖检查:强制要求本地已安装 `openclaw`。
9
+ - 实现用户简单的邮箱登录态管理(记录至 `~/.aisoulhub.json`)。
10
+ - 实现 `npx aisoulhub install <ai_soul_id>` 命令:包含 Agent 选择/创建、文件下载、解压、覆盖 workspace 以及根据 `meta.json` 安装技能。
11
+ - 实现 `npx aisoulhub update` 命令:包含待更新 Agent 的选择、配置文件下载及技能更新。
12
+
13
+ ## Impact
14
+ - Affected specs: 提供了快速部署和更新 openclaw agent 的能力。
15
+ - Affected code: 新增 `aisoulhub` CLI 的全套业务逻辑代码,包括依赖检查、网络请求、文件系统操作及外部命令调用。
16
+
17
+ ## ADDED Requirements
18
+ ### Requirement: 环境依赖检查
19
+ 系统必须在执行核心命令前检查是否安装了 `openclaw`。
20
+ #### Scenario: 未安装 openclaw
21
+ - **WHEN** 用户执行 `aisoulhub install` 或 `update`,且系统中找不到 `openclaw` 命令。
22
+ - **THEN** 提示用户 "请按照 https://openclaw.ai 的指引安装 openclaw",随后退出程序。
23
+
24
+ ### Requirement: 用户登录态管理
25
+ 系统必须在执行安装或更新前验证用户是否已提供邮箱。
26
+ #### Scenario: 首次使用未登录
27
+ - **WHEN** 用户执行 `aisoulhub install` 或 `update`,且 `~/.aisoulhub.json` 不存在或未包含邮箱信息。
28
+ - **THEN** 提示用户输入邮箱,无需密码验证,将邮箱保存在 `~/.aisoulhub.json` 中并继续后续流程。
29
+
30
+ ### Requirement: Install 命令
31
+ 系统必须支持从远端下载指定的 AI Soul 包并部署到选定的 Agent。
32
+ #### Scenario: 成功安装
33
+ - **WHEN** 用户执行 `npx aisoulhub install <ai_soul_id>`。
34
+ - **THEN**
35
+ 1. 提示用户选择操作:创建新 agent (`openclaw agents add` 命令) 或 选择已有 agent (`openclaw agents list` 命令)。
36
+ 2. 从 `https://aisoulhub.com/packages/<ai_soul_id>.zip` 下载对应包。
37
+ 3. 将包解压到所选 agent 的 workspace 目录,释放出 `SOUL.md`、`AGENTS.md`、`IDENTITY.md`、`meta.json`。
38
+ 4. 解析 `meta.json` 中的 skill 列表,遍历并执行对应 skill 的安装命令。
39
+
40
+ ### Requirement: Update 命令
41
+ 系统必须支持对已部署的 Agent 进行更新。
42
+ #### Scenario: 成功更新
43
+ - **WHEN** 用户执行 `npx aisoulhub update`。
44
+ - **THEN**
45
+ 1. 调用 `openclaw agents list` 获取 agent 列表,并提示用户选择需要更新的 agent。
46
+ 2. 获取所选 agent 对应的 `<ai_soul_id>`(可能需要从本地 workspace 的 meta.json 中读取,或要求用户输入)。
47
+ 3. 从远端下载对应的 zip 包。
48
+ 4. 解压并覆盖文件(`SOUL.md`、`AGENTS.md`、`IDENTITY.md`、`meta.json`)。
49
+ 5. 重新解析并执行 `meta.json` 中 skill 列表的安装或更新流程。
@@ -0,0 +1,24 @@
1
+ # Tasks
2
+ - [x] Task 1: 项目初始化
3
+ - [x] SubTask 1.1: 初始化 package.json,配置 bin 字段,包名为 aisoulhub。
4
+ - [x] SubTask 1.2: 安装所需依赖项 (例如 axios, commander, inquirer/prompts, unzipper, shelljs 等)。
5
+ - [x] Task 2: 实现前置检查模块
6
+ - [x] SubTask 2.1: 实现 `openclaw` 环境依赖检查逻辑。
7
+ - [x] SubTask 2.2: 实现读取/写入 `~/.aisoulhub.json` 的邮箱登录逻辑。
8
+ - [x] Task 3: 实现工具类与核心业务逻辑
9
+ - [x] SubTask 3.1: 封装调用 `openclaw agents list` 和 `openclaw agents add` 的方法。
10
+ - [x] SubTask 3.2: 封装 zip 文件下载和解压方法。
11
+ - [x] SubTask 3.3: 封装读取解析 `meta.json` 并遍历执行 skill 安装的逻辑。
12
+ - [x] Task 4: 实现 `install` 命令
13
+ - [x] SubTask 4.1: 解析 `<ai_soul_id>` 参数。
14
+ - [x] SubTask 4.2: 串联前置检查、Agent 选择/创建、文件下载解压及技能安装流程。
15
+ - [x] Task 5: 实现 `update` 命令
16
+ - [x] SubTask 5.1: 串联前置检查、Agent 选择列表展示。
17
+ - [x] SubTask 5.2: 确定所选 Agent 对应的 ai_soul_id 并下载对应的 zip 包。
18
+ - [x] SubTask 5.3: 执行文件解压覆盖及技能更新覆盖流程。
19
+
20
+ # Task Dependencies
21
+ - [Task 2] depends on [Task 1]
22
+ - [Task 3] depends on [Task 1]
23
+ - [Task 4] depends on [Task 2, Task 3]
24
+ - [Task 5] depends on [Task 2, Task 3]
package/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # install-tool
2
+
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { main } from '../src/index.js';
4
+
5
+ main().catch(err => {
6
+ console.error(err);
7
+ process.exit(1);
8
+ });
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "aisoulhub",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1",
8
+ "publish": "npm publish --access public"
9
+ },
10
+ "keywords": [],
11
+ "author": "",
12
+ "license": "ISC",
13
+ "bin": {
14
+ "aisoulhub": "./bin/aisoulhub.js"
15
+ },
16
+ "type": "module",
17
+ "dependencies": {
18
+ "@inquirer/prompts": "^8.3.2",
19
+ "axios": "^1.13.6",
20
+ "chalk": "^5.6.2",
21
+ "commander": "^14.0.3",
22
+ "ora": "^9.3.0",
23
+ "shelljs": "^0.10.0",
24
+ "unzipper": "^0.12.3"
25
+ }
26
+ }
@@ -0,0 +1,91 @@
1
+ import { checkOpenclaw, checkLogin } from '../utils/check.js';
2
+ import { getAgents, addAgent } 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 chalk from 'chalk';
7
+ import path from 'path';
8
+ import os from 'os';
9
+
10
+ export async function installCommand(aiSoulId) {
11
+ if (!aiSoulId) {
12
+ console.error(chalk.red('❌ 错误: 必须提供 ai_soul_id,例如: aisoulhub install <ai_soul_id>'));
13
+ process.exit(1);
14
+ }
15
+
16
+ // 1. 前置检查
17
+ checkOpenclaw();
18
+ await checkLogin();
19
+
20
+ // 2. 获取当前的 agents 列表
21
+ let agents = [];
22
+ try {
23
+ agents = getAgents();
24
+ } catch (err) {
25
+ console.error(chalk.yellow(`⚠️ 获取 agents 列表失败: ${err.message}`));
26
+ }
27
+
28
+ // 3. 提示用户选择或创建 Agent
29
+ const choices = [
30
+ { name: '✨ 创建新的 Agent', value: 'create_new' },
31
+ ...agents.map(a => ({ name: `📁 ${a.name} (${a.path})`, value: a.path }))
32
+ ];
33
+
34
+ const agentChoice = await select({
35
+ message: '请选择要安装的 Agent:',
36
+ choices
37
+ });
38
+
39
+ let targetPath = agentChoice;
40
+
41
+ if (agentChoice === 'create_new') {
42
+ const newAgentName = await input({
43
+ message: '请输入新 Agent 的名称:',
44
+ validate: (val) => val.trim().length > 0 ? true : 'Agent 名称不能为空'
45
+ });
46
+
47
+ const defaultWorkspace = path.join(os.homedir(), '.openclaw', `workspace-${newAgentName}`);
48
+
49
+ const newWorkspacePath = await input({
50
+ message: '请输入新 Agent 的工作目录:',
51
+ default: defaultWorkspace,
52
+ validate: (val) => val.trim().length > 0 ? true : '工作目录不能为空'
53
+ });
54
+
55
+ console.log(chalk.cyan(`🔄 正在创建 Agent [${newAgentName}]...`));
56
+ try {
57
+ addAgent(newAgentName, newWorkspacePath);
58
+ console.log(chalk.green(`✅ Agent [${newAgentName}] 创建成功。`));
59
+
60
+ // 重新获取列表以获取新 agent 的路径
61
+ const updatedAgents = getAgents();
62
+ const newAgent = updatedAgents.find(a => a.name === newAgentName);
63
+ if (!newAgent || !newAgent.path) {
64
+ targetPath = newWorkspacePath;
65
+ } else {
66
+ targetPath = newAgent.path;
67
+ }
68
+ } catch (err) {
69
+ console.error(chalk.red(err.message));
70
+ process.exit(1);
71
+ }
72
+ }
73
+
74
+ if (!targetPath) {
75
+ console.error(chalk.red('❌ 错误: 无法确定 Agent 的工作目录路径。'));
76
+ process.exit(1);
77
+ }
78
+
79
+ console.log(chalk.blue(`ℹ️ 目标工作目录: ${targetPath}`));
80
+
81
+ // 4. 下载并解压 ZIP 文件
82
+ const downloadSuccess = await downloadAndExtract(aiSoulId, targetPath);
83
+ if (!downloadSuccess) {
84
+ process.exit(1);
85
+ }
86
+
87
+ // 5. 解析 meta.json 并安装技能
88
+ await processMetaJson(targetPath);
89
+
90
+ console.log(chalk.green(`🎉 成功将 ${aiSoulId} 安装到 Agent。`));
91
+ }
@@ -0,0 +1,76 @@
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
+ }
package/src/index.js ADDED
@@ -0,0 +1,38 @@
1
+ import { Command } from 'commander';
2
+ import { installCommand } from './commands/install.js';
3
+ import { updateCommand } from './commands/update.js';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ export async function main() {
12
+ const pkgPath = path.join(__dirname, '../package.json');
13
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
14
+
15
+ const program = new Command();
16
+
17
+ program
18
+ .name('aisoulhub')
19
+ .description('AISoulHub CLI 命令行工具,用于安装和更新 Agent 技能与配置')
20
+ .version(pkg.version);
21
+
22
+ program
23
+ .command('install')
24
+ .description('安装新的 Agent 配置及技能')
25
+ .argument('<ai_soul_id>', '指定要安装的 ai_soul_id (例如:12345)')
26
+ .action(async (aiSoulId) => {
27
+ await installCommand(aiSoulId);
28
+ });
29
+
30
+ program
31
+ .command('update')
32
+ .description('更新已存在的 Agent 配置及技能')
33
+ .action(async () => {
34
+ await updateCommand();
35
+ });
36
+
37
+ program.parseAsync(process.argv);
38
+ }
@@ -0,0 +1,53 @@
1
+ import shell from 'shelljs';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import chalk from 'chalk';
6
+ import { input } from '@inquirer/prompts';
7
+
8
+ export function checkOpenclaw() {
9
+ if (!shell.which('openclaw')) {
10
+ console.error(chalk.red('❌ 错误: 未找到 openclaw 命令。请按照 https://openclaw.ai 的指引安装 openclaw,然后重试。'));
11
+ process.exit(1);
12
+ }
13
+ }
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
+ }
@@ -0,0 +1,57 @@
1
+ import axios from 'axios';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import unzipper from 'unzipper';
6
+ import ora from 'ora';
7
+ import chalk from 'chalk';
8
+
9
+ export async function downloadAndExtract(aiSoulId, targetPath) {
10
+ const url = `https://aisoulhub.com/packages/${aiSoulId}.zip`;
11
+ const tempDir = os.tmpdir();
12
+ const zipPath = path.join(tempDir, `${aiSoulId}.zip`);
13
+
14
+ const spinner = ora(`正在下载包 ${aiSoulId}...`).start();
15
+
16
+ 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
+ // Ensure target path exists
36
+ if (!fs.existsSync(targetPath)) {
37
+ fs.mkdirSync(targetPath, { recursive: true });
38
+ }
39
+
40
+ // Unzip
41
+ await fs.createReadStream(zipPath)
42
+ .pipe(unzipper.Extract({ path: targetPath }))
43
+ .promise();
44
+
45
+ spinner.succeed(chalk.green(`包 ${aiSoulId} 下载并解压成功!`));
46
+
47
+ // Cleanup temp zip
48
+ fs.unlinkSync(zipPath);
49
+ return true;
50
+ } catch (err) {
51
+ spinner.fail(chalk.red(`下载或解压失败: ${err.message}`));
52
+ if (fs.existsSync(zipPath)) {
53
+ fs.unlinkSync(zipPath);
54
+ }
55
+ return false;
56
+ }
57
+ }
@@ -0,0 +1,37 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import { installSkill } from './openclaw.js';
5
+
6
+ export async function processMetaJson(workspacePath) {
7
+ const metaPath = path.join(workspacePath, 'meta.json');
8
+ if (!fs.existsSync(metaPath)) {
9
+ console.log(chalk.yellow(`ℹ️ 未找到 meta.json,跳过技能安装。(${metaPath})`));
10
+ return;
11
+ }
12
+
13
+ try {
14
+ const content = fs.readFileSync(metaPath, 'utf-8');
15
+ const meta = JSON.parse(content);
16
+ const skills = meta.skills || [];
17
+
18
+ if (skills.length === 0) {
19
+ console.log(chalk.blue('ℹ️ meta.json 中没有定义需要安装的技能。'));
20
+ return;
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
+ } catch (err) {
35
+ console.error(chalk.red(`❌ 读取或解析 meta.json 失败: ${err.message}`));
36
+ }
37
+ }
@@ -0,0 +1,65 @@
1
+ import shell from 'shelljs';
2
+
3
+ export function getAgents() {
4
+ const result = shell.exec('openclaw agents list --json', { silent: true });
5
+ if (result.code !== 0) {
6
+ throw new Error('获取 agents 列表失败: ' + result.stderr);
7
+ }
8
+
9
+ // Try to parse JSON first
10
+ try {
11
+ const list = JSON.parse(result.stdout);
12
+ if (Array.isArray(list)) {
13
+ return list.map(item => ({
14
+ name: item.id || item.name,
15
+ path: item.workspace || item.path || item.agentDir
16
+ }));
17
+ }
18
+ } catch (err) {
19
+ // Not JSON, parse text
20
+ }
21
+
22
+ // Fallback text parsing
23
+ const lines = result.stdout.split('\n');
24
+ const agents = [];
25
+
26
+ // Regex to match agent name and path. E.g., "- agent1 (/path/to/agent1)" or "agent1: /path/to/agent1"
27
+ // Let's just do a generic parsing or return raw lines if we can't be sure.
28
+ for (const line of lines) {
29
+ const match = line.match(/(?:-\s+)?(\w+)(?:\s+\((.*?)\)|:\s+(.*))?/);
30
+ if (match && match[1] && match[1] !== 'Agents') {
31
+ const name = match[1].trim();
32
+ const pathStr = (match[2] || match[3] || '').trim();
33
+ if (name) {
34
+ agents.push({ name, path: pathStr || null });
35
+ }
36
+ }
37
+ }
38
+ return agents;
39
+ }
40
+
41
+ export function addAgent(name, workspacePath) {
42
+ let cmd = `openclaw agents add "${name}"`;
43
+ if (workspacePath) {
44
+ cmd += ` --non-interactive --workspace "${workspacePath}"`;
45
+ }
46
+ const result = shell.exec(cmd, { silent: true });
47
+ if (result.code !== 0) {
48
+ throw new Error(`创建 agent [${name}] 失败: ` + result.stderr);
49
+ }
50
+ return result.stdout;
51
+ }
52
+
53
+ export function installSkill(workspacePath, skillStr) {
54
+ // Try `openclaw mcp install` or similar
55
+ const result = shell.exec(`openclaw mcp add ${skillStr}`, { cwd: workspacePath, silent: true });
56
+ if (result.code !== 0) {
57
+ // fallback
58
+ const fallback = shell.exec(`openclaw skills add ${skillStr}`, { cwd: workspacePath, silent: true });
59
+ if (fallback.code !== 0) {
60
+ console.error(`安装技能 ${skillStr} 失败: `, fallback.stderr || result.stderr);
61
+ return false;
62
+ }
63
+ }
64
+ return true;
65
+ }