soulhubcli 1.0.9 → 1.0.10

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 +165 -137
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -966,7 +966,7 @@ var require_command = __commonJS({
966
966
  var EventEmitter = require("events").EventEmitter;
967
967
  var childProcess = require("child_process");
968
968
  var path6 = require("path");
969
- var fs7 = require("fs");
969
+ var fs6 = require("fs");
970
970
  var process3 = require("process");
971
971
  var { Argument: Argument2, humanReadableArgName } = require_argument();
972
972
  var { CommanderError: CommanderError2 } = require_error();
@@ -1899,10 +1899,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
1899
1899
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1900
1900
  function findFile(baseDir, baseName) {
1901
1901
  const localBin = path6.resolve(baseDir, baseName);
1902
- if (fs7.existsSync(localBin)) return localBin;
1902
+ if (fs6.existsSync(localBin)) return localBin;
1903
1903
  if (sourceExt.includes(path6.extname(baseName))) return void 0;
1904
1904
  const foundExt = sourceExt.find(
1905
- (ext) => fs7.existsSync(`${localBin}${ext}`)
1905
+ (ext) => fs6.existsSync(`${localBin}${ext}`)
1906
1906
  );
1907
1907
  if (foundExt) return `${localBin}${foundExt}`;
1908
1908
  return void 0;
@@ -1914,7 +1914,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1914
1914
  if (this._scriptPath) {
1915
1915
  let resolvedScriptPath;
1916
1916
  try {
1917
- resolvedScriptPath = fs7.realpathSync(this._scriptPath);
1917
+ resolvedScriptPath = fs6.realpathSync(this._scriptPath);
1918
1918
  } catch (err) {
1919
1919
  resolvedScriptPath = this._scriptPath;
1920
1920
  }
@@ -16134,11 +16134,6 @@ function recordInstall(name, version, workspace) {
16134
16134
  saveConfig(config);
16135
16135
  logger.debug(`Recorded install`, { name, version, workspace });
16136
16136
  }
16137
- function removeInstallRecord(name) {
16138
- const config = loadConfig();
16139
- config.installed = config.installed.filter((a) => a.name !== name);
16140
- saveConfig(config);
16141
- }
16142
16137
  function getWorkspaceDir(clawDir, agentName) {
16143
16138
  return import_node_path2.default.join(clawDir, `workspace-${agentName}`);
16144
16139
  }
@@ -17397,43 +17392,23 @@ var updateCommand = new Command("update").description("Update installed agent te
17397
17392
  }
17398
17393
  });
17399
17394
 
17400
- // src/commands/uninstall.ts
17395
+ // src/commands/rollback.ts
17401
17396
  var import_node_fs5 = __toESM(require("fs"), 1);
17402
- var uninstallCommand = new Command("uninstall").description("Uninstall an agent template").alias("rm").argument("<name>", "Agent name to uninstall").option("--keep-files", "Remove from registry but keep workspace files").action(async (name, options) => {
17397
+ var import_node_path5 = __toESM(require("path"), 1);
17398
+ 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(
17399
+ "--claw-dir <path>",
17400
+ "OpenClaw installation directory (overrides OPENCLAW_HOME env var)"
17401
+ ).action(async (options) => {
17403
17402
  try {
17404
- const config = loadConfig();
17405
- const installed = config.installed.find((a) => a.name === name);
17406
- if (!installed) {
17407
- console.error(
17408
- source_default.red(`
17409
- Agent "${name}" is not installed.
17410
- `)
17411
- );
17412
- console.log(
17413
- source_default.dim(" Use 'soulhub list' to see installed agents.")
17414
- );
17415
- process.exit(1);
17416
- }
17417
- const spinner = createSpinner(
17418
- `Uninstalling ${source_default.cyan(name)}...`
17419
- ).start();
17420
- if (!options.keepFiles && import_node_fs5.default.existsSync(installed.workspace)) {
17421
- import_node_fs5.default.rmSync(installed.workspace, { recursive: true, force: true });
17422
- spinner.text = `Removed workspace: ${installed.workspace}`;
17423
- }
17424
- removeInstallRecord(name);
17425
- logger.info(`Agent uninstalled: ${name}`, { workspace: installed.workspace, keepFiles: !!options.keepFiles });
17426
- spinner.succeed(
17427
- `${source_default.cyan.bold(name)} uninstalled.`
17428
- );
17429
- if (options.keepFiles) {
17430
- console.log(
17431
- source_default.dim(` Files kept at: ${installed.workspace}`)
17432
- );
17403
+ if (options.list) {
17404
+ listBackupRecords();
17405
+ } else if (options.id) {
17406
+ await performRollback(options.id, options.clawDir);
17407
+ } else {
17408
+ await performRollback(void 0, options.clawDir);
17433
17409
  }
17434
- console.log();
17435
17410
  } catch (error) {
17436
- logger.errorObj("Uninstall command failed", error);
17411
+ logger.errorObj("Rollback command failed", error);
17437
17412
  console.error(
17438
17413
  source_default.red(`Error: ${error instanceof Error ? error.message : error}`)
17439
17414
  );
@@ -17441,107 +17416,161 @@ var uninstallCommand = new Command("uninstall").description("Uninstall an agent
17441
17416
  process.exit(1);
17442
17417
  }
17443
17418
  });
17444
-
17445
- // src/commands/publish.ts
17446
- var import_node_fs6 = __toESM(require("fs"), 1);
17447
- var import_node_path5 = __toESM(require("path"), 1);
17448
- var publishCommand = new Command("publish").description("Publish an agent template to the SoulHub community").argument(
17449
- "[directory]",
17450
- "Agent workspace directory (defaults to current directory)"
17451
- ).action(async (directory) => {
17452
- try {
17453
- const dir = directory ? import_node_path5.default.resolve(directory) : process.cwd();
17454
- const spinner = createSpinner("Validating agent template...").start();
17455
- const requiredFiles = ["manifest.yaml", "IDENTITY.md", "SOUL.md"];
17456
- const missing = requiredFiles.filter(
17457
- (f) => !import_node_fs6.default.existsSync(import_node_path5.default.join(dir, f))
17458
- );
17459
- if (missing.length > 0) {
17460
- spinner.fail("Missing required files:");
17461
- for (const f of missing) {
17462
- console.log(source_default.red(` - ${f}`));
17463
- }
17464
- console.log();
17465
- console.log(source_default.dim(" Required files: manifest.yaml, IDENTITY.md, SOUL.md"));
17466
- console.log(
17467
- source_default.dim(
17468
- " See: https://soulhub.dev/docs/contributing"
17469
- )
17470
- );
17471
- return;
17472
- }
17473
- const manifestContent = import_node_fs6.default.readFileSync(
17474
- import_node_path5.default.join(dir, "manifest.yaml"),
17475
- "utf-8"
17476
- );
17477
- const manifest = jsYaml.load(manifestContent);
17478
- const requiredFields = [
17479
- "name",
17480
- "displayName",
17481
- "description",
17482
- "category",
17483
- "version",
17484
- "author"
17485
- ];
17486
- const missingFields = requiredFields.filter(
17487
- (f) => !manifest[f]
17419
+ function listBackupRecords() {
17420
+ const manifest = loadBackupManifest();
17421
+ if (manifest.records.length === 0) {
17422
+ console.log(source_default.yellow("No backup records found."));
17423
+ console.log(source_default.dim(" Backup records are created automatically when you install agents."));
17424
+ return;
17425
+ }
17426
+ console.log(source_default.bold("\nAvailable rollback records:\n"));
17427
+ console.log(
17428
+ source_default.dim(
17429
+ ` ${"ID".padEnd(20)} ${"Type".padEnd(20)} ${"Package".padEnd(20)} ${"Date".padEnd(22)} Items`
17430
+ )
17431
+ );
17432
+ console.log(source_default.dim(" " + "\u2500".repeat(90)));
17433
+ for (const record of manifest.records) {
17434
+ const date = new Date(record.createdAt).toLocaleString();
17435
+ const typeLabel = formatInstallType(record.installType);
17436
+ const itemCount = record.items.length;
17437
+ console.log(
17438
+ ` ${source_default.cyan(record.id.padEnd(20))} ${typeLabel.padEnd(20)} ${source_default.white(record.packageName.padEnd(20))} ${source_default.dim(date.padEnd(22))} ${itemCount} backup(s)`
17488
17439
  );
17489
- if (missingFields.length > 0) {
17490
- spinner.fail("Missing required fields in manifest.yaml:");
17491
- for (const f of missingFields) {
17492
- console.log(source_default.red(` - ${f}`));
17493
- }
17494
- return;
17495
- }
17496
- const validCategories = [
17497
- "self-media",
17498
- "development",
17499
- "operations",
17500
- "support",
17501
- "education",
17502
- "dispatcher"
17503
- ];
17504
- if (!validCategories.includes(manifest.category)) {
17505
- spinner.fail(
17506
- `Invalid category: ${manifest.category}. Must be one of: ${validCategories.join(", ")}`
17507
- );
17440
+ }
17441
+ console.log();
17442
+ console.log(source_default.dim(" Usage:"));
17443
+ console.log(source_default.dim(" soulhub rollback # Rollback last installation"));
17444
+ console.log(source_default.dim(" soulhub rollback --id <id> # Rollback to a specific record"));
17445
+ console.log();
17446
+ }
17447
+ async function performRollback(recordId, clawDir) {
17448
+ const manifest = loadBackupManifest();
17449
+ if (manifest.records.length === 0) {
17450
+ console.log(source_default.yellow("No backup records found. Nothing to rollback."));
17451
+ return;
17452
+ }
17453
+ let record;
17454
+ if (recordId) {
17455
+ record = manifest.records.find((r) => r.id === recordId);
17456
+ if (!record) {
17457
+ console.error(source_default.red(`Backup record "${recordId}" not found.`));
17458
+ console.log(source_default.dim(" Use 'soulhub rollback --list' to see available records."));
17508
17459
  return;
17509
17460
  }
17510
- spinner.succeed("Template validation passed!");
17511
- console.log();
17512
- console.log(source_default.bold(` ${manifest.displayName}`));
17513
- console.log(
17514
- source_default.dim(` ${manifest.name} v${manifest.version}`)
17515
- );
17516
- console.log(` ${manifest.description}`);
17517
- console.log();
17518
- console.log(source_default.bold(" Next steps to publish:"));
17519
- console.log();
17520
- console.log(
17521
- ` 1. Fork ${source_default.cyan("github.com/soulhub-community/soulhub")}`
17522
- );
17523
- console.log(
17524
- ` 2. Copy your agent directory to ${source_default.cyan(`registry/agents/${manifest.name}/`)}`
17525
- );
17526
- console.log(
17527
- ` 3. Submit a Pull Request`
17528
- );
17529
- console.log();
17461
+ } else {
17462
+ record = manifest.records[0];
17530
17463
  console.log(
17531
17464
  source_default.dim(
17532
- " Your template will be reviewed and added to the community registry."
17465
+ `Rolling back last installation: ${source_default.cyan(record.id)} (${record.packageName})`
17533
17466
  )
17534
17467
  );
17535
- console.log();
17536
- } catch (error) {
17537
- logger.errorObj("Publish command failed", error);
17538
- console.error(
17539
- source_default.red(`Error: ${error instanceof Error ? error.message : error}`)
17540
- );
17541
- console.error(source_default.dim(` See logs: ${logger.getTodayLogFile()}`));
17542
- process.exit(1);
17543
17468
  }
17544
- });
17469
+ const spinner = createSpinner(
17470
+ `Rolling back ${source_default.cyan(record.packageName)}...`
17471
+ ).start();
17472
+ const resolvedClawDir = findOpenClawDir(clawDir) || record.clawDir;
17473
+ if (!resolvedClawDir || !import_node_fs5.default.existsSync(resolvedClawDir)) {
17474
+ spinner.fail(`OpenClaw directory not found: ${record.clawDir}`);
17475
+ return;
17476
+ }
17477
+ if (record.openclawJsonSnapshot) {
17478
+ spinner.text = "Restoring openclaw.json...";
17479
+ try {
17480
+ const configObj = JSON.parse(record.openclawJsonSnapshot);
17481
+ writeOpenClawConfig(resolvedClawDir, configObj);
17482
+ logger.info("openclaw.json restored from snapshot", { recordId: record.id });
17483
+ } catch (err) {
17484
+ logger.error("Failed to restore openclaw.json", { error: err });
17485
+ console.log(source_default.yellow(" \u26A0 Failed to restore openclaw.json, skipping..."));
17486
+ }
17487
+ }
17488
+ if (record.installedWorkerIds.length > 0) {
17489
+ spinner.text = "Removing installed workers...";
17490
+ for (const workerId of record.installedWorkerIds) {
17491
+ const workerDir = getWorkspaceDir(resolvedClawDir, workerId);
17492
+ if (import_node_fs5.default.existsSync(workerDir)) {
17493
+ import_node_fs5.default.rmSync(workerDir, { recursive: true, force: true });
17494
+ logger.info(`Removed installed worker directory`, { workerId, dir: workerDir });
17495
+ }
17496
+ const agentConfigDir = import_node_path5.default.join(resolvedClawDir, "agents", workerId);
17497
+ if (import_node_fs5.default.existsSync(agentConfigDir)) {
17498
+ import_node_fs5.default.rmSync(agentConfigDir, { recursive: true, force: true });
17499
+ }
17500
+ }
17501
+ }
17502
+ if (record.installedMainAgent) {
17503
+ const mainWorkspace = getMainWorkspaceDir(resolvedClawDir);
17504
+ const hasMainBackup = record.items.some((item) => item.role === "main");
17505
+ if (hasMainBackup && import_node_fs5.default.existsSync(mainWorkspace)) {
17506
+ spinner.text = "Cleaning current main workspace...";
17507
+ const entries = import_node_fs5.default.readdirSync(mainWorkspace);
17508
+ for (const entry of entries) {
17509
+ import_node_fs5.default.rmSync(import_node_path5.default.join(mainWorkspace, entry), { recursive: true, force: true });
17510
+ }
17511
+ }
17512
+ }
17513
+ let restoredCount = 0;
17514
+ for (const item of record.items) {
17515
+ if (!import_node_fs5.default.existsSync(item.backupPath)) {
17516
+ logger.warn(`Backup path not found, skipping`, { backupPath: item.backupPath });
17517
+ console.log(source_default.yellow(` \u26A0 Backup not found: ${item.backupPath}, skipping...`));
17518
+ continue;
17519
+ }
17520
+ spinner.text = `Restoring ${source_default.cyan(item.agentId)} (${item.role})...`;
17521
+ if (item.method === "mv") {
17522
+ if (import_node_fs5.default.existsSync(item.originalPath)) {
17523
+ import_node_fs5.default.rmSync(item.originalPath, { recursive: true, force: true });
17524
+ }
17525
+ import_node_fs5.default.mkdirSync(import_node_path5.default.dirname(item.originalPath), { recursive: true });
17526
+ import_node_fs5.default.renameSync(item.backupPath, item.originalPath);
17527
+ logger.info(`Restored (mv back)`, { from: item.backupPath, to: item.originalPath });
17528
+ } else {
17529
+ if (import_node_fs5.default.existsSync(item.originalPath)) {
17530
+ const entries = import_node_fs5.default.readdirSync(item.originalPath);
17531
+ for (const entry of entries) {
17532
+ import_node_fs5.default.rmSync(import_node_path5.default.join(item.originalPath, entry), { recursive: true, force: true });
17533
+ }
17534
+ } else {
17535
+ import_node_fs5.default.mkdirSync(item.originalPath, { recursive: true });
17536
+ }
17537
+ import_node_fs5.default.cpSync(item.backupPath, item.originalPath, { recursive: true });
17538
+ import_node_fs5.default.rmSync(item.backupPath, { recursive: true, force: true });
17539
+ logger.info(`Restored (cp back)`, { from: item.backupPath, to: item.originalPath });
17540
+ }
17541
+ restoredCount++;
17542
+ }
17543
+ manifest.records = manifest.records.filter((r) => r.id !== record.id);
17544
+ saveBackupManifest(manifest);
17545
+ spinner.succeed(
17546
+ `Rolled back ${source_default.cyan.bold(record.packageName)} successfully! (${restoredCount} item(s) restored)`
17547
+ );
17548
+ const restartSpinner = createSpinner("Restarting OpenClaw Gateway...").start();
17549
+ const result = restartOpenClawGateway();
17550
+ if (result.success) {
17551
+ restartSpinner.succeed("OpenClaw Gateway restarted successfully.");
17552
+ } else {
17553
+ restartSpinner.warn("Failed to restart OpenClaw Gateway.");
17554
+ console.log(source_default.yellow(` Reason: ${result.message}`));
17555
+ console.log(source_default.dim(" Please restart manually:"));
17556
+ console.log(source_default.dim(" openclaw gateway restart"));
17557
+ }
17558
+ console.log();
17559
+ }
17560
+ function formatInstallType(type2) {
17561
+ switch (type2) {
17562
+ case "single-agent":
17563
+ return source_default.blue("Single Agent");
17564
+ case "team-registry":
17565
+ return source_default.magenta("Team (Registry)");
17566
+ case "team-local":
17567
+ return source_default.magenta("Team (Local)");
17568
+ case "single-agent-local":
17569
+ return source_default.blue("Agent (Local)");
17570
+ default:
17571
+ return type2;
17572
+ }
17573
+ }
17545
17574
 
17546
17575
  // src/index.ts
17547
17576
  var program2 = new Command();
@@ -17560,8 +17589,7 @@ program2.addCommand(infoCommand);
17560
17589
  program2.addCommand(installCommand);
17561
17590
  program2.addCommand(listCommand);
17562
17591
  program2.addCommand(updateCommand);
17563
- program2.addCommand(uninstallCommand);
17564
- program2.addCommand(publishCommand);
17592
+ program2.addCommand(rollbackCommand);
17565
17593
  program2.parse();
17566
17594
  /*! Bundled license information:
17567
17595
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "soulhubcli",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "SoulHub CLI - Install and manage AI agent persona templates for OpenClaw",
5
5
  "type": "module",
6
6
  "bin": {