soulhubcli 1.0.15 → 1.0.17

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.
Files changed (3) hide show
  1. package/README.md +31 -22
  2. package/dist/index.cjs +117 -113
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # SoulHub CLI
2
2
 
3
- SoulHub CLI — 用于安装和管理 OpenClaw AI Agent 人格模板的命令行工具。支持从 SoulHub Registry 或本地目录安装单 Agent 和多 Agent 团队。
3
+ SoulHub CLI — 用于发现、安装和管理 AI Agent 灵魂(Soul)的命令行工具。支持从 SoulHub Registry 或本地目录安装单 Agent 和多 Agent 团队,兼容 OpenClaw 和 LightClaw。
4
4
 
5
5
  ## 安装
6
6
 
@@ -34,14 +34,14 @@ npx soulhubcli <command>
34
34
 
35
35
  | 命令 | 说明 |
36
36
  |------|------|
37
- | `soulhub search <keyword>` | 按关键词搜索 Agent 模板 |
38
- | `soulhub info <name>` | 查看 Agent 详细信息 |
39
- | `soulhub install <name>` | 从 Registry 安装 Agent 或团队 |
37
+ | `soulhub search <keyword>` | 搜索 Agent |
38
+ | `soulhub info <name>` | 查看 Agent 详细信息(identity、soul、skills 等) |
39
+ | `soulhub install <name>` | 从 Registry 安装 Agent 或团队(默认为 worker,安装到所有检测到的 claw) |
40
+ | `soulhub install <name> --main` | 安装为主 Agent |
40
41
  | `soulhub install --from <source>` | 从本地目录、ZIP 文件或 URL 安装 |
41
42
  | `soulhub list` | 列出已安装的 Agent |
42
43
  | `soulhub update [name]` | 更新已安装的 Agent |
43
- | `soulhub uninstall <name>` | 卸载已安装的 Agent |
44
- | `soulhub publish` | 验证并发布你的 Agent |
44
+ | `soulhub rollback` | 回滚到上一次安装状态 |
45
45
 
46
46
  ## 使用方法
47
47
 
@@ -56,11 +56,16 @@ soulhub search "content writing"
56
56
 
57
57
  CLI 会自动识别目标是单 Agent 还是多 Agent 团队,无需手动区分。
58
58
 
59
+ **默认行为:安装为 Worker Agent,自动安装到所有检测到的 claw 目录。**
60
+
59
61
  **从 Registry 安装:**
60
62
 
61
63
  ```bash
62
- # 安装单 Agent(作为主 Agent,部署到 workspace 目录)
63
- soulhub install ops-assistant
64
+ # 安装单 Agent(默认为 worker,安装到所有检测到的 claw)
65
+ soulhub install writer-wechat
66
+
67
+ # 安装为主 Agent
68
+ soulhub install writer-wechat --main
64
69
 
65
70
  # 安装多 Agent 团队(调度 Agent + 工作 Agent)
66
71
  soulhub install dev-squad
@@ -82,11 +87,11 @@ soulhub install --from https://example.com/agent-team.zip
82
87
  **指定目标目录:**
83
88
 
84
89
  ```bash
85
- # 安装到自定义目录(不依赖 OpenClaw 环境)
86
- soulhub install ops-assistant --dir ./my-agents
90
+ # 安装到自定义目录(不依赖 OpenClaw/LightClaw 环境)
91
+ soulhub install writer-wechat --dir ./my-agents
87
92
 
