soulhubcli 1.0.12 → 1.0.14

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 (2) hide show
  1. package/dist/index.cjs +279 -142
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -13284,6 +13284,7 @@ var import_node_fs8 = __toESM(require("fs"), 1);
13284
13284
  var import_node_path11 = __toESM(require("path"), 1);
13285
13285
  var import_node_os2 = __toESM(require("os"), 1);
13286
13286
  var import_node_child_process = require("child_process");
13287
+ var import_node_readline = __toESM(require("readline"), 1);
13287
13288
 
13288
13289
  // node_modules/js-yaml/dist/js-yaml.mjs
13289
13290
  function isNothing(subject) {
@@ -19038,7 +19039,7 @@ function findOpenClawDir(customDir) {
19038
19039
  }
19039
19040
  return resolved;
19040
19041
  }
19041
- const envHome = process.env.OPENCLAW_HOME;
19042
+ const envHome = process.env.OPENCLAW_HOME || process.env.LIGHTCLAW_HOME;
19042
19043
  if (envHome) {
19043
19044
  const resolved = import_node_path11.default.resolve(envHome);
19044
19045
  if (import_node_fs8.default.existsSync(resolved)) {
@@ -19046,9 +19047,12 @@ function findOpenClawDir(customDir) {
19046
19047
  }
19047
19048
  return resolved;
19048
19049
  }
19050
+ const home = process.env.HOME || "~";
19049
19051
  const candidates = [
19050
- import_node_path11.default.join(process.env.HOME || "~", ".openclaw"),
19051
- import_node_path11.default.join(process.cwd(), ".openclaw")
19052
+ import_node_path11.default.join(home, ".openclaw"),
19053
+ import_node_path11.default.join(home, ".lightclaw"),
19054
+ import_node_path11.default.join(process.cwd(), ".openclaw"),
19055
+ import_node_path11.default.join(process.cwd(), ".lightclaw")
19052
19056
  ];
19053
19057
  for (const candidate of candidates) {
19054
19058
  if (import_node_fs8.default.existsSync(candidate)) {
@@ -19057,6 +19061,65 @@ function findOpenClawDir(customDir) {
19057
19061
  }
19058
19062
  return null;
19059
19063
  }
19064
+ function findAllClawDirs(customDir) {
19065
+ if (customDir) {
19066
+ const resolved = import_node_path11.default.resolve(customDir);
19067
+ return import_node_fs8.default.existsSync(resolved) ? [resolved] : [resolved];
19068
+ }
19069
+ const envHome = process.env.OPENCLAW_HOME || process.env.LIGHTCLAW_HOME;
19070
+ if (envHome) {
19071
+ const resolved = import_node_path11.default.resolve(envHome);
19072
+ return [resolved];
19073
+ }
19074
+ const home = process.env.HOME || "~";
19075
+ const candidates = [
19076
+ import_node_path11.default.join(home, ".openclaw"),
19077
+ import_node_path11.default.join(home, ".lightclaw"),
19078
+ import_node_path11.default.join(process.cwd(), ".openclaw"),
19079
+ import_node_path11.default.join(process.cwd(), ".lightclaw")
19080
+ ];
19081
+ const existing = candidates.filter((c) => import_node_fs8.default.existsSync(c));
19082
+ const seen = /* @__PURE__ */ new Set();
19083
+ return existing.filter((c) => {
19084
+ const real = import_node_fs8.default.realpathSync(c);
19085
+ if (seen.has(real)) return false;
19086
+ seen.add(real);
19087
+ return true;
19088
+ });
19089
+ }
19090
+ async function promptSelectClawDir(customDir) {
19091
+ const dirs = findAllClawDirs(customDir);
19092
+ if (dirs.length === 0) {
19093
+ return null;
19094
+ }
19095
+ if (dirs.length === 1) {
19096
+ return dirs[0];
19097
+ }
19098
+ console.log();
19099
+ console.log(" Detected multiple Claw installations:");
19100
+ console.log();
19101
+ dirs.forEach((dir, index) => {
19102
+ const brand = detectClawBrand(dir);
19103
+ console.log(` ${index + 1}) ${brand} ${dir}`);
19104
+ });
19105
+ console.log();
19106
+ const rl = import_node_readline.default.createInterface({
19107
+ input: process.stdin,
19108
+ output: process.stdout
19109
+ });
19110
+ return new Promise((resolve) => {
19111
+ rl.question(` Please select target (1-${dirs.length}): `, (answer) => {
19112
+ rl.close();
19113
+ const idx = parseInt(answer.trim(), 10);
19114
+ if (idx >= 1 && idx <= dirs.length) {
19115
+ resolve(dirs[idx - 1]);
19116
+ } else {
19117
+ console.log(" Invalid selection, operation cancelled.");
19118
+ resolve(null);
19119
+ }
19120
+ });
19121
+ });
19122
+ }
19060
19123
  function getConfigPath() {
19061
19124
  const home = process.env.HOME || "~";
19062
19125
  return import_node_path11.default.join(home, ".soulhub", "config.json");
@@ -19111,8 +19174,22 @@ function checkMainAgentExists(clawDir) {
19111
19174
  workspaceDir
19112
19175
  };
19113
19176
  }
19177
+ function detectClawBrand(clawDir) {
19178
+ const dirName = import_node_path11.default.basename(clawDir).toLowerCase();
19179
+ if (dirName.includes("lightclaw")) {
19180
+ return "LightClaw";
19181
+ }
19182
+ if (import_node_fs8.default.existsSync(import_node_path11.default.join(clawDir, "lightclaw.json"))) {
19183
+ return "LightClaw";
19184
+ }
19185
+ return "OpenClaw";
19186
+ }
19187
+ function getClawConfigFileName(clawDir) {
19188
+ const brand = detectClawBrand(clawDir);
19189
+ return brand === "LightClaw" ? "lightclaw.json" : "openclaw.json";
19190
+ }
19114
19191
  function getOpenClawConfigPath(clawDir) {
19115
- return import_node_path11.default.join(clawDir, "openclaw.json");
19192
+ return import_node_path11.default.join(clawDir, getClawConfigFileName(clawDir));
19116
19193
  }
19117
19194
  function readOpenClawConfig(clawDir) {
19118
19195
  const configPath = getOpenClawConfigPath(clawDir);
@@ -19208,32 +19285,6 @@ function detectPackageKind(dir) {
19208
19285
  }
19209
19286
  return "unknown";
19210
19287
  }
19211
- function checkOpenClawInstalled(customDir) {
19212
- const clawDir = findOpenClawDir(customDir);
19213
- if (clawDir) {
19214
- return {
19215
- installed: true,
19216
- clawDir,
19217
- message: `OpenClaw detected at: ${clawDir}`
19218
- };
19219
- }
19220
- try {
19221
- (0, import_node_child_process.execSync)("which openclaw 2>/dev/null || where openclaw 2>nul", {
19222
- stdio: "pipe"
19223
- });
19224
- return {
19225
- installed: true,
19226
- clawDir: null,
19227
- message: "OpenClaw command found in PATH, but workspace directory not detected."
19228
- };
19229
- } catch {
19230
- }
19231
- return {
19232
- installed: false,
19233
- clawDir: null,
19234
- message: "OpenClaw is not installed. Please install OpenClaw first, use --claw-dir to specify OpenClaw directory, or set OPENCLAW_HOME environment variable."
19235
- };
19236
- }
19237
19288
  function backupAgentWorkspace(workspaceDir) {
19238
19289
  if (!import_node_fs8.default.existsSync(workspaceDir)) {
19239
19290
  return null;
@@ -19333,7 +19384,7 @@ function generateBackupId() {
19333
19384
  }
19334
19385
  function createBackupRecord(installType, packageName, clawDir) {
19335
19386
  let openclawJsonSnapshot = null;
19336
- const configPath = import_node_path11.default.join(clawDir, "openclaw.json");
19387
+ const configPath = getOpenClawConfigPath(clawDir);
19337
19388
  if (import_node_fs8.default.existsSync(configPath)) {
19338
19389
  try {
19339
19390
  openclawJsonSnapshot = import_node_fs8.default.readFileSync(configPath, "utf-8");
@@ -19375,30 +19426,31 @@ var CATEGORY_LABELS = {
19375
19426
  };
19376
19427
  function registerAgentToOpenClaw(agentName, workspaceDir, _clawDir) {
19377
19428
  const agentId = agentName.toLowerCase().replace(/[\s_]+/g, "-");
19378
- logger.debug(`Registering agent to OpenClaw`, { agentId, workspaceDir });
19429
+ logger.debug(`Registering agent to OpenClaw/LightClaw`, { agentId, workspaceDir });
19379
19430
  try {
19431
+ const clawCmd = detectClawCommand();
19380
19432
  (0, import_node_child_process.execSync)(
19381
- `openclaw agents add "${agentId}" --workspace "${workspaceDir}" --non-interactive --json`,
19433
+ `${clawCmd} agents add "${agentId}" --workspace "${workspaceDir}" --non-interactive --json`,
19382
19434
  { stdio: "pipe", timeout: 15e3 }
19383
19435
  );
19384
19436
  return {
19385
19437
  success: true,
19386
- message: `Agent "${agentId}" registered via OpenClaw CLI.`
19438
+ message: `Agent "${agentId}" registered via CLI.`
19387
19439
  };
19388
19440
  } catch (cliError) {
19389
19441
  const stderr = cliError && typeof cliError === "object" && "stderr" in cliError ? String(cliError.stderr) : "";
19390
19442
  if (stderr.includes("already exists")) {
19391
19443
  return {
19392
19444
  success: true,
19393
- message: `Agent "${agentId}" already registered in OpenClaw.`
19445
+ message: `Agent "${agentId}" already registered.`
19394
19446
  };
19395
19447
  }
19396
19448
  const isCommandNotFound = cliError && typeof cliError === "object" && "code" in cliError && cliError.code === "ENOENT" || stderr.includes("not found") || stderr.includes("not recognized");
19397
19449
  if (isCommandNotFound) {
19398
- logger.error(`OpenClaw CLI not found`);
19450
+ logger.error(`OpenClaw/LightClaw CLI not found`);
19399
19451
  return {
19400
19452
  success: false,
19401
- message: "OpenClaw CLI not found. Please install OpenClaw first: https://github.com/anthropics/openclaw"
19453
+ message: "OpenClaw/LightClaw CLI not found. Please install first."
19402
19454
  };
19403
19455
  }
19404
19456
  const errMsg = cliError instanceof Error ? cliError.message : String(cliError);
@@ -19409,17 +19461,31 @@ function registerAgentToOpenClaw(agentName, workspaceDir, _clawDir) {
19409
19461
  };
19410
19462
  }
19411
19463
  }
19464
+ function detectClawCommand() {
19465
+ try {
19466
+ (0, import_node_child_process.execSync)("which lightclaw 2>/dev/null || where lightclaw 2>nul", { stdio: "pipe" });
19467
+ return "lightclaw";
19468
+ } catch {
19469
+ }
19470
+ try {
19471
+ (0, import_node_child_process.execSync)("which openclaw 2>/dev/null || where openclaw 2>nul", { stdio: "pipe" });
19472
+ return "openclaw";
19473
+ } catch {
19474
+ }
19475
+ return "openclaw";
19476
+ }
19412
19477
  function restartOpenClawGateway() {
19413
- logger.debug(`Restarting OpenClaw Gateway`);
19478
+ const clawCmd = detectClawCommand();
19479
+ logger.debug(`Restarting ${clawCmd} Gateway`);
19414
19480
  try {
19415
- (0, import_node_child_process.execSync)("openclaw gateway restart", {
19481
+ (0, import_node_child_process.execSync)(`${clawCmd} gateway restart`, {
19416
19482
  stdio: "pipe",
19417
19483
  timeout: 3e4
19418
19484
  // 30 秒超时
19419
19485
  });
19420
19486
  return {
19421
19487
  success: true,
19422
- message: "OpenClaw Gateway restarted successfully."
19488
+ message: `${clawCmd} Gateway restarted successfully.`
19423
19489
  };
19424
19490
  } catch (error) {
19425
19491
  const stderr = error && typeof error === "object" && "stderr" in error ? String(error.stderr).trim() : "";
@@ -19432,7 +19498,7 @@ function restartOpenClawGateway() {
19432
19498
  }
19433
19499
 
19434
19500
  // src/commands/search.ts
19435
- var searchCommand = new Command("search").description("Search for agent templates in the SoulHub registry").argument("[query]", "Search query (matches name, description, tags)").option("-c, --category <category>", "Filter by category").option("-l, --limit <number>", "Max results to show", "20").action(async (query, options) => {
19501
+ var searchCommand = new Command("search").description("Search for agents in the SoulHub registry").argument("[query]", "Search query (matches name, description, tags)").option("-c, --category <category>", "Filter by category").option("-l, --limit <number>", "Max results to show", "20").action(async (query, options) => {
19436
19502
  try {
19437
19503
  const index = await fetchIndex();
19438
19504
  let agents = index.agents;
@@ -19498,7 +19564,7 @@ var searchCommand = new Command("search").description("Search for agent template
19498
19564
  });
19499
19565
 
19500
19566
  // src/commands/info.ts
19501
- var infoCommand = new Command("info").description("View detailed information about an agent template").argument("<name>", "Agent name").option("--identity", "Show IDENTITY.md content").option("--soul", "Show SOUL.md content").action(async (name, options) => {
19567
+ var infoCommand = new Command("info").description("Show details of an agent (identity, soul, skills, etc.)").argument("<name>", "Agent name").option("--identity", "Show IDENTITY.md content").option("--soul", "Show SOUL.md content").action(async (name, options) => {
19502
19568
  try {
19503
19569
  const index = await fetchIndex();
19504
19570
  const agent = index.agents.find((a) => a.name === name);
@@ -19659,22 +19725,30 @@ function createSpinner(initialText = "") {
19659
19725
  // src/commands/install.ts
19660
19726
  var import_node_fs9 = __toESM(require("fs"), 1);
19661
19727
  var import_node_path12 = __toESM(require("path"), 1);
19662
- var installCommand = new Command("install").description("Install an agent or team from the SoulHub registry").argument("[name]", "Agent or team name to install").option("--from <source>", "Install from a local directory, ZIP file, or URL").option(
19728
+ async function resolveClawDir(clawDir) {
19729
+ if (clawDir) {
19730
+ return findOpenClawDir(clawDir);
19731
+ }
19732
+ return promptSelectClawDir();
19733
+ }
19734
+ 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 (default is worker/sub-agent)").option(
19663
19735
  "--dir <path>",
19664
- "Target directory (defaults to OpenClaw workspace)"
19736
+ "Target directory (defaults to OpenClaw/LightClaw workspace)"
19665
19737
  ).option(
19666
19738
  "--claw-dir <path>",
19667
- "OpenClaw installation directory (overrides OPENCLAW_HOME env var, defaults to ~/.openclaw)"
19739
+ "OpenClaw/LightClaw installation directory (overrides OPENCLAW_HOME/LIGHTCLAW_HOME env var, defaults to ~/.openclaw or ~/.lightclaw)"
19668
19740
  ).action(async (name, options) => {
19669
19741
  try {
19742
+ const asMain = !!options.main;
19670
19743
  if (options.from) {
19671
- await installFromSource(options.from, options.dir, options.clawDir);
19744
+ await installFromSource(options.from, options.dir, options.clawDir, asMain);
19672
19745
  } else if (name) {
19673
- await installFromRegistry(name, options.dir, options.clawDir);
19746
+ await installFromRegistry(name, options.dir, options.clawDir, asMain);
19674
19747
  } else {
19675
19748
  console.error(source_default.red("Please specify an agent or team name, or use --from to install from a local source."));
19676
19749
  console.log(source_default.dim(" Examples:"));
19677
- console.log(source_default.dim(" soulhub install writer-wechat # \u4ECE registry \u5B89\u88C5\u5355 agent"));
19750
+ console.log(source_default.dim(" soulhub install writer-wechat # \u5B89\u88C5\u4E3A\u5B50 agent (workspace-writer-wechat/)"));
19751
+ console.log(source_default.dim(" soulhub install writer-wechat --main # \u5B89\u88C5\u4E3A\u4E3B agent (workspace/)"));
19678
19752
  console.log(source_default.dim(" soulhub install dev-squad # \u4ECE registry \u5B89\u88C5\u56E2\u961F"));
19679
19753
  console.log(source_default.dim(" soulhub install --from ./agent-team/ # \u4ECE\u672C\u5730\u76EE\u5F55\u5B89\u88C5"));
19680
19754
  process.exit(1);
@@ -19688,15 +19762,15 @@ var installCommand = new Command("install").description("Install an agent or tea
19688
19762
  process.exit(1);
19689
19763
  }
19690
19764
  });
19691
- async function installFromRegistry(name, targetDir, clawDir) {
19765
+ async function installFromRegistry(name, targetDir, clawDir, asMain = false) {
19692
19766
  const spinner = createSpinner(`Checking registry for ${source_default.cyan(name)}...`).start();
19693
19767
  const index = await fetchIndex();
19694
19768
  const agent = index.agents.find((a) => a.name === name);
19695
19769
  const recipe = index.recipes.find((r) => r.name === name);
19696
19770
  if (agent && !recipe) {
19697
19771
  spinner.stop();
19698
- logger.info(`Installing single agent from registry: ${name}`);
19699
- await installSingleAgent(name, targetDir, clawDir);
19772
+ logger.info(`Installing single agent from registry: ${name}, asMain=${asMain}`);
19773
+ await installSingleAgent(name, targetDir, clawDir, asMain);
19700
19774
  } else if (recipe) {
19701
19775
  spinner.stop();
19702
19776
  logger.info(`Installing team recipe from registry: ${name}`);
@@ -19707,16 +19781,20 @@ async function installFromRegistry(name, targetDir, clawDir) {
19707
19781
  console.log(source_default.dim(" Use 'soulhub search' to find available agents and teams."));
19708
19782
  }
19709
19783
  }
19710
- async function installSingleAgent(name, targetDir, clawDir) {
19784
+ async function installSingleAgent(name, targetDir, clawDir, asMain = false) {
19711
19785
  const spinner = createSpinner(`Checking environment...`).start();
19786
+ let selectedClawDir = null;
19712
19787
  if (!targetDir) {
19713
- const clawCheck = checkOpenClawInstalled(clawDir);
19714
- if (!clawCheck.installed) {
19715
- spinner.fail("OpenClaw is not installed.");
19788
+ spinner.stop();
19789
+ selectedClawDir = await resolveClawDir(clawDir);
19790
+ if (!selectedClawDir) {
19791
+ console.log(source_default.red("OpenClaw/LightClaw workspace directory not found."));
19716
19792
  printOpenClawInstallHelp();
19717
19793
  return;
19718
19794
  }
19719
- spinner.text = source_default.dim(`OpenClaw detected: ${clawCheck.clawDir || "via PATH"}`);
19795
+ spinner.start();
19796
+ const brand = detectClawBrand(selectedClawDir);
19797
+ spinner.text = source_default.dim(`${brand} detected: ${selectedClawDir}`);
19720
19798
  }
19721
19799
  spinner.text = `Fetching agent ${source_default.cyan(name)}...`;
19722
19800
  const index = await fetchIndex();
@@ -19727,21 +19805,17 @@ async function installSingleAgent(name, targetDir, clawDir) {
19727
19805
  return;
19728
19806
  }
19729
19807
  let workspaceDir;
19808
+ const agentId = name.toLowerCase().replace(/[\s_]+/g, "-");
19730
19809
  if (targetDir) {
19731
19810
  workspaceDir = import_node_path12.default.resolve(targetDir);
19811
+ } else if (asMain) {
19812
+ workspaceDir = getMainWorkspaceDir(selectedClawDir);
19732
19813
  } else {
19733
- const resolvedClawDir = findOpenClawDir(clawDir);
19734
- if (!resolvedClawDir) {
19735
- spinner.fail("OpenClaw workspace directory not found.");
19736
- printOpenClawInstallHelp();
19737
- return;
19738
- }
19739
- workspaceDir = getMainWorkspaceDir(resolvedClawDir);
19814
+ workspaceDir = getWorkspaceDir(selectedClawDir, agentId);
19740
19815
  }
19741
- const resolvedClawDirForBackup = findOpenClawDir(clawDir);
19742
- const backupRecord = !targetDir ? createBackupRecord("single-agent", name, resolvedClawDirForBackup) : null;
19743
- if (!targetDir) {
19744
- const mainCheck = checkMainAgentExists(resolvedClawDirForBackup);
19816
+ const backupRecord = !targetDir ? createBackupRecord("single-agent", name, selectedClawDir) : null;
19817
+ if (!targetDir && asMain) {
19818
+ const mainCheck = checkMainAgentExists(selectedClawDir);
19745
19819
  if (mainCheck.hasContent) {
19746
19820
  spinner.warn(
19747
19821
  `Existing main agent detected. Backing up workspace...`
@@ -19758,7 +19832,21 @@ async function installSingleAgent(name, targetDir, clawDir) {
19758
19832
  });
19759
19833
  }
19760
19834
  }
19761
- } else {
19835
+ } else if (!targetDir && !asMain) {
19836
+ if (import_node_fs9.default.existsSync(workspaceDir)) {
19837
+ const backupDir = backupAgentWorkspace(workspaceDir);
19838
+ if (backupDir) {
19839
+ console.log(source_default.yellow(` \u26A0 Existing worker ${agentId} backed up to: ${backupDir}`));
19840
+ addBackupItem(backupRecord, {
19841
+ originalPath: workspaceDir,
19842
+ backupPath: backupDir,
19843
+ method: "cp",
19844
+ role: "worker",
19845
+ agentId
19846
+ });
19847
+ }
19848
+ }
19849
+ } else if (targetDir) {
19762
19850
  const backupDir = backupAgentWorkspace(workspaceDir);
19763
19851
  if (backupDir) {
19764
19852
  console.log(source_default.yellow(` \u26A0 Existing agent backed up to: ${backupDir}`));
@@ -19768,10 +19856,19 @@ async function installSingleAgent(name, targetDir, clawDir) {
19768
19856
  import_node_fs9.default.mkdirSync(workspaceDir, { recursive: true });
19769
19857
  }
19770
19858
  if (!targetDir) {
19771
- spinner.text = `Registering ${source_default.cyan(agent.displayName)} as main agent...`;
19772
- const resolvedClawDir = findOpenClawDir(clawDir);
19773
- addAgentToOpenClawConfig(resolvedClawDir, "main", name, true);
19774
- spinner.text = source_default.dim(`Main agent registered in openclaw.json`);
19859
+ if (asMain) {
19860
+ spinner.text = `Registering ${source_default.cyan(agent.displayName)} as main agent...`;
19861
+ addAgentToOpenClawConfig(selectedClawDir, "main", name, true);
19862
+ spinner.text = source_default.dim(`Main agent registered in config`);
19863
+ } else {
19864
+ spinner.text = `Registering ${source_default.cyan(agent.displayName)} as worker agent...`;
19865
+ const regResult = registerAgentToOpenClaw(agentId, workspaceDir, clawDir);
19866
+ if (!regResult.success) {
19867
+ spinner.fail(`Failed to register ${agentId}: ${regResult.message}`);
19868
+ return;
19869
+ }
19870
+ spinner.text = source_default.dim(`Worker agent ${agentId} registered in config`);
19871
+ }
19775
19872
  }
19776
19873
  spinner.text = `Downloading ${source_default.cyan(agent.displayName)} package...`;
19777
19874
  const pkgDir = await downloadAgentPackage(name, agent.version);
@@ -19779,17 +19876,23 @@ async function installSingleAgent(name, targetDir, clawDir) {
19779
19876
  import_node_fs9.default.rmSync(pkgDir, { recursive: true, force: true });
19780
19877
  recordInstall(name, agent.version, workspaceDir);
19781
19878
  if (backupRecord) {
19782
- backupRecord.installedMainAgent = name;
19879
+ if (asMain) {
19880
+ backupRecord.installedMainAgent = name;
19881
+ } else {
19882
+ backupRecord.installedWorkerIds = [agentId];
19883
+ }
19783
19884
  commitBackupRecord(backupRecord);
19784
19885
  }
19785
- logger.info(`Single agent installed: ${name}`, { version: agent.version, workspace: workspaceDir });
19886
+ const roleLabel = asMain ? "main agent" : "worker agent";
19887
+ const typeLabel = asMain ? source_default.blue("Single Agent (Main)") : source_default.green("Single Agent (Worker)");
19888
+ logger.info(`Single agent installed as ${roleLabel}: ${name}`, { version: agent.version, workspace: workspaceDir });
19786
19889
  spinner.succeed(
19787
- `${source_default.cyan.bold(agent.displayName)} installed as main agent!`
19890
+ `${source_default.cyan.bold(agent.displayName)} installed as ${roleLabel}!`
19788
19891
  );
19789
19892
  console.log();
19790
19893
  console.log(` ${source_default.dim("Location:")} ${workspaceDir}`);
19791
19894
  console.log(` ${source_default.dim("Version:")} ${agent.version}`);
19792
- console.log(` ${source_default.dim("Type:")} ${source_default.blue("Single Agent (Main)")}`);
19895
+ console.log(` ${source_default.dim("Type:")} ${typeLabel}`);
19793
19896
  if (!targetDir) {
19794
19897
  await tryRestartGateway();
19795
19898
  }
@@ -19797,19 +19900,19 @@ async function installSingleAgent(name, targetDir, clawDir) {
19797
19900
  }
19798
19901
  async function installRecipeFromRegistry(name, recipe, targetDir, clawDir) {
19799
19902
  const spinner = createSpinner(`Installing team ${source_default.cyan(recipe.displayName)}...`).start();
19800
- if (!targetDir) {
19801
- const clawCheck = checkOpenClawInstalled(clawDir);
19802
- if (!clawCheck.installed) {
19803
- spinner.fail("OpenClaw is not installed.");
19903
+ let resolvedClawDir;
19904
+ if (targetDir) {
19905
+ resolvedClawDir = import_node_path12.default.resolve(targetDir);
19906
+ } else {
19907
+ spinner.stop();
19908
+ const selected = await resolveClawDir(clawDir);
19909
+ if (!selected) {
19910
+ console.log(source_default.red("OpenClaw/LightClaw workspace directory not found."));
19804
19911
  printOpenClawInstallHelp();
19805
19912
  return;
19806
19913
  }
19807
- }
19808
- const resolvedClawDir = targetDir ? import_node_path12.default.resolve(targetDir) : findOpenClawDir(clawDir);
19809
- if (!resolvedClawDir) {
19810
- spinner.fail("OpenClaw workspace directory not found.");
19811
- printOpenClawInstallHelp();
19812
- return;
19914
+ resolvedClawDir = selected;
19915
+ spinner.start();
19813
19916
  }
19814
19917
  spinner.text = `Fetching team configuration...`;
19815
19918
  let pkg;
@@ -19887,7 +19990,7 @@ async function installRecipeFromRegistry(name, recipe, targetDir, clawDir) {
19887
19990
  await tryRestartGateway();
19888
19991
  }
19889
19992
  }
19890
- async function installFromSource(source, targetDir, clawDir) {
19993
+ async function installFromSource(source, targetDir, clawDir, asMain = false) {
19891
19994
  const spinner = createSpinner("Analyzing package...").start();
19892
19995
  let packageDir;
19893
19996
  let tempDir = null;
@@ -19941,7 +20044,7 @@ async function installFromSource(source, targetDir, clawDir) {
19941
20044
  spinner.text = `Detected package type: ${source_default.blue(kind)}`;
19942
20045
  if (kind === "agent") {
19943
20046
  spinner.stop();
19944
- await installSingleAgentFromDir(packageDir, targetDir, clawDir);
20047
+ await installSingleAgentFromDir(packageDir, targetDir, clawDir, asMain);
19945
20048
  } else if (kind === "team") {
19946
20049
  spinner.stop();
19947
20050
  await installTeamFromDir(packageDir, targetDir, clawDir);
@@ -19953,33 +20056,33 @@ async function installFromSource(source, targetDir, clawDir) {
19953
20056
  import_node_fs9.default.rmSync(tempDir, { recursive: true, force: true });
19954
20057
  }
19955
20058
  }
19956
- async function installSingleAgentFromDir(packageDir, targetDir, clawDir) {
20059
+ async function installSingleAgentFromDir(packageDir, targetDir, clawDir, asMain = false) {
19957
20060
  const spinner = createSpinner("Installing single agent...").start();
19958
20061
  const pkg = readSoulHubPackage(packageDir);
19959
20062
  const agentName = pkg?.name || import_node_path12.default.basename(packageDir);
20063
+ let selectedClawDir = null;
19960
20064
  if (!targetDir) {
19961
- const clawCheck = checkOpenClawInstalled(clawDir);
19962
- if (!clawCheck.installed) {
19963
- spinner.fail("OpenClaw is not installed.");
20065
+ spinner.stop();
20066
+ selectedClawDir = await resolveClawDir(clawDir);
20067
+ if (!selectedClawDir) {
20068
+ console.log(source_default.red("OpenClaw/LightClaw workspace directory not found."));
19964
20069
  printOpenClawInstallHelp();
19965
20070
  return;
19966
20071
  }
20072
+ spinner.start();
19967
20073
  }
19968
20074
  let workspaceDir;
20075
+ const agentId = agentName.toLowerCase().replace(/[\s_]+/g, "-");
19969
20076
  if (targetDir) {
19970
20077
  workspaceDir = import_node_path12.default.resolve(targetDir);
20078
+ } else if (asMain) {
20079
+ workspaceDir = getMainWorkspaceDir(selectedClawDir);
19971
20080
  } else {
19972
- const resolvedClawDir = findOpenClawDir(clawDir);
19973
- if (!resolvedClawDir) {
19974
- spinner.fail("OpenClaw workspace directory not found.");
19975
- return;
19976
- }
19977
- workspaceDir = getMainWorkspaceDir(resolvedClawDir);
20081
+ workspaceDir = getWorkspaceDir(selectedClawDir, agentId);
19978
20082
  }
19979
- const localBackupRecord = !targetDir ? createBackupRecord("single-agent-local", agentName, findOpenClawDir(clawDir)) : null;
19980
- if (!targetDir) {
19981
- const resolvedClawDir = findOpenClawDir(clawDir);
19982
- const mainCheck = checkMainAgentExists(resolvedClawDir);
20083
+ const localBackupRecord = !targetDir ? createBackupRecord("single-agent-local", agentName, selectedClawDir) : null;
20084
+ if (!targetDir && asMain) {
20085
+ const mainCheck = checkMainAgentExists(selectedClawDir);
19983
20086
  if (mainCheck.hasContent) {
19984
20087
  spinner.warn("Existing main agent detected. Backing up...");
19985
20088
  const backupDir = backupAgentWorkspace(workspaceDir);
@@ -19994,28 +20097,56 @@ async function installSingleAgentFromDir(packageDir, targetDir, clawDir) {
19994
20097
  });
19995
20098
  }
19996
20099
  }
20100
+ } else if (!targetDir && !asMain) {
20101
+ if (import_node_fs9.default.existsSync(workspaceDir)) {
20102
+ const backupDir = backupAgentWorkspace(workspaceDir);
20103
+ if (backupDir) {
20104
+ console.log(source_default.yellow(` \u26A0 Existing worker ${agentId} backed up to: ${backupDir}`));
20105
+ addBackupItem(localBackupRecord, {
20106
+ originalPath: workspaceDir,
20107
+ backupPath: backupDir,
20108
+ method: "cp",
20109
+ role: "worker",
20110
+ agentId
20111
+ });
20112
+ }
20113
+ }
19997
20114
  }
19998
20115
  if (!import_node_fs9.default.existsSync(workspaceDir)) {
19999
20116
  import_node_fs9.default.mkdirSync(workspaceDir, { recursive: true });
20000
20117
  }
20001
20118
  if (!targetDir) {
20002
- spinner.text = `Registering ${source_default.cyan(agentName)} as main agent...`;
20003
- const resolvedClawDir = findOpenClawDir(clawDir);
20004
- addAgentToOpenClawConfig(resolvedClawDir, "main", agentName, true);
20119
+ if (asMain) {
20120
+ spinner.text = `Registering ${source_default.cyan(agentName)} as main agent...`;
20121
+ addAgentToOpenClawConfig(selectedClawDir, "main", agentName, true);
20122
+ } else {
20123
+ spinner.text = `Registering ${source_default.cyan(agentName)} as worker agent...`;
20124
+ const regResult = registerAgentToOpenClaw(agentId, workspaceDir, clawDir);
20125
+ if (!regResult.success) {
20126
+ spinner.fail(`Failed to register ${agentId}: ${regResult.message}`);
20127
+ return;
20128
+ }
20129
+ }
20005
20130
  }
20006
20131
  spinner.text = `Copying soul files...`;
20007
20132
  copyAgentFilesFromDir(packageDir, workspaceDir);
20008
20133
  recordInstall(agentName, pkg?.version || "local", workspaceDir);
20009
20134
  if (localBackupRecord) {
20010
- localBackupRecord.installedMainAgent = agentName;
20135
+ if (asMain) {
20136
+ localBackupRecord.installedMainAgent = agentName;
20137
+ } else {
20138
+ localBackupRecord.installedWorkerIds = [agentId];
20139
+ }
20011
20140
  commitBackupRecord(localBackupRecord);
20012
20141
  }
20013
- logger.info(`Single agent installed from dir: ${agentName}`, { source: packageDir, workspace: workspaceDir });
20014
- spinner.succeed(`${source_default.cyan.bold(agentName)} installed as main agent!`);
20142
+ const roleLabel = asMain ? "main agent" : "worker agent";
20143
+ const typeLabel = asMain ? source_default.blue("Single Agent (Main)") : source_default.green("Single Agent (Worker)");
20144
+ logger.info(`Single agent installed from dir as ${roleLabel}: ${agentName}`, { source: packageDir, workspace: workspaceDir });
20145
+ spinner.succeed(`${source_default.cyan.bold(agentName)} installed as ${roleLabel}!`);
20015
20146
  console.log();
20016
20147
  console.log(` ${source_default.dim("Location:")} ${workspaceDir}`);
20017
20148
  console.log(` ${source_default.dim("Source:")} ${packageDir}`);
20018
- console.log(` ${source_default.dim("Type:")} ${source_default.blue("Single Agent (Main)")}`);
20149
+ console.log(` ${source_default.dim("Type:")} ${typeLabel}`);
20019
20150
  if (!targetDir) {
20020
20151
  await tryRestartGateway();
20021
20152
  }
@@ -20028,18 +20159,19 @@ async function installTeamFromDir(packageDir, targetDir, clawDir) {
20028
20159
  spinner.fail("Invalid team package. Missing soulhub.yaml.");
20029
20160
  return;
20030
20161
  }
20031
- if (!targetDir) {
20032
- const clawCheck = checkOpenClawInstalled(clawDir);
20033
- if (!clawCheck.installed) {
20034
- spinner.fail("OpenClaw is not installed.");
20162
+ let resolvedClawDir;
20163
+ if (targetDir) {
20164
+ resolvedClawDir = import_node_path12.default.resolve(targetDir);
20165
+ } else {
20166
+ spinner.stop();
20167
+ const selected = await resolveClawDir(clawDir);
20168
+ if (!selected) {
20169
+ console.log(source_default.red("OpenClaw/LightClaw workspace directory not found."));
20035
20170
  printOpenClawInstallHelp();
20036
20171
  return;
20037
20172
  }
20038
- }
20039
- const resolvedClawDir = targetDir ? import_node_path12.default.resolve(targetDir) : findOpenClawDir(clawDir);
20040
- if (!resolvedClawDir) {
20041
- spinner.fail("OpenClaw workspace directory not found.");
20042
- return;
20173
+ resolvedClawDir = selected;
20174
+ spinner.start();
20043
20175
  }
20044
20176
  const teamBackupRecord = !targetDir ? createBackupRecord("team-local", pkg.name, resolvedClawDir) : null;
20045
20177
  if (!targetDir) {
@@ -20199,11 +20331,11 @@ async function extractZipToDir(zip, targetDir) {
20199
20331
  }
20200
20332
  }
20201
20333
  function printOpenClawInstallHelp() {
20202
- console.log(source_default.dim(" Please install OpenClaw first, or use one of the following options:"));
20203
- console.log(source_default.dim(" --claw-dir <path> Specify OpenClaw installation directory"));
20204
- console.log(source_default.dim(" --dir <path> Specify agent target directory directly"));
20205
- console.log(source_default.dim(" OPENCLAW_HOME=<path> Set environment variable"));
20206
- console.log(source_default.dim(" Visit: https://github.com/anthropics/openclaw for installation instructions."));
20334
+ console.log(source_default.dim(" Please install OpenClaw or LightClaw first, or use one of the following options:"));
20335
+ console.log(source_default.dim(" --claw-dir <path> Specify OpenClaw/LightClaw installation directory"));
20336
+ console.log(source_default.dim(" --dir <path> Specify agent target directory directly"));
20337
+ console.log(source_default.dim(" OPENCLAW_HOME=<path> Set environment variable (for OpenClaw)"));
20338
+ console.log(source_default.dim(" LIGHTCLAW_HOME=<path> Set environment variable (for LightClaw)"));
20207
20339
  }
20208
20340
  function printTeamSummary(pkg, workerIds) {
20209
20341
  console.log();
@@ -20221,20 +20353,22 @@ function printTeamSummary(pkg, workerIds) {
20221
20353
  console.log();
20222
20354
  }
20223
20355
  async function tryRestartGateway() {
20224
- const restartSpinner = createSpinner("Restarting OpenClaw Gateway...").start();
20356
+ const clawCmd = detectClawCommand();
20357
+ const brandName = clawCmd === "lightclaw" ? "LightClaw" : "OpenClaw";
20358
+ const restartSpinner = createSpinner(`Restarting ${brandName} Gateway...`).start();
20225
20359
  const result = restartOpenClawGateway();
20226
20360
  if (result.success) {
20227
- restartSpinner.succeed("OpenClaw Gateway restarted successfully.");
20361
+ restartSpinner.succeed(`${brandName} Gateway restarted successfully.`);
20228
20362
  } else {
20229
- restartSpinner.warn("Failed to restart OpenClaw Gateway.");
20363
+ restartSpinner.warn(`Failed to restart ${brandName} Gateway.`);
20230
20364
  console.log(source_default.yellow(` Reason: ${result.message}`));
20231
20365
  console.log(source_default.dim(" Please restart manually:"));
20232
- console.log(source_default.dim(" openclaw gateway restart"));
20366
+ console.log(source_default.dim(` ${clawCmd} gateway restart`));
20233
20367
  }
20234
20368
  }
20235
20369
 
20236
20370
  // src/commands/list.ts
20237
- var listCommand = new Command("list").description("List installed agent templates").alias("ls").action(async () => {
20371
+ var listCommand = new Command("list").description("List installed agents").alias("ls").action(async () => {
20238
20372
  try {
20239
20373
  const config = loadConfig();
20240
20374
  if (config.installed.length === 0) {
@@ -20274,7 +20408,7 @@ var listCommand = new Command("list").description("List installed agent template
20274
20408
 
20275
20409
  // src/commands/update.ts
20276
20410
  var import_node_fs10 = __toESM(require("fs"), 1);
20277
- var updateCommand = new Command("update").description("Update installed agent templates to latest versions").argument("[name]", "Agent name to update (updates all if omitted)").action(async (name) => {
20411
+ var updateCommand = new Command("update").description("Update installed agents to latest versions").argument("[name]", "Agent name to update (updates all if omitted)").action(async (name) => {
20278
20412
  try {
20279
20413
  const config = loadConfig();
20280
20414
  if (config.installed.length === 0) {
@@ -20335,7 +20469,7 @@ var import_node_fs11 = __toESM(require("fs"), 1);
20335
20469
  var import_node_path13 = __toESM(require("path"), 1);
20336
20470
  var rollbackCommand = new Command("rollback").description("Rollback to a previous agent installation state").option("--list", "List available rollback records").option("--id <id>", "Rollback to a specific backup record by ID").option(
20337
20471
  "--claw-dir <path>",
20338
- "OpenClaw installation directory (overrides OPENCLAW_HOME env var)"
20472
+ "OpenClaw/LightClaw installation directory (overrides OPENCLAW_HOME/LIGHTCLAW_HOME env var)"
20339
20473
  ).action(async (options) => {
20340
20474
  try {
20341
20475
  if (options.list) {
@@ -20407,20 +20541,21 @@ async function performRollback(recordId, clawDir) {
20407
20541
  const spinner = createSpinner(
20408
20542
  `Rolling back ${source_default.cyan(record.packageName)}...`
20409
20543
  ).start();
20410
- const resolvedClawDir = findOpenClawDir(clawDir) || record.clawDir;
20544
+ const resolvedClawDir = clawDir ? findOpenClawDir(clawDir) || record.clawDir : await promptSelectClawDir() || record.clawDir;
20411
20545
  if (!resolvedClawDir || !import_node_fs11.default.existsSync(resolvedClawDir)) {
20412
- spinner.fail(`OpenClaw directory not found: ${record.clawDir}`);
20546
+ spinner.fail(`OpenClaw/LightClaw directory not found: ${record.clawDir}`);
20413
20547
  return;
20414
20548
  }
20549
+ const brand = detectClawBrand(resolvedClawDir);
20415
20550
  if (record.openclawJsonSnapshot) {
20416
- spinner.text = "Restoring openclaw.json...";
20551
+ spinner.text = `Restoring ${brand.toLowerCase()}.json...`;
20417
20552
  try {
20418
20553
  const configObj = JSON.parse(record.openclawJsonSnapshot);
20419
20554
  writeOpenClawConfig(resolvedClawDir, configObj);
20420
- logger.info("openclaw.json restored from snapshot", { recordId: record.id });
20555
+ logger.info(`${brand.toLowerCase()}.json restored from snapshot`, { recordId: record.id });
20421
20556
  } catch (err) {
20422
- logger.error("Failed to restore openclaw.json", { error: err });
20423
- console.log(source_default.yellow(" \u26A0 Failed to restore openclaw.json, skipping..."));
20557
+ logger.error(`Failed to restore ${brand.toLowerCase()}.json`, { error: err });
20558
+ console.log(source_default.yellow(` \u26A0 Failed to restore ${brand.toLowerCase()}.json, skipping...`));
20424
20559
  }
20425
20560
  }
20426
20561
  if (record.installedWorkerIds.length > 0) {
@@ -20483,15 +20618,17 @@ async function performRollback(recordId, clawDir) {
20483
20618
  spinner.succeed(
20484
20619
  `Rolled back ${source_default.cyan.bold(record.packageName)} successfully! (${restoredCount} item(s) restored)`
20485
20620
  );
20486
- const restartSpinner = createSpinner("Restarting OpenClaw Gateway...").start();
20621
+ const clawCmd = detectClawCommand();
20622
+ const brandName = clawCmd === "lightclaw" ? "LightClaw" : "OpenClaw";
20623
+ const restartSpinner = createSpinner(`Restarting ${brandName} Gateway...`).start();
20487
20624
  const result = restartOpenClawGateway();
20488
20625
  if (result.success) {
20489
- restartSpinner.succeed("OpenClaw Gateway restarted successfully.");
20626
+ restartSpinner.succeed(`${brandName} Gateway restarted successfully.`);
20490
20627
  } else {
20491
- restartSpinner.warn("Failed to restart OpenClaw Gateway.");
20628
+ restartSpinner.warn(`Failed to restart ${brandName} Gateway.`);
20492
20629
  console.log(source_default.yellow(` Reason: ${result.message}`));
20493
20630
  console.log(source_default.dim(" Please restart manually:"));
20494
- console.log(source_default.dim(" openclaw gateway restart"));
20631
+ console.log(source_default.dim(` ${clawCmd} gateway restart`));
20495
20632
  }
20496
20633
  console.log();
20497
20634
  }
@@ -20512,13 +20649,13 @@ function formatInstallType(type2) {
20512
20649
 
20513
20650
  // src/index.ts
20514
20651
  var program2 = new Command();
20515
- program2.name("soulhub").description("SoulHub CLI - Install and manage AI agent persona templates").version("1.0.12").option("--verbose", "Enable verbose debug logging").hook("preAction", () => {
20652
+ program2.name("soulhub").description("SoulHub CLI - Discover, install and manage AI agent souls").version("1.0.14").option("--verbose", "Enable verbose debug logging").hook("preAction", () => {
20516
20653
  const opts = program2.opts();
20517
20654
  const verbose = opts.verbose || process.env.SOULHUB_DEBUG === "1";
20518
20655
  logger.init(verbose);
20519
20656
  logger.info("CLI started", {
20520
20657
  args: process.argv.slice(2),
20521
- version: "1.0.12",
20658
+ version: "1.0.14",
20522
20659
  node: process.version
20523
20660
  });
20524
20661
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "soulhubcli",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "SoulHub CLI - Install and manage AI agent persona templates for OpenClaw",
5
5
  "type": "module",
6
6
  "bin": {