claw-control-center 0.1.8 → 0.1.9

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/dist/index.cjs CHANGED
@@ -8886,9 +8886,17 @@ var import_promises5 = require("fs/promises");
8886
8886
  var import_node_fs4 = require("fs");
8887
8887
  var import_node_os2 = require("os");
8888
8888
  var import_node_path6 = require("path");
8889
+ var import_yaml = require("yaml");
8889
8890
  var PLUGIN_ID = "claw-control-center";
8890
8891
  var LEGACY_PLUGIN_ID = "53ai-openclaw";
8891
8892
  var COPY_ITEMS = ["dist", "openclaw.plugin.json", "package.json", "bin", "web-dist"];
8893
+ var HERMES_PLATFORM_ID = "53aihub";
8894
+ var HERMES_PLUGIN_KEY = `platforms/${HERMES_PLATFORM_ID}`;
8895
+ var HERMES_ENV_KEYS = {
8896
+ botId: "HUB53AI_BOT_ID",
8897
+ secret: "HUB53AI_SECRET",
8898
+ wsUrl: "HUB53AI_WS_URL"
8899
+ };
8892
8900
  var SUPPORTED_ARGS = /* @__PURE__ */ new Set([
8893
8901
  "gateway",
8894
8902
  "bot-id",
@@ -8910,6 +8918,31 @@ async function installIntoQClaw(input) {
8910
8918
  async function installIntoOpenClaw(input) {
8911
8919
  return installIntoHost(input, "OpenClaw");
8912
8920
  }
8921
+ async function installIntoHermes(input) {
8922
+ const hubWsUrl = input.hubWsUrl?.trim();
8923
+ const hubBotId = input.hubBotId?.trim();
8924
+ const hubSecret = input.hubSecret?.trim();
8925
+ if (!hubWsUrl || !hubBotId || !hubSecret) {
8926
+ throw new Error("Hermes install requires --hub-ws-url, --hub-bot-id, and --hub-secret");
8927
+ }
8928
+ const platformsDir = normalizeHermesPlatformsDir(input.extensionsDir);
8929
+ const destination = (0, import_node_path6.join)(platformsDir, HERMES_PLATFORM_ID);
8930
+ await (0, import_promises5.mkdir)(destination, { recursive: true });
8931
+ await copyHermesPlatformPackage(input.packageRoot, destination);
8932
+ await updateHermesConfig(input.configPath);
8933
+ await updateHermesEnv(input.configPath, {
8934
+ botId: hubBotId,
8935
+ secret: hubSecret,
8936
+ wsUrl: hubWsUrl
8937
+ });
8938
+ return {
8939
+ configPath: input.configPath,
8940
+ extensionsDir: platformsDir,
8941
+ destination,
8942
+ hub53aiConfigured: true,
8943
+ pluginBuild: await readHermesPluginBuildInfo(destination)
8944
+ };
8945
+ }
8913
8946
  async function installIntoHost(input, hostLabel) {
8914
8947
  await (0, import_promises5.mkdir)(input.extensionsDir, { recursive: true });
8915
8948
  const destination = (0, import_node_path6.join)(input.extensionsDir, PLUGIN_ID);
@@ -9027,13 +9060,14 @@ async function runInstallCommand(input) {
9027
9060
  throw new Error("expected subcommand: install");
9028
9061
  }
9029
9062
  const args = parseArgs(argv.slice(1));
9030
- const destination = await resolveInstallDestination(args, {
9063
+ const destinations = await resolveInstallDestinations(args, {
9031
9064
  hostDefinitions: input.hostDefinitions,
9065
+ selectHosts: input.selectHosts,
9032
9066
  selectHost: input.selectHost,
9033
9067
  ttyPath: input.ttyPath
9034
9068
  });
9035
- const result = await installIntoHost(
9036
- {
9069
+ for (const destination of destinations) {
9070
+ const installInput = {
9037
9071
  packageRoot: input.packageRoot,
9038
9072
  extensionsDir: destination.extensionsDir,
9039
9073
  configPath: destination.configPath,
@@ -9048,41 +9082,45 @@ async function runInstallCommand(input) {
9048
9082
  hubEnabled: parseOptionalBoolean(args["hub-enabled"]),
9049
9083
  consoleHost: args["console-host"],
9050
9084
  consolePort: args["console-port"] ? Number(args["console-port"]) : void 0
9051
- },
9052
- destination.label
9053
- );
9054
- process.stdout.write(
9055
- [
9056
- `Installed ${PLUGIN_ID} into ${destination.label}.`,
9057
- `Extensions: ${result.extensionsDir}`,
9058
- `Config: ${result.configPath}`,
9059
- `Gateway: ${result.gatewayBaseUrl}`,
9060
- `53AIHub: ${result.hub53aiConfigured ? "configured" : "not configured"}`,
9061
- `Plugin build: ${result.pluginBuild}`,
9062
- `Restart ${destination.label} to load the plugin.`
9063
- ].join("\n") + "\n"
9064
- );
9085
+ };
9086
+ const result = destination.installKind === "hermes" ? await installIntoHermes(installInput) : await installIntoHost(installInput, destination.label);
9087
+ process.stdout.write(
9088
+ [
9089
+ `Installed ${PLUGIN_ID} into ${destination.label}.`,
9090
+ `Extensions: ${result.extensionsDir}`,
9091
+ `Config: ${result.configPath}`,
9092
+ ...destination.installKind === "hermes" ? [] : [`Gateway: ${result.gatewayBaseUrl}`],
9093
+ `53AIHub: ${result.hub53aiConfigured ? "configured" : "not configured"}`,
9094
+ `Plugin build: ${result.pluginBuild}`,
9095
+ `Restart ${destination.label} to load the plugin.`
9096
+ ].join("\n") + "\n"
9097
+ );
9098
+ }
9065
9099
  }
9066
- async function resolveInstallDestination(args, options = {}) {
9100
+ async function resolveInstallDestinations(args, options = {}) {
9067
9101
  const explicitConfigPath = args["config-path"] ? (0, import_node_path6.resolve)(args["config-path"]) : void 0;
9068
9102
  const explicitExtensionsDir = args["extensions-dir"] ? (0, import_node_path6.resolve)(args["extensions-dir"]) : void 0;
9069
9103
  if (explicitConfigPath && explicitExtensionsDir) {
9070
- return {
9104
+ const hermes = isHermesDestination(explicitConfigPath, explicitExtensionsDir);
9105
+ return [{
9071
9106
  configPath: explicitConfigPath,
9072
- extensionsDir: explicitExtensionsDir,
9073
- label: "Claw"
9074
- };
9107
+ extensionsDir: hermes ? normalizeHermesPlatformsDir(explicitExtensionsDir) : explicitExtensionsDir,
9108
+ label: hermes ? "Hermes" : "Claw",
9109
+ installKind: hermes ? "hermes" : "openclaw"
9110
+ }];
9075
9111
  }
9076
9112
  if (explicitConfigPath || explicitExtensionsDir) {
9077
9113
  throw new Error("pass --config-path and --extensions-dir together, or omit both to auto-detect Claw");
9078
9114
  }
9079
9115
  const detected = detectInstallHosts(options.hostDefinitions ?? getDefaultHostDefinitions());
9080
- if (detected.length === 1) {
9081
- return toInstallDestination(detected[0]);
9116
+ const compatible = detected;
9117
+ const incompatible = [];
9118
+ if (compatible.length === 1) {
9119
+ return [toInstallDestination(compatible[0])];
9082
9120
  }
9083
- if (detected.length > 1) {
9084
- const selected = options.selectHost ? await options.selectHost(detected) : await promptForInstallHost(detected, options.ttyPath ?? "/dev/tty");
9085
- return toInstallDestination(selected);
9121
+ if (compatible.length > 1) {
9122
+ const selected = options.selectHosts ? await options.selectHosts(compatible, incompatible) : options.selectHost ? [await options.selectHost(compatible)] : await promptForInstallHosts(compatible, incompatible, options.ttyPath ?? "/dev/tty");
9123
+ return validateSelectedHosts(selected, compatible).map(toInstallDestination);
9086
9124
  }
9087
9125
  throw new Error(
9088
9126
  [
@@ -9098,6 +9136,7 @@ function getDefaultHostDefinitions() {
9098
9136
  const home = (0, import_node_os2.homedir)();
9099
9137
  const qclawHome = (0, import_node_path6.resolve)(home, ".qclaw");
9100
9138
  const openClawHome = (0, import_node_path6.resolve)(home, ".openclaw");
9139
+ const hermesHome = (0, import_node_path6.resolve)(home, ".hermes");
9101
9140
  return [
9102
9141
  {
9103
9142
  id: "qclaw",
@@ -9110,6 +9149,13 @@ function getDefaultHostDefinitions() {
9110
9149
  label: "OpenClaw",
9111
9150
  configPath: (0, import_node_path6.join)(openClawHome, "openclaw.json"),
9112
9151
  extensionsDir: (0, import_node_path6.resolve)(openClawHome, "extensions")
9152
+ },
9153
+ {
9154
+ id: "hermes",
9155
+ label: "Hermes",
9156
+ configPath: (0, import_node_path6.join)(hermesHome, "config.yaml"),
9157
+ extensionsDir: (0, import_node_path6.resolve)(hermesHome, "plugins", "platforms"),
9158
+ installKind: "hermes"
9113
9159
  }
9114
9160
  ];
9115
9161
  }
@@ -9124,7 +9170,7 @@ function detectInstallHosts(hosts) {
9124
9170
  return true;
9125
9171
  });
9126
9172
  }
9127
- async function promptForInstallHost(hosts, ttyPath) {
9173
+ async function promptForInstallHosts(hosts, incompatibleHosts, ttyPath) {
9128
9174
  let handle;
9129
9175
  try {
9130
9176
  handle = await (0, import_promises5.open)(ttyPath, "r+");
@@ -9133,7 +9179,7 @@ async function promptForInstallHost(hosts, ttyPath) {
9133
9179
  const readline = (0, import_promises4.createInterface)({ input, output });
9134
9180
  try {
9135
9181
  output.write("Multiple Claw installations were detected.\n");
9136
- output.write("Choose where to install claw-control-center:\n");
9182
+ output.write("Choose one or more locations to install claw-control-center:\n");
9137
9183
  hosts.forEach((host, index) => {
9138
9184
  output.write(`${index + 1}. ${host.label}
9139
9185
  `);
@@ -9142,12 +9188,19 @@ async function promptForInstallHost(hosts, ttyPath) {
9142
9188
  output.write(` Extensions: ${host.extensionsDir}
9143
9189
  `);
9144
9190
  });
9145
- const answer = await readline.question(`Install location [1-${hosts.length}]: `);
9146
- const selectedIndex = Number(answer.trim());
9147
- if (!Number.isInteger(selectedIndex) || selectedIndex < 1 || selectedIndex > hosts.length) {
9191
+ if (incompatibleHosts.length > 0) {
9192
+ output.write("\nDetected but not installable by this OpenClaw plugin installer:\n");
9193
+ for (const host of incompatibleHosts) {
9194
+ output.write(`- ${host.label}: ${host.incompatibilityReason ?? "incompatible plugin format"}
9195
+ `);
9196
+ }
9197
+ }
9198
+ const answer = await readline.question(`Install location(s) [1-${hosts.length}, comma-separated, or all]: `);
9199
+ const selectedIndexes = parseInstallSelection(answer, hosts.length);
9200
+ if (selectedIndexes.length === 0) {
9148
9201
  throw new Error(`invalid install location: ${answer}`);
9149
9202
  }
9150
- return hosts[selectedIndex - 1];
9203
+ return selectedIndexes.map((index) => hosts[index - 1]);
9151
9204
  } finally {
9152
9205
  readline.close();
9153
9206
  input.destroy();
@@ -9174,14 +9227,39 @@ function toInstallDestination(host) {
9174
9227
  return {
9175
9228
  configPath: (0, import_node_path6.resolve)(host.configPath),
9176
9229
  extensionsDir: (0, import_node_path6.resolve)(host.extensionsDir),
9177
- label: host.label
9230
+ label: host.label,
9231
+ installKind: host.installKind ?? "openclaw"
9178
9232
  };
9179
9233
  }
9234
+ function validateSelectedHosts(selected, compatible) {
9235
+ const compatibleIds = new Set(compatible.map((host) => host.id));
9236
+ const invalid = selected.find((host) => !compatibleIds.has(host.id));
9237
+ if (invalid) {
9238
+ throw new Error(`selected host is not installable: ${invalid.label}`);
9239
+ }
9240
+ const seen = /* @__PURE__ */ new Set();
9241
+ return selected.filter((host) => {
9242
+ if (seen.has(host.id)) {
9243
+ return false;
9244
+ }
9245
+ seen.add(host.id);
9246
+ return true;
9247
+ });
9248
+ }
9249
+ function parseInstallSelection(answer, hostCount) {
9250
+ const trimmed = answer.trim().toLowerCase();
9251
+ if (trimmed === "all" || trimmed === "*") {
9252
+ return Array.from({ length: hostCount }, (_, index) => index + 1);
9253
+ }
9254
+ const indexes = trimmed.split(",").map((part) => Number(part.trim())).filter((index) => Number.isInteger(index) && index >= 1 && index <= hostCount);
9255
+ return Array.from(new Set(indexes));
9256
+ }
9180
9257
  function formatHostList(hosts) {
9181
9258
  return hosts.flatMap((host) => [
9182
9259
  `- ${host.label}`,
9183
9260
  ` Config: ${host.configPath}`,
9184
- ` Extensions: ${host.extensionsDir}`
9261
+ ` Extensions: ${host.extensionsDir}`,
9262
+ ...host.incompatibilityReason ? [` Note: ${host.incompatibilityReason}`] : []
9185
9263
  ]);
9186
9264
  }
9187
9265
  function parseArgs(argv) {
@@ -9222,6 +9300,15 @@ async function copyPublishablePackage(packageRoot, destination) {
9222
9300
  await (0, import_promises5.cp)(source, target, { recursive: true, force: true });
9223
9301
  }
9224
9302
  }
9303
+ async function copyHermesPlatformPackage(packageRoot, destination) {
9304
+ const source = (0, import_node_path6.join)(packageRoot, "hermes", "platforms", HERMES_PLATFORM_ID);
9305
+ if (!(0, import_node_fs4.existsSync)(source)) {
9306
+ throw new Error(`Hermes platform package does not exist: ${source}`);
9307
+ }
9308
+ await (0, import_promises5.rm)(destination, { recursive: true, force: true });
9309
+ await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(destination), { recursive: true });
9310
+ await (0, import_promises5.cp)(source, destination, { recursive: true, force: true });
9311
+ }
9225
9312
  async function readPluginBuildInfo(destination) {
9226
9313
  const packagePath = (0, import_node_path6.join)(destination, "package.json");
9227
9314
  const entryPath = (0, import_node_path6.join)(destination, "dist", "index.cjs");
@@ -9242,12 +9329,86 @@ async function readPluginBuildInfo(destination) {
9242
9329
  return `${PLUGIN_ID}@${version} sha256:missing`;
9243
9330
  }
9244
9331
  }
9332
+ async function readHermesPluginBuildInfo(destination) {
9333
+ const manifestPath = (0, import_node_path6.join)(destination, "plugin.yaml");
9334
+ try {
9335
+ const manifest = (0, import_yaml.parse)(await (0, import_promises5.readFile)(manifestPath, "utf8"));
9336
+ const version = typeof manifest?.version === "string" || typeof manifest?.version === "number" ? String(manifest.version) : "unknown";
9337
+ const adapter = await (0, import_promises5.readFile)((0, import_node_path6.join)(destination, "adapter.py"));
9338
+ const digest = (0, import_node_crypto4.createHash)("sha256").update(adapter).digest("hex").slice(0, 12);
9339
+ return `${PLUGIN_ID}/hermes@${version} sha256:${digest}`;
9340
+ } catch {
9341
+ return `${PLUGIN_ID}/hermes@unknown sha256:missing`;
9342
+ }
9343
+ }
9245
9344
  async function readOpenClawConfig(configPath) {
9246
9345
  if (!(0, import_node_fs4.existsSync)(configPath)) {
9247
9346
  return {};
9248
9347
  }
9249
9348
  return JSON.parse(await (0, import_promises5.readFile)(configPath, "utf8"));
9250
9349
  }
9350
+ async function updateHermesConfig(configPath) {
9351
+ await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(configPath), { recursive: true });
9352
+ const config = await readHermesConfig(configPath);
9353
+ const plugins = ensureObject(config, "plugins");
9354
+ plugins.enabled = dedupeStrings([...Array.isArray(plugins.enabled) ? plugins.enabled : [], HERMES_PLUGIN_KEY, HERMES_PLATFORM_ID]);
9355
+ const platforms = ensureObject(config, "platforms");
9356
+ const platform = ensureObject(platforms, HERMES_PLATFORM_ID);
9357
+ platform.enabled = true;
9358
+ ensureObject(platform, "extra");
9359
+ await (0, import_promises5.writeFile)(configPath, (0, import_yaml.stringify)(config));
9360
+ }
9361
+ async function readHermesConfig(configPath) {
9362
+ if (!(0, import_node_fs4.existsSync)(configPath)) {
9363
+ return {};
9364
+ }
9365
+ const parsed = (0, import_yaml.parse)(await (0, import_promises5.readFile)(configPath, "utf8"));
9366
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
9367
+ }
9368
+ async function updateHermesEnv(configPath, values) {
9369
+ const envPath = (0, import_node_path6.join)((0, import_node_path6.dirname)(configPath), ".env");
9370
+ await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(envPath), { recursive: true });
9371
+ const existing = (0, import_node_fs4.existsSync)(envPath) ? await (0, import_promises5.readFile)(envPath, "utf8") : "";
9372
+ const updates = /* @__PURE__ */ new Map([
9373
+ [HERMES_ENV_KEYS.botId, values.botId],
9374
+ [HERMES_ENV_KEYS.secret, values.secret],
9375
+ [HERMES_ENV_KEYS.wsUrl, values.wsUrl]
9376
+ ]);
9377
+ const seen = /* @__PURE__ */ new Set();
9378
+ const lines = existing.split(/\r?\n/).map((line) => {
9379
+ const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=/);
9380
+ if (!match || !updates.has(match[1])) {
9381
+ return line;
9382
+ }
9383
+ const key = match[1];
9384
+ seen.add(key);
9385
+ return `${key}=${quoteEnvValue(updates.get(key))}`;
9386
+ });
9387
+ for (const [key, value] of updates) {
9388
+ if (!seen.has(key)) {
9389
+ lines.push(`${key}=${quoteEnvValue(value)}`);
9390
+ }
9391
+ }
9392
+ await (0, import_promises5.writeFile)(envPath, `${lines.filter((line, index, all) => line || index < all.length - 1).join("\n")}
9393
+ `);
9394
+ }
9395
+ function quoteEnvValue(value) {
9396
+ return JSON.stringify(value);
9397
+ }
9398
+ function isHermesDestination(configPath, extensionsDir) {
9399
+ const tail = extensionsDir.split(/[\\/]/).filter(Boolean).slice(-2).join("/");
9400
+ return configPath.endsWith("config.yaml") && (tail === "plugins/platforms" || tail.endsWith("/plugins"));
9401
+ }
9402
+ function normalizeHermesPlatformsDir(extensionsDir) {
9403
+ const parts = extensionsDir.split(/[\\/]/).filter(Boolean);
9404
+ if (parts.at(-1) === "platforms" && parts.at(-2) === "plugins") {
9405
+ return extensionsDir;
9406
+ }
9407
+ if (parts.at(-1) === "plugins") {
9408
+ return (0, import_node_path6.join)(extensionsDir, "platforms");
9409
+ }
9410
+ return extensionsDir;
9411
+ }
9251
9412
  function inferGatewaySettings(config) {
9252
9413
  const gateway = config.gateway ?? {};
9253
9414
  const host = typeof gateway.host === "string" && gateway.host.trim() ? gateway.host.trim() : "127.0.0.1";