88
- # 指定 OpenClaw 安装目录
89
- soulhub install ops-assistant --claw-dir /opt/openclaw
93
+ # 指定 claw 安装目录(只安装到该 claw)
94
+ soulhub install writer-wechat --claw-dir ~/.lightclaw
90
95
  ```
91
96
 
92
97
  ### 列出已安装的 Agent
@@ -112,9 +117,11 @@ soulhub uninstall ops-assistant
112
117
 
113
118
  ### 单 Agent 安装
114
119
 
115
- - Agent 会作为**主 Agent** 安装到 OpenClaw 的 `workspace/` 目录
116
- - 如果已存在主 Agent,CLI 会**自动备份**(复制到 `agentbackup/workspace`),原目录保持不变
117
- - 仅覆盖 `IDENTITY.md` `SOUL.md` 等灵魂文件,不影响 workspace 中的其他运行时文件
120
+ - **默认安装为 Worker Agent**(子 agent),部署到 `workspace-<agentId>/` 目录
121
+ - 使用 `--main` 参数可安装为主 Agent,部署到 `workspace/` 目录
122
+ - **自动安装到所有检测到的 claw 目录**(OpenClaw / LightClaw),使用 `--claw-dir` 可指定单个 claw
123
+ - 如果目标目录已存在,CLI 会**自动备份**(复制到 `agentbackup/`)
124
+ - 仅覆盖 `IDENTITY.md`、`SOUL.md` 等灵魂文件,不影响 workspace 中的其他运行时文件
118
125
  - 安装完成后自动重启 OpenClaw Gateway;若重启失败会提示手动重启
119
126
 
120
127
  ### 多 Agent 团队安装
@@ -122,7 +129,7 @@ soulhub uninstall ops-assistant
122
129
  - **调度 Agent(Dispatcher)** 作为主 Agent 安装到 `workspace/` 目录
123
130
  - **工作 Agent(Worker)** 安装到各自的 `workspace-<agentId>/` 目录
124
131
  - 自动配置多 Agent 之间的通信
125
- - Worker Agent 通过 `openclaw agents add` 命令注册
132
+ - Worker Agent 自动注册到 claw 配置中
126
133
  - 安装完成后自动重启 OpenClaw Gateway
127
134
 
128
135
  ## 配置
@@ -137,18 +144,20 @@ CLI 将配置存储在 `~/.soulhub/config.json`。
137
144
  export SOULHUB_REGISTRY_URL=https://your-registry.example.com
138
145
  ```
139
146
 
140
- ### OpenClaw 目录
147
+ ### OpenClaw / LightClaw 目录
148
+
149
+ CLI 按以下优先级查找 claw 安装目录:
141
150
 
142
- CLI 按以下优先级查找 OpenClaw 安装目录:
151
+ 1. `--claw-dir` 命令行参数(指定时只安装到该 claw)
152
+ 2. `OPENCLAW_HOME` / `LIGHTCLAW_HOME` 环境变量
153
+ 3. 默认路径 `~/.openclaw`、`~/.lightclaw`
143
154
 
144
- 1. `--claw-dir` 命令行参数
145
- 2. `OPENCLAW_HOME` 环境变量
146
- 3. 默认路径 `~/.openclaw`
155
+ 未指定 `--claw-dir` 时,CLI 会检测所有可用的 claw 目录并全部安装。
147
156
 
148
157
  ## 环境要求
149
158
 
150
159
  - Node.js >= 18.0.0
151
- - OpenClaw(可选,使用 `--dir` 参数时不需要)
160
+ - OpenClaw 或 LightClaw(可选,使用 `--dir` 参数时不需要)
152
161
 
153
162
  ## License
154
163
 
