claw-control-center 0.1.7 → 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
@@ -5449,10 +5449,10 @@ function resolvePluginConfig(config) {
5449
5449
  function resolvePluginConfigWithHostDefaults(configPath, config) {
5450
5450
  const resolved = resolvePluginConfig(config);
5451
5451
  const hostConfig = readHostGatewayConfig(resolveHostConfigPath(configPath));
5452
- if (!resolved.gateway.baseUrl && hostConfig.baseUrl) {
5452
+ if (hostConfig.baseUrl && (!resolved.gateway.baseUrl || shouldPreferHostGateway(resolved.gateway.baseUrl, hostConfig.baseUrl))) {
5453
5453
  resolved.gateway.baseUrl = hostConfig.baseUrl;
5454
5454
  }
5455
- if (!resolved.gateway.secret && hostConfig.secret) {
5455
+ if (hostConfig.secret && (!resolved.gateway.secret || resolved.gateway.baseUrl === hostConfig.baseUrl)) {
5456
5456
  resolved.gateway.secret = hostConfig.secret;
5457
5457
  }
5458
5458
  if (!resolved.hub53ai.botId && hostConfig.hub53ai?.botId) {
@@ -5466,6 +5466,28 @@ function resolvePluginConfigWithHostDefaults(configPath, config) {
5466
5466
  }
5467
5467
  return resolved;
5468
5468
  }
5469
+ function shouldPreferHostGateway(configuredBaseUrl, hostBaseUrl) {
5470
+ if (configuredBaseUrl === hostBaseUrl) {
5471
+ return false;
5472
+ }
5473
+ const configured = parseGatewayUrl(configuredBaseUrl);
5474
+ const host = parseGatewayUrl(hostBaseUrl);
5475
+ if (!configured || !host) {
5476
+ return false;
5477
+ }
5478
+ return configured.isLoopback && host.isLoopback;
5479
+ }
5480
+ function parseGatewayUrl(value) {
5481
+ try {
5482
+ const url = new URL(value);
5483
+ const hostname = url.hostname.replace(/^\[|\]$/g, "").toLowerCase();
5484
+ return {
5485
+ isLoopback: hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1"
5486
+ };
5487
+ } catch {
5488
+ return void 0;
5489
+ }
5490
+ }
5469
5491
  function sanitizePluginConfig(config) {
5470
5492
  if (!config) {
5471
5493
  return {};
@@ -8859,32 +8881,84 @@ function toRecord3(value) {
8859
8881
 
8860
8882
  // src/install-qclaw.ts
8861
8883
  var import_node_crypto4 = require("crypto");
8862
- var import_promises4 = require("fs/promises");
8884
+ var import_promises4 = require("readline/promises");
8885
+ var import_promises5 = require("fs/promises");
8863
8886
  var import_node_fs4 = require("fs");
8887
+ var import_node_os2 = require("os");
8864
8888
  var import_node_path6 = require("path");
8889
+ var import_yaml = require("yaml");
8865
8890
  var PLUGIN_ID = "claw-control-center";
8866
8891
  var LEGACY_PLUGIN_ID = "53ai-openclaw";
8867
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
+ };
8900
+ var SUPPORTED_ARGS = /* @__PURE__ */ new Set([
8901
+ "gateway",
8902
+ "bot-id",
8903
+ "secret",
8904
+ "prefer-responses-api",
8905
+ "gateway-model",
8906
+ "hub-ws-url",
8907
+ "hub-bot-id",
8908
+ "hub-secret",
8909
+ "hub-enabled",
8910
+ "extensions-dir",
8911
+ "config-path",
8912
+ "console-host",
8913
+ "console-port"
8914
+ ]);
8868
8915
  async function installIntoQClaw(input) {
8869
8916
  return installIntoHost(input, "QClaw");
8870
8917
  }
8871
8918
  async function installIntoOpenClaw(input) {
8872
8919
  return installIntoHost(input, "OpenClaw");
8873
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
+ }
8874
8946
  async function installIntoHost(input, hostLabel) {
8875
- await (0, import_promises4.mkdir)(input.extensionsDir, { recursive: true });
8947
+ await (0, import_promises5.mkdir)(input.extensionsDir, { recursive: true });
8876
8948
  const destination = (0, import_node_path6.join)(input.extensionsDir, PLUGIN_ID);
8877
- await (0, import_promises4.mkdir)(destination, { recursive: true });
8949
+ await (0, import_promises5.mkdir)(destination, { recursive: true });
8878
8950
  await copyPublishablePackage(input.packageRoot, destination);
8879
- await (0, import_promises4.mkdir)((0, import_node_path6.dirname)(input.configPath), { recursive: true });
8951
+ await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(input.configPath), { recursive: true });
8880
8952
  const config = await readOpenClawConfig(input.configPath);
8881
8953
  const inferredGateway = inferGatewaySettings(config);
8882
8954
  const inferredHub53AI = inferHub53AISettings(config);
8883
- const gatewayBaseUrl = input.gateway?.trim() || inferredGateway.baseUrl;
8955
+ const explicitGatewayBaseUrl = input.gateway?.trim();
8956
+ const explicitGatewaySecret = input.secret?.trim();
8957
+ const gatewayBaseUrl = explicitGatewayBaseUrl || inferredGateway.baseUrl;
8884
8958
  if (!gatewayBaseUrl) {
8885
8959
  throw new Error(`missing gateway URL and no local ${hostLabel} gateway could be inferred`);
8886
8960
  }
8887
- const secret = input.secret?.trim() || inferredGateway.secret;
8961
+ const secret = explicitGatewaySecret || inferredGateway.secret;
8888
8962
  if (!secret) {
8889
8963
  throw new Error(`missing gateway secret and no local ${hostLabel} gateway token could be inferred`);
8890
8964
  }
@@ -8919,8 +8993,16 @@ async function installIntoHost(input, hostLabel) {
8919
8993
  const previousEntry = ensureObject(entries, PLUGIN_ID);
8920
8994
  const previousConfig = ensureObject(previousEntry, "config");
8921
8995
  const previousGateway = ensureObject(previousConfig, "gateway");
8922
- previousGateway.baseUrl = gatewayBaseUrl;
8923
- previousGateway.secret = secret;
8996
+ if (explicitGatewayBaseUrl) {
8997
+ previousGateway.baseUrl = explicitGatewayBaseUrl;
8998
+ } else {
8999
+ delete previousGateway.baseUrl;
9000
+ }
9001
+ if (explicitGatewaySecret) {
9002
+ previousGateway.secret = explicitGatewaySecret;
9003
+ } else {
9004
+ delete previousGateway.secret;
9005
+ }
8924
9006
  previousGateway.preferResponsesApi = preferResponsesApi;
8925
9007
  if (botId) {
8926
9008
  previousGateway.botId = botId;
@@ -8961,7 +9043,7 @@ async function installIntoHost(input, hostLabel) {
8961
9043
  }
8962
9044
  }
8963
9045
  previousEntry.enabled = true;
8964
- await (0, import_promises4.writeFile)(input.configPath, `${JSON.stringify(config, null, 2)}
9046
+ await (0, import_promises5.writeFile)(input.configPath, `${JSON.stringify(config, null, 2)}
8965
9047
  `);
8966
9048
  return {
8967
9049
  configPath: input.configPath,
@@ -8978,9 +9060,14 @@ async function runInstallCommand(input) {
8978
9060
  throw new Error("expected subcommand: install");
8979
9061
  }
8980
9062
  const args = parseArgs(argv.slice(1));
8981
- const destination = resolveInstallDestination(args);
8982
- const result = await installIntoHost(
8983
- {
9063
+ const destinations = await resolveInstallDestinations(args, {
9064
+ hostDefinitions: input.hostDefinitions,
9065
+ selectHosts: input.selectHosts,
9066
+ selectHost: input.selectHost,
9067
+ ttyPath: input.ttyPath
9068
+ });
9069
+ for (const destination of destinations) {
9070
+ const installInput = {
8984
9071
  packageRoot: input.packageRoot,
8985
9072
  extensionsDir: destination.extensionsDir,
8986
9073
  configPath: destination.configPath,
@@ -8995,35 +9082,186 @@ async function runInstallCommand(input) {
8995
9082
  hubEnabled: parseOptionalBoolean(args["hub-enabled"]),
8996
9083
  consoleHost: args["console-host"],
8997
9084
  consolePort: args["console-port"] ? Number(args["console-port"]) : void 0
8998
- },
8999
- "Claw"
9000
- );
9001
- process.stdout.write(
9002
- [
9003
- `Installed ${PLUGIN_ID} into Claw.`,
9004
- `Extensions: ${result.extensionsDir}`,
9005
- `Config: ${result.configPath}`,
9006
- `Gateway: ${result.gatewayBaseUrl}`,
9007
- `53AIHub: ${result.hub53aiConfigured ? "configured" : "not configured"}`,
9008
- `Plugin build: ${result.pluginBuild}`,
9009
- "Restart your Claw host to load the plugin."
9010
- ].join("\n") + "\n"
9011
- );
9012
- }
9013
- function resolveInstallDestination(args) {
9014
- if (args.target !== void 0) {
9015
- throw new Error("--target is no longer supported; pass --config-path and --extensions-dir from your Claw host");
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
+ );
9016
9098
  }
9099
+ }
9100
+ async function resolveInstallDestinations(args, options = {}) {
9017
9101
  const explicitConfigPath = args["config-path"] ? (0, import_node_path6.resolve)(args["config-path"]) : void 0;
9018
9102
  const explicitExtensionsDir = args["extensions-dir"] ? (0, import_node_path6.resolve)(args["extensions-dir"]) : void 0;
9019
- if (!explicitConfigPath || !explicitExtensionsDir) {
9020
- throw new Error("missing --config-path and --extensions-dir; provide the paths generated by your Claw host");
9103
+ if (explicitConfigPath && explicitExtensionsDir) {
9104
+ const hermes = isHermesDestination(explicitConfigPath, explicitExtensionsDir);
9105
+ return [{
9106
+ configPath: explicitConfigPath,
9107
+ extensionsDir: hermes ? normalizeHermesPlatformsDir(explicitExtensionsDir) : explicitExtensionsDir,
9108
+ label: hermes ? "Hermes" : "Claw",
9109
+ installKind: hermes ? "hermes" : "openclaw"
9110
+ }];
9111
+ }
9112
+ if (explicitConfigPath || explicitExtensionsDir) {
9113
+ throw new Error("pass --config-path and --extensions-dir together, or omit both to auto-detect Claw");
9114
+ }
9115
+ const detected = detectInstallHosts(options.hostDefinitions ?? getDefaultHostDefinitions());
9116
+ const compatible = detected;
9117
+ const incompatible = [];
9118
+ if (compatible.length === 1) {
9119
+ return [toInstallDestination(compatible[0])];
9120
+ }
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);
9124
+ }
9125
+ throw new Error(
9126
+ [
9127
+ "could not auto-detect an installed Claw host.",
9128
+ "Pass --config-path and --extensions-dir to install into a specific Claw.",
9129
+ "",
9130
+ "Supported default locations:",
9131
+ ...formatHostList(options.hostDefinitions ?? getDefaultHostDefinitions())
9132
+ ].join("\n")
9133
+ );
9134
+ }
9135
+ function getDefaultHostDefinitions() {
9136
+ const home = (0, import_node_os2.homedir)();
9137
+ const qclawHome = (0, import_node_path6.resolve)(home, ".qclaw");
9138
+ const openClawHome = (0, import_node_path6.resolve)(home, ".openclaw");
9139
+ const hermesHome = (0, import_node_path6.resolve)(home, ".hermes");
9140
+ return [
9141
+ {
9142
+ id: "qclaw",
9143
+ label: "QClaw",
9144
+ configPath: (0, import_node_path6.join)(qclawHome, "openclaw.json"),
9145
+ extensionsDir: (0, import_node_path6.resolve)(home, "Library/Application Support/QClaw/openclaw/config/extensions")
9146
+ },
9147
+ {
9148
+ id: "openclaw",
9149
+ label: "OpenClaw",
9150
+ configPath: (0, import_node_path6.join)(openClawHome, "openclaw.json"),
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"
9159
+ }
9160
+ ];
9161
+ }
9162
+ function detectInstallHosts(hosts) {
9163
+ const seen = /* @__PURE__ */ new Set();
9164
+ return hosts.filter((host) => {
9165
+ const key = `${(0, import_node_path6.resolve)(host.configPath)}\0${(0, import_node_path6.resolve)(host.extensionsDir)}`;
9166
+ if (seen.has(key) || !(0, import_node_fs4.existsSync)(host.configPath)) {
9167
+ return false;
9168
+ }
9169
+ seen.add(key);
9170
+ return true;
9171
+ });
9172
+ }
9173
+ async function promptForInstallHosts(hosts, incompatibleHosts, ttyPath) {
9174
+ let handle;
9175
+ try {
9176
+ handle = await (0, import_promises5.open)(ttyPath, "r+");
9177
+ const input = handle.createReadStream();
9178
+ const output = handle.createWriteStream();
9179
+ const readline = (0, import_promises4.createInterface)({ input, output });
9180
+ try {
9181
+ output.write("Multiple Claw installations were detected.\n");
9182
+ output.write("Choose one or more locations to install claw-control-center:\n");
9183
+ hosts.forEach((host, index) => {
9184
+ output.write(`${index + 1}. ${host.label}
9185
+ `);
9186
+ output.write(` Config: ${host.configPath}
9187
+ `);
9188
+ output.write(` Extensions: ${host.extensionsDir}
9189
+ `);
9190
+ });
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) {
9201
+ throw new Error(`invalid install location: ${answer}`);
9202
+ }
9203
+ return selectedIndexes.map((index) => hosts[index - 1]);
9204
+ } finally {
9205
+ readline.close();
9206
+ input.destroy();
9207
+ output.end();
9208
+ }
9209
+ } catch (error) {
9210
+ if (error instanceof Error && error.message.startsWith("invalid install location:")) {
9211
+ throw error;
9212
+ }
9213
+ throw new Error(
9214
+ [
9215
+ "multiple Claw installations were detected, but no interactive terminal was available.",
9216
+ "Run the installer again with --config-path and --extensions-dir.",
9217
+ "",
9218
+ "Detected locations:",
9219
+ ...formatHostList(hosts)
9220
+ ].join("\n")
9221
+ );
9222
+ } finally {
9223
+ await handle?.close().catch(() => void 0);
9021
9224
  }
9225
+ }
9226
+ function toInstallDestination(host) {
9022
9227
  return {
9023
- configPath: explicitConfigPath,
9024
- extensionsDir: explicitExtensionsDir
9228
+ configPath: (0, import_node_path6.resolve)(host.configPath),
9229
+ extensionsDir: (0, import_node_path6.resolve)(host.extensionsDir),
9230
+ label: host.label,
9231
+ installKind: host.installKind ?? "openclaw"
9025
9232
  };
9026
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
+ }
9257
+ function formatHostList(hosts) {
9258
+ return hosts.flatMap((host) => [
9259
+ `- ${host.label}`,
9260
+ ` Config: ${host.configPath}`,
9261
+ ` Extensions: ${host.extensionsDir}`,
9262
+ ...host.incompatibilityReason ? [` Note: ${host.incompatibilityReason}`] : []
9263
+ ]);
9264
+ }
9027
9265
  function parseArgs(argv) {
9028
9266
  const parsed = {};
9029
9267
  for (let index = 0; index < argv.length; index += 1) {
@@ -9031,12 +9269,19 @@ function parseArgs(argv) {
9031
9269
  if (!entry.startsWith("--")) {
9032
9270
  continue;
9033
9271
  }
9272
+ const key = entry.slice(2);
9273
+ if (key === "target") {
9274
+ throw new Error("--target has been removed; omit it to auto-detect Claw or pass --config-path and --extensions-dir");
9275
+ }
9276
+ if (!SUPPORTED_ARGS.has(key)) {
9277
+ throw new Error(`unknown option: --${key}`);
9278
+ }
9034
9279
  const next = argv[index + 1];
9035
9280
  if (next && !next.startsWith("--")) {
9036
- parsed[entry.slice(2)] = next;
9281
+ parsed[key] = next;
9037
9282
  index += 1;
9038
9283
  } else {
9039
- parsed[entry.slice(2)] = "true";
9284
+ parsed[key] = "true";
9040
9285
  }
9041
9286
  }
9042
9287
  return parsed;
@@ -9051,16 +9296,25 @@ async function copyPublishablePackage(packageRoot, destination) {
9051
9296
  continue;
9052
9297
  }
9053
9298
  const target = (0, import_node_path6.join)(destination, relativePath);
9054
- await (0, import_promises4.rm)(target, { recursive: true, force: true });
9055
- await (0, import_promises4.cp)(source, target, { recursive: true, force: true });
9299
+ await (0, import_promises5.rm)(target, { recursive: true, force: true });
9300
+ await (0, import_promises5.cp)(source, target, { recursive: true, force: true });
9056
9301
  }
9057
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
+ }
9058
9312
  async function readPluginBuildInfo(destination) {
9059
9313
  const packagePath = (0, import_node_path6.join)(destination, "package.json");
9060
9314
  const entryPath = (0, import_node_path6.join)(destination, "dist", "index.cjs");
9061
9315
  let version = "unknown";
9062
9316
  try {
9063
- const packageJson = JSON.parse(await (0, import_promises4.readFile)(packagePath, "utf8"));
9317
+ const packageJson = JSON.parse(await (0, import_promises5.readFile)(packagePath, "utf8"));
9064
9318
  if (typeof packageJson.version === "string" && packageJson.version.trim()) {
9065
9319
  version = packageJson.version.trim();
9066
9320
  }
@@ -9068,18 +9322,92 @@ async function readPluginBuildInfo(destination) {
9068
9322
  version = "unknown";
9069
9323
  }
9070
9324
  try {
9071
- const entry = await (0, import_promises4.readFile)(entryPath);
9325
+ const entry = await (0, import_promises5.readFile)(entryPath);
9072
9326
  const digest = (0, import_node_crypto4.createHash)("sha256").update(entry).digest("hex").slice(0, 12);
9073
9327
  return `${PLUGIN_ID}@${version} sha256:${digest}`;
9074
9328
  } catch {
9075
9329
  return `${PLUGIN_ID}@${version} sha256:missing`;
9076
9330
  }
9077
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
+ }
9078
9344
  async function readOpenClawConfig(configPath) {
9079
9345
  if (!(0, import_node_fs4.existsSync)(configPath)) {
9080
9346
  return {};
9081
9347
  }
9082
- return JSON.parse(await (0, import_promises4.readFile)(configPath, "utf8"));
9348
+ return JSON.parse(await (0, import_promises5.readFile)(configPath, "utf8"));
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;
9083
9411
  }
9084
9412
  function inferGatewaySettings(config) {
9085
9413
  const gateway = config.gateway ?? {};