clawdentity 0.0.11 → 0.0.13

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/bin.js CHANGED
@@ -14355,6 +14355,8 @@ var AGENT_AUTH_REFRESH_PATH = "/v1/agents/auth/refresh";
14355
14355
  var INVITES_PATH = "/v1/invites";
14356
14356
  var INVITES_REDEEM_PATH = "/v1/invites/redeem";
14357
14357
  var ME_API_KEYS_PATH = "/v1/me/api-keys";
14358
+ var REGISTRY_METADATA_PATH = "/v1/metadata";
14359
+ var RELAY_CONNECT_PATH = "/v1/relay/connect";
14358
14360
  var RELAY_RECIPIENT_AGENT_DID_HEADER = "x-claw-recipient-agent-did";
14359
14361
 
14360
14362
  // ../../packages/protocol/src/http-signing.ts
@@ -17062,8 +17064,8 @@ function createLogger(baseFields = {}) {
17062
17064
  var DEFAULT_NONCE_TTL_MS = 5 * 60 * 1e3;
17063
17065
 
17064
17066
  // src/index.ts
17065
- import { createRequire } from "module";
17066
- import { Command as Command10 } from "commander";
17067
+ import { createRequire as createRequire2 } from "module";
17068
+ import { Command as Command11 } from "commander";
17067
17069
 
17068
17070
  // src/commands/admin.ts
17069
17071
  import { Command } from "commander";
@@ -18395,6 +18397,139 @@ var createApiKeyCommand = (dependencies = {}) => {
18395
18397
  // src/commands/config.ts
18396
18398
  import { access as access2 } from "fs/promises";
18397
18399
  import { Command as Command4 } from "commander";
18400
+
18401
+ // src/config/registry-metadata.ts
18402
+ function isRecord4(value) {
18403
+ return typeof value === "object" && value !== null;
18404
+ }
18405
+ function parseNonEmptyString5(value) {
18406
+ if (typeof value !== "string") {
18407
+ return "";
18408
+ }
18409
+ return value.trim();
18410
+ }
18411
+ function createCliError3(code, message2) {
18412
+ return new AppError({
18413
+ code,
18414
+ message: message2,
18415
+ status: 400
18416
+ });
18417
+ }
18418
+ function parseUrl(candidate, label) {
18419
+ let parsed;
18420
+ try {
18421
+ parsed = new URL(candidate);
18422
+ } catch {
18423
+ throw createCliError3("CLI_REGISTRY_URL_INVALID", `${label} is invalid`);
18424
+ }
18425
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
18426
+ throw createCliError3("CLI_REGISTRY_URL_INVALID", `${label} is invalid`);
18427
+ }
18428
+ return parsed;
18429
+ }
18430
+ function normalizeRegistryUrl(registryUrl) {
18431
+ return parseUrl(registryUrl, "Registry URL").toString();
18432
+ }
18433
+ function toRegistryRequestUrl(registryUrl, path) {
18434
+ const normalizedRegistryUrl = normalizeRegistryUrl(registryUrl);
18435
+ const base = normalizedRegistryUrl.endsWith("/") ? normalizedRegistryUrl : `${normalizedRegistryUrl}/`;
18436
+ return new URL(path.slice(1), base).toString();
18437
+ }
18438
+ function extractRegistryErrorMessage3(payload) {
18439
+ if (!isRecord4(payload)) {
18440
+ return void 0;
18441
+ }
18442
+ const envelope = payload;
18443
+ if (!envelope.error || typeof envelope.error.message !== "string") {
18444
+ return void 0;
18445
+ }
18446
+ const trimmed = envelope.error.message.trim();
18447
+ return trimmed.length > 0 ? trimmed : void 0;
18448
+ }
18449
+ async function parseJsonResponse4(response) {
18450
+ try {
18451
+ return await response.json();
18452
+ } catch {
18453
+ return void 0;
18454
+ }
18455
+ }
18456
+ function parseMetadataPayload(payload, fallbackRegistryUrl) {
18457
+ if (!isRecord4(payload)) {
18458
+ throw createCliError3(
18459
+ "CLI_REGISTRY_METADATA_INVALID_RESPONSE",
18460
+ "Registry metadata response is invalid"
18461
+ );
18462
+ }
18463
+ const proxyUrlRaw = parseNonEmptyString5(payload.proxyUrl);
18464
+ if (proxyUrlRaw.length === 0) {
18465
+ throw createCliError3(
18466
+ "CLI_REGISTRY_METADATA_INVALID_RESPONSE",
18467
+ "Registry metadata response is invalid"
18468
+ );
18469
+ }
18470
+ const proxyUrl = parseUrl(proxyUrlRaw, "Proxy URL").toString();
18471
+ const registryUrlRaw = parseNonEmptyString5(payload.registryUrl);
18472
+ const registryUrl = registryUrlRaw.length > 0 ? parseUrl(registryUrlRaw, "Registry URL").toString() : normalizeRegistryUrl(fallbackRegistryUrl);
18473
+ const environment = parseNonEmptyString5(payload.environment);
18474
+ const version2 = parseNonEmptyString5(payload.version);
18475
+ return {
18476
+ proxyUrl,
18477
+ registryUrl,
18478
+ environment: environment.length > 0 ? environment : void 0,
18479
+ version: version2.length > 0 ? version2 : void 0
18480
+ };
18481
+ }
18482
+ function mapMetadataError(status, payload) {
18483
+ const registryMessage = extractRegistryErrorMessage3(payload);
18484
+ if (status === 404) {
18485
+ return "Registry metadata endpoint is unavailable (404).";
18486
+ }
18487
+ if (status >= 500) {
18488
+ return `Registry metadata request failed (${status}). Try again later.`;
18489
+ }
18490
+ if (registryMessage) {
18491
+ return `Registry metadata request failed (${status}): ${registryMessage}`;
18492
+ }
18493
+ return `Registry metadata request failed (${status}).`;
18494
+ }
18495
+ async function fetchRegistryMetadata(registryUrl, dependencies = {}) {
18496
+ const fetchImpl = dependencies.fetchImpl ?? globalThis.fetch;
18497
+ if (typeof fetchImpl !== "function") {
18498
+ throw createCliError3(
18499
+ "CLI_REGISTRY_METADATA_FETCH_UNAVAILABLE",
18500
+ "Runtime fetch is unavailable for registry metadata lookup"
18501
+ );
18502
+ }
18503
+ const normalizedRegistryUrl = normalizeRegistryUrl(registryUrl);
18504
+ const requestUrl = toRegistryRequestUrl(
18505
+ normalizedRegistryUrl,
18506
+ REGISTRY_METADATA_PATH
18507
+ );
18508
+ let response;
18509
+ try {
18510
+ response = await fetchImpl(requestUrl, {
18511
+ method: "GET",
18512
+ headers: {
18513
+ accept: "application/json"
18514
+ }
18515
+ });
18516
+ } catch {
18517
+ throw createCliError3(
18518
+ "CLI_REGISTRY_METADATA_REQUEST_FAILED",
18519
+ "Unable to reach registry metadata endpoint. Check registryUrl and network access."
18520
+ );
18521
+ }
18522
+ const payload = await parseJsonResponse4(response);
18523
+ if (!response.ok) {
18524
+ throw createCliError3(
18525
+ "CLI_REGISTRY_METADATA_FETCH_FAILED",
18526
+ mapMetadataError(response.status, payload)
18527
+ );
18528
+ }
18529
+ return parseMetadataPayload(payload, normalizedRegistryUrl);
18530
+ }
18531
+
18532
+ // src/commands/config.ts
18398
18533
  var logger5 = createLogger({ service: "cli", module: "config" });
18399
18534
  var VALID_KEYS = [
18400
18535
  "registryUrl",
@@ -18437,7 +18572,7 @@ var getEnvRegistryUrlOverride = () => {
18437
18572
  return typeof value === "string" && value.length > 0;
18438
18573
  });
18439
18574
  };
18440
- var createConfigCommand = () => {
18575
+ var createConfigCommand = (dependencies = {}) => {
18441
18576
  const configCommand = new Command4("config").description(
18442
18577
  "Manage local CLI configuration"
18443
18578
  );
@@ -18454,14 +18589,27 @@ var createConfigCommand = () => {
18454
18589
  }
18455
18590
  }
18456
18591
  const config2 = await readConfig();
18457
- const registryUrl = options.registryUrl ?? getEnvRegistryUrlOverride() ?? config2.registryUrl;
18592
+ const requestedRegistryUrl = options.registryUrl ?? getEnvRegistryUrlOverride() ?? config2.registryUrl;
18593
+ const normalizedRegistryUrl = normalizeRegistryUrl(requestedRegistryUrl);
18594
+ const metadata = await fetchRegistryMetadata(normalizedRegistryUrl, {
18595
+ fetchImpl: dependencies.fetchImpl
18596
+ });
18458
18597
  await writeConfig({
18459
18598
  ...config2,
18460
- registryUrl
18599
+ registryUrl: metadata.registryUrl,
18600
+ proxyUrl: metadata.proxyUrl
18461
18601
  });
18462
18602
  writeStdoutLine(`Initialized config at ${configFilePath}`);
18463
18603
  writeStdoutLine(
18464
- JSON.stringify(maskApiKey({ ...config2, registryUrl }), null, 2)
18604
+ JSON.stringify(
18605
+ maskApiKey({
18606
+ ...config2,
18607
+ registryUrl: metadata.registryUrl,
18608
+ proxyUrl: metadata.proxyUrl
18609
+ }),
18610
+ null,
18611
+ 2
18612
+ )
18465
18613
  );
18466
18614
  })
18467
18615
  );
