aisoulhub 1.0.8 → 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 CHANGED
@@ -1,10 +1,11 @@
1
1
  # AISoulHub CLI
2
2
 
3
- A powerful 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.
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
4
 
5
5
  ## Prerequisites
6
6
 
7
7
  Before using this tool, ensure you have the following installed:
8
+
8
9
  - [Node.js](https://nodejs.org/) (v16 or higher recommended)
9
10
  - [OpenClaw](https://openclaw.ai/) CLI (`openclaw` command must be available in your PATH)
10
11
 
@@ -20,14 +21,6 @@ Downloads and installs an AI Soul package into a new or existing OpenClaw Agent.
20
21
  npx aisoulhub install <ai_soul_id>
21
22
  ```
22
23
 
23
- **What happens during install?**
24
- - Checks for OpenClaw dependency.
25
- - Prompts for your email (if not already logged in) and saves it to `~/.aisoulhub.json`.
26
- - Asks you to select an existing Agent or create a new one.
27
- - Downloads the corresponding zip package from AISoulHub.
28
- - Extracts files (like `SOUL.md`, `AGENTS.md`, `IDENTITY.md`, `aisoul-meta.json`) into the Agent's workspace.
29
- - Automatically reads `aisoul-meta.json` and installs all defined skills via OpenClaw.
30
-
31
24
  ### 2. Update an AI Soul
32
25
 
33
26
  Updates an existing OpenClaw Agent with the latest AI Soul package configuration and skills.
@@ -36,19 +29,8 @@ Updates an existing OpenClaw Agent with the latest AI Soul package configuration
36
29
  npx aisoulhub update
37
30
  ```
38
31
 
39
- **What happens during update?**
40
- - Displays a list of your existing Agents for you to select.
41
- - Attempts to read the `ai_soul_id` from the Agent's `aisoul-meta.json`. If missing, prompts you to input it.
42
- - Downloads the latest zip package from AISoulHub.
43
- - Extracts and overwrites the existing configuration files in the workspace.
44
- - Updates/re-installs the skills defined in the new `aisoul-meta.json`.
45
-
46
- ## Configuration
47
-
48
- The CLI saves a minimal configuration file at `~/.aisoulhub.json` which currently stores your login email to streamline subsequent command executions.
32
+ <br />
49
33
 
50
34
  ## License
51
35
 
52
36
  ISC
53
-
54
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aisoulhub",
3
- "version": "1.0.8",
3
+ "version": "1.0.11",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -23,7 +23,6 @@
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",
28
27
  "clawhub": "^0.9.0",
29
28
  "commander": "^14.0.3",
@@ -1,21 +1,29 @@
1
- import { checkOpenclaw, checkLogin } from '../utils/check.js';
2
- import { getAgents, addAgent } from '../utils/openclaw.js';
3
- import { downloadAndExtract } from '../utils/download.js';
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
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(aiSoulId) {
11
- if (!aiSoulId) {
12
- console.error(chalk.red('❌ Error: ai_soul_id is required, e.g.: aisoulhub install <ai_soul_id>'));
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 = [];
@@ -49,12 +57,41 @@ export async function installCommand(aiSoulId) {
49
57
  }
50
58
 
51
59
  let targetPath = agentChoice.path;
60
+ let finalAgentName = agentChoice.name;
52
61
 
53
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
+ }
83
+
54
84
  const newAgentName = await input({
55
85
  message: 'Please enter the name of the new Agent:',
56
- validate: (val) => val.trim().length > 0 ? true : 'Agent name cannot be empty'
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
+ }
57
92
  });
93
+
94
+ finalAgentName = newAgentName;
58
95
 
59
96
  const defaultWorkspace = path.join(os.homedir(), '.openclaw', `workspace-${newAgentName}`);
60
97
 
@@ -69,6 +106,16 @@ export async function installCommand(aiSoulId) {
69
106
  addAgent(newAgentName, newWorkspacePath);
70
107
  console.log(chalk.green(`✅ Agent [${newAgentName}] created successfully.`));
71
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
+ }
118
+
72
119
  // 重新获取列表以获取新 agent 的路径
73
120
  const updatedAgents = getAgents();
74
121
  const newAgent = updatedAgents.find(a => a.name === newAgentName);
@@ -90,14 +137,28 @@ export async function installCommand(aiSoulId) {
90
137
 
91
138
  console.log(chalk.blue(`ℹ️ Target workspace directory: ${targetPath}`));
92
139
 
93
- // 4. 下载并解压 ZIP 文件
94
- const downloadSuccess = await downloadAndExtract(aiSoulId, targetPath);
95
- if (!downloadSuccess) {
140
+ // 4. 解压本地 ZIP 文件
141
+ const extractSuccess = await extractLocalZip(localZipPath, targetPath);
142
+
143
+ if (!extractSuccess) {
96
144
  process.exit(1);
97
145
  }
98
146
 
99
147
  // 5. 解析 aisoul-meta.json 并安装技能
100
148
  await processMetaJson(targetPath);
101
149
 
102
- console.log(chalk.green(`🎉 Successfully installed ${aiSoulId} to the Agent.`));
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
+ });
103
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 tool for installing and updating Agent skills and configurations')
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('Install new Agent configuration and skills')
25
- .argument('<ai_soul_id>', 'Specify the ai_soul_id to install (e.g.: 12345)')
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('Update existing Agent configuration and skills')
33
- .action(async () => {
34
- await updateCommand();
35
- });
36
-
37
29
  program.parseAsync(process.argv);
38
30
  }
@@ -1,9 +1,5 @@
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')) {
@@ -11,43 +7,3 @@ export function checkOpenclaw() {
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('ℹ️ No login information detected. Please enter your email to login:'));
30
- const email = await input({
31
- message: 'Please enter your email address:',
32
- validate: (value) => {
33
- const pass = value.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
34
- if (pass) {
35
- return true;
36
- }
37
- return 'Please enter a valid email address';
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(`✅ Login successful! Email: ${email} saved.`));
45
- } catch (e) {
46
- console.log(chalk.yellow(`⚠️ Login successful, but unable to save to ${CONFIG_PATH}: ${e.message}`));
47
- }
48
- } else {
49
- console.log(chalk.blue(`ℹ️ Currently logged in as: ${config.email}`));
50
- }
51
-
52
- return config.email;
53
- }
@@ -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 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(`Downloading package ${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 = `Download complete, extracting...`;
34
-
35
10
  // Ensure target path exists
36
11
  if (!fs.existsSync(targetPath)) {
37
12
  fs.mkdirSync(targetPath, { recursive: true });
@@ -42,20 +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(`Package ${aiSoulId} downloaded and extracted successfully.`));
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
- if (err.response && err.response.status === 404) {
52
- spinner.fail(chalk.red(`Error: Package not found. Please check if the ai_soul_id [${aiSoulId}] is correct.`));
53
- } else {
54
- spinner.fail(chalk.red(`Error: Failed to download or extract the package: ${err.message}`));
55
- }
56
- if (fs.existsSync(zipPath)) {
57
- fs.unlinkSync(zipPath);
58
- }
23
+ spinner.fail(chalk.red(`Error: Failed to extract the local package: ${err.message}`));
59
24
  return false;
60
25
  }
61
26
  }
package/src/utils/meta.js CHANGED
@@ -19,18 +19,6 @@ export async function processMetaJson(workspacePath) {
19
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(`🔄 Starting installation of ${skills.length} skills...`));
24
- for (const skill of skills) {
25
- console.log(chalk.blue(`⏳ Installing skill: ${skill}...`));
26
- const success = await installSkill(workspacePath, skill);
27
- if (success) {
28
- console.log(chalk.green(`✅ Skill ${skill} installed successfully.`));
29
- } else {
30
- console.log(chalk.red(`❌ Failed to install skill ${skill}.`));
31
- }
32
- }
33
- console.log(chalk.green('🎉 All skills installation processes completed.'));
34
22
  } catch (err) {
35
23
  console.error(chalk.red(`❌ Failed to read or parse aisoul-meta.json: ${err.message}`));
36
24
  }
@@ -8,12 +8,26 @@ export function getAgents() {
8
8
 
9
9
  // Try to parse JSON first
10
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
- }));
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
@@ -50,6 +64,15 @@ export function addAgent(name, workspacePath) {
50
64
  return result.stdout;
51
65
  }
52
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);
72
+ }
73
+ return result.stdout;
74
+ }
75
+
53
76
  export async function installSkill(workspacePath, skillStr, retryCount = 0) {
54
77
  const maxRetries = 3;
55
78
  const result = shell.exec(`npx clawhub install ${skillStr} --workdir "${workspacePath}"`, { silent: true });
@@ -1,90 +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, confirm } 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(`❌ Failed to get agents list: ${err.message}`));
21
- process.exit(1);
22
- }
23
-
24
- if (agents.length === 0) {
25
- console.error(chalk.yellow('⚠️ No agents available to update. Please install one first using the install command.'));
26
- process.exit(1);
27
- }
28
-
29
- // 3. 提示用户选择 Agent
30
- const choices = agents.map(a => ({ name: `📁 ${a.name} (${a.path})`, value: { name: a.name, path: a.path } }));
31
-
32
- const agentChoice = await select({
33
- message: 'Please select the Agent to update:',
34
- choices
35
- });
36
-
37
- if (agentChoice.name === 'main') {
38
- const isConfirmed = await confirm({
39
- message: chalk.yellow('⚠️ Warning: Overwriting the "main" agent might cause unexpected issues. Are you sure you want to continue?'),
40
- default: false
41
- });
42
-
43
- if (!isConfirmed) {
44
- console.log(chalk.blue('ℹ️ Operation cancelled by user.'));
45
- process.exit(0);
46
- }
47
- }
48
-
49
- const targetPath = agentChoice.path;
50
-
51
- if (!targetPath || !fs.existsSync(targetPath)) {
52
- console.error(chalk.red('❌ Error: Invalid Agent workspace directory.'));
53
- process.exit(1);
54
- }
55
-
56
- // 4. 确定 ai_soul_id
57
- let aiSoulId = null;
58
- const metaPath = path.join(targetPath, 'aisoul-meta.json');
59
- if (fs.existsSync(metaPath)) {
60
- try {
61
- const content = fs.readFileSync(metaPath, 'utf-8');
62
- const meta = JSON.parse(content);
63
- if (meta.ai_soul_id) {
64
- aiSoulId = meta.ai_soul_id;
65
- console.log(chalk.blue(`ℹ️ Detected ai_soul_id from aisoul-meta.json: ${aiSoulId}`));
66
- }
67
- } catch (e) {
68
- console.warn(chalk.yellow('⚠️ Unable to parse aisoul-meta.json.'));
69
- }
70
- }
71
-
72
- if (!aiSoulId) {
73
- console.log(chalk.yellow('ℹ️ Unable to get ai_soul_id from the current Agent\'s aisoul-meta.json.'));
74
- aiSoulId = await input({
75
- message: 'Please enter the ai_soul_id to update:',
76
- validate: (val) => val.trim().length > 0 ? true : 'ai_soul_id cannot be empty'
77
- });
78
- }
79
-
80
- // 5. 下载并解压 ZIP 文件 (覆盖)
81
- const downloadSuccess = await downloadAndExtract(aiSoulId, targetPath);
82
- if (!downloadSuccess) {
83
- process.exit(1);
84
- }
85
-
86
- // 6. 解析 aisoul-meta.json 并更新技能
87
- await processMetaJson(targetPath);
88
-
89
- console.log(chalk.green(`🎉 Successfully updated Agent [${aiSoulId}].`));
90
- }