package/dist/index.cjs CHANGED
@@ -19246,6 +19246,10 @@ function addAgentToOpenClawConfig(clawDir, agentId, agentName, isMain) {
19246
19246
  const existing = config.agents.list.find((a) => a.id === agentId);
19247
19247
  if (existing) {
19248
19248
  existing.name = agentName;
19249
+ if (!isMain) {
19250
+ existing.workspace = import_node_path11.default.join(clawDir, `workspace-${agentId}`);
19251
+ existing.agentDir = import_node_path11.default.join(clawDir, `agents/${agentId}/agent`);
19252
+ }
19249
19253
  return config;
19250
19254
  }
19251
19255
  if (isMain) {
@@ -19440,25 +19444,44 @@ function registerAgentToOpenClaw(agentName, workspaceDir, _clawDir) {
19440
19444
  } catch (cliError) {
19441
19445
  const stderr = cliError && typeof cliError === "object" && "stderr" in cliError ? String(cliError.stderr) : "";
19442
19446
  if (stderr.includes("already exists")) {
19447
+ logger.info(`Agent "${agentId}" already exists in CLI, updating config...`);
19448
+ try {
19449
+ const clawDir = _clawDir || import_node_path11.default.dirname(workspaceDir);
19450
+ addAgentToOpenClawConfig(clawDir, agentId, agentName, false);
19451
+ } catch {
19452
+ logger.warn(`Failed to update config for existing agent "${agentId}", skipping.`);
19453
+ }
19443
19454
  return {
19444
19455
  success: true,
19445
- message: `Agent "${agentId}" already registered.`
19456
+ message: `Agent "${agentId}" already registered, config updated.`
19446
19457
  };
19447
19458
  }
19448
- const isCommandNotFound = cliError && typeof cliError === "object" && "code" in cliError && cliError.code === "ENOENT" || stderr.includes("not found") || stderr.includes("not recognized");
19449
- if (isCommandNotFound) {
19450
- logger.error(`OpenClaw/LightClaw CLI not found`);
19459
+ const errMsg = cliError instanceof Error ? cliError.message : String(cliError);
19460
+ logger.warn(`CLI agents add failed, falling back to config file modification`, { agentId, stderr, error: errMsg });
19461
+ try {
19462
+ const clawDir = _clawDir || import_node_path11.default.dirname(workspaceDir);
19463
+ const configUpdated = addAgentToOpenClawConfig(clawDir, agentId, agentName, false);
19464
+ if (configUpdated) {
19465
+ logger.info(`Agent "${agentId}" registered via config file fallback.`);
19466
+ return {
19467
+ success: true,
19468
+ message: `Agent "${agentId}" registered via config file (CLI fallback).`
19469
+ };
19470
+ } else {
19471
+ logger.error(`Failed to update config file for agent "${agentId}"`);
19472
+ return {
19473
+ success: false,
19474
+ message: `Failed to register "${agentId}": CLI command failed and config file update also failed.`
19475
+ };
19476
+ }
19477
+ } catch (configError) {
19478
+ const configErrMsg = configError instanceof Error ? configError.message : String(configError);
19479
+ logger.error(`Config file fallback also failed`, { agentId, error: configErrMsg });
19451
19480
  return {
19452
19481
  success: false,
19453
- message: "OpenClaw/LightClaw CLI not found. Please install first."
19482
+ message: `Failed to register "${agentId}": CLI failed (${stderr || errMsg}), config fallback also failed (${configErrMsg}).`
19454
19483
  };
19455
19484
  }
19456
- const errMsg = cliError instanceof Error ? cliError.message : String(cliError);
19457
- logger.error(`openclaw agents add failed`, { agentId, stderr, error: errMsg });
19458
- return {
19459
- success: false,
19460
- message: `openclaw agents add failed: ${stderr || errMsg}`
19461
- };
19462
19485
  }
19463
19486
  }
19464
19487
  function detectClawCommand() {
@@ -19725,40 +19748,20 @@ function createSpinner(initialText = "") {
19725
19748
  // src/commands/install.ts
19726
19749
  var import_node_fs9 = __toESM(require("fs"), 1);
19727
19750
  var import_node_path12 = __toESM(require("path"), 1);
19728
- var import_node_readline2 = __toESM(require("readline"), 1);
19729
19751
  async function resolveClawDir(clawDir) {
19730
19752
  if (clawDir) {
19731
19753
  return findOpenClawDir(clawDir);
19732
19754
  }
19733
19755
  return promptSelectClawDir();
19734
19756
  }
19735
- async function promptInstallRole(agentName) {
19736
- console.log();
19737
- console.log(` How would you like to install ${source_default.cyan(agentName)}?`);
19738
- console.log();
19739
- console.log(` 1) ${source_default.blue("Main Agent")} Install as main agent (workspace/)`);
19740
- console.log(` 2) ${source_default.green("Worker Agent")} Install as worker agent (workspace-${agentName}/)`);
19741
- console.log();
19742
- const rl = import_node_readline2.default.createInterface({
19743
- input: process.stdin,
19744
- output: process.stdout
19745
- });
19746
- return new Promise((resolve) => {
19747
- rl.question(" Please select (1-2, default: 2): ", (answer) => {
19748
- rl.close();
19749
- const trimmed = answer.trim();
19750
- if (trimmed === "" || trimmed === "2") {
19751
- resolve(false);
19752
- } else if (trimmed === "1") {
19753
- resolve(true);
19754
- } else {
19755
- console.log(" Invalid selection, operation cancelled.");
19756
- resolve(null);
19757
- }
19758
- });
19759
- });
19757
+ function resolveAllClawDirs(clawDir) {
19758
+ if (clawDir) {
19759
+ const resolved = findOpenClawDir(clawDir);
19760
+ return resolved ? [resolved] : [];
19761
+ }
19762
+ return findAllClawDirs();
19760
19763
  }
19761
- var installCommand = new Command("install").description("Install an agent or team from the SoulHub registry (default: as worker)").argument("[name]", "Agent or team name to install").option("--from <source>", "Install from a local directory, ZIP file, or URL").option("--main", "Install as main agent (non-interactive)").option("--worker", "Install as worker/sub-agent (non-interactive)").option(
19764
+ var installCommand = new Command("install").description("Install an agent or team from the SoulHub registry (default: as worker, installs to all detected claws)").argument("[name]", "Agent or team name to install").option("--from <source>", "Install from a local directory, ZIP file, or URL").option("--main", "Install as main agent").option(
19762
19765
  "--dir <path>",
19763
19766
  "Target directory (defaults to OpenClaw/LightClaw workspace)"
19764
19767
  ).option(
@@ -19766,13 +19769,7 @@ var installCommand = new Command("install").description("Install an agent or tea
19766
19769
  "OpenClaw/LightClaw installation directory (overrides OPENCLAW_HOME/LIGHTCLAW_HOME env var, defaults to ~/.openclaw or ~/.lightclaw)"
19767
19770
  ).action(async (name, options) => {
19768
19771
  try {
19769
- let asMain;
19770
- if (options.main && options.worker) {
19771
- console.error(source_default.red("Error: --main and --worker cannot be used together."));
19772
- process.exit(1);
19773
- }
19774
- if (options.main) asMain = true;
19775
- if (options.worker) asMain = false;
19772
+ const asMain = !!options.main;
19776
19773
  if (options.from) {
19777
19774
  await installFromSource(options.from, options.dir, options.clawDir, asMain);
19778
19775
  } else if (name) {
@@ -19780,12 +19777,11 @@ var installCommand = new Command("install").description("Install an agent or tea
19780
19777
  } else {
19781
19778
  console.error(source_default.red("Please specify an agent or team name, or use --from to install from a local source."));
19782
19779
  console.log(source_default.dim(" Examples:"));
19783
- console.log(source_default.dim(" soulhub install writer-wechat # \u4EA4\u4E92\u5F0F\u9009\u62E9\u5B89\u88C5\u4E3A\u4E3B/\u5B50 agent"));
19784
- console.log(source_default.dim(" soulhub install writer-wechat --main # \u975E\u4EA4\u4E92\u5F0F\uFF0C\u5B89\u88C5\u4E3A\u4E3B agent (workspace/)"));
19785
- console.log(source_default.dim(" soulhub install writer-wechat --worker # \u975E\u4EA4\u4E92\u5F0F\uFF0C\u5B89\u88C5\u4E3A\u5B50 agent (workspace-xxx/)"));
19786
- console.log(source_default.dim(" soulhub install dev-squad # \u4ECE registry \u5B89\u88C5\u56E2\u961F"));
19787
- console.log(source_default.dim(" soulhub install --from ./agent-team/ # \u4ECE\u672C\u5730\u76EE\u5F55\u5B89\u88C5"));
19788
- console.log(source_default.dim(" soulhub install writer-wechat --claw-dir ~/.lightclaw # \u975E\u4EA4\u4E92\u5F0F\u6307\u5B9A claw \u76EE\u5F55"));
19780
+ console.log(source_default.dim(" soulhub install writer-wechat # Install as worker to all detected claws"));
19781
+ console.log(source_default.dim(" soulhub install writer-wechat --main # Install as main agent"));
19782
+ console.log(source_default.dim(" soulhub install dev-squad # Install a team from registry"));
19783
+ console.log(source_default.dim(" soulhub install --from ./agent-team/ # Install from local directory"));
19784
+ console.log(source_default.dim(" soulhub install writer-wechat --claw-dir ~/.lightclaw # Install to specific claw"));
19789
19785
  process.exit(1);
19790
19786
  }
19791
19787
  } catch (error) {
@@ -19804,7 +19800,7 @@ async function installFromRegistry(name, targetDir, clawDir, asMain) {
19804
19800
  const recipe = index.recipes.find((r) => r.name === name);
19805
19801
  if (agent && !recipe) {
19806
19802
  spinner.stop();
19807
- logger.info(`Installing single agent from registry: ${name}, asMain=${asMain ?? "interactive"}`);
19803
+ logger.info(`Installing single agent from registry: ${name}, asMain=${asMain}`);
19808
19804
  await installSingleAgent(name, targetDir, clawDir, asMain);
19809
19805
  } else if (recipe) {
19810
19806
  spinner.stop();
@@ -19816,31 +19812,18 @@ async function installFromRegistry(name, targetDir, clawDir, asMain) {
19816
19812
  console.log(source_default.dim(" Use 'soulhub search' to find available agents and teams."));
19817
19813
  }
19818
19814
  }
19819
- async function installSingleAgent(name, targetDir, clawDir, asMain) {
19820
- const spinner = createSpinner(`Checking environment...`).start();
19821
- let selectedClawDir = null;
19822
- if (!targetDir) {
19823
- spinner.stop();
19824
- selectedClawDir = await resolveClawDir(clawDir);
19825
- if (!selectedClawDir) {
19826
- console.log(source_default.red("OpenClaw/LightClaw workspace directory not found."));
19827
- printOpenClawInstallHelp();
19828
- return;
19829
- }
19830
- const brand = detectClawBrand(selectedClawDir);
19831
- console.log(source_default.dim(` ${brand} detected: ${selectedClawDir}`));
19832
- }
19833
- const agentId = name.toLowerCase().replace(/[\s_]+/g, "-");
19834
- if (asMain === void 0) {
19835
- const choice = await promptInstallRole(agentId);
19836
- if (choice === null) return;
19837
- asMain = choice;
19815
+ async function installSingleAgent(name, targetDir, clawDir, asMain = false) {
19816
+ if (targetDir) {
19817
+ await installSingleAgentToClaw(name, null, targetDir, asMain);
19818
+ return;
19838
19819
  }
19839
- logger.info(`Install role resolved: asMain=${asMain}`);
19840
- if (!targetDir) {
19841
- spinner.start();
19820
+ const allClawDirs = resolveAllClawDirs(clawDir);
19821
+ if (allClawDirs.length === 0) {
19822
+ console.log(source_default.red("OpenClaw/LightClaw workspace directory not found."));
19823
+ printOpenClawInstallHelp();
19824
+ return;
19842
19825
  }
19843
- spinner.text = `Fetching agent ${source_default.cyan(name)}...`;
19826
+ const spinner = createSpinner(`Fetching agent ${source_default.cyan(name)}...`).start();
19844
19827
  const index = await fetchIndex();
19845
19828
  const agent = index.agents.find((a) => a.name === name);
19846
19829
  if (!agent) {
@@ -19848,6 +19831,32 @@ async function installSingleAgent(name, targetDir, clawDir, asMain) {
19848
19831
  console.log(source_default.dim(" Use 'soulhub search' to find available agents."));
19849
19832
  return;
19850
19833
  }
19834
+ spinner.text = `Downloading ${source_default.cyan(agent.displayName)} package...`;
19835
+ const pkgDir = await downloadAgentPackage(name, agent.version);
19836
+ spinner.succeed(`Package ${source_default.cyan(agent.displayName)} downloaded.`);
19837
+ for (const selectedClawDir of allClawDirs) {
19838
+ await installSingleAgentToClaw(name, selectedClawDir, void 0, asMain, pkgDir, agent);
19839
+ }
19840
+ import_node_fs9.default.rmSync(pkgDir, { recursive: true, force: true });
19841
+ }
19842
+ async function installSingleAgentToClaw(name, selectedClawDir, targetDir, asMain = false, preDownloadedPkgDir, preLoadedAgent) {
19843
+ const brand = selectedClawDir ? detectClawBrand(selectedClawDir) : null;
19844
+ const clawLabel = brand ? `${brand} (${selectedClawDir})` : targetDir;
19845
+ const spinner = createSpinner(`Installing to ${source_default.dim(clawLabel)}...`).start();
19846
+ const agentId = name.toLowerCase().replace(/[\s_]+/g, "-");
19847
+ logger.info(`Install role resolved: asMain=${asMain}, claw=${selectedClawDir || targetDir}`);
19848
+ let agent = preLoadedAgent;
19849
+ if (!agent) {
19850
+ spinner.text = `Fetching agent ${source_default.cyan(name)}...`;
19851
+ const index = await fetchIndex();
19852
+ const found = index.agents.find((a) => a.name === name);
19853
+ if (!found) {
19854
+ spinner.fail(`Agent "${name}" not found in registry.`);
19855
+ console.log(source_default.dim(" Use 'soulhub search' to find available agents."));
19856
+ return;
19857
+ }
19858
+ agent = found;
19859
+ }
19851
19860
  let workspaceDir;
19852
19861
  if (targetDir) {
19853
19862
  workspaceDir = import_node_path12.default.resolve(targetDir);
@@ -19860,9 +19869,7 @@ async function installSingleAgent(name, targetDir, clawDir, asMain) {
19860
19869
  if (!targetDir && asMain) {
19861
19870
  const mainCheck = checkMainAgentExists(selectedClawDir);
19862
19871
  if (mainCheck.hasContent) {
19863
- spinner.warn(
19864
- `Existing main agent detected. Backing up workspace...`
19865
- );
19872
+ spinner.warn(`Existing main agent detected. Backing up workspace...`);
19866
19873
  const backupDir = backupAgentWorkspace(workspaceDir);
19867
19874
  if (backupDir) {
19868
19875
  console.log(source_default.yellow(` \u26A0 Existing main agent backed up to: ${backupDir}`));
@@ -19902,21 +19909,23 @@ async function installSingleAgent(name, targetDir, clawDir, asMain) {
19902
19909
  if (asMain) {
19903
19910
  spinner.text = `Registering ${source_default.cyan(agent.displayName)} as main agent...`;
19904
19911
  addAgentToOpenClawConfig(selectedClawDir, "main", name, true);
19905
- spinner.text = source_default.dim(`Main agent registered in config`);
19906
19912
  } else {
19907
19913
  spinner.text = `Registering ${source_default.cyan(agent.displayName)} as worker agent...`;
19908
- const regResult = registerAgentToOpenClaw(agentId, workspaceDir, clawDir);
19914
+ const regResult = registerAgentToOpenClaw(agentId, workspaceDir);
19909
19915
  if (!regResult.success) {
19910
19916
  spinner.fail(`Failed to register ${agentId}: ${regResult.message}`);
19911
19917
  return;
19912
19918
  }
19913
- spinner.text = source_default.dim(`Worker agent ${agentId} registered in config`);
19914
19919
  }
19915
19920
  }
19916
- spinner.text = `Downloading ${source_default.cyan(agent.displayName)} package...`;
19917
- const pkgDir = await downloadAgentPackage(name, agent.version);
19918
- copyAgentFilesFromPackage(pkgDir, workspaceDir);
19919
- import_node_fs9.default.rmSync(pkgDir, { recursive: true, force: true });
19921
+ if (preDownloadedPkgDir) {
19922
+ copyAgentFilesFromPackage(preDownloadedPkgDir, workspaceDir);
19923
+ } else {
19924
+ spinner.text = `Downloading ${source_default.cyan(agent.displayName)} package...`;
19925
+ const pkgDir = await downloadAgentPackage(name, agent.version);
19926
+ copyAgentFilesFromPackage(pkgDir, workspaceDir);
19927
+ import_node_fs9.default.rmSync(pkgDir, { recursive: true, force: true });
19928
+ }
19920
19929
  recordInstall(name, agent.version, workspaceDir);
19921
19930
  if (backupRecord) {
19922
19931
  if (asMain) {
@@ -19930,9 +19939,8 @@ async function installSingleAgent(name, targetDir, clawDir, asMain) {
19930
19939
  const typeLabel = asMain ? source_default.blue("Single Agent (Main)") : source_default.green("Single Agent (Worker)");
19931
19940
  logger.info(`Single agent installed as ${roleLabel}: ${name}`, { version: agent.version, workspace: workspaceDir });
19932
19941
  spinner.succeed(
19933
- `${source_default.cyan.bold(agent.displayName)} installed as ${roleLabel}!`
19942
+ `${source_default.cyan.bold(agent.displayName)} installed as ${roleLabel} \u2192 ${source_default.dim(clawLabel)}`
19934
19943
  );
19935
- console.log();
19936
19944
  console.log(` ${source_default.dim("Location:")} ${workspaceDir}`);
19937
19945
  console.log(` ${source_default.dim("Version:")} ${agent.version}`);
19938
19946
  console.log(` ${source_default.dim("Type:")} ${typeLabel}`);
@@ -20099,32 +20107,29 @@ async function installFromSource(source, targetDir, clawDir, asMain) {
20099
20107
  import_node_fs9.default.rmSync(tempDir, { recursive: true, force: true });
20100
20108
  }
20101
20109
  }
20102
- async function installSingleAgentFromDir(packageDir, targetDir, clawDir, asMain) {
20103
- const spinner = createSpinner("Installing single agent...").start();
20110
+ async function installSingleAgentFromDir(packageDir, targetDir, clawDir, asMain = false) {
20104
20111
  const pkg = readSoulHubPackage(packageDir);
20105
20112
  const agentName = pkg?.name || import_node_path12.default.basename(packageDir);
20106
- let selectedClawDir = null;
20107
- if (!targetDir) {
20108
- spinner.stop();
20109
- selectedClawDir = await resolveClawDir(clawDir);
20110
- if (!selectedClawDir) {
20111
- console.log(source_default.red("OpenClaw/LightClaw workspace directory not found."));
20112
- printOpenClawInstallHelp();
20113
- return;
20114
- }
20115
- const brand = detectClawBrand(selectedClawDir);
20116
- console.log(source_default.dim(` ${brand} detected: ${selectedClawDir}`));
20113
+ if (targetDir) {
20114
+ await installSingleAgentFromDirToClaw(packageDir, agentName, pkg, null, targetDir, asMain);
20115
+ return;
20117
20116
  }
20118
- const agentId = agentName.toLowerCase().replace(/[\s_]+/g, "-");
20119
- if (asMain === void 0) {
20120
- const choice = await promptInstallRole(agentId);
20121
- if (choice === null) return;
20122
- asMain = choice;
20117
+ const allClawDirs = resolveAllClawDirs(clawDir);
20118
+ if (allClawDirs.length === 0) {
20119
+ console.log(source_default.red("OpenClaw/LightClaw workspace directory not found."));
20120
+ printOpenClawInstallHelp();
20121
+ return;
20123
20122
  }
20124
- logger.info(`Install role resolved: asMain=${asMain}`);
20125
- if (!targetDir) {
20126
- spinner.start();
20123
+ for (const selectedClawDir of allClawDirs) {
20124
+ await installSingleAgentFromDirToClaw(packageDir, agentName, pkg, selectedClawDir, void 0, asMain);
20127
20125
  }
20126
+ }
20127
+ async function installSingleAgentFromDirToClaw(packageDir, agentName, pkg, selectedClawDir, targetDir, asMain = false) {
20128
+ const brand = selectedClawDir ? detectClawBrand(selectedClawDir) : null;
20129
+ const clawLabel = brand ? `${brand} (${selectedClawDir})` : targetDir;
20130
+ const spinner = createSpinner(`Installing to ${source_default.dim(clawLabel)}...`).start();
20131
+ const agentId = agentName.toLowerCase().replace(/[\s_]+/g, "-");
20132
+ logger.info(`Install role resolved: asMain=${asMain}, claw=${selectedClawDir || targetDir}`);
20128
20133
  let workspaceDir;
20129
20134
  if (targetDir) {
20130
20135
  workspaceDir = import_node_path12.default.resolve(targetDir);
@@ -20174,7 +20179,7 @@ async function installSingleAgentFromDir(packageDir, targetDir, clawDir, asMain)
20174
20179
  addAgentToOpenClawConfig(selectedClawDir, "main", agentName, true);
20175
20180
  } else {
20176
20181
  spinner.text = `Registering ${source_default.cyan(agentName)} as worker agent...`;
20177
- const regResult = registerAgentToOpenClaw(agentId, workspaceDir, clawDir);
20182
+ const regResult = registerAgentToOpenClaw(agentId, workspaceDir);
20178
20183
  if (!regResult.success) {
20179
20184
  spinner.fail(`Failed to register ${agentId}: ${regResult.message}`);
20180
20185
  return;
@@ -20195,8 +20200,7 @@ async function installSingleAgentFromDir(packageDir, targetDir, clawDir, asMain)
20195
20200
  const roleLabel = asMain ? "main agent" : "worker agent";
20196
20201
  const typeLabel = asMain ? source_default.blue("Single Agent (Main)") : source_default.green("Single Agent (Worker)");
20197
20202
  logger.info(`Single agent installed from dir as ${roleLabel}: ${agentName}`, { source: packageDir, workspace: workspaceDir });
20198
- spinner.succeed(`${source_default.cyan.bold(agentName)} installed as ${roleLabel}!`);
20199
- console.log();
20203
+ spinner.succeed(`${source_default.cyan.bold(agentName)} installed as ${roleLabel} \u2192 ${source_default.dim(clawLabel)}`);
20200
20204
  console.log(` ${source_default.dim("Location:")} ${workspaceDir}`);
20201
20205
  console.log(` ${source_default.dim("Source:")} ${packageDir}`);
20202
20206
  console.log(` ${source_default.dim("Type:")} ${typeLabel}`);
@@ -20702,13 +20706,13 @@ function formatInstallType(type2) {
20702
20706
 
20703
20707
  // src/index.ts
20704
20708
  var program2 = new Command();
20705
- program2.name("soulhub").description("SoulHub CLI - Discover, install and manage AI agent souls").version("1.0.15").option("--verbose", "Enable verbose debug logging").hook("preAction", () => {
20709
+ program2.name("soulhub").description("SoulHub CLI - Discover, install and manage AI agent souls").version("1.0.17").option("--verbose", "Enable verbose debug logging").hook("preAction", () => {
20706
20710
  const opts = program2.opts();
20707
20711
  const verbose = opts.verbose || process.env.SOULHUB_DEBUG === "1";
20708
20712
  logger.init(verbose);
20709
20713
  logger.info("CLI started", {
20710
20714
  args: process.argv.slice(2),
20711
- version: "1.0.15",
20715
+ version: "1.0.17",
20712
20716
  node: process.version
20713
20717
  });
20714
20718
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "soulhubcli",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "description": "SoulHub CLI - Install and manage AI agent persona templates for OpenClaw",
5
5
  "type": "module",
6
6
  "bin": {