@@ -18768,6 +18916,7 @@ function normalizeConnectionHeaders(headers) {
18768
18916
  var ConnectorClient = class {
18769
18917
  connectorUrl;
18770
18918
  connectionHeaders;
18919
+ connectionHeadersProvider;
18771
18920
  openclawHookUrl;
18772
18921
  openclawHookToken;
18773
18922
  heartbeatIntervalMs;
@@ -18799,6 +18948,7 @@ var ConnectorClient = class {
18799
18948
  this.connectionHeaders = normalizeConnectionHeaders(
18800
18949
  options.connectionHeaders
18801
18950
  );
18951
+ this.connectionHeadersProvider = options.connectionHeadersProvider;
18802
18952
  this.openclawHookToken = options.openclawHookToken;
18803
18953
  this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
18804
18954
  this.reconnectMinDelayMs = options.reconnectMinDelayMs ?? DEFAULT_RECONNECT_MIN_DELAY_MS;
@@ -18851,7 +19001,7 @@ var ConnectorClient = class {
18851
19001
  return;
18852
19002
  }
18853
19003
  this.started = true;
18854
- this.connectSocket();
19004
+ void this.connectSocket();
18855
19005
  }
18856
19006
  disconnect() {
18857
19007
  this.started = false;
@@ -18884,13 +19034,27 @@ var ConnectorClient = class {
18884
19034
  this.flushOutboundQueue();
18885
19035
  return frame;
18886
19036
  }
18887
- connectSocket() {
19037
+ async connectSocket() {
18888
19038
  this.clearReconnectTimeout();
19039
+ let connectionHeaders = this.connectionHeaders;
19040
+ if (this.connectionHeadersProvider) {
19041
+ try {
19042
+ connectionHeaders = normalizeConnectionHeaders(
19043
+ await this.connectionHeadersProvider()
19044
+ );
19045
+ } catch (error48) {
19046
+ this.logger.warn("connector.websocket.create_failed", {
19047
+ reason: sanitizeErrorReason(error48)
19048
+ });
19049
+ this.scheduleReconnect();
19050
+ return;
19051
+ }
19052
+ }
19053
+ if (!this.started) {
19054
+ return;
19055
+ }
18889
19056
  try {
18890
- this.socket = this.webSocketFactory(
18891
- this.connectorUrl,
18892
- this.connectionHeaders
18893
- );
19057
+ this.socket = this.webSocketFactory(this.connectorUrl, connectionHeaders);
18894
19058
  } catch (error48) {
18895
19059
  this.logger.warn("connector.websocket.create_failed", {
18896
19060
  reason: sanitizeErrorReason(error48)
@@ -18945,7 +19109,7 @@ var ConnectorClient = class {
18945
19109
  const delayMs = Math.max(0, Math.floor(boundedDelay + jitterOffset));
18946
19110
  this.reconnectAttempt += 1;
18947
19111
  this.reconnectTimeout = setTimeout(() => {
18948
- this.connectSocket();
19112
+ void this.connectSocket();
18949
19113
  }, delayMs);
18950
19114
  }
18951
19115
  clearReconnectTimeout() {
@@ -19165,7 +19329,7 @@ var REFRESH_SINGLE_FLIGHT_PREFIX = "connector-runtime";
19165
19329
  var NONCE_SIZE = 16;
19166
19330
  var MAX_OUTBOUND_BODY_BYTES = 1024 * 1024;
19167
19331
  var ACCESS_TOKEN_REFRESH_SKEW_MS = 3e4;
19168
- function isRecord4(value) {
19332
+ function isRecord5(value) {
19169
19333
  return typeof value === "object" && value !== null;
19170
19334
  }
19171
19335
  function toPathWithQuery2(url2) {
@@ -19213,6 +19377,9 @@ function normalizeWebSocketUrl(urlInput) {
19213
19377
  if (parsed.protocol !== "wss:" && parsed.protocol !== "ws:") {
19214
19378
  throw new Error("Proxy websocket URL must use ws:// or wss://");
19215
19379
  }
19380
+ if (parsed.pathname === "/") {
19381
+ parsed.pathname = RELAY_CONNECT_PATH;
19382
+ }
19216
19383
  return parsed.toString();
19217
19384
  }
19218
19385
  function resolveOpenclawBaseUrl(input) {
@@ -19257,7 +19424,7 @@ function shouldRefreshAccessToken(auth, nowMs) {
19257
19424
  return expiresAtMs <= nowMs + ACCESS_TOKEN_REFRESH_SKEW_MS;
19258
19425
  }
19259
19426
  function parseOutboundRelayRequest(payload) {
19260
- if (!isRecord4(payload)) {
19427
+ if (!isRecord5(payload)) {
19261
19428
  throw new AppError({
19262
19429
  code: "CONNECTOR_OUTBOUND_INVALID_REQUEST",
19263
19430
  message: "Outbound relay request must be an object",
@@ -19390,7 +19557,10 @@ async function startConnectorRuntime(input) {
19390
19557
  parseRequiredString(input.credentials.secretKey, "secretKey")
19391
19558
  );
19392
19559
  let currentAuth = toInitialAuthBundle(input.credentials);
19393
- if (shouldRefreshAccessToken(currentAuth, Date.now())) {
19560
+ const refreshCurrentAuthIfNeeded = async () => {
19561
+ if (!shouldRefreshAccessToken(currentAuth, Date.now())) {
19562
+ return;
19563
+ }
19394
19564
  currentAuth = await refreshAgentAuthWithClawProof({
19395
19565
  registryUrl: input.registryUrl,
19396
19566
  ait: input.credentials.ait,
@@ -19403,18 +19573,22 @@ async function startConnectorRuntime(input) {
19403
19573
  agentName: input.agentName,
19404
19574
  auth: currentAuth
19405
19575
  });
19406
- }
19576
+ };
19577
+ await refreshCurrentAuthIfNeeded();
19407
19578
  const wsUrl = normalizeWebSocketUrl(input.proxyWebsocketUrl);
19408
19579
  const wsParsed = new URL(wsUrl);
19409
- const upgradeHeaders = await buildUpgradeHeaders({
19410
- wsUrl: wsParsed,
19411
- ait: input.credentials.ait,
19412
- accessToken: currentAuth.accessToken,
19413
- secretKey
19414
- });
19580
+ const resolveUpgradeHeaders = async () => {
19581
+ await refreshCurrentAuthIfNeeded();
19582
+ return buildUpgradeHeaders({
19583
+ wsUrl: wsParsed,
19584
+ ait: input.credentials.ait,
19585
+ accessToken: currentAuth.accessToken,
19586
+ secretKey
19587
+ });
19588
+ };
19415
19589
  const connectorClient = new ConnectorClient({
19416
19590
  connectorUrl: wsParsed.toString(),
19417
- connectionHeaders: upgradeHeaders,
19591
+ connectionHeadersProvider: resolveUpgradeHeaders,
19418
19592
  openclawBaseUrl: resolveOpenclawBaseUrl(input.openclawBaseUrl),
19419
19593
  openclawHookPath: resolveOpenclawHookPath(input.openclawHookPath),
19420
19594
  openclawHookToken: resolveOpenclawHookToken(input.openclawHookToken),
@@ -19588,16 +19762,16 @@ var OPENCLAW_CONNECTORS_FILE_NAME = "openclaw-connectors.json";
19588
19762
  var SERVICE_LOG_DIR_NAME = "logs";
19589
19763
  var DEFAULT_CONNECTOR_BASE_URL2 = "http://127.0.0.1:19400";
19590
19764
  var DEFAULT_CONNECTOR_OUTBOUND_PATH2 = "/v1/outbound";
19591
- function isRecord5(value) {
19765
+ function isRecord6(value) {
19592
19766
  return typeof value === "object" && value !== null;
19593
19767
  }
19594
19768
  function getErrorCode(error48) {
19595
- if (!isRecord5(error48)) {
19769
+ if (!isRecord6(error48)) {
19596
19770
  return void 0;
19597
19771
  }
19598
19772
  return typeof error48.code === "string" ? error48.code : void 0;
19599
19773
  }
19600
- function createCliError3(code, message2, details) {
19774
+ function createCliError4(code, message2, details) {
19601
19775
  return new AppError({
19602
19776
  code,
19603
19777
  message: message2,
@@ -19605,9 +19779,9 @@ function createCliError3(code, message2, details) {
19605
19779
  details
19606
19780
  });
19607
19781
  }
19608
- function parseNonEmptyString5(value, label) {
19782
+ function parseNonEmptyString6(value, label) {
19609
19783
  if (typeof value !== "string") {
19610
- throw createCliError3(
19784
+ throw createCliError4(
19611
19785
  "CLI_CONNECTOR_INVALID_INPUT",
19612
19786
  "Connector input is invalid",
19613
19787
  {
@@ -19617,7 +19791,7 @@ function parseNonEmptyString5(value, label) {
19617
19791
  }
19618
19792
  const trimmed = value.trim();
19619
19793
  if (trimmed.length === 0) {
19620
- throw createCliError3(
19794
+ throw createCliError4(
19621
19795
  "CLI_CONNECTOR_INVALID_INPUT",
19622
19796
  "Connector input is invalid",
19623
19797
  {
@@ -19628,9 +19802,9 @@ function parseNonEmptyString5(value, label) {
19628
19802
  return trimmed;
19629
19803
  }
19630
19804
  function parseAgentDid(value) {
19631
- const did = parseNonEmptyString5(value, "agent did");
19805
+ const did = parseNonEmptyString6(value, "agent did");
19632
19806
  if (!did.startsWith("did:claw:agent:")) {
19633
- throw createCliError3(
19807
+ throw createCliError4(
19634
19808
  "CLI_CONNECTOR_INVALID_AGENT_IDENTITY",
19635
19809
  "Agent identity is invalid for connector startup"
19636
19810
  );
@@ -19642,13 +19816,13 @@ function parseConnectorBaseUrl(value) {
19642
19816
  try {
19643
19817
  parsed = new URL(value);
19644
19818
  } catch {
19645
- throw createCliError3(
19819
+ throw createCliError4(
19646
19820
  "CLI_CONNECTOR_INVALID_BASE_URL",
19647
19821
  "Connector base URL is invalid"
19648
19822
  );
19649
19823
  }
19650
19824
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
19651
- throw createCliError3(
19825
+ throw createCliError4(
19652
19826
  "CLI_CONNECTOR_INVALID_BASE_URL",
19653
19827
  "Connector base URL is invalid"
19654
19828
  );
@@ -19658,10 +19832,65 @@ function parseConnectorBaseUrl(value) {
19658
19832
  }
19659
19833
  return parsed.toString();
19660
19834
  }
19835
+ function parseProxyWebsocketUrl(value) {
19836
+ let parsed;
19837
+ try {
19838
+ parsed = new URL(value);
19839
+ } catch {
19840
+ throw createCliError4(
19841
+ "CLI_CONNECTOR_INVALID_PROXY_URL",
19842
+ "Proxy websocket URL is invalid"
19843
+ );
19844
+ }
19845
+ if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:" && parsed.protocol !== "http:" && parsed.protocol !== "https:") {
19846
+ throw createCliError4(
19847
+ "CLI_CONNECTOR_INVALID_PROXY_URL",
19848
+ "Proxy websocket URL is invalid"
19849
+ );
19850
+ }
19851
+ return parsed.toString();
19852
+ }
19853
+ function resolveProxyWebsocketUrlFromEnv() {
19854
+ const explicitProxyWsUrl = process.env.CLAWDENTITY_PROXY_WS_URL;
19855
+ if (typeof explicitProxyWsUrl === "string" && explicitProxyWsUrl.trim().length > 0) {
19856
+ return parseProxyWebsocketUrl(explicitProxyWsUrl.trim());
19857
+ }
19858
+ const proxyUrl = process.env.CLAWDENTITY_PROXY_URL;
19859
+ if (typeof proxyUrl === "string" && proxyUrl.trim().length > 0) {
19860
+ return parseProxyWebsocketUrl(proxyUrl.trim());
19861
+ }
19862
+ return void 0;
19863
+ }
19864
+ async function resolveProxyWebsocketUrl(input) {
19865
+ if (typeof input.explicitProxyWsUrl === "string" && input.explicitProxyWsUrl.trim().length > 0) {
19866
+ return parseProxyWebsocketUrl(input.explicitProxyWsUrl.trim());
19867
+ }
19868
+ const fromEnv = resolveProxyWebsocketUrlFromEnv();
19869
+ if (fromEnv !== void 0) {
19870
+ return fromEnv;
19871
+ }
19872
+ if (typeof input.configProxyUrl === "string" && input.configProxyUrl.trim().length > 0) {
19873
+ return parseProxyWebsocketUrl(input.configProxyUrl.trim());
19874
+ }
19875
+ const fetchImpl = input.fetchImpl ?? globalThis.fetch;
19876
+ if (typeof fetchImpl === "function") {
19877
+ try {
19878
+ const metadata = await fetchRegistryMetadata(input.registryUrl, {
19879
+ fetchImpl
19880
+ });
19881
+ return parseProxyWebsocketUrl(metadata.proxyUrl);
19882
+ } catch {
19883
+ }
19884
+ }
19885
+ throw createCliError4(
19886
+ "CLI_CONNECTOR_PROXY_URL_REQUIRED",
19887
+ "Proxy URL is required for connector startup. Run `clawdentity invite redeem <clw_inv_...>` or set CLAWDENTITY_PROXY_URL / CLAWDENTITY_PROXY_WS_URL."
19888
+ );
19889
+ }
19661
19890
  function normalizeOutboundPath2(pathValue) {
19662
19891
  const trimmed = pathValue.trim();
19663
19892
  if (trimmed.length === 0) {
19664
- throw createCliError3(
19893
+ throw createCliError4(
19665
19894
  "CLI_CONNECTOR_INVALID_OUTBOUND_PATH",
19666
19895
  "Connector outbound path is invalid"
19667
19896
  );
@@ -19690,17 +19919,17 @@ async function readConnectorAssignedBaseUrl(configDir, agentName, readFileImpl)
19690
19919
  try {
19691
19920
  parsed = JSON.parse(raw);
19692
19921
  } catch {
19693
- throw createCliError3(
19922
+ throw createCliError4(
19694
19923
  "CLI_CONNECTOR_INVALID_ASSIGNMENTS",
19695
19924
  "Connector assignments config is invalid JSON",
19696
19925
  { assignmentsPath }
19697
19926
  );
19698
19927
  }
19699
- if (!isRecord5(parsed) || !isRecord5(parsed.agents)) {
19928
+ if (!isRecord6(parsed) || !isRecord6(parsed.agents)) {
19700
19929
  return void 0;
19701
19930
  }
19702
19931
  const entry = parsed.agents[agentName];
19703
- if (!isRecord5(entry) || typeof entry.connectorBaseUrl !== "string") {
19932
+ if (!isRecord6(entry) || typeof entry.connectorBaseUrl !== "string") {
19704
19933
  return void 0;
19705
19934
  }
19706
19935
  return parseConnectorBaseUrl(entry.connectorBaseUrl);
@@ -19721,7 +19950,7 @@ async function readRequiredTrimmedFile(filePath, label, readFileImpl) {
19721
19950
  raw = await readFileImpl(filePath, "utf8");
19722
19951
  } catch (error48) {
19723
19952
  if (getErrorCode(error48) === "ENOENT") {
19724
- throw createCliError3(
19953
+ throw createCliError4(
19725
19954
  "CLI_CONNECTOR_MISSING_AGENT_MATERIAL",
19726
19955
  "Local agent credentials are missing for connector startup",
19727
19956
  { label }
@@ -19731,7 +19960,7 @@ async function readRequiredTrimmedFile(filePath, label, readFileImpl) {
19731
19960
  }
19732
19961
  const trimmed = raw.trim();
19733
19962
  if (trimmed.length === 0) {
19734
- throw createCliError3(
19963
+ throw createCliError4(
19735
19964
  "CLI_CONNECTOR_MISSING_AGENT_MATERIAL",
19736
19965
  "Local agent credentials are missing for connector startup",
19737
19966
  { label }
@@ -19756,7 +19985,7 @@ async function readRelayRuntimeConfig(configDir, readFileImpl) {
19756
19985
  } catch {
19757
19986
  return void 0;
19758
19987
  }
19759
- if (!isRecord5(parsed)) {
19988
+ if (!isRecord6(parsed)) {
19760
19989
  return void 0;
19761
19990
  }
19762
19991
  const openclawHookToken = typeof parsed.openclawHookToken === "string" && parsed.openclawHookToken.trim().length > 0 ? parsed.openclawHookToken.trim() : void 0;
@@ -19772,10 +20001,10 @@ function parseJsonRecord(value, code, message2) {
19772
20001
  try {
19773
20002
  parsed = JSON.parse(value);
19774
20003
  } catch {
19775
- throw createCliError3(code, message2);
20004
+ throw createCliError4(code, message2);
19776
20005
  }
19777
- if (!isRecord5(parsed)) {
19778
- throw createCliError3(code, message2);
20006
+ if (!isRecord6(parsed)) {
20007
+ throw createCliError4(code, message2);
19779
20008
  }
19780
20009
  return parsed;
19781
20010
  }
@@ -19785,7 +20014,7 @@ function parseRegistryAuth(rawRegistryAuth) {
19785
20014
  "CLI_CONNECTOR_INVALID_REGISTRY_AUTH",
19786
20015
  "Agent registry auth is invalid for connector startup"
19787
20016
  );
19788
- const refreshToken = parseNonEmptyString5(parsed.refreshToken, "refreshToken");
20017
+ const refreshToken = parseNonEmptyString6(parsed.refreshToken, "refreshToken");
19789
20018
  const accessToken = typeof parsed.accessToken === "string" && parsed.accessToken.trim().length > 0 ? parsed.accessToken.trim() : void 0;
19790
20019
  const accessExpiresAt = typeof parsed.accessExpiresAt === "string" && parsed.accessExpiresAt.trim().length > 0 ? parsed.accessExpiresAt.trim() : void 0;
19791
20020
  const refreshExpiresAt = typeof parsed.refreshExpiresAt === "string" && parsed.refreshExpiresAt.trim().length > 0 ? parsed.refreshExpiresAt.trim() : void 0;
@@ -19814,7 +20043,7 @@ async function loadDefaultConnectorModule() {
19814
20043
  };
19815
20044
  }
19816
20045
  function resolveWaitPromise(runtime) {
19817
- if (!runtime || !isRecord5(runtime)) {
20046
+ if (!runtime || !isRecord6(runtime)) {
19818
20047
  return void 0;
19819
20048
  }
19820
20049
  if (typeof runtime.waitUntilStopped === "function") {
@@ -19838,7 +20067,7 @@ function parseConnectorServicePlatformOption(value) {
19838
20067
  if (value === "auto" || value === "launchd" || value === "systemd") {
19839
20068
  return value;
19840
20069
  }
19841
- throw createCliError3(
20070
+ throw createCliError4(
19842
20071
  "CLI_CONNECTOR_SERVICE_PLATFORM_INVALID",
19843
20072
  "Connector service platform must be one of: auto, launchd, systemd"
19844
20073
  );
@@ -19853,7 +20082,7 @@ function resolveConnectorServicePlatform(inputPlatform, currentPlatform) {
19853
20082
  if (currentPlatform === "linux") {
19854
20083
  return "systemd";
19855
20084
  }
19856
- throw createCliError3(
20085
+ throw createCliError4(
19857
20086
  "CLI_CONNECTOR_SERVICE_PLATFORM_UNSUPPORTED",
19858
20087
  "Connector service install is supported only on macOS (launchd) and Linux (systemd)",
19859
20088
  {
@@ -19955,7 +20184,7 @@ function resolveServiceDependencies(dependencies) {
19955
20184
  resolveCurrentPlatformImpl: dependencies.resolveCurrentPlatformImpl ?? (() => process.platform),
19956
20185
  resolveCurrentUidImpl: dependencies.resolveCurrentUidImpl ?? (() => {
19957
20186
  if (typeof process.getuid !== "function") {
19958
- throw createCliError3(
20187
+ throw createCliError4(
19959
20188
  "CLI_CONNECTOR_SERVICE_UID_UNAVAILABLE",
19960
20189
  "Current user id is unavailable in this runtime"
19961
20190
  );
@@ -20017,7 +20246,7 @@ async function installConnectorServiceForAgent(agentName, commandOptions = {}, d
20017
20246
  `${serviceName}.service`
20018
20247
  ]);
20019
20248
  } catch (error48) {
20020
- throw createCliError3(
20249
+ throw createCliError4(
20021
20250
  "CLI_CONNECTOR_SERVICE_INSTALL_FAILED",
20022
20251
  "Failed to install systemd connector service",
20023
20252
  {
@@ -20064,7 +20293,7 @@ async function installConnectorServiceForAgent(agentName, commandOptions = {}, d
20064
20293
  serviceFilePath
20065
20294
  ]);
20066
20295
  } catch (error48) {
20067
- throw createCliError3(
20296
+ throw createCliError4(
20068
20297
  "CLI_CONNECTOR_SERVICE_INSTALL_FAILED",
20069
20298
  "Failed to install launchd connector service",
20070
20299
  {
@@ -20148,6 +20377,7 @@ async function startConnectorForAgent(agentName, commandOptions = {}, dependenci
20148
20377
  const resolveConfigImpl = dependencies.resolveConfigImpl ?? resolveConfig;
20149
20378
  const getConfigDirImpl = dependencies.getConfigDirImpl ?? getConfigDir;
20150
20379
  const readFileImpl = dependencies.readFileImpl ?? ((path, encoding) => readFile3(path, encoding));
20380
+ const fetchImpl = dependencies.fetchImpl ?? globalThis.fetch;
20151
20381
  const loadConnectorModule = dependencies.loadConnectorModule ?? loadDefaultConnectorModule;
20152
20382
  const configDir = getConfigDirImpl();
20153
20383
  const agentDirectory = join5(configDir, AGENTS_DIR_NAME3, agentName);
@@ -20187,13 +20417,19 @@ async function startConnectorForAgent(agentName, commandOptions = {}, dependenci
20187
20417
  loadConnectorModule()
20188
20418
  ]);
20189
20419
  if (typeof connectorModule.startConnectorRuntime !== "function") {
20190
- throw createCliError3(
20420
+ throw createCliError4(
20191
20421
  "CLI_CONNECTOR_INVALID_PACKAGE_API",
20192
20422
  "Connector package does not expose startConnectorRuntime"
20193
20423
  );
20194
20424
  }
20195
20425
  const identity = parseAgentIdentity(rawIdentity);
20196
20426
  const registryAuth = parseRegistryAuth(rawRegistryAuth);
20427
+ const resolvedProxyWebsocketUrl = await resolveProxyWebsocketUrl({
20428
+ explicitProxyWsUrl: commandOptions.proxyWsUrl,
20429
+ configProxyUrl: config2.proxyUrl,
20430
+ registryUrl: config2.registryUrl,
20431
+ fetchImpl
20432
+ });
20197
20433
  const openclawHookToken = commandOptions.openclawHookToken ?? relayRuntimeConfig?.openclawHookToken;
20198
20434
  const outboundBaseUrl = resolveConnectorBaseUrlFromEnv() ?? assignedConnectorBaseUrl ?? DEFAULT_CONNECTOR_BASE_URL2;
20199
20435
  const outboundPath = resolveConnectorOutboundPath();
@@ -20203,7 +20439,7 @@ async function startConnectorForAgent(agentName, commandOptions = {}, dependenci
20203
20439
  registryUrl: config2.registryUrl,
20204
20440
  outboundBaseUrl,
20205
20441
  outboundPath,
20206
- proxyWebsocketUrl: commandOptions.proxyWsUrl,
20442
+ proxyWebsocketUrl: resolvedProxyWebsocketUrl,
20207
20443
  openclawBaseUrl: commandOptions.openclawBaseUrl,
20208
20444
  openclawHookPath: commandOptions.openclawHookPath,
20209
20445
  openclawHookToken,
@@ -20218,8 +20454,8 @@ async function startConnectorForAgent(agentName, commandOptions = {}, dependenci
20218
20454
  tokenType: registryAuth.tokenType
20219
20455
  }
20220
20456
  });
20221
- const outboundUrl = runtime && isRecord5(runtime) && typeof runtime.outboundUrl === "string" ? runtime.outboundUrl : resolveOutboundUrl(outboundBaseUrl, outboundPath);
20222
- const proxyWebsocketUrl = runtime && isRecord5(runtime) ? typeof runtime.websocketUrl === "string" ? runtime.websocketUrl : typeof runtime.proxyWebsocketUrl === "string" ? runtime.proxyWebsocketUrl : void 0 : void 0;
20457
+ const outboundUrl = runtime && isRecord6(runtime) && typeof runtime.outboundUrl === "string" ? runtime.outboundUrl : resolveOutboundUrl(outboundBaseUrl, outboundPath);
20458
+ const proxyWebsocketUrl = runtime && isRecord6(runtime) ? typeof runtime.websocketUrl === "string" ? runtime.websocketUrl : typeof runtime.proxyWebsocketUrl === "string" ? runtime.proxyWebsocketUrl : resolvedProxyWebsocketUrl : void 0;
20223
20459
  return {
20224
20460
  outboundUrl,
20225
20461
  proxyWebsocketUrl,
@@ -20349,107 +20585,52 @@ function createConnectorCommand(dependencies = {}) {
20349
20585
 
20350
20586
  // src/commands/invite.ts
20351
20587
  import { Command as Command6 } from "commander";
20352
-
20353
- // src/config/endpoints.ts
20354
- var PRODUCTION_REGISTRY_HOST = "registry.clawdentity.com";
20355
- var DEVELOPMENT_REGISTRY_HOST = "dev.registry.clawdentity.com";
20356
- var PRODUCTION_PROXY_HOST = "proxy.clawdentity.com";
20357
- var DEVELOPMENT_PROXY_HOST = "dev.proxy.clawdentity.com";
20358
- var LOCAL_REGISTRY_PORT = "8788";
20359
- var LOCAL_PROXY_PORT = "8787";
20360
- var LOCAL_HOSTNAMES = /* @__PURE__ */ new Set([
20361
- "localhost",
20362
- "127.0.0.1",
20363
- "host.docker.internal",
20364
- "gateway.docker.internal"
20365
- ]);
20366
- var normalizeUrl = (candidate) => {
20367
- try {
20368
- const parsed = new URL(candidate);
20369
- if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
20370
- return void 0;
20371
- }
20372
- return parsed;
20373
- } catch {
20374
- return void 0;
20375
- }
20376
- };
20377
- var toProxyHostname = (registryHostname) => {
20378
- if (registryHostname === PRODUCTION_REGISTRY_HOST) {
20379
- return PRODUCTION_PROXY_HOST;
20380
- }
20381
- if (registryHostname === DEVELOPMENT_REGISTRY_HOST) {
20382
- return DEVELOPMENT_PROXY_HOST;
20383
- }
20384
- if (registryHostname.startsWith("dev.registry.")) {
20385
- return `dev.proxy.${registryHostname.slice("dev.registry.".length)}`;
20386
- }
20387
- if (registryHostname.startsWith("registry.")) {
20388
- return `proxy.${registryHostname.slice("registry.".length)}`;
20389
- }
20390
- return void 0;
20391
- };
20392
- var deriveProxyUrlFromRegistryUrl = (registryUrl) => {
20393
- const parsedRegistryUrl = normalizeUrl(registryUrl);
20394
- if (!parsedRegistryUrl) {
20395
- return void 0;
20396
- }
20397
- const hostname3 = parsedRegistryUrl.hostname.toLowerCase();
20398
- if (LOCAL_HOSTNAMES.has(hostname3) && parsedRegistryUrl.port === LOCAL_REGISTRY_PORT) {
20399
- return `${parsedRegistryUrl.protocol}//${hostname3}:${LOCAL_PROXY_PORT}/`;
20400
- }
20401
- const mappedHostname = toProxyHostname(hostname3);
20402
- if (!mappedHostname) {
20403
- return void 0;
20404
- }
20405
- const port = parsedRegistryUrl.port.length > 0 ? `:${parsedRegistryUrl.port}` : "";
20406
- return `${parsedRegistryUrl.protocol}//${mappedHostname}${port}/`;
20407
- };
20408
-
20409
- // src/commands/invite.ts
20410
20588
  var logger7 = createLogger({ service: "cli", module: "invite" });
20411
- var isRecord6 = (value) => {
20589
+ var isRecord7 = (value) => {
20412
20590
  return typeof value === "object" && value !== null;
20413
20591
  };
20414
- function parseNonEmptyString6(value) {
20592
+ function parseNonEmptyString7(value) {
20415
20593
  if (typeof value !== "string") {
20416
20594
  return "";
20417
20595
  }
20418
20596
  return value.trim();
20419
20597
  }
20420
- function createCliError4(code, message2) {
20598
+ function createCliError5(code, message2) {
20421
20599
  return new AppError({
20422
20600
  code,
20423
20601
  message: message2,
20424
20602
  status: 400
20425
20603
  });
20426
20604
  }
20427
- function resolveRegistryUrl2(input) {
20428
- const candidate = parseNonEmptyString6(input.overrideRegistryUrl) || input.configRegistryUrl;
20605
+ function normalizeProxyUrl(value) {
20429
20606
  try {
20430
- return new URL(candidate).toString();
20607
+ const parsed = new URL(value);
20608
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
20609
+ throw new Error("invalid protocol");
20610
+ }
20611
+ return parsed.toString();
20431
20612
  } catch {
20432
- throw createCliError4(
20433
- "CLI_INVITE_INVALID_REGISTRY_URL",
20434
- "Registry URL is invalid"
20613
+ throw createCliError5(
20614
+ "CLI_INVITE_REDEEM_INVALID_RESPONSE",
20615
+ "Invite redeem response is invalid"
20435
20616
  );
20436
20617
  }
20437
20618
  }
20619
+ function resolveRegistryUrl2(input) {
20620
+ const candidate = parseNonEmptyString7(input.overrideRegistryUrl) || input.configRegistryUrl;
20621
+ return normalizeRegistryUrl(candidate);
20622
+ }
20438
20623
  function requireApiKey2(config2) {
20439
20624
  if (typeof config2.apiKey === "string" && config2.apiKey.trim().length > 0) {
20440
20625
  return config2.apiKey;
20441
20626
  }
20442
- throw createCliError4(
20627
+ throw createCliError5(
20443
20628
  "CLI_INVITE_MISSING_LOCAL_CREDENTIALS",
20444
20629
  "API key is not configured. Run `clawdentity config set apiKey <token>` or set CLAWDENTITY_API_KEY."
20445
20630
  );
20446
20631
  }
20447
- function toRegistryRequestUrl(registryUrl, path) {
20448
- const normalizedBaseUrl = registryUrl.endsWith("/") ? registryUrl : `${registryUrl}/`;
20449
- return new URL(path.slice(1), normalizedBaseUrl).toString();
20450
- }
20451
20632
  function extractRegistryErrorCode(payload) {
20452
- if (!isRecord6(payload)) {
20633
+ if (!isRecord7(payload)) {
20453
20634
  return void 0;
20454
20635
  }
20455
20636
  const envelope = payload;
@@ -20459,8 +20640,8 @@ function extractRegistryErrorCode(payload) {
20459
20640
  const trimmed = envelope.error.code.trim();
20460
20641
  return trimmed.length > 0 ? trimmed : void 0;
20461
20642
  }
20462
- function extractRegistryErrorMessage3(payload) {
20463
- if (!isRecord6(payload)) {
20643
+ function extractRegistryErrorMessage4(payload) {
20644
+ if (!isRecord7(payload)) {
20464
20645
  return void 0;
20465
20646
  }
20466
20647
  const envelope = payload;
@@ -20470,7 +20651,7 @@ function extractRegistryErrorMessage3(payload) {
20470
20651
  const trimmed = envelope.error.message.trim();
20471
20652
  return trimmed.length > 0 ? trimmed : void 0;
20472
20653
  }
20473
- async function parseJsonResponse4(response) {
20654
+ async function parseJsonResponse5(response) {
20474
20655
  try {
20475
20656
  return await response.json();
20476
20657
  } catch {
@@ -20481,7 +20662,7 @@ async function executeInviteRequest(input) {
20481
20662
  try {
20482
20663
  return await input.fetchImpl(input.url, input.init);
20483
20664
  } catch {
20484
- throw createCliError4(
20665
+ throw createCliError5(
20485
20666
  "CLI_INVITE_REQUEST_FAILED",
20486
20667
  "Unable to connect to the registry. Check network access and registryUrl."
20487
20668
  );
@@ -20489,7 +20670,7 @@ async function executeInviteRequest(input) {
20489
20670
  }
20490
20671
  function mapCreateInviteError(status, payload) {
20491
20672
  const errorCode = extractRegistryErrorCode(payload);
20492
- const registryMessage = extractRegistryErrorMessage3(payload);
20673
+ const registryMessage = extractRegistryErrorMessage4(payload);
20493
20674
  if (status === 401) {
20494
20675
  return registryMessage ? `Registry authentication failed (401): ${registryMessage}` : "Registry authentication failed (401). Check your API key.";
20495
20676
  }
@@ -20512,7 +20693,7 @@ function mapCreateInviteError(status, payload) {
20512
20693
  }
20513
20694
  function mapRedeemInviteError(status, payload) {
20514
20695
  const errorCode = extractRegistryErrorCode(payload);
20515
- const registryMessage = extractRegistryErrorMessage3(payload);
20696
+ const registryMessage = extractRegistryErrorMessage4(payload);
20516
20697
  if (errorCode === "INVITE_REDEEM_ALREADY_USED" || errorCode === "INVITE_REDEEM_ALREADY_REDEEMED") {
20517
20698
  return "Invite code has already been redeemed";
20518
20699
  }
@@ -20534,16 +20715,16 @@ function mapRedeemInviteError(status, payload) {
20534
20715
  return `Invite redeem failed (${status})`;
20535
20716
  }
20536
20717
  function parseInviteRecord(payload) {
20537
- if (!isRecord6(payload)) {
20538
- throw createCliError4(
20718
+ if (!isRecord7(payload)) {
20719
+ throw createCliError5(
20539
20720
  "CLI_INVITE_CREATE_INVALID_RESPONSE",
20540
20721
  "Invite response is invalid"
20541
20722
  );
20542
20723
  }
20543
- const source = isRecord6(payload.invite) ? payload.invite : payload;
20544
- const code = parseNonEmptyString6(source.code);
20724
+ const source = isRecord7(payload.invite) ? payload.invite : payload;
20725
+ const code = parseNonEmptyString7(source.code);
20545
20726
  if (code.length === 0) {
20546
- throw createCliError4(
20727
+ throw createCliError5(
20547
20728
  "CLI_INVITE_CREATE_INVALID_RESPONSE",
20548
20729
  "Invite response is invalid"
20549
20730
  );
@@ -20551,11 +20732,11 @@ function parseInviteRecord(payload) {
20551
20732
  const invite = {
20552
20733
  code
20553
20734
  };
20554
- const id = parseNonEmptyString6(source.id);
20735
+ const id = parseNonEmptyString7(source.id);
20555
20736
  if (id.length > 0) {
20556
20737
  invite.id = id;
20557
20738
  }
20558
- const createdAt = parseNonEmptyString6(source.createdAt);
20739
+ const createdAt = parseNonEmptyString7(source.createdAt);
20559
20740
  if (createdAt.length > 0) {
20560
20741
  invite.createdAt = createdAt;
20561
20742
  }
@@ -20565,36 +20746,25 @@ function parseInviteRecord(payload) {
20565
20746
  return invite;
20566
20747
  }
20567
20748
  function parseInviteRedeemResponse(payload) {
20568
- if (!isRecord6(payload)) {
20569
- throw createCliError4(
20749
+ if (!isRecord7(payload)) {
20750
+ throw createCliError5(
20570
20751
  "CLI_INVITE_REDEEM_INVALID_RESPONSE",
20571
20752
  "Invite redeem response is invalid"
20572
20753
  );
20573
20754
  }
20574
- const apiKeySource = isRecord6(payload.apiKey) ? payload.apiKey : payload;
20575
- const apiKeyToken = parseNonEmptyString6(
20576
- isRecord6(payload.apiKey) ? payload.apiKey.token : payload.token
20755
+ const apiKeySource = isRecord7(payload.apiKey) ? payload.apiKey : payload;
20756
+ const apiKeyToken = parseNonEmptyString7(
20757
+ isRecord7(payload.apiKey) ? payload.apiKey.token : payload.token
20577
20758
  );
20578
20759
  if (apiKeyToken.length === 0) {
20579
- throw createCliError4(
20760
+ throw createCliError5(
20580
20761
  "CLI_INVITE_REDEEM_INVALID_RESPONSE",
20581
20762
  "Invite redeem response is invalid"
20582
20763
  );
20583
20764
  }
20584
- const apiKeyId = parseNonEmptyString6(apiKeySource.id);
20585
- const apiKeyName = parseNonEmptyString6(apiKeySource.name);
20586
- const proxyUrlCandidate = parseNonEmptyString6(payload.proxyUrl);
20587
- let proxyUrl;
20588
- if (proxyUrlCandidate.length > 0) {
20589
- try {
20590
- const parsed = new URL(proxyUrlCandidate);
20591
- if (parsed.protocol === "https:" || parsed.protocol === "http:") {
20592
- proxyUrl = parsed.toString();
20593
- }
20594
- } catch {
20595
- proxyUrl = void 0;
20596
- }
20597
- }
20765
+ const apiKeyId = parseNonEmptyString7(apiKeySource.id);
20766
+ const apiKeyName = parseNonEmptyString7(apiKeySource.name);
20767
+ const proxyUrl = parseNonEmptyString7(payload.proxyUrl);
20598
20768
  return {
20599
20769
  apiKeyToken,
20600
20770
  apiKeyId: apiKeyId.length > 0 ? apiKeyId : void 0,
@@ -20629,13 +20799,13 @@ async function createInvite(options, dependencies = {}) {
20629
20799
  "content-type": "application/json"
20630
20800
  },
20631
20801
  body: JSON.stringify({
20632
- expiresAt: parseNonEmptyString6(options.expiresAt) || void 0
20802
+ expiresAt: parseNonEmptyString7(options.expiresAt) || void 0
20633
20803
  })
20634
20804
  }
20635
20805
  });
20636
- const responseBody = await parseJsonResponse4(response);
20806
+ const responseBody = await parseJsonResponse5(response);
20637
20807
  if (!response.ok) {
20638
- throw createCliError4(
20808
+ throw createCliError5(
20639
20809
  "CLI_INVITE_CREATE_FAILED",
20640
20810
  mapCreateInviteError(response.status, responseBody)
20641
20811
  );
@@ -20646,9 +20816,9 @@ async function createInvite(options, dependencies = {}) {
20646
20816
  };
20647
20817
  }
20648
20818
  async function redeemInvite(code, options, dependencies = {}) {
20649
- const inviteCode = parseNonEmptyString6(code);
20819
+ const inviteCode = parseNonEmptyString7(code);
20650
20820
  if (inviteCode.length === 0) {
20651
- throw createCliError4(
20821
+ throw createCliError5(
20652
20822
  "CLI_INVITE_REDEEM_CODE_REQUIRED",
20653
20823
  "Invite code is required"
20654
20824
  );
@@ -20665,36 +20835,34 @@ async function redeemInvite(code, options, dependencies = {}) {
20665
20835
  body: JSON.stringify({ code: inviteCode })
20666
20836
  }
20667
20837
  });
20668
- const responseBody = await parseJsonResponse4(response);
20838
+ const responseBody = await parseJsonResponse5(response);
20669
20839
  if (!response.ok) {
20670
- throw createCliError4(
20840
+ throw createCliError5(
20671
20841
  "CLI_INVITE_REDEEM_FAILED",
20672
20842
  mapRedeemInviteError(response.status, responseBody)
20673
20843
  );
20674
20844
  }
20845
+ const parsedRedeem = parseInviteRedeemResponse(responseBody);
20846
+ const proxyUrl = parsedRedeem.proxyUrl.length > 0 ? parsedRedeem.proxyUrl : (await fetchRegistryMetadata(runtime.registryUrl, {
20847
+ fetchImpl: runtime.fetchImpl
20848
+ })).proxyUrl;
20675
20849
  return {
20676
- ...parseInviteRedeemResponse(responseBody),
20850
+ ...parsedRedeem,
20851
+ proxyUrl: normalizeProxyUrl(proxyUrl),
20677
20852
  registryUrl: runtime.registryUrl
20678
20853
  };
20679
20854
  }
20680
- async function persistRedeemConfig(input, dependencies = {}) {
20855
+ async function persistRedeemConfig(registryUrl, apiKeyToken, proxyUrl, dependencies = {}) {
20681
20856
  const setConfigValueImpl = dependencies.setConfigValueImpl ?? setConfigValue;
20682
20857
  try {
20683
- await setConfigValueImpl("registryUrl", input.registryUrl);
20684
- await setConfigValueImpl("apiKey", input.apiKeyToken);
20685
- const resolvedProxyUrl = parseNonEmptyString6(input.proxyUrl) || deriveProxyUrlFromRegistryUrl(input.registryUrl);
20686
- if (!resolvedProxyUrl) {
20687
- throw createCliError4(
20688
- "CLI_INVITE_REDEEM_PROXY_URL_MISSING",
20689
- "Proxy URL is missing from onboarding response and could not be derived from registry URL."
20690
- );
20691
- }
20692
- await setConfigValueImpl("proxyUrl", resolvedProxyUrl);
20858
+ await setConfigValueImpl("registryUrl", registryUrl);
20859
+ await setConfigValueImpl("apiKey", apiKeyToken);
20860
+ await setConfigValueImpl("proxyUrl", proxyUrl);
20693
20861
  } catch (error48) {
20694
20862
  logger7.warn("cli.invite_redeem_config_persist_failed", {
20695
20863
  errorName: error48 instanceof Error ? error48.name : "unknown"
20696
20864
  });
20697
- throw createCliError4(
20865
+ throw createCliError5(
20698
20866
  "CLI_INVITE_REDEEM_CONFIG_PERSISTENCE_FAILED",
20699
20867
  "Failed to save redeemed API key locally"
20700
20868
  );
@@ -20702,7 +20870,7 @@ async function persistRedeemConfig(input, dependencies = {}) {
20702
20870
  }
20703
20871
  var createInviteCommand = (dependencies = {}) => {
20704
20872
  const inviteCommand = new Command6("invite").description(
20705
- "Manage registry onboarding invites"
20873
+ "Manage registry onboarding invites (not OpenClaw peer relay invites)"
20706
20874
  );
20707
20875
  inviteCommand.command("create").description("Create a registry invite code (admin only)").option("--expires-at <timestamp>", "Optional invite expiry (ISO-8601)").option("--registry-url <url>", "Override registry URL").action(
20708
20876
  withErrorHandling(
@@ -20740,15 +20908,12 @@ var createInviteCommand = (dependencies = {}) => {
20740
20908
  writeStdoutLine("API key token (shown once):");
20741
20909
  writeStdoutLine(result.apiKeyToken);
20742
20910
  await persistRedeemConfig(
20743
- {
20744
- registryUrl: result.registryUrl,
20745
- apiKeyToken: result.apiKeyToken,
20746
- proxyUrl: result.proxyUrl
20747
- },
20911
+ result.registryUrl,
20912
+ result.apiKeyToken,
20913
+ result.proxyUrl,
20748
20914
  dependencies
20749
20915
  );
20750
20916
  writeStdoutLine("API key saved to local config");
20751
- writeStdoutLine("Onboarding auth complete");
20752
20917
  }
20753
20918
  )
20754
20919
  );
@@ -20770,8 +20935,16 @@ var SECRET_KEY_FILE_NAME2 = "secret.key";
20770
20935
  var PEERS_FILE_NAME = "peers.json";
20771
20936
  var OPENCLAW_DIR_NAME = ".openclaw";
20772
20937
  var OPENCLAW_CONFIG_FILE_NAME = "openclaw.json";
20773
- var LEGACY_OPENCLAW_STATE_DIR_NAMES = [".clawdbot", ".moldbot", ".moltbot"];
20774
- var LEGACY_OPENCLAW_CONFIG_FILE_NAMES = ["clawdbot.json", "moldbot.json", "moltbot.json"];
20938
+ var LEGACY_OPENCLAW_STATE_DIR_NAMES = [
20939
+ ".clawdbot",
20940
+ ".moldbot",
20941
+ ".moltbot"
20942
+ ];
20943
+ var LEGACY_OPENCLAW_CONFIG_FILE_NAMES = [
20944
+ "clawdbot.json",
20945
+ "moldbot.json",
20946
+ "moltbot.json"
20947
+ ];
20775
20948
  var OPENCLAW_AGENT_FILE_NAME = "openclaw-agent-name";
20776
20949
  var OPENCLAW_RELAY_RUNTIME_FILE_NAME2 = "openclaw-relay.json";
20777
20950
  var OPENCLAW_CONNECTORS_FILE_NAME2 = "openclaw-connectors.json";
@@ -20783,6 +20956,8 @@ var HOOK_MAPPING_ID = "clawdentity-send-to-peer";
20783
20956
  var HOOK_PATH_SEND_TO_PEER = "send-to-peer";
20784
20957
  var OPENCLAW_SEND_TO_PEER_HOOK_PATH = "hooks/send-to-peer";
20785
20958
  var DEFAULT_OPENCLAW_BASE_URL2 = "http://127.0.0.1:18789";
20959
+ var DEFAULT_OPENCLAW_MAIN_SESSION_KEY = "main";
20960
+ var DEFAULT_OPENCLAW_AGENT_ID = "main";
20786
20961
  var DEFAULT_CONNECTOR_PORT = 19400;
20787
20962
  var DEFAULT_CONNECTOR_OUTBOUND_PATH3 = "/v1/outbound";
20788
20963
  var CONNECTOR_HOST_LOOPBACK = "127.0.0.1";
@@ -20798,10 +20973,10 @@ var OPENCLAW_SETUP_WITH_BASE_URL_HINT = `${OPENCLAW_SETUP_COMMAND_HINT} --opencl
20798
20973
  var OPENCLAW_PAIRING_COMMAND_HINT = "Run QR pairing first: clawdentity pair start <agentName> --qr and clawdentity pair confirm <agentName> --qr-file <path>";
20799
20974
  var textEncoder2 = new TextEncoder();
20800
20975
  var textDecoder = new TextDecoder();
20801
- function isRecord7(value) {
20976
+ function isRecord8(value) {
20802
20977
  return typeof value === "object" && value !== null;
20803
20978
  }
20804
- function createCliError5(code, message2, details) {
20979
+ function createCliError6(code, message2, details) {
20805
20980
  return new AppError({
20806
20981
  code,
20807
20982
  message: message2,
@@ -20810,14 +20985,14 @@ function createCliError5(code, message2, details) {
20810
20985
  });
20811
20986
  }
20812
20987
  function getErrorCode2(error48) {
20813
- if (!isRecord7(error48)) {
20988
+ if (!isRecord8(error48)) {
20814
20989
  return void 0;
20815
20990
  }
20816
20991
  return typeof error48.code === "string" ? error48.code : void 0;
20817
20992
  }
20818
- function parseNonEmptyString7(value, label) {
20993
+ function parseNonEmptyString8(value, label) {
20819
20994
  if (typeof value !== "string") {
20820
- throw createCliError5(
20995
+ throw createCliError6(
20821
20996
  "CLI_OPENCLAW_INVALID_INPUT",
20822
20997
  "Input must be a string",
20823
20998
  {
@@ -20827,7 +21002,7 @@ function parseNonEmptyString7(value, label) {
20827
21002
  }
20828
21003
  const trimmed = value.trim();
20829
21004
  if (trimmed.length === 0) {
20830
- throw createCliError5(
21005
+ throw createCliError6(
20831
21006
  "CLI_OPENCLAW_INVALID_INPUT",
20832
21007
  "Input must not be empty",
20833
21008
  { label }
@@ -20839,18 +21014,18 @@ function parseOptionalName(value) {
20839
21014
  if (value === void 0) {
20840
21015
  return void 0;
20841
21016
  }
20842
- return parseNonEmptyString7(value, "name");
21017
+ return parseNonEmptyString8(value, "name");
20843
21018
  }
20844
21019
  function parsePeerAlias(value) {
20845
- const alias = parseNonEmptyString7(value, "peer alias");
21020
+ const alias = parseNonEmptyString8(value, "peer alias");
20846
21021
  if (alias.length > 128) {
20847
- throw createCliError5(
21022
+ throw createCliError6(
20848
21023
  "CLI_OPENCLAW_INVALID_PEER_ALIAS",
20849
21024
  "peer alias must be at most 128 characters"
20850
21025
  );
20851
21026
  }
20852
21027
  if (!PEER_ALIAS_PATTERN.test(alias)) {
20853
- throw createCliError5(
21028
+ throw createCliError6(
20854
21029
  "CLI_OPENCLAW_INVALID_PEER_ALIAS",
20855
21030
  "peer alias must use only letters, numbers, dot, underscore, or hyphen"
20856
21031
  );
@@ -20865,15 +21040,15 @@ function parseProxyUrl(value) {
20865
21040
  });
20866
21041
  }
20867
21042
  function parseHttpUrl(value, input) {
20868
- const candidate = parseNonEmptyString7(value, input.label);
21043
+ const candidate = parseNonEmptyString8(value, input.label);
20869
21044
  let parsedUrl;
20870
21045
  try {
20871
21046
  parsedUrl = new URL(candidate);
20872
21047
  } catch {
20873
- throw createCliError5(input.code, input.message);
21048
+ throw createCliError6(input.code, input.message);
20874
21049
  }
20875
21050
  if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
20876
- throw createCliError5(input.code, `${input.label} must use http or https`);
21051
+ throw createCliError6(input.code, `${input.label} must use http or https`);
20877
21052
  }
20878
21053
  if (parsedUrl.pathname === "/" && parsedUrl.search.length === 0 && parsedUrl.hash.length === 0) {
20879
21054
  return parsedUrl.origin;
@@ -20888,17 +21063,17 @@ function parseOpenclawBaseUrl(value) {
20888
21063
  });
20889
21064
  }
20890
21065
  function parseAgentDid2(value, label) {
20891
- const did = parseNonEmptyString7(value, label);
21066
+ const did = parseNonEmptyString8(value, label);
20892
21067
  try {
20893
21068
  const parsed = parseDid(did);
20894
21069
  if (parsed.kind !== "agent") {
20895
- throw createCliError5(
21070
+ throw createCliError6(
20896
21071
  "CLI_OPENCLAW_INVALID_DID",
20897
21072
  "DID is not an agent DID"
20898
21073
  );
20899
21074
  }
20900
21075
  } catch {
20901
- throw createCliError5("CLI_OPENCLAW_INVALID_DID", "Agent DID is invalid", {
21076
+ throw createCliError6("CLI_OPENCLAW_INVALID_DID", "Agent DID is invalid", {
20902
21077
  label
20903
21078
  });
20904
21079
  }
@@ -20924,7 +21099,10 @@ function readNonEmptyEnvPath(value, homeDir) {
20924
21099
  return resolveHomePrefixedPath(value, homeDir);
20925
21100
  }
20926
21101
  function resolveOpenclawHomeDir(homeDir) {
20927
- const envOpenclawHome = readNonEmptyEnvPath(process.env.OPENCLAW_HOME, homeDir);
21102
+ const envOpenclawHome = readNonEmptyEnvPath(
21103
+ process.env.OPENCLAW_HOME,
21104
+ homeDir
21105
+ );
20928
21106
  return envOpenclawHome ?? homeDir;
20929
21107
  }
20930
21108
  function resolveDefaultOpenclawStateDir(openclawHomeDir) {
@@ -20989,13 +21167,7 @@ function resolveOpenclawConfigPath(openclawDir, homeDir) {
20989
21167
  return configCandidates[0];
20990
21168
  }
20991
21169
  function resolveDefaultTransformSource(openclawDir) {
20992
- return join6(
20993
- openclawDir,
20994
- "workspace",
20995
- "skills",
20996
- SKILL_DIR_NAME,
20997
- RELAY_MODULE_FILE_NAME
20998
- );
21170
+ return join6(openclawDir, "skills", SKILL_DIR_NAME, RELAY_MODULE_FILE_NAME);
20999
21171
  }
21000
21172
  function resolveTransformTargetPath(openclawDir) {
21001
21173
  return join6(openclawDir, "hooks", "transforms", RELAY_MODULE_FILE_NAME);
@@ -21020,7 +21192,7 @@ async function readJsonFile(filePath) {
21020
21192
  try {
21021
21193
  return JSON.parse(raw);
21022
21194
  } catch {
21023
- throw createCliError5("CLI_OPENCLAW_INVALID_JSON", "JSON file is invalid", {
21195
+ throw createCliError6("CLI_OPENCLAW_INVALID_JSON", "JSON file is invalid", {
21024
21196
  filePath
21025
21197
  });
21026
21198
  }
@@ -21037,7 +21209,7 @@ async function ensureLocalAgentCredentials(homeDir, agentName) {
21037
21209
  content = await readFile4(filePath, "utf8");
21038
21210
  } catch (error48) {
21039
21211
  if (getErrorCode2(error48) === "ENOENT") {
21040
- throw createCliError5(
21212
+ throw createCliError6(
21041
21213
  "CLI_OPENCLAW_MISSING_AGENT_CREDENTIALS",
21042
21214
  "Local agent credentials are missing",
21043
21215
  { agentName, filePath }
@@ -21046,7 +21218,7 @@ async function ensureLocalAgentCredentials(homeDir, agentName) {
21046
21218
  throw error48;
21047
21219
  }
21048
21220
  if (content.trim().length === 0) {
21049
- throw createCliError5(
21221
+ throw createCliError6(
21050
21222
  "CLI_OPENCLAW_EMPTY_AGENT_CREDENTIALS",
21051
21223
  "Agent credential file is empty",
21052
21224
  { filePath }
@@ -21069,8 +21241,8 @@ async function loadPeersConfig(peersPath) {
21069
21241
  }
21070
21242
  throw error48;
21071
21243
  }
21072
- if (!isRecord7(parsed)) {
21073
- throw createCliError5(
21244
+ if (!isRecord8(parsed)) {
21245
+ throw createCliError6(
21074
21246
  "CLI_OPENCLAW_INVALID_PEERS_CONFIG",
21075
21247
  "Peer config root must be a JSON object",
21076
21248
  { peersPath }
@@ -21080,8 +21252,8 @@ async function loadPeersConfig(peersPath) {
21080
21252
  if (peersValue === void 0) {
21081
21253
  return { peers: {} };
21082
21254
  }
21083
- if (!isRecord7(peersValue)) {
21084
- throw createCliError5(
21255
+ if (!isRecord8(peersValue)) {
21256
+ throw createCliError6(
21085
21257
  "CLI_OPENCLAW_INVALID_PEERS_CONFIG",
21086
21258
  "Peer config peers field must be an object",
21087
21259
  { peersPath }
@@ -21090,8 +21262,8 @@ async function loadPeersConfig(peersPath) {
21090
21262
  const peers = {};
21091
21263
  for (const [alias, value] of Object.entries(peersValue)) {
21092
21264
  const normalizedAlias = parsePeerAlias(alias);
21093
- if (!isRecord7(value)) {
21094
- throw createCliError5(
21265
+ if (!isRecord8(value)) {
21266
+ throw createCliError6(
21095
21267
  "CLI_OPENCLAW_INVALID_PEERS_CONFIG",
21096
21268
  "Peer entry must be an object",
21097
21269
  { alias: normalizedAlias }
@@ -21120,21 +21292,21 @@ function parseConnectorBaseUrlForAssignment(value, label) {
21120
21292
  });
21121
21293
  }
21122
21294
  function parseConnectorAssignments(value, connectorAssignmentsPath) {
21123
- if (!isRecord7(value)) {
21124
- throw createCliError5(
21295
+ if (!isRecord8(value)) {
21296
+ throw createCliError6(
21125
21297
  "CLI_OPENCLAW_INVALID_CONNECTOR_ASSIGNMENTS",
21126
21298
  "Connector assignments config must be an object",
21127
21299
  { connectorAssignmentsPath }
21128
21300
  );
21129
21301
  }
21130
21302
  const agentsRaw = value.agents;
21131
- if (!isRecord7(agentsRaw)) {
21303
+ if (!isRecord8(agentsRaw)) {
21132
21304
  return { agents: {} };
21133
21305
  }
21134
21306
  const agents = {};
21135
21307
  for (const [agentName, entryValue] of Object.entries(agentsRaw)) {
21136
- if (!isRecord7(entryValue)) {
21137
- throw createCliError5(
21308
+ if (!isRecord8(entryValue)) {
21309
+ throw createCliError6(
21138
21310
  "CLI_OPENCLAW_INVALID_CONNECTOR_ASSIGNMENTS",
21139
21311
  "Connector assignment entry must be an object",
21140
21312
  { connectorAssignmentsPath, agentName }
@@ -21205,8 +21377,8 @@ function buildRelayConnectorBaseUrls(port) {
21205
21377
  ];
21206
21378
  }
21207
21379
  function parseRelayRuntimeConfig(value, relayRuntimeConfigPath) {
21208
- if (!isRecord7(value)) {
21209
- throw createCliError5(
21380
+ if (!isRecord8(value)) {
21381
+ throw createCliError6(
21210
21382
  "CLI_OPENCLAW_INVALID_RELAY_RUNTIME_CONFIG",
21211
21383
  "Relay runtime config must be an object",
21212
21384
  { relayRuntimeConfigPath }
@@ -21260,7 +21432,7 @@ async function resolveOpenclawBaseUrl2(input) {
21260
21432
  }
21261
21433
  return DEFAULT_OPENCLAW_BASE_URL2;
21262
21434
  }
21263
- function normalizeStringArrayWithValue(value, requiredValue) {
21435
+ function normalizeStringArrayWithValues(value, requiredValues) {
21264
21436
  const normalized = /* @__PURE__ */ new Set();
21265
21437
  if (Array.isArray(value)) {
21266
21438
  for (const item of value) {
@@ -21273,27 +21445,61 @@ function normalizeStringArrayWithValue(value, requiredValue) {
21273
21445
  }
21274
21446
  }
21275
21447
  }
21276
- normalized.add(requiredValue);
21448
+ for (const requiredValue of requiredValues) {
21449
+ const trimmed = requiredValue.trim();
21450
+ if (trimmed.length > 0) {
21451
+ normalized.add(trimmed);
21452
+ }
21453
+ }
21277
21454
  return Array.from(normalized);
21278
21455
  }
21456
+ function resolveHookDefaultSessionKey(config2, hooks) {
21457
+ if (typeof hooks.defaultSessionKey === "string" && hooks.defaultSessionKey.trim().length > 0) {
21458
+ return hooks.defaultSessionKey.trim();
21459
+ }
21460
+ const session = isRecord8(config2.session) ? config2.session : {};
21461
+ const scope = typeof session.scope === "string" ? session.scope.trim().toLowerCase() : "";
21462
+ if (scope === "global") {
21463
+ return "global";
21464
+ }
21465
+ const agents = isRecord8(config2.agents) ? config2.agents : {};
21466
+ const agentList = Array.isArray(agents.list) ? agents.list : [];
21467
+ const defaultAgentId = resolveDefaultOpenclawAgentId(agentList);
21468
+ return `agent:${defaultAgentId}:${DEFAULT_OPENCLAW_MAIN_SESSION_KEY}`;
21469
+ }
21470
+ function resolveDefaultOpenclawAgentId(agentList) {
21471
+ const preferred = agentList.find(
21472
+ (agent) => isRecord8(agent) && agent.default === true && typeof agent.id === "string" && agent.id.trim().length > 0
21473
+ ) ?? agentList.find(
21474
+ (agent) => isRecord8(agent) && typeof agent.id === "string" && agent.id.trim().length > 0
21475
+ );
21476
+ if (isRecord8(preferred) && typeof preferred.id === "string" && preferred.id.trim().length > 0) {
21477
+ return normalizeOpenclawIdToken(preferred.id, DEFAULT_OPENCLAW_AGENT_ID);
21478
+ }
21479
+ return DEFAULT_OPENCLAW_AGENT_ID;
21480
+ }
21481
+ function normalizeOpenclawIdToken(value, fallback) {
21482
+ const normalized = value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+/g, "").replace(/-+$/g, "").slice(0, 64);
21483
+ return normalized.length > 0 ? normalized : fallback;
21484
+ }
21279
21485
  function generateOpenclawHookToken() {
21280
21486
  return randomBytes3(OPENCLAW_HOOK_TOKEN_BYTES).toString("hex");
21281
21487
  }
21282
21488
  function upsertRelayHookMapping(mappingsValue) {
21283
- const mappings = Array.isArray(mappingsValue) ? mappingsValue.filter(isRecord7).map((mapping) => ({ ...mapping })) : [];
21489
+ const mappings = Array.isArray(mappingsValue) ? mappingsValue.filter(isRecord8).map((mapping) => ({ ...mapping })) : [];
21284
21490
  const existingIndex = mappings.findIndex((mapping) => {
21285
21491
  if (mapping.id === HOOK_MAPPING_ID) {
21286
21492
  return true;
21287
21493
  }
21288
- if (!isRecord7(mapping.match)) {
21494
+ if (!isRecord8(mapping.match)) {
21289
21495
  return false;
21290
21496
  }
21291
21497
  return mapping.match.path === HOOK_PATH_SEND_TO_PEER;
21292
21498
  });
21293
- const baseMapping = existingIndex >= 0 && isRecord7(mappings[existingIndex]) ? mappings[existingIndex] : {};
21294
- const nextMatch = isRecord7(baseMapping.match) ? { ...baseMapping.match } : {};
21499
+ const baseMapping = existingIndex >= 0 && isRecord8(mappings[existingIndex]) ? mappings[existingIndex] : {};
21500
+ const nextMatch = isRecord8(baseMapping.match) ? { ...baseMapping.match } : {};
21295
21501
  nextMatch.path = HOOK_PATH_SEND_TO_PEER;
21296
- const nextTransform = isRecord7(baseMapping.transform) ? { ...baseMapping.transform } : {};
21502
+ const nextTransform = isRecord8(baseMapping.transform) ? { ...baseMapping.transform } : {};
21297
21503
  nextTransform.module = RELAY_MODULE_FILE_NAME;
21298
21504
  const relayMapping = {
21299
21505
  ...baseMapping,
@@ -21316,7 +21522,7 @@ async function patchOpenclawConfig(openclawConfigPath, hookToken) {
21316
21522
  config2 = await readJsonFile(openclawConfigPath);
21317
21523
  } catch (error48) {
21318
21524
  if (getErrorCode2(error48) === "ENOENT") {
21319
- throw createCliError5(
21525
+ throw createCliError6(
21320
21526
  "CLI_OPENCLAW_CONFIG_NOT_FOUND",
21321
21527
  "OpenClaw config file was not found",
21322
21528
  { openclawConfigPath }
@@ -21324,23 +21530,25 @@ async function patchOpenclawConfig(openclawConfigPath, hookToken) {
21324
21530
  }
21325
21531
  throw error48;
21326
21532
  }
21327
- if (!isRecord7(config2)) {
21328
- throw createCliError5(
21533
+ if (!isRecord8(config2)) {
21534
+ throw createCliError6(
21329
21535
  "CLI_OPENCLAW_INVALID_CONFIG",
21330
21536
  "OpenClaw config root must be an object",
21331
21537
  { openclawConfigPath }
21332
21538
  );
21333
21539
  }
21334
- const hooks = isRecord7(config2.hooks) ? { ...config2.hooks } : {};
21540
+ const hooks = isRecord8(config2.hooks) ? { ...config2.hooks } : {};
21335
21541
  const existingHookToken = typeof hooks.token === "string" && hooks.token.trim().length > 0 ? hooks.token.trim() : void 0;
21336
21542
  const preferredHookToken = typeof hookToken === "string" && hookToken.trim().length > 0 ? hookToken.trim() : void 0;
21337
21543
  const resolvedHookToken = existingHookToken ?? preferredHookToken ?? generateOpenclawHookToken();
21544
+ const defaultSessionKey = resolveHookDefaultSessionKey(config2, hooks);
21338
21545
  hooks.enabled = true;
21339
21546
  hooks.token = resolvedHookToken;
21547
+ hooks.defaultSessionKey = defaultSessionKey;
21340
21548
  hooks.allowRequestSessionKey = false;
21341
- hooks.allowedSessionKeyPrefixes = normalizeStringArrayWithValue(
21549
+ hooks.allowedSessionKeyPrefixes = normalizeStringArrayWithValues(
21342
21550
  hooks.allowedSessionKeyPrefixes,
21343
- "hook:"
21551
+ ["hook:", defaultSessionKey]
21344
21552
  );
21345
21553
  hooks.mappings = upsertRelayHookMapping(hooks.mappings);
21346
21554
  const nextConfig = {
@@ -21368,10 +21576,10 @@ function toDoctorResult(checks) {
21368
21576
  };
21369
21577
  }
21370
21578
  function isRelayHookMapping(value) {
21371
- if (!isRecord7(value)) {
21579
+ if (!isRecord8(value)) {
21372
21580
  return false;
21373
21581
  }
21374
- if (!isRecord7(value.match) || value.match.path !== HOOK_PATH_SEND_TO_PEER) {
21582
+ if (!isRecord8(value.match) || value.match.path !== HOOK_PATH_SEND_TO_PEER) {
21375
21583
  return false;
21376
21584
  }
21377
21585
  if (typeof value.id === "string" && value.id !== HOOK_MAPPING_ID) {
@@ -21380,7 +21588,7 @@ function isRelayHookMapping(value) {
21380
21588
  return true;
21381
21589
  }
21382
21590
  function hasRelayTransformModule(value) {
21383
- if (!isRecord7(value) || !isRecord7(value.transform)) {
21591
+ if (!isRecord8(value) || !isRecord8(value.transform)) {
21384
21592
  return false;
21385
21593
  }
21386
21594
  return value.transform.module === RELAY_MODULE_FILE_NAME;
@@ -21459,6 +21667,7 @@ async function runOpenclawDoctor(options = {}) {
21459
21667
  const resolveConfigImpl = options.resolveConfigImpl ?? resolveConfig;
21460
21668
  try {
21461
21669
  const resolvedConfig = await resolveConfigImpl();
21670
+ const envProxyUrl = typeof process.env.CLAWDENTITY_PROXY_URL === "string" ? process.env.CLAWDENTITY_PROXY_URL.trim() : "";
21462
21671
  if (typeof resolvedConfig.registryUrl !== "string" || resolvedConfig.registryUrl.trim().length === 0) {
21463
21672
  checks.push(
21464
21673
  toDoctorCheck({
@@ -21479,15 +21688,68 @@ async function runOpenclawDoctor(options = {}) {
21479
21688
  remediationHint: "Run: clawdentity config set apiKey <API_KEY>"
21480
21689
  })
21481
21690
  );
21482
- } else {
21691
+ } else if (envProxyUrl.length > 0) {
21692
+ let hasValidEnvProxyUrl = true;
21693
+ try {
21694
+ parseProxyUrl(envProxyUrl);
21695
+ } catch {
21696
+ hasValidEnvProxyUrl = false;
21697
+ checks.push(
21698
+ toDoctorCheck({
21699
+ id: "config.registry",
21700
+ label: "CLI config",
21701
+ status: "fail",
21702
+ message: "CLAWDENTITY_PROXY_URL is invalid",
21703
+ remediationHint: "Set CLAWDENTITY_PROXY_URL to a valid http(s) URL or unset it"
21704
+ })
21705
+ );
21706
+ }
21707
+ if (hasValidEnvProxyUrl) {
21708
+ checks.push(
21709
+ toDoctorCheck({
21710
+ id: "config.registry",
21711
+ label: "CLI config",
21712
+ status: "pass",
21713
+ message: "registryUrl and apiKey are configured (proxy URL override is active via CLAWDENTITY_PROXY_URL)"
21714
+ })
21715
+ );
21716
+ }
21717
+ } else if (typeof resolvedConfig.proxyUrl !== "string" || resolvedConfig.proxyUrl.trim().length === 0) {
21483
21718
  checks.push(
21484
21719
  toDoctorCheck({
21485
21720
  id: "config.registry",
21486
21721
  label: "CLI config",
21487
- status: "pass",
21488
- message: "registryUrl and apiKey are configured"
21722
+ status: "fail",
21723
+ message: "proxyUrl is missing",
21724
+ remediationHint: "Run: clawdentity invite redeem <clw_inv_...> or clawdentity config init"
21489
21725
  })
21490
21726
  );
21727
+ } else {
21728
+ let hasValidConfigProxyUrl = true;
21729
+ try {
21730
+ parseProxyUrl(resolvedConfig.proxyUrl);
21731
+ } catch {
21732
+ hasValidConfigProxyUrl = false;
21733
+ checks.push(
21734
+ toDoctorCheck({
21735
+ id: "config.registry",
21736
+ label: "CLI config",
21737
+ status: "fail",
21738
+ message: "proxyUrl is invalid",
21739
+ remediationHint: "Run: clawdentity invite redeem <clw_inv_...> or clawdentity config init"
21740
+ })
21741
+ );
21742
+ }
21743
+ if (hasValidConfigProxyUrl) {
21744
+ checks.push(
21745
+ toDoctorCheck({
21746
+ id: "config.registry",
21747
+ label: "CLI config",
21748
+ status: "pass",
21749
+ message: "registryUrl, apiKey, and proxyUrl are configured"
21750
+ })
21751
+ );
21752
+ }
21491
21753
  }
21492
21754
  } catch {
21493
21755
  checks.push(
@@ -21639,7 +21901,7 @@ async function runOpenclawDoctor(options = {}) {
21639
21901
  label: "Relay transform",
21640
21902
  status: "fail",
21641
21903
  message: "relay transform artifacts are missing or empty",
21642
- remediationHint: "Run: npm install clawdentity --skill",
21904
+ remediationHint: "Run: clawdentity skill install",
21643
21905
  details: {
21644
21906
  transformTargetPath,
21645
21907
  relayTransformRuntimePath,
@@ -21669,7 +21931,7 @@ async function runOpenclawDoctor(options = {}) {
21669
21931
  label: "Relay transform",
21670
21932
  status: "fail",
21671
21933
  message: "missing relay transform artifacts",
21672
- remediationHint: "Run: npm install clawdentity --skill",
21934
+ remediationHint: "Run: clawdentity skill install",
21673
21935
  details: {
21674
21936
  transformTargetPath,
21675
21937
  relayTransformRuntimePath,
@@ -21681,13 +21943,13 @@ async function runOpenclawDoctor(options = {}) {
21681
21943
  const openclawConfigPath = resolveOpenclawConfigPath(openclawDir, homeDir);
21682
21944
  try {
21683
21945
  const openclawConfig = await readJsonFile(openclawConfigPath);
21684
- if (!isRecord7(openclawConfig)) {
21946
+ if (!isRecord8(openclawConfig)) {
21685
21947
  throw new Error("root");
21686
21948
  }
21687
- const hooks = isRecord7(openclawConfig.hooks) ? openclawConfig.hooks : {};
21949
+ const hooks = isRecord8(openclawConfig.hooks) ? openclawConfig.hooks : {};
21688
21950
  const hooksEnabled = hooks.enabled === true;
21689
21951
  const hookToken = typeof hooks.token === "string" && hooks.token.trim().length > 0 ? hooks.token.trim() : void 0;
21690
- const mappings = Array.isArray(hooks.mappings) ? hooks.mappings.filter(isRecord7) : [];
21952
+ const mappings = Array.isArray(hooks.mappings) ? hooks.mappings.filter(isRecord8) : [];
21691
21953
  const relayMapping = mappings.find(
21692
21954
  (mapping) => isRelayHookMapping(mapping)
21693
21955
  );
@@ -21835,13 +22097,13 @@ async function resolveRelayProbePeerAlias(input) {
21835
22097
  return peerAliases[0];
21836
22098
  }
21837
22099
  if (peerAliases.length === 0) {
21838
- throw createCliError5(
22100
+ throw createCliError6(
21839
22101
  "CLI_OPENCLAW_RELAY_TEST_PEER_REQUIRED",
21840
22102
  "No paired peer is configured yet. Complete QR pairing first.",
21841
22103
  { peersPath }
21842
22104
  );
21843
22105
  }
21844
- throw createCliError5(
22106
+ throw createCliError6(
21845
22107
  "CLI_OPENCLAW_RELAY_TEST_PEER_REQUIRED",
21846
22108
  "Multiple peers are configured. Pass --peer <alias> to choose one.",
21847
22109
  { peersPath, peerAliases }
@@ -21998,7 +22260,7 @@ async function setupOpenclawRelay(agentName, options) {
21998
22260
  await copyFile(transformSource, transformTargetPath);
21999
22261
  } catch (error48) {
22000
22262
  if (getErrorCode2(error48) === "ENOENT") {
22001
- throw createCliError5(
22263
+ throw createCliError6(
22002
22264
  "CLI_OPENCLAW_TRANSFORM_NOT_FOUND",
22003
22265
  "Relay transform source file was not found",
22004
22266
  { transformSource }
@@ -22093,7 +22355,7 @@ var createOpenclawCommand = () => {
22093
22355
  "OpenClaw state directory (default ~/.openclaw)"
22094
22356
  ).option(
22095
22357
  "--transform-source <path>",
22096
- "Path to relay-to-peer.mjs (default <openclaw-dir>/workspace/skills/clawdentity-openclaw-relay/relay-to-peer.mjs)"
22358
+ "Path to relay-to-peer.mjs (default <openclaw-dir>/skills/clawdentity-openclaw-relay/relay-to-peer.mjs)"
22097
22359
  ).option(
22098
22360
  "--openclaw-base-url <url>",
22099
22361
  "Base URL for local OpenClaw hook API (default http://127.0.0.1:18789)"
@@ -22216,26 +22478,26 @@ var PAIRING_QR_MAX_AGE_SECONDS = 900;
22216
22478
  var PAIRING_QR_FILENAME_PATTERN = /-pair-(\d+)\.png$/;
22217
22479
  var FILE_MODE4 = 384;
22218
22480
  var PEER_ALIAS_PATTERN2 = /^[a-zA-Z0-9._-]+$/;
22219
- var isRecord8 = (value) => {
22481
+ var isRecord9 = (value) => {
22220
22482
  return typeof value === "object" && value !== null;
22221
22483
  };
22222
- function createCliError6(code, message2) {
22484
+ function createCliError7(code, message2) {
22223
22485
  return new AppError({
22224
22486
  code,
22225
22487
  message: message2,
22226
22488
  status: 400
22227
22489
  });
22228
22490
  }
22229
- function parseNonEmptyString8(value) {
22491
+ function parseNonEmptyString9(value) {
22230
22492
  if (typeof value !== "string") {
22231
22493
  return "";
22232
22494
  }
22233
22495
  return value.trim();
22234
22496
  }
22235
22497
  function parsePairingTicket(value) {
22236
- const ticket = parseNonEmptyString8(value);
22498
+ const ticket = parseNonEmptyString9(value);
22237
22499
  if (!ticket.startsWith(PAIRING_TICKET_PREFIX)) {
22238
- throw createCliError6(
22500
+ throw createCliError7(
22239
22501
  "CLI_PAIR_CONFIRM_TICKET_INVALID",
22240
22502
  "Pairing ticket is invalid"
22241
22503
  );
@@ -22245,7 +22507,7 @@ function parsePairingTicket(value) {
22245
22507
  function parsePairingTicketIssuerOrigin(ticket) {
22246
22508
  const encodedPayload = ticket.slice(PAIRING_TICKET_PREFIX.length);
22247
22509
  if (encodedPayload.length === 0) {
22248
- throw createCliError6(
22510
+ throw createCliError7(
22249
22511
  "CLI_PAIR_CONFIRM_TICKET_INVALID",
22250
22512
  "Pairing ticket is invalid"
22251
22513
  );
@@ -22254,7 +22516,7 @@ function parsePairingTicketIssuerOrigin(ticket) {
22254
22516
  try {
22255
22517
  payloadRaw = new TextDecoder().decode(decodeBase64url(encodedPayload));
22256
22518
  } catch {
22257
- throw createCliError6(
22519
+ throw createCliError7(
22258
22520
  "CLI_PAIR_CONFIRM_TICKET_INVALID",
22259
22521
  "Pairing ticket is invalid"
22260
22522
  );
@@ -22263,13 +22525,13 @@ function parsePairingTicketIssuerOrigin(ticket) {
22263
22525
  try {
22264
22526
  payload = JSON.parse(payloadRaw);
22265
22527
  } catch {
22266
- throw createCliError6(
22528
+ throw createCliError7(
22267
22529
  "CLI_PAIR_CONFIRM_TICKET_INVALID",
22268
22530
  "Pairing ticket is invalid"
22269
22531
  );
22270
22532
  }
22271
- if (!isRecord8(payload) || typeof payload.iss !== "string") {
22272
- throw createCliError6(
22533
+ if (!isRecord9(payload) || typeof payload.iss !== "string") {
22534
+ throw createCliError7(
22273
22535
  "CLI_PAIR_CONFIRM_TICKET_INVALID",
22274
22536
  "Pairing ticket is invalid"
22275
22537
  );
@@ -22278,13 +22540,13 @@ function parsePairingTicketIssuerOrigin(ticket) {
22278
22540
  try {
22279
22541
  issuerUrl = new URL(payload.iss);
22280
22542
  } catch {
22281
- throw createCliError6(
22543
+ throw createCliError7(
22282
22544
  "CLI_PAIR_CONFIRM_TICKET_INVALID",
22283
22545
  "Pairing ticket is invalid"
22284
22546
  );
22285
22547
  }
22286
22548
  if (issuerUrl.protocol !== "https:" && issuerUrl.protocol !== "http:") {
22287
- throw createCliError6(
22549
+ throw createCliError7(
22288
22550
  "CLI_PAIR_CONFIRM_TICKET_INVALID",
22289
22551
  "Pairing ticket is invalid"
22290
22552
  );
@@ -22293,13 +22555,13 @@ function parsePairingTicketIssuerOrigin(ticket) {
22293
22555
  }
22294
22556
  function parsePeerAlias2(value) {
22295
22557
  if (value.length === 0 || value.length > 128) {
22296
- throw createCliError6(
22558
+ throw createCliError7(
22297
22559
  "CLI_PAIR_PEER_ALIAS_INVALID",
22298
22560
  "Generated peer alias is invalid"
22299
22561
  );
22300
22562
  }
22301
22563
  if (!PEER_ALIAS_PATTERN2.test(value)) {
22302
- throw createCliError6(
22564
+ throw createCliError7(
22303
22565
  "CLI_PAIR_PEER_ALIAS_INVALID",
22304
22566
  "Generated peer alias is invalid"
22305
22567
  );
@@ -22336,16 +22598,16 @@ function resolvePeersConfigPath(getConfigDirImpl) {
22336
22598
  return join7(getConfigDirImpl(), PEERS_FILE_NAME2);
22337
22599
  }
22338
22600
  function parsePeerEntry(value) {
22339
- if (!isRecord8(value)) {
22340
- throw createCliError6(
22601
+ if (!isRecord9(value)) {
22602
+ throw createCliError7(
22341
22603
  "CLI_PAIR_PEERS_CONFIG_INVALID",
22342
22604
  "Peer entry must be an object"
22343
22605
  );
22344
22606
  }
22345
- const did = parseNonEmptyString8(value.did);
22346
- const proxyUrl = parseNonEmptyString8(value.proxyUrl);
22607
+ const did = parseNonEmptyString9(value.did);
22608
+ const proxyUrl = parseNonEmptyString9(value.proxyUrl);
22347
22609
  if (did.length === 0 || proxyUrl.length === 0) {
22348
- throw createCliError6(
22610
+ throw createCliError7(
22349
22611
  "CLI_PAIR_PEERS_CONFIG_INVALID",
22350
22612
  "Peer entry is invalid"
22351
22613
  );
@@ -22371,13 +22633,13 @@ async function loadPeersConfig2(input) {
22371
22633
  try {
22372
22634
  parsed = JSON.parse(raw);
22373
22635
  } catch {
22374
- throw createCliError6(
22636
+ throw createCliError7(
22375
22637
  "CLI_PAIR_PEERS_CONFIG_INVALID",
22376
22638
  "Peer config is not valid JSON"
22377
22639
  );
22378
22640
  }
22379
- if (!isRecord8(parsed)) {
22380
- throw createCliError6(
22641
+ if (!isRecord9(parsed)) {
22642
+ throw createCliError7(
22381
22643
  "CLI_PAIR_PEERS_CONFIG_INVALID",
22382
22644
  "Peer config must be a JSON object"
22383
22645
  );
@@ -22385,8 +22647,8 @@ async function loadPeersConfig2(input) {
22385
22647
  if (parsed.peers === void 0) {
22386
22648
  return { peers: {} };
22387
22649
  }
22388
- if (!isRecord8(parsed.peers)) {
22389
- throw createCliError6(
22650
+ if (!isRecord9(parsed.peers)) {
22651
+ throw createCliError7(
22390
22652
  "CLI_PAIR_PEERS_CONFIG_INVALID",
22391
22653
  "Peer config peers field must be an object"
22392
22654
  );
@@ -22409,13 +22671,13 @@ async function savePeersConfig2(input) {
22409
22671
  await input.chmodImpl(peersPath, FILE_MODE4);
22410
22672
  }
22411
22673
  function parseTtlSeconds(value) {
22412
- const raw = parseNonEmptyString8(value);
22674
+ const raw = parseNonEmptyString9(value);
22413
22675
  if (raw.length === 0) {
22414
22676
  return void 0;
22415
22677
  }
22416
22678
  const parsed = Number.parseInt(raw, 10);
22417
22679
  if (!Number.isInteger(parsed) || parsed < 1) {
22418
- throw createCliError6(
22680
+ throw createCliError7(
22419
22681
  "CLI_PAIR_START_INVALID_TTL",
22420
22682
  "ttlSeconds must be a positive integer"
22421
22683
  );
@@ -22430,36 +22692,31 @@ function parseProxyUrl2(candidate) {
22430
22692
  }
22431
22693
  return parsed.toString();
22432
22694
  } catch {
22433
- throw createCliError6("CLI_PAIR_INVALID_PROXY_URL", "Proxy URL is invalid");
22695
+ throw createCliError7("CLI_PAIR_INVALID_PROXY_URL", "Proxy URL is invalid");
22434
22696
  }
22435
22697
  }
22436
- function resolveProxyUrlCandidates(input) {
22437
- const explicit = parseNonEmptyString8(input.overrideProxyUrl);
22438
- if (explicit.length > 0) {
22439
- return [parseProxyUrl2(explicit)];
22440
- }
22441
- const fromEnv = parseNonEmptyString8(process.env.CLAWDENTITY_PROXY_URL);
22698
+ async function resolveProxyUrl(input) {
22699
+ const fromEnv = parseNonEmptyString9(process.env.CLAWDENTITY_PROXY_URL);
22442
22700
  if (fromEnv.length > 0) {
22443
- return [parseProxyUrl2(fromEnv)];
22701
+ return parseProxyUrl2(fromEnv);
22444
22702
  }
22445
- const fromConfig = parseNonEmptyString8(input.config.proxyUrl);
22446
- if (fromConfig.length > 0) {
22447
- return [parseProxyUrl2(fromConfig)];
22703
+ const metadata = await fetchRegistryMetadata(input.config.registryUrl, {
22704
+ fetchImpl: input.fetchImpl
22705
+ });
22706
+ const metadataProxyUrl = parseProxyUrl2(metadata.proxyUrl);
22707
+ const configuredProxyUrl = parseNonEmptyString9(input.config.proxyUrl);
22708
+ if (configuredProxyUrl.length === 0) {
22709
+ return metadataProxyUrl;
22448
22710
  }
22449
- const derivedFromRegistry = deriveProxyUrlFromRegistryUrl(
22450
- input.config.registryUrl || DEFAULT_REGISTRY_URL
22451
- );
22452
- if (typeof derivedFromRegistry === "string" && derivedFromRegistry.length > 0) {
22453
- return [parseProxyUrl2(derivedFromRegistry)];
22711
+ const normalizedConfiguredProxyUrl = parseProxyUrl2(configuredProxyUrl);
22712
+ if (normalizedConfiguredProxyUrl === metadataProxyUrl) {
22713
+ return metadataProxyUrl;
22454
22714
  }
22455
- throw createCliError6(
22456
- "CLI_PAIR_PROXY_URL_REQUIRED",
22457
- "Proxy URL could not be resolved. Run onboarding invite redeem again or set proxyUrl via `clawdentity config set proxyUrl <url>`."
22715
+ throw createCliError7(
22716
+ "CLI_PAIR_PROXY_URL_MISMATCH",
22717
+ `Configured proxy URL does not match registry metadata. config=${normalizedConfiguredProxyUrl} metadata=${metadataProxyUrl}. Rerun onboarding invite redeem to refresh config.`
22458
22718
  );
22459
22719
  }
22460
- function resolveProxyUrl(input) {
22461
- return resolveProxyUrlCandidates(input)[0];
22462
- }
22463
22720
  function toProxyRequestUrl(proxyUrl, path) {
22464
22721
  const normalizedBase = proxyUrl.endsWith("/") ? proxyUrl : `${proxyUrl}/`;
22465
22722
  return new URL(path.slice(1), normalizedBase).toString();
@@ -22469,7 +22726,7 @@ function toPathWithQuery3(url2) {
22469
22726
  return `${parsed.pathname}${parsed.search}`;
22470
22727
  }
22471
22728
  function extractErrorCode(payload) {
22472
- if (!isRecord8(payload)) {
22729
+ if (!isRecord9(payload)) {
22473
22730
  return void 0;
22474
22731
  }
22475
22732
  const envelope = payload;
@@ -22480,7 +22737,7 @@ function extractErrorCode(payload) {
22480
22737
  return code.length > 0 ? code : void 0;
22481
22738
  }
22482
22739
  function extractErrorMessage(payload) {
22483
- if (!isRecord8(payload)) {
22740
+ if (!isRecord9(payload)) {
22484
22741
  return void 0;
22485
22742
  }
22486
22743
  const envelope = payload;
@@ -22490,7 +22747,7 @@ function extractErrorMessage(payload) {
22490
22747
  const message2 = envelope.error.message.trim();
22491
22748
  return message2.length > 0 ? message2 : void 0;
22492
22749
  }
22493
- async function parseJsonResponse5(response) {
22750
+ async function parseJsonResponse6(response) {
22494
22751
  try {
22495
22752
  return await response.json();
22496
22753
  } catch {
@@ -22501,7 +22758,7 @@ async function executePairRequest(input) {
22501
22758
  try {
22502
22759
  return await input.fetchImpl(input.url, input.init);
22503
22760
  } catch {
22504
- throw createCliError6(
22761
+ throw createCliError7(
22505
22762
  "CLI_PAIR_REQUEST_FAILED",
22506
22763
  "Unable to connect to proxy URL. Check network access and proxyUrl."
22507
22764
  );
@@ -22548,17 +22805,17 @@ function mapConfirmPairError(status, payload) {
22548
22805
  return `Pair confirm failed (${status})`;
22549
22806
  }
22550
22807
  function parsePairStartResponse(payload) {
22551
- if (!isRecord8(payload)) {
22552
- throw createCliError6(
22808
+ if (!isRecord9(payload)) {
22809
+ throw createCliError7(
22553
22810
  "CLI_PAIR_START_INVALID_RESPONSE",
22554
22811
  "Pair start response is invalid"
22555
22812
  );
22556
22813
  }
22557
22814
  const ticket = parsePairingTicket(payload.ticket);
22558
- const initiatorAgentDid = parseNonEmptyString8(payload.initiatorAgentDid);
22559
- const expiresAt = parseNonEmptyString8(payload.expiresAt);
22815
+ const initiatorAgentDid = parseNonEmptyString9(payload.initiatorAgentDid);
22816
+ const expiresAt = parseNonEmptyString9(payload.expiresAt);
22560
22817
  if (initiatorAgentDid.length === 0 || expiresAt.length === 0) {
22561
- throw createCliError6(
22818
+ throw createCliError7(
22562
22819
  "CLI_PAIR_START_INVALID_RESPONSE",
22563
22820
  "Pair start response is invalid"
22564
22821
  );
@@ -22570,17 +22827,17 @@ function parsePairStartResponse(payload) {
22570
22827
  };
22571
22828
  }
22572
22829
  function parsePairConfirmResponse(payload) {
22573
- if (!isRecord8(payload)) {
22574
- throw createCliError6(
22830
+ if (!isRecord9(payload)) {
22831
+ throw createCliError7(
22575
22832
  "CLI_PAIR_CONFIRM_INVALID_RESPONSE",
22576
22833
  "Pair confirm response is invalid"
22577
22834
  );
22578
22835
  }
22579
22836
  const paired = payload.paired === true;
22580
- const initiatorAgentDid = parseNonEmptyString8(payload.initiatorAgentDid);
22581
- const responderAgentDid = parseNonEmptyString8(payload.responderAgentDid);
22837
+ const initiatorAgentDid = parseNonEmptyString9(payload.initiatorAgentDid);
22838
+ const responderAgentDid = parseNonEmptyString9(payload.responderAgentDid);
22582
22839
  if (!paired || initiatorAgentDid.length === 0 || responderAgentDid.length === 0) {
22583
- throw createCliError6(
22840
+ throw createCliError7(
22584
22841
  "CLI_PAIR_CONFIRM_INVALID_RESPONSE",
22585
22842
  "Pair confirm response is invalid"
22586
22843
  );
@@ -22608,7 +22865,7 @@ async function readAgentProofMaterial(agentName, dependencies) {
22608
22865
  } catch (error48) {
22609
22866
  const nodeError = error48;
22610
22867
  if (nodeError.code === "ENOENT") {
22611
- throw createCliError6(
22868
+ throw createCliError7(
22612
22869
  "CLI_PAIR_AGENT_NOT_FOUND",
22613
22870
  `Agent "${normalizedAgentName}" is missing ${AIT_FILE_NAME4}. Run agent create first.`
22614
22871
  );
@@ -22616,7 +22873,7 @@ async function readAgentProofMaterial(agentName, dependencies) {
22616
22873
  throw error48;
22617
22874
  }
22618
22875
  if (ait.length === 0) {
22619
- throw createCliError6(
22876
+ throw createCliError7(
22620
22877
  "CLI_PAIR_AGENT_NOT_FOUND",
22621
22878
  `Agent "${normalizedAgentName}" has an empty ${AIT_FILE_NAME4}`
22622
22879
  );
@@ -22627,7 +22884,7 @@ async function readAgentProofMaterial(agentName, dependencies) {
22627
22884
  } catch (error48) {
22628
22885
  const nodeError = error48;
22629
22886
  if (nodeError.code === "ENOENT") {
22630
- throw createCliError6(
22887
+ throw createCliError7(
22631
22888
  "CLI_PAIR_AGENT_NOT_FOUND",
22632
22889
  `Agent "${normalizedAgentName}" is missing ${SECRET_KEY_FILE_NAME3}. Run agent create first.`
22633
22890
  );
@@ -22635,7 +22892,7 @@ async function readAgentProofMaterial(agentName, dependencies) {
22635
22892
  throw error48;
22636
22893
  }
22637
22894
  if (encodedSecretKey.length === 0) {
22638
- throw createCliError6(
22895
+ throw createCliError7(
22639
22896
  "CLI_PAIR_AGENT_NOT_FOUND",
22640
22897
  `Agent "${normalizedAgentName}" has an empty ${SECRET_KEY_FILE_NAME3}`
22641
22898
  );
@@ -22644,7 +22901,7 @@ async function readAgentProofMaterial(agentName, dependencies) {
22644
22901
  try {
22645
22902
  secretKey = decodeBase64url(encodedSecretKey);
22646
22903
  } catch {
22647
- throw createCliError6(
22904
+ throw createCliError7(
22648
22905
  "CLI_PAIR_AGENT_NOT_FOUND",
22649
22906
  `Agent "${normalizedAgentName}" has invalid ${SECRET_KEY_FILE_NAME3}`
22650
22907
  );
@@ -22655,11 +22912,11 @@ async function readAgentProofMaterial(agentName, dependencies) {
22655
22912
  };
22656
22913
  }
22657
22914
  function resolveOwnerPat(options) {
22658
- const ownerPat = parseNonEmptyString8(options.explicitOwnerPat) || parseNonEmptyString8(options.config.apiKey);
22915
+ const ownerPat = parseNonEmptyString9(options.explicitOwnerPat) || parseNonEmptyString9(options.config.apiKey);
22659
22916
  if (ownerPat.length > 0) {
22660
22917
  return ownerPat;
22661
22918
  }
22662
- throw createCliError6(
22919
+ throw createCliError7(
22663
22920
  "CLI_PAIR_START_OWNER_PAT_REQUIRED",
22664
22921
  "Owner PAT is required. Pass --owner-pat <token> or configure API key with `clawdentity invite redeem` / `clawdentity config set apiKey <token>`."
22665
22922
  );
@@ -22689,7 +22946,7 @@ function decodeTicketFromPng(imageBytes) {
22689
22946
  try {
22690
22947
  decodedPng = PNG.sync.read(Buffer.from(imageBytes));
22691
22948
  } catch {
22692
- throw createCliError6(
22949
+ throw createCliError7(
22693
22950
  "CLI_PAIR_CONFIRM_QR_FILE_INVALID",
22694
22951
  "QR image file is invalid or unsupported"
22695
22952
  );
@@ -22700,8 +22957,8 @@ function decodeTicketFromPng(imageBytes) {
22700
22957
  decodedPng.data.byteLength
22701
22958
  );
22702
22959
  const decoded = jsQR(imageData, decodedPng.width, decodedPng.height);
22703
- if (!decoded || parseNonEmptyString8(decoded.data).length === 0) {
22704
- throw createCliError6(
22960
+ if (!decoded || parseNonEmptyString9(decoded.data).length === 0) {
22961
+ throw createCliError7(
22705
22962
  "CLI_PAIR_CONFIRM_QR_NOT_FOUND",
22706
22963
  "No pairing QR code was found in the image"
22707
22964
  );
@@ -22716,7 +22973,7 @@ async function persistPairingQr(input) {
22716
22973
  const getConfigDirImpl = input.dependencies.getConfigDirImpl ?? getConfigDir;
22717
22974
  const qrEncodeImpl = input.dependencies.qrEncodeImpl ?? encodeTicketQrPng;
22718
22975
  const baseDir = join7(getConfigDirImpl(), PAIRING_QR_DIR_NAME);
22719
- const outputPath = parseNonEmptyString8(input.qrOutput) ? resolve(input.qrOutput ?? "") : join7(
22976
+ const outputPath = parseNonEmptyString9(input.qrOutput) ? resolve(input.qrOutput ?? "") : join7(
22720
22977
  baseDir,
22721
22978
  `${assertValidAgentName(input.agentName)}-pair-${input.nowSeconds}.png`
22722
22979
  );
@@ -22757,10 +23014,10 @@ async function persistPairingQr(input) {
22757
23014
  return outputPath;
22758
23015
  }
22759
23016
  function resolveConfirmTicketSource(options) {
22760
- const inlineTicket = parseNonEmptyString8(options.ticket);
22761
- const qrFile = parseNonEmptyString8(options.qrFile);
23017
+ const inlineTicket = parseNonEmptyString9(options.ticket);
23018
+ const qrFile = parseNonEmptyString9(options.qrFile);
22762
23019
  if (inlineTicket.length > 0 && qrFile.length > 0) {
22763
- throw createCliError6(
23020
+ throw createCliError7(
22764
23021
  "CLI_PAIR_CONFIRM_INPUT_CONFLICT",
22765
23022
  "Provide either --ticket or --qr-file, not both"
22766
23023
  );
@@ -22778,7 +23035,7 @@ function resolveConfirmTicketSource(options) {
22778
23035
  qrFilePath: resolve(qrFile)
22779
23036
  };
22780
23037
  }
22781
- throw createCliError6(
23038
+ throw createCliError7(
22782
23039
  "CLI_PAIR_CONFIRM_TICKET_REQUIRED",
22783
23040
  "Pairing ticket is required. Pass --ticket <clwpair1_...> or --qr-file <path>."
22784
23041
  );
@@ -22819,14 +23076,14 @@ async function startPairing(agentName, options, dependencies = {}) {
22819
23076
  const nonceFactoryImpl = dependencies.nonceFactoryImpl ?? (() => randomBytes4(NONCE_SIZE2).toString("base64url"));
22820
23077
  const ttlSeconds = parseTtlSeconds(options.ttlSeconds);
22821
23078
  const config2 = await resolveConfigImpl();
22822
- const proxyUrl = resolveProxyUrl({
22823
- overrideProxyUrl: options.proxyUrl,
22824
- config: config2
22825
- });
22826
23079
  const ownerPat = resolveOwnerPat({
22827
23080
  explicitOwnerPat: options.ownerPat,
22828
23081
  config: config2
22829
23082
  });
23083
+ const proxyUrl = await resolveProxyUrl({
23084
+ config: config2,
23085
+ fetchImpl
23086
+ });
22830
23087
  const { ait, secretKey } = await readAgentProofMaterial(
22831
23088
  agentName,
22832
23089
  dependencies
@@ -22860,9 +23117,9 @@ async function startPairing(agentName, options, dependencies = {}) {
22860
23117
  body: requestBody
22861
23118
  }
22862
23119
  });
22863
- const responseBody = await parseJsonResponse5(response);
23120
+ const responseBody = await parseJsonResponse6(response);
22864
23121
  if (!response.ok) {
22865
- throw createCliError6(
23122
+ throw createCliError7(
22866
23123
  "CLI_PAIR_START_FAILED",
22867
23124
  mapStartPairError(response.status, responseBody)
22868
23125
  );
@@ -22892,14 +23149,14 @@ async function confirmPairing(agentName, options, dependencies = {}) {
22892
23149
  const qrDecodeImpl = dependencies.qrDecodeImpl ?? decodeTicketFromPng;
22893
23150
  const config2 = await resolveConfigImpl();
22894
23151
  const ticketSource = resolveConfirmTicketSource(options);
22895
- const proxyUrl = resolveProxyUrl({
22896
- overrideProxyUrl: options.proxyUrl,
22897
- config: config2
23152
+ const proxyUrl = await resolveProxyUrl({
23153
+ config: config2,
23154
+ fetchImpl
22898
23155
  });
22899
23156
  let ticket = ticketSource.ticket;
22900
23157
  if (ticketSource.source === "qr-file") {
22901
23158
  if (!ticketSource.qrFilePath) {
22902
- throw createCliError6(
23159
+ throw createCliError7(
22903
23160
  "CLI_PAIR_CONFIRM_QR_FILE_REQUIRED",
22904
23161
  "QR file path is required"
22905
23162
  );
@@ -22910,7 +23167,7 @@ async function confirmPairing(agentName, options, dependencies = {}) {
22910
23167
  } catch (error48) {
22911
23168
  const nodeError = error48;
22912
23169
  if (nodeError.code === "ENOENT") {
22913
- throw createCliError6(
23170
+ throw createCliError7(
22914
23171
  "CLI_PAIR_CONFIRM_QR_FILE_NOT_FOUND",
22915
23172
  `QR file not found: ${ticketSource.qrFilePath}`
22916
23173
  );
@@ -22949,9 +23206,9 @@ async function confirmPairing(agentName, options, dependencies = {}) {
22949
23206
  body: requestBody
22950
23207
  }
22951
23208
  });
22952
- const responseBody = await parseJsonResponse5(response);
23209
+ const responseBody = await parseJsonResponse6(response);
22953
23210
  if (!response.ok) {
22954
- throw createCliError6(
23211
+ throw createCliError7(
22955
23212
  "CLI_PAIR_CONFIRM_FAILED",
22956
23213
  mapConfirmPairError(response.status, responseBody)
22957
23214
  );
@@ -22985,7 +23242,7 @@ var createPairCommand = (dependencies = {}) => {
22985
23242
  const pairCommand = new Command8("pair").description(
22986
23243
  "Manage proxy trust pairing between agents"
22987
23244
  );
22988
- pairCommand.command("start <agentName>").description("Start pairing and issue one-time pairing ticket").option("--proxy-url <url>", "Optional initiator proxy base URL override").option(
23245
+ pairCommand.command("start <agentName>").description("Start pairing and issue one-time pairing ticket").option(
22989
23246
  "--owner-pat <token>",
22990
23247
  "Owner PAT override (defaults to configured API key)"
22991
23248
  ).option("--ttl-seconds <seconds>", "Pairing ticket expiry in seconds").option("--qr", "Generate a local QR file for sharing").option("--qr-output <path>", "Write QR PNG to a specific file path").action(
@@ -23009,7 +23266,7 @@ var createPairCommand = (dependencies = {}) => {
23009
23266
  }
23010
23267
  )
23011
23268
  );
23012
- pairCommand.command("confirm <agentName>").description("Confirm pairing using one-time pairing ticket").option("--ticket <ticket>", "One-time pairing ticket (clwpair1_...)").option("--qr-file <path>", "Path to pairing QR PNG file").option("--proxy-url <url>", "Optional responder proxy base URL override").action(
23269
+ pairCommand.command("confirm <agentName>").description("Confirm pairing using one-time pairing ticket").option("--ticket <ticket>", "One-time pairing ticket (clwpair1_...)").option("--qr-file <path>", "Path to pairing QR PNG file").action(
23013
23270
  withErrorHandling(
23014
23271
  "pair confirm",
23015
23272
  async (agentName, options) => {
@@ -23033,9 +23290,341 @@ var createPairCommand = (dependencies = {}) => {
23033
23290
  return pairCommand;
23034
23291
  };
23035
23292
 
23036
- // src/commands/verify.ts
23037
- import { readFile as readFile6 } from "fs/promises";
23293
+ // src/commands/skill.ts
23038
23294
  import { Command as Command9 } from "commander";
23295
+
23296
+ // src/install-skill-mode.ts
23297
+ import { constants, existsSync as existsSync2 } from "fs";
23298
+ import { access as access3, copyFile as copyFile2, mkdir as mkdir7, readdir as readdir2, readFile as readFile6 } from "fs/promises";
23299
+ import { createRequire } from "module";
23300
+ import { homedir as homedir4 } from "os";
23301
+ import { dirname as dirname6, join as join8, relative } from "path";
23302
+ import { fileURLToPath as fileURLToPath2 } from "url";
23303
+ var OPENCLAW_DIR_NAME2 = ".openclaw";
23304
+ var SKILL_PACKAGE_NAME = "@clawdentity/openclaw-skill";
23305
+ var SKILL_DIR_NAME2 = "clawdentity-openclaw-relay";
23306
+ var RELAY_MODULE_FILE_NAME2 = "relay-to-peer.mjs";
23307
+ function isRecord10(value) {
23308
+ return typeof value === "object" && value !== null;
23309
+ }
23310
+ var SkillInstallError = class extends Error {
23311
+ code;
23312
+ details;
23313
+ constructor(input) {
23314
+ super(input.message);
23315
+ this.name = "SkillInstallError";
23316
+ this.code = input.code;
23317
+ this.details = input.details ?? {};
23318
+ }
23319
+ };
23320
+ function getErrorCode3(error48) {
23321
+ if (!isRecord10(error48)) {
23322
+ return void 0;
23323
+ }
23324
+ return typeof error48.code === "string" ? error48.code : void 0;
23325
+ }
23326
+ function resolveHomeDir2(inputHomeDir) {
23327
+ if (typeof inputHomeDir === "string" && inputHomeDir.trim().length > 0) {
23328
+ return inputHomeDir.trim();
23329
+ }
23330
+ return homedir4();
23331
+ }
23332
+ function resolveOpenclawDir2(homeDir, inputOpenclawDir) {
23333
+ if (typeof inputOpenclawDir === "string" && inputOpenclawDir.trim().length > 0) {
23334
+ return inputOpenclawDir.trim();
23335
+ }
23336
+ return join8(homeDir, OPENCLAW_DIR_NAME2);
23337
+ }
23338
+ function resolveSkillPackageRoot(input) {
23339
+ if (typeof input.skillPackageRoot === "string" && input.skillPackageRoot.trim().length > 0) {
23340
+ return input.skillPackageRoot.trim();
23341
+ }
23342
+ const overriddenRoot = input.env.CLAWDENTITY_SKILL_PACKAGE_ROOT;
23343
+ if (typeof overriddenRoot === "string" && overriddenRoot.trim().length > 0) {
23344
+ return overriddenRoot.trim();
23345
+ }
23346
+ const bundledSkillRoot = join8(
23347
+ dirname6(fileURLToPath2(import.meta.url)),
23348
+ "..",
23349
+ "skill-bundle",
23350
+ "openclaw-skill"
23351
+ );
23352
+ if (existsSync2(bundledSkillRoot)) {
23353
+ return bundledSkillRoot;
23354
+ }
23355
+ const require3 = createRequire(import.meta.url);
23356
+ let packageJsonPath;
23357
+ try {
23358
+ packageJsonPath = require3.resolve(`${SKILL_PACKAGE_NAME}/package.json`);
23359
+ return dirname6(packageJsonPath);
23360
+ } catch {
23361
+ const workspaceFallbackRoot = join8(
23362
+ dirname6(fileURLToPath2(import.meta.url)),
23363
+ "..",
23364
+ "..",
23365
+ "openclaw-skill"
23366
+ );
23367
+ if (existsSync2(workspaceFallbackRoot)) {
23368
+ return workspaceFallbackRoot;
23369
+ }
23370
+ throw new SkillInstallError({
23371
+ code: "CLI_SKILL_PACKAGE_NOT_FOUND",
23372
+ message: "Skill artifacts are unavailable. Set CLAWDENTITY_SKILL_PACKAGE_ROOT or provide bundled skill assets before running skill install.",
23373
+ details: {
23374
+ packageName: SKILL_PACKAGE_NAME,
23375
+ bundledSkillRoot,
23376
+ workspaceFallbackRoot
23377
+ }
23378
+ });
23379
+ }
23380
+ }
23381
+ async function assertReadableFile(filePath, details) {
23382
+ try {
23383
+ await access3(filePath, constants.R_OK);
23384
+ } catch (error48) {
23385
+ if (getErrorCode3(error48) === "ENOENT") {
23386
+ throw new SkillInstallError({
23387
+ code: "CLI_SKILL_ARTIFACT_MISSING",
23388
+ message: "Required skill artifact is missing",
23389
+ details: {
23390
+ ...details,
23391
+ sourcePath: filePath
23392
+ }
23393
+ });
23394
+ }
23395
+ throw error48;
23396
+ }
23397
+ }
23398
+ async function listFilesRecursively(directoryPath) {
23399
+ const entries = await readdir2(directoryPath, { withFileTypes: true });
23400
+ const files = [];
23401
+ for (const entry of entries.sort(
23402
+ (left, right) => left.name.localeCompare(right.name)
23403
+ )) {
23404
+ const entryPath = join8(directoryPath, entry.name);
23405
+ if (entry.isDirectory()) {
23406
+ files.push(...await listFilesRecursively(entryPath));
23407
+ continue;
23408
+ }
23409
+ if (entry.isFile()) {
23410
+ files.push(entryPath);
23411
+ }
23412
+ }
23413
+ return files;
23414
+ }
23415
+ async function resolveArtifacts(input) {
23416
+ const skillRoot = join8(input.skillPackageRoot, "skill");
23417
+ const skillDocSource = join8(skillRoot, "SKILL.md");
23418
+ const referencesRoot = join8(skillRoot, "references");
23419
+ const relaySource = join8(
23420
+ input.skillPackageRoot,
23421
+ "dist",
23422
+ RELAY_MODULE_FILE_NAME2
23423
+ );
23424
+ await assertReadableFile(skillDocSource, {
23425
+ artifact: "SKILL.md"
23426
+ });
23427
+ await assertReadableFile(relaySource, {
23428
+ artifact: RELAY_MODULE_FILE_NAME2
23429
+ });
23430
+ let referenceFiles;
23431
+ try {
23432
+ referenceFiles = await listFilesRecursively(referencesRoot);
23433
+ } catch (error48) {
23434
+ if (getErrorCode3(error48) === "ENOENT") {
23435
+ throw new SkillInstallError({
23436
+ code: "CLI_SKILL_ARTIFACT_MISSING",
23437
+ message: "Required skill references directory is missing",
23438
+ details: {
23439
+ sourcePath: referencesRoot,
23440
+ artifact: "references"
23441
+ }
23442
+ });
23443
+ }
23444
+ throw error48;
23445
+ }
23446
+ if (referenceFiles.length === 0) {
23447
+ throw new SkillInstallError({
23448
+ code: "CLI_SKILL_REFERENCE_DIR_EMPTY",
23449
+ message: "Required skill references directory is empty",
23450
+ details: {
23451
+ sourcePath: referencesRoot
23452
+ }
23453
+ });
23454
+ }
23455
+ const targetSkillRoot = join8(
23456
+ input.openclawDir,
23457
+ "skills",
23458
+ SKILL_DIR_NAME2
23459
+ );
23460
+ const artifacts = [
23461
+ {
23462
+ sourcePath: skillDocSource,
23463
+ targetPath: join8(targetSkillRoot, "SKILL.md")
23464
+ },
23465
+ {
23466
+ sourcePath: relaySource,
23467
+ targetPath: join8(targetSkillRoot, RELAY_MODULE_FILE_NAME2)
23468
+ },
23469
+ {
23470
+ sourcePath: relaySource,
23471
+ targetPath: join8(
23472
+ input.openclawDir,
23473
+ "hooks",
23474
+ "transforms",
23475
+ RELAY_MODULE_FILE_NAME2
23476
+ )
23477
+ }
23478
+ ];
23479
+ for (const referenceFile of referenceFiles) {
23480
+ const relativePath = relative(referencesRoot, referenceFile);
23481
+ artifacts.push({
23482
+ sourcePath: referenceFile,
23483
+ targetPath: join8(targetSkillRoot, "references", relativePath)
23484
+ });
23485
+ }
23486
+ return artifacts.sort(
23487
+ (left, right) => left.targetPath.localeCompare(right.targetPath)
23488
+ );
23489
+ }
23490
+ async function copyArtifact(input) {
23491
+ const sourceContent = await readFile6(input.sourcePath);
23492
+ let existingContent;
23493
+ try {
23494
+ existingContent = await readFile6(input.targetPath);
23495
+ } catch (error48) {
23496
+ if (getErrorCode3(error48) !== "ENOENT") {
23497
+ throw error48;
23498
+ }
23499
+ }
23500
+ if (existingContent !== void 0 && sourceContent.equals(existingContent)) {
23501
+ return "unchanged";
23502
+ }
23503
+ await mkdir7(dirname6(input.targetPath), { recursive: true });
23504
+ await copyFile2(input.sourcePath, input.targetPath);
23505
+ if (existingContent !== void 0) {
23506
+ return "updated";
23507
+ }
23508
+ return "installed";
23509
+ }
23510
+ async function installOpenclawSkillArtifacts(options = {}) {
23511
+ const env = options.env ?? process.env;
23512
+ const homeDir = resolveHomeDir2(options.homeDir);
23513
+ const openclawDir = resolveOpenclawDir2(homeDir, options.openclawDir);
23514
+ const skillPackageRoot = resolveSkillPackageRoot({
23515
+ skillPackageRoot: options.skillPackageRoot,
23516
+ env
23517
+ });
23518
+ const artifacts = await resolveArtifacts({
23519
+ skillPackageRoot,
23520
+ openclawDir
23521
+ });
23522
+ const records = [];
23523
+ for (const artifact of artifacts) {
23524
+ const action = await copyArtifact(artifact);
23525
+ records.push({
23526
+ action,
23527
+ sourcePath: artifact.sourcePath,
23528
+ targetPath: artifact.targetPath
23529
+ });
23530
+ }
23531
+ return {
23532
+ homeDir,
23533
+ openclawDir,
23534
+ skillPackageRoot,
23535
+ targetSkillDirectory: join8(openclawDir, "skills", SKILL_DIR_NAME2),
23536
+ records
23537
+ };
23538
+ }
23539
+ function formatSkillInstallError(error48) {
23540
+ if (error48 instanceof SkillInstallError) {
23541
+ const details = Object.entries(error48.details).map(([key, value]) => `${key}=${value}`).join(" ");
23542
+ if (details.length === 0) {
23543
+ return `${error48.code}: ${error48.message}`;
23544
+ }
23545
+ return `${error48.code}: ${error48.message} (${details})`;
23546
+ }
23547
+ if (error48 instanceof Error) {
23548
+ return error48.message;
23549
+ }
23550
+ return String(error48);
23551
+ }
23552
+
23553
+ // src/commands/skill.ts
23554
+ function collectStringOption(value, previous) {
23555
+ const trimmed = value.trim();
23556
+ if (trimmed.length === 0) {
23557
+ return previous;
23558
+ }
23559
+ return [...previous, trimmed];
23560
+ }
23561
+ function toInstallSummary(records) {
23562
+ const installed = records.filter((record2) => record2.action === "installed");
23563
+ const updated = records.filter((record2) => record2.action === "updated");
23564
+ const unchanged = records.filter((record2) => record2.action === "unchanged");
23565
+ return `installed=${installed.length} updated=${updated.length} unchanged=${unchanged.length}`;
23566
+ }
23567
+ async function runSkillInstall(options) {
23568
+ const requestedDirs = (options.openclawDir ?? []).filter(
23569
+ (dir) => dir.trim().length > 0
23570
+ );
23571
+ const dirs = requestedDirs.length > 0 ? requestedDirs : [void 0];
23572
+ const results = [];
23573
+ for (const openclawDir of dirs) {
23574
+ const result = await installOpenclawSkillArtifacts({
23575
+ openclawDir,
23576
+ skillPackageRoot: options.skillPackageRoot
23577
+ });
23578
+ results.push(result);
23579
+ }
23580
+ return results;
23581
+ }
23582
+ var createSkillCommand = () => {
23583
+ const skillCommand = new Command9("skill").description(
23584
+ "Install and manage Clawdentity skill artifacts"
23585
+ );
23586
+ skillCommand.command("install").description("Install Clawdentity OpenClaw skill artifacts").option(
23587
+ "--openclaw-dir <path>",
23588
+ "OpenClaw state directory target (repeat for multiple profiles)",
23589
+ collectStringOption,
23590
+ []
23591
+ ).option(
23592
+ "--skill-package-root <path>",
23593
+ "Override skill package root (defaults to bundled assets)"
23594
+ ).option("--json", "Print machine-readable JSON output").action(
23595
+ withErrorHandling(
23596
+ "skill install",
23597
+ async (options) => {
23598
+ let results;
23599
+ try {
23600
+ results = await runSkillInstall(options);
23601
+ } catch (error48) {
23602
+ throw new Error(formatSkillInstallError(error48));
23603
+ }
23604
+ if (options.json) {
23605
+ writeStdoutLine(JSON.stringify({ installs: results }, null, 2));
23606
+ return;
23607
+ }
23608
+ for (const result of results) {
23609
+ writeStdoutLine(`OpenClaw dir: ${result.openclawDir}`);
23610
+ writeStdoutLine(`Skill source: ${result.skillPackageRoot}`);
23611
+ writeStdoutLine(`Target skill dir: ${result.targetSkillDirectory}`);
23612
+ for (const record2 of result.records) {
23613
+ writeStdoutLine(
23614
+ `${record2.action}: ${record2.targetPath} (source: ${record2.sourcePath})`
23615
+ );
23616
+ }
23617
+ writeStdoutLine(toInstallSummary(result.records));
23618
+ }
23619
+ }
23620
+ )
23621
+ );
23622
+ return skillCommand;
23623
+ };
23624
+
23625
+ // src/commands/verify.ts
23626
+ import { readFile as readFile7 } from "fs/promises";
23627
+ import { Command as Command10 } from "commander";
23039
23628
  var logger10 = createLogger({ service: "cli", module: "verify" });
23040
23629
  var REGISTRY_KEYS_CACHE_FILE = "registry-keys.json";
23041
23630
  var CRL_CLAIMS_CACHE_FILE = "crl-claims.json";
@@ -23047,10 +23636,10 @@ var VerifyCommandError = class extends Error {
23047
23636
  this.name = "VerifyCommandError";
23048
23637
  }
23049
23638
  };
23050
- var isRecord9 = (value) => {
23639
+ var isRecord11 = (value) => {
23051
23640
  return typeof value === "object" && value !== null;
23052
23641
  };
23053
- var normalizeRegistryUrl = (registryUrl) => {
23642
+ var normalizeRegistryUrl2 = (registryUrl) => {
23054
23643
  try {
23055
23644
  return new URL(registryUrl).toString();
23056
23645
  } catch {
@@ -23083,7 +23672,7 @@ var resolveToken = async (tokenOrFile) => {
23083
23672
  throw new VerifyCommandError("invalid token (value is empty)");
23084
23673
  }
23085
23674
  try {
23086
- const fileContents = await readFile6(input, "utf-8");
23675
+ const fileContents = await readFile7(input, "utf-8");
23087
23676
  const token = fileContents.trim();
23088
23677
  if (token.length === 0) {
23089
23678
  throw new VerifyCommandError(`invalid token (${input} is empty)`);
@@ -23115,7 +23704,7 @@ var parseResponseJson = async (response) => {
23115
23704
  }
23116
23705
  };
23117
23706
  var parseSigningKeys = (payload) => {
23118
- if (!isRecord9(payload) || !Array.isArray(payload.keys)) {
23707
+ if (!isRecord11(payload) || !Array.isArray(payload.keys)) {
23119
23708
  throw new VerifyCommandError(
23120
23709
  "verification keys unavailable (response payload is invalid)"
23121
23710
  );
@@ -23134,7 +23723,7 @@ var parseSigningKeys = (payload) => {
23134
23723
  };
23135
23724
  var parseRegistryKeysCache = (rawCache) => {
23136
23725
  const parsed = parseJson(rawCache);
23137
- if (!isRecord9(parsed)) {
23726
+ if (!isRecord11(parsed)) {
23138
23727
  return void 0;
23139
23728
  }
23140
23729
  const { registryUrl, fetchedAtMs, keys } = parsed;
@@ -23160,7 +23749,7 @@ var parseRegistryKeysCache = (rawCache) => {
23160
23749
  };
23161
23750
  var parseCrlCache = (rawCache) => {
23162
23751
  const parsed = parseJson(rawCache);
23163
- if (!isRecord9(parsed)) {
23752
+ if (!isRecord11(parsed)) {
23164
23753
  return void 0;
23165
23754
  }
23166
23755
  const { registryUrl, fetchedAtMs, claims } = parsed;
@@ -23258,7 +23847,7 @@ var fetchCrlClaims = async (input) => {
23258
23847
  );
23259
23848
  }
23260
23849
  const payload = await parseResponseJson(response);
23261
- if (!isRecord9(payload) || typeof payload.crl !== "string") {
23850
+ if (!isRecord11(payload) || typeof payload.crl !== "string") {
23262
23851
  throw new VerifyCommandError(
23263
23852
  "revocation check unavailable (response payload is invalid)"
23264
23853
  );
@@ -23303,7 +23892,7 @@ var loadCrlClaims = async (input) => {
23303
23892
  return claims;
23304
23893
  };
23305
23894
  var toInvalidTokenReason = (error48) => {
23306
- if (isRecord9(error48) && typeof error48.message === "string") {
23895
+ if (isRecord11(error48) && typeof error48.message === "string") {
23307
23896
  return `invalid token (${error48.message})`;
23308
23897
  }
23309
23898
  if (error48 instanceof Error && error48.message.length > 0) {
@@ -23321,7 +23910,7 @@ var printResult = (passed, reason) => {
23321
23910
  };
23322
23911
  var runVerify = async (tokenOrFile) => {
23323
23912
  const config2 = await resolveConfig();
23324
- const registryUrl = normalizeRegistryUrl(config2.registryUrl);
23913
+ const registryUrl = normalizeRegistryUrl2(config2.registryUrl);
23325
23914
  const expectedIssuer = toExpectedIssuer(registryUrl);
23326
23915
  const token = await resolveToken(tokenOrFile);
23327
23916
  let keys;
@@ -23379,7 +23968,7 @@ var runVerify = async (tokenOrFile) => {
23379
23968
  printResult(true, `token verified (${claims.sub})`);
23380
23969
  };
23381
23970
  var createVerifyCommand = () => {
23382
- return new Command9("verify").description("Verify an AIT using registry keys and CRL state").argument(
23971
+ return new Command10("verify").description("Verify an AIT using registry keys and CRL state").argument(
23383
23972
  "<tokenOrFile>",
23384
23973
  "Raw AIT token or file path containing the token"
23385
23974
  ).action(
@@ -23390,7 +23979,7 @@ var createVerifyCommand = () => {
23390
23979
  };
23391
23980
 
23392
23981
  // src/index.ts
23393
- var require2 = createRequire(import.meta.url);
23982
+ var require2 = createRequire2(import.meta.url);
23394
23983
  var resolveCliVersion = () => {
23395
23984
  const packageJson = require2("../package.json");
23396
23985
  if (typeof packageJson.version === "string" && packageJson.version.length > 0) {
@@ -23400,7 +23989,7 @@ var resolveCliVersion = () => {
23400
23989
  };
23401
23990
  var CLI_VERSION = resolveCliVersion();
23402
23991
  var createProgram = () => {
23403
- return new Command10("clawdentity").description("Clawdentity CLI - Agent identity management").version(CLI_VERSION).addCommand(createAdminCommand()).addCommand(createAgentCommand()).addCommand(createApiKeyCommand()).addCommand(createConnectorCommand()).addCommand(createConfigCommand()).addCommand(createInviteCommand()).addCommand(createOpenclawCommand()).addCommand(createPairCommand()).addCommand(createVerifyCommand());
23992
+ return new Command11("clawdentity").description("Clawdentity CLI - Agent identity management").version(CLI_VERSION).addCommand(createAdminCommand()).addCommand(createAgentCommand()).addCommand(createApiKeyCommand()).addCommand(createConnectorCommand()).addCommand(createConfigCommand()).addCommand(createInviteCommand()).addCommand(createOpenclawCommand()).addCommand(createPairCommand()).addCommand(createSkillCommand()).addCommand(createVerifyCommand());
23404
23993
  };
23405
23994
 
23406
23995
  // src/bin.ts