clawdentity 0.0.8 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -15709,6 +15709,7 @@ var registrySigningKeysEnvSchema = external_exports.string().min(1).transform((v
15709
15709
  var registryConfigSchema = external_exports.object({
15710
15710
  ENVIRONMENT: environmentSchema,
15711
15711
  APP_VERSION: external_exports.string().min(1).optional(),
15712
+ PROXY_URL: external_exports.string().url().optional(),
15712
15713
  BOOTSTRAP_SECRET: external_exports.string().min(1).optional(),
15713
15714
  REGISTRY_SIGNING_KEY: external_exports.string().min(1).optional(),
15714
15715
  REGISTRY_SIGNING_KEYS: registrySigningKeysEnvSchema.optional()
@@ -17071,13 +17072,14 @@ import { Command } from "commander";
17071
17072
  import { chmod, mkdir, readFile, writeFile } from "fs/promises";
17072
17073
  import { homedir } from "os";
17073
17074
  import { dirname, join as join2 } from "path";
17074
- var DEFAULT_REGISTRY_URL = "https://api.clawdentity.com";
17075
+ var DEFAULT_REGISTRY_URL = "https://registry.clawdentity.com";
17075
17076
  var CONFIG_DIR = ".clawdentity";
17076
17077
  var CONFIG_FILE = "config.json";
17077
17078
  var CACHE_DIR = "cache";
17078
17079
  var FILE_MODE = 384;
17079
17080
  var ENV_KEY_MAP = {
17080
17081
  registryUrl: "CLAWDENTITY_REGISTRY_URL",
17082
+ proxyUrl: "CLAWDENTITY_PROXY_URL",
17081
17083
  apiKey: "CLAWDENTITY_API_KEY"
17082
17084
  };
17083
17085
  var LEGACY_ENV_KEY_MAP = {
@@ -17099,6 +17101,9 @@ var normalizeConfig = (raw) => {
17099
17101
  if (typeof raw.registryUrl === "string" && raw.registryUrl.length > 0) {
17100
17102
  config2.registryUrl = raw.registryUrl;
17101
17103
  }
17104
+ if (typeof raw.proxyUrl === "string" && raw.proxyUrl.length > 0) {
17105
+ config2.proxyUrl = raw.proxyUrl;
17106
+ }
17102
17107
  if (typeof raw.apiKey === "string" && raw.apiKey.length > 0) {
17103
17108
  config2.apiKey = raw.apiKey;
17104
17109
  }
@@ -18393,6 +18398,7 @@ import { Command as Command4 } from "commander";
18393
18398
  var logger5 = createLogger({ service: "cli", module: "config" });
18394
18399
  var VALID_KEYS = [
18395
18400
  "registryUrl",
18401
+ "proxyUrl",
18396
18402
  "apiKey"
18397
18403
  ];
18398
18404
  var isValidConfigKey = (value) => {
@@ -20343,6 +20349,64 @@ function createConnectorCommand(dependencies = {}) {
20343
20349
 
20344
20350
  // src/commands/invite.ts
20345
20351
  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
20346
20410
  var logger7 = createLogger({ service: "cli", module: "invite" });
20347
20411
  var isRecord6 = (value) => {
20348
20412
  return typeof value === "object" && value !== null;
@@ -20519,10 +20583,23 @@ function parseInviteRedeemResponse(payload) {
20519
20583
  }
20520
20584
  const apiKeyId = parseNonEmptyString6(apiKeySource.id);
20521
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
+ }
20522
20598
  return {
20523
20599
  apiKeyToken,
20524
20600
  apiKeyId: apiKeyId.length > 0 ? apiKeyId : void 0,
20525
- apiKeyName: apiKeyName.length > 0 ? apiKeyName : void 0
20601
+ apiKeyName: apiKeyName.length > 0 ? apiKeyName : void 0,
20602
+ proxyUrl
20526
20603
  };
20527
20604
  }
20528
20605
  async function resolveInviteRuntime(overrideRegistryUrl, dependencies) {
@@ -20600,11 +20677,19 @@ async function redeemInvite(code, options, dependencies = {}) {
20600
20677
  registryUrl: runtime.registryUrl
20601
20678
  };
20602
20679
  }
20603
- async function persistRedeemConfig(registryUrl, apiKeyToken, dependencies = {}) {
20680
+ async function persistRedeemConfig(input, dependencies = {}) {
20604
20681
  const setConfigValueImpl = dependencies.setConfigValueImpl ?? setConfigValue;
20605
20682
  try {
20606
- await setConfigValueImpl("registryUrl", registryUrl);
20607
- await setConfigValueImpl("apiKey", apiKeyToken);
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);
20608
20693
  } catch (error48) {
20609
20694
  logger7.warn("cli.invite_redeem_config_persist_failed", {
20610
20695
  errorName: error48 instanceof Error ? error48.name : "unknown"
@@ -20655,8 +20740,11 @@ var createInviteCommand = (dependencies = {}) => {
20655
20740
  writeStdoutLine("API key token (shown once):");
20656
20741
  writeStdoutLine(result.apiKeyToken);
20657
20742
  await persistRedeemConfig(
20658
- result.registryUrl,
20659
- result.apiKeyToken,
20743
+ {
20744
+ registryUrl: result.registryUrl,
20745
+ apiKeyToken: result.apiKeyToken,
20746
+ proxyUrl: result.proxyUrl
20747
+ },
20660
20748
  dependencies
20661
20749
  );
20662
20750
  writeStdoutLine("API key saved to local config");
@@ -20701,13 +20789,13 @@ var CONNECTOR_HOST_LOOPBACK = "127.0.0.1";
20701
20789
  var CONNECTOR_HOST_DOCKER = "host.docker.internal";
20702
20790
  var CONNECTOR_HOST_DOCKER_GATEWAY = "gateway.docker.internal";
20703
20791
  var CONNECTOR_HOST_LINUX_BRIDGE = "172.17.0.1";
20704
- var INVITE_CODE_PREFIX = "clawd1_";
20705
20792
  var PEER_ALIAS_PATTERN = /^[a-zA-Z0-9._-]+$/;
20706
20793
  var FILE_MODE3 = 384;
20707
20794
  var OPENCLAW_HOOK_TOKEN_BYTES = 32;
20708
- var OPENCLAW_SETUP_COMMAND_HINT = "Run: clawdentity openclaw setup <agentName> --peer-alias <alias> --peer-did <did> --peer-proxy-url <proxy-url>";
20795
+ var OPENCLAW_SETUP_COMMAND_HINT = "Run: clawdentity openclaw setup <agentName>";
20709
20796
  var OPENCLAW_SETUP_RESTART_COMMAND_HINT = `${OPENCLAW_SETUP_COMMAND_HINT} and restart OpenClaw`;
20710
20797
  var OPENCLAW_SETUP_WITH_BASE_URL_HINT = `${OPENCLAW_SETUP_COMMAND_HINT} --openclaw-base-url <url>`;
20798
+ var OPENCLAW_PAIRING_COMMAND_HINT = "Run QR pairing first: clawdentity pair start <agentName> --qr and clawdentity pair confirm <agentName> --qr-file <path>";
20711
20799
  var textEncoder2 = new TextEncoder();
20712
20800
  var textDecoder = new TextDecoder();
20713
20801
  function isRecord7(value) {
@@ -20816,50 +20904,6 @@ function parseAgentDid2(value, label) {
20816
20904
  }
20817
20905
  return did;
20818
20906
  }
20819
- function parseInvitePayload(value) {
20820
- if (!isRecord7(value)) {
20821
- throw createCliError5(
20822
- "CLI_OPENCLAW_INVALID_INVITE",
20823
- "invite payload must be an object"
20824
- );
20825
- }
20826
- if (value.v !== 1) {
20827
- throw createCliError5(
20828
- "CLI_OPENCLAW_INVALID_INVITE",
20829
- "invite payload version is unsupported"
20830
- );
20831
- }
20832
- const issuedAt = parseNonEmptyString7(value.issuedAt, "invite issuedAt");
20833
- const did = parseAgentDid2(value.did, "invite did");
20834
- const proxyUrl = parseProxyUrl(value.proxyUrl);
20835
- const alias = value.alias === void 0 ? void 0 : parsePeerAlias(value.alias);
20836
- const name = parseOptionalName(value.name);
20837
- if (alias === void 0 && name === void 0) {
20838
- return {
20839
- v: 1,
20840
- issuedAt,
20841
- did,
20842
- proxyUrl
20843
- };
20844
- }
20845
- if (name === void 0) {
20846
- return {
20847
- v: 1,
20848
- issuedAt,
20849
- did,
20850
- proxyUrl,
20851
- alias
20852
- };
20853
- }
20854
- return {
20855
- v: 1,
20856
- issuedAt,
20857
- did,
20858
- proxyUrl,
20859
- alias,
20860
- name
20861
- };
20862
- }
20863
20907
  function resolveHomeDir(homeDir) {
20864
20908
  if (typeof homeDir === "string" && homeDir.trim().length > 0) {
20865
20909
  return homeDir.trim();
@@ -21010,41 +21054,6 @@ async function ensureLocalAgentCredentials(homeDir, agentName) {
21010
21054
  }
21011
21055
  }
21012
21056
  }
21013
- function decodeInvitePayload(code) {
21014
- const rawCode = parseNonEmptyString7(code, "invite code");
21015
- if (!rawCode.startsWith(INVITE_CODE_PREFIX)) {
21016
- throw createCliError5(
21017
- "CLI_OPENCLAW_INVALID_INVITE",
21018
- "Invite code has invalid prefix"
21019
- );
21020
- }
21021
- const encoded = rawCode.slice(INVITE_CODE_PREFIX.length);
21022
- if (encoded.length === 0) {
21023
- throw createCliError5(
21024
- "CLI_OPENCLAW_INVALID_INVITE",
21025
- "invite code payload is empty"
21026
- );
21027
- }
21028
- let decodedJson;
21029
- try {
21030
- decodedJson = textDecoder.decode(decodeBase64url(encoded));
21031
- } catch {
21032
- throw createCliError5(
21033
- "CLI_OPENCLAW_INVALID_INVITE",
21034
- "invite code payload is not valid base64url"
21035
- );
21036
- }
21037
- let parsedPayload;
21038
- try {
21039
- parsedPayload = JSON.parse(decodedJson);
21040
- } catch {
21041
- throw createCliError5(
21042
- "CLI_OPENCLAW_INVALID_INVITE",
21043
- "invite code payload is not valid JSON"
21044
- );
21045
- }
21046
- return parseInvitePayload(parsedPayload);
21047
- }
21048
21057
  async function writeSecureFile3(filePath, content) {
21049
21058
  await mkdir5(dirname4(filePath), { recursive: true });
21050
21059
  await writeFile5(filePath, content, "utf8");
@@ -21565,7 +21574,7 @@ async function runOpenclawDoctor(options = {}) {
21565
21574
  label: "Peers map",
21566
21575
  status: "fail",
21567
21576
  message: `peer alias is missing: ${peerAlias}`,
21568
- remediationHint: OPENCLAW_SETUP_COMMAND_HINT,
21577
+ remediationHint: OPENCLAW_PAIRING_COMMAND_HINT,
21569
21578
  details: { peersPath, peerAlias }
21570
21579
  })
21571
21580
  );
@@ -21585,9 +21594,8 @@ async function runOpenclawDoctor(options = {}) {
21585
21594
  toDoctorCheck({
21586
21595
  id: "state.peers",
21587
21596
  label: "Peers map",
21588
- status: "fail",
21589
- message: "no peers are configured",
21590
- remediationHint: OPENCLAW_SETUP_COMMAND_HINT,
21597
+ status: "pass",
21598
+ message: "no peers are configured yet (optional until pairing)",
21591
21599
  details: { peersPath }
21592
21600
  })
21593
21601
  );
@@ -21808,7 +21816,7 @@ function parseRelayProbeFailure(input) {
21808
21816
  if (input.status === 500) {
21809
21817
  return {
21810
21818
  message: "Relay probe failed inside local relay pipeline",
21811
- remediationHint: "Check connector runtime and peer alias; rerun clawdentity openclaw doctor --peer <alias>"
21819
+ remediationHint: "Check connector runtime and peer pairing; rerun clawdentity openclaw doctor"
21812
21820
  };
21813
21821
  }
21814
21822
  return {
@@ -21816,17 +21824,57 @@ function parseRelayProbeFailure(input) {
21816
21824
  remediationHint: input.responseBody.trim().length > 0 ? `Inspect response body: ${input.responseBody.trim()}` : "Check local OpenClaw and connector logs"
21817
21825
  };
21818
21826
  }
21827
+ async function resolveRelayProbePeerAlias(input) {
21828
+ if (typeof input.peerAliasOption === "string" && input.peerAliasOption.trim().length > 0) {
21829
+ return parsePeerAlias(input.peerAliasOption);
21830
+ }
21831
+ const peersPath = resolvePeersPath(input.homeDir);
21832
+ const peersConfig = await loadPeersConfig(peersPath);
21833
+ const peerAliases = Object.keys(peersConfig.peers);
21834
+ if (peerAliases.length === 1) {
21835
+ return peerAliases[0];
21836
+ }
21837
+ if (peerAliases.length === 0) {
21838
+ throw createCliError5(
21839
+ "CLI_OPENCLAW_RELAY_TEST_PEER_REQUIRED",
21840
+ "No paired peer is configured yet. Complete QR pairing first.",
21841
+ { peersPath }
21842
+ );
21843
+ }
21844
+ throw createCliError5(
21845
+ "CLI_OPENCLAW_RELAY_TEST_PEER_REQUIRED",
21846
+ "Multiple peers are configured. Pass --peer <alias> to choose one.",
21847
+ { peersPath, peerAliases }
21848
+ );
21849
+ }
21819
21850
  async function runOpenclawRelayTest(options) {
21820
21851
  const homeDir = resolveHomeDir(options.homeDir);
21821
21852
  const openclawDir = resolveOpenclawDir(options.openclawDir, homeDir);
21822
- const peerAlias = parsePeerAlias(options.peer);
21853
+ const checkedAt = nowIso();
21854
+ let peerAlias;
21855
+ try {
21856
+ peerAlias = await resolveRelayProbePeerAlias({
21857
+ homeDir,
21858
+ peerAliasOption: options.peer
21859
+ });
21860
+ } catch (error48) {
21861
+ const appError = error48 instanceof AppError ? error48 : void 0;
21862
+ return {
21863
+ status: "failure",
21864
+ checkedAt,
21865
+ peerAlias: "unresolved",
21866
+ endpoint: toSendToPeerEndpoint(DEFAULT_OPENCLAW_BASE_URL2),
21867
+ message: appError?.message ?? "Unable to resolve relay peer alias",
21868
+ remediationHint: OPENCLAW_PAIRING_COMMAND_HINT,
21869
+ details: appError?.details
21870
+ };
21871
+ }
21823
21872
  const preflight = await runOpenclawDoctor({
21824
21873
  homeDir,
21825
21874
  openclawDir,
21826
21875
  peerAlias,
21827
21876
  resolveConfigImpl: options.resolveConfigImpl
21828
21877
  });
21829
- const checkedAt = nowIso();
21830
21878
  const relayRuntimeConfigPath = resolveRelayRuntimeConfigPath(homeDir);
21831
21879
  let openclawBaseUrl = DEFAULT_OPENCLAW_BASE_URL2;
21832
21880
  try {
@@ -21929,37 +21977,6 @@ async function runOpenclawRelayTest(options) {
21929
21977
  preflight
21930
21978
  };
21931
21979
  }
21932
- function resolveOpenclawSetupPeerInput(options) {
21933
- const inviteCode = options.inviteCode?.trim();
21934
- const invite = inviteCode !== void 0 && inviteCode.length > 0 ? decodeInvitePayload(inviteCode) : void 0;
21935
- const peerAliasCandidate = options.peerAlias ?? invite?.alias;
21936
- const peerDidCandidate = options.peerDid ?? invite?.did;
21937
- const peerProxyUrlCandidate = options.peerProxyUrl ?? invite?.proxyUrl;
21938
- const peerNameCandidate = options.peerName ?? invite?.name;
21939
- const missingFields = [];
21940
- if (peerAliasCandidate === void 0 || peerAliasCandidate.trim().length === 0) {
21941
- missingFields.push("peerAlias");
21942
- }
21943
- if (peerDidCandidate === void 0 || peerDidCandidate.trim().length === 0) {
21944
- missingFields.push("peerDid");
21945
- }
21946
- if (peerProxyUrlCandidate === void 0 || peerProxyUrlCandidate.trim().length === 0) {
21947
- missingFields.push("peerProxyUrl");
21948
- }
21949
- if (missingFields.length > 0) {
21950
- throw createCliError5(
21951
- "CLI_OPENCLAW_PEER_INPUT_REQUIRED",
21952
- "Peer routing details are required. Provide --peer-alias, --peer-did, and --peer-proxy-url.",
21953
- { missingFields }
21954
- );
21955
- }
21956
- return {
21957
- peerAlias: parsePeerAlias(peerAliasCandidate),
21958
- peerDid: parseAgentDid2(peerDidCandidate, "peer DID"),
21959
- peerProxyUrl: parseProxyUrl(peerProxyUrlCandidate),
21960
- peerName: parseOptionalName(peerNameCandidate)
21961
- };
21962
- }
21963
21980
  async function setupOpenclawRelay(agentName, options) {
21964
21981
  const normalizedAgentName = assertValidAgentName(agentName);
21965
21982
  const homeDir = resolveHomeDir(options.homeDir);
@@ -21975,7 +21992,6 @@ async function setupOpenclawRelay(agentName, options) {
21975
21992
  optionValue: options.openclawBaseUrl,
21976
21993
  relayRuntimeConfigPath
21977
21994
  });
21978
- const peerInput = resolveOpenclawSetupPeerInput(options);
21979
21995
  await ensureLocalAgentCredentials(homeDir, normalizedAgentName);
21980
21996
  await mkdir5(dirname4(transformTargetPath), { recursive: true });
21981
21997
  try {
@@ -21996,11 +22012,6 @@ async function setupOpenclawRelay(agentName, options) {
21996
22012
  );
21997
22013
  const peersPath = resolvePeersPath(homeDir);
21998
22014
  const peers = await loadPeersConfig(peersPath);
21999
- peers.peers[peerInput.peerAlias] = peerInput.peerName === void 0 ? { did: peerInput.peerDid, proxyUrl: peerInput.peerProxyUrl } : {
22000
- did: peerInput.peerDid,
22001
- proxyUrl: peerInput.peerProxyUrl,
22002
- name: peerInput.peerName
22003
- };
22004
22015
  await savePeersConfig(peersPath, peers);
22005
22016
  const relayTransformPeersPath = resolveTransformPeersPath(openclawDir);
22006
22017
  await writeSecureFile3(
@@ -22055,9 +22066,6 @@ async function setupOpenclawRelay(agentName, options) {
22055
22066
  );
22056
22067
  logger8.info("cli.openclaw_setup_completed", {
22057
22068
  agentName: normalizedAgentName,
22058
- peerAlias: peerInput.peerAlias,
22059
- peerDid: peerInput.peerDid,
22060
- peerProxyUrl: peerInput.peerProxyUrl,
22061
22069
  openclawConfigPath,
22062
22070
  transformTargetPath,
22063
22071
  relayTransformRuntimePath,
@@ -22067,9 +22075,6 @@ async function setupOpenclawRelay(agentName, options) {
22067
22075
  relayRuntimeConfigPath
22068
22076
  });
22069
22077
  return {
22070
- peerAlias: peerInput.peerAlias,
22071
- peerDid: peerInput.peerDid,
22072
- peerProxyUrl: peerInput.peerProxyUrl,
22073
22078
  openclawConfigPath,
22074
22079
  transformTargetPath,
22075
22080
  relayTransformRuntimePath,
@@ -22083,10 +22088,7 @@ var createOpenclawCommand = () => {
22083
22088
  const openclawCommand = new Command7("openclaw").description(
22084
22089
  "Manage OpenClaw relay setup"
22085
22090
  );
22086
- openclawCommand.command("setup <agentName>").description("Apply OpenClaw relay setup using peer routing details").requiredOption("--peer-alias <alias>", "Peer alias for local routing").requiredOption("--peer-did <did>", "Peer agent DID (did:claw:agent:...)").requiredOption(
22087
- "--peer-proxy-url <url>",
22088
- "Peer proxy URL ending in /hooks/agent"
22089
- ).option("--peer-name <displayName>", "Human-friendly peer display name").option(
22091
+ openclawCommand.command("setup <agentName>").description("Apply OpenClaw relay setup").option(
22090
22092
  "--openclaw-dir <path>",
22091
22093
  "OpenClaw state directory (default ~/.openclaw)"
22092
22094
  ).option(
@@ -22100,9 +22102,7 @@ var createOpenclawCommand = () => {
22100
22102
  "openclaw setup",
22101
22103
  async (agentName, options) => {
22102
22104
  const result = await setupOpenclawRelay(agentName, options);
22103
- writeStdoutLine(`Peer alias configured: ${result.peerAlias}`);
22104
- writeStdoutLine(`Peer DID: ${result.peerDid}`);
22105
- writeStdoutLine(`Peer proxy URL: ${result.peerProxyUrl}`);
22105
+ writeStdoutLine("Self setup complete");
22106
22106
  writeStdoutLine(
22107
22107
  `Updated OpenClaw config: ${result.openclawConfigPath}`
22108
22108
  );
@@ -22145,7 +22145,9 @@ var createOpenclawCommand = () => {
22145
22145
  )
22146
22146
  );
22147
22147
  const relayCommand = openclawCommand.command("relay").description("Run OpenClaw relay diagnostics");
22148
- relayCommand.command("test").description("Send a relay probe to a configured peer alias").requiredOption("--peer <alias>", "Peer alias in ~/.clawdentity/peers.json").option(
22148
+ relayCommand.command("test").description(
22149
+ "Send a relay probe to a configured peer (auto-selects when one peer exists)"
22150
+ ).option("--peer <alias>", "Peer alias in ~/.clawdentity/peers.json").option(
22149
22151
  "--openclaw-base-url <url>",
22150
22152
  "Base URL for local OpenClaw hook API (default OPENCLAW_BASE_URL or relay runtime config)"
22151
22153
  ).option(
@@ -22186,7 +22188,14 @@ var createOpenclawCommand = () => {
22186
22188
 
22187
22189
  // src/commands/pair.ts
22188
22190
  import { randomBytes as randomBytes4 } from "crypto";
22189
- import { mkdir as mkdir6, readdir, readFile as readFile5, unlink as unlink2, writeFile as writeFile6 } from "fs/promises";
22191
+ import {
22192
+ chmod as chmod4,
22193
+ mkdir as mkdir6,
22194
+ readdir,
22195
+ readFile as readFile5,
22196
+ unlink as unlink2,
22197
+ writeFile as writeFile6
22198
+ } from "fs/promises";
22190
22199
  import { dirname as dirname5, join as join7, resolve } from "path";
22191
22200
  import { Command as Command8 } from "commander";
22192
22201
  import jsQR from "jsqr";
@@ -22197,6 +22206,7 @@ var AGENTS_DIR_NAME5 = "agents";
22197
22206
  var AIT_FILE_NAME4 = "ait.jwt";
22198
22207
  var SECRET_KEY_FILE_NAME3 = "secret.key";
22199
22208
  var PAIRING_QR_DIR_NAME = "pairing";
22209
+ var PEERS_FILE_NAME2 = "peers.json";
22200
22210
  var PAIR_START_PATH = "/pair/start";
22201
22211
  var PAIR_CONFIRM_PATH = "/pair/confirm";
22202
22212
  var OWNER_PAT_HEADER = "x-claw-owner-pat";
@@ -22204,6 +22214,8 @@ var NONCE_SIZE2 = 24;
22204
22214
  var PAIRING_TICKET_PREFIX = "clwpair1_";
22205
22215
  var PAIRING_QR_MAX_AGE_SECONDS = 900;
22206
22216
  var PAIRING_QR_FILENAME_PATTERN = /-pair-(\d+)\.png$/;
22217
+ var FILE_MODE4 = 384;
22218
+ var PEER_ALIAS_PATTERN2 = /^[a-zA-Z0-9._-]+$/;
22207
22219
  var isRecord8 = (value) => {
22208
22220
  return typeof value === "object" && value !== null;
22209
22221
  };
@@ -22230,6 +22242,172 @@ function parsePairingTicket(value) {
22230
22242
  }
22231
22243
  return ticket;
22232
22244
  }
22245
+ function parsePairingTicketIssuerOrigin(ticket) {
22246
+ const encodedPayload = ticket.slice(PAIRING_TICKET_PREFIX.length);
22247
+ if (encodedPayload.length === 0) {
22248
+ throw createCliError6(
22249
+ "CLI_PAIR_CONFIRM_TICKET_INVALID",
22250
+ "Pairing ticket is invalid"
22251
+ );
22252
+ }
22253
+ let payloadRaw;
22254
+ try {
22255
+ payloadRaw = new TextDecoder().decode(decodeBase64url(encodedPayload));
22256
+ } catch {
22257
+ throw createCliError6(
22258
+ "CLI_PAIR_CONFIRM_TICKET_INVALID",
22259
+ "Pairing ticket is invalid"
22260
+ );
22261
+ }
22262
+ let payload;
22263
+ try {
22264
+ payload = JSON.parse(payloadRaw);
22265
+ } catch {
22266
+ throw createCliError6(
22267
+ "CLI_PAIR_CONFIRM_TICKET_INVALID",
22268
+ "Pairing ticket is invalid"
22269
+ );
22270
+ }
22271
+ if (!isRecord8(payload) || typeof payload.iss !== "string") {
22272
+ throw createCliError6(
22273
+ "CLI_PAIR_CONFIRM_TICKET_INVALID",
22274
+ "Pairing ticket is invalid"
22275
+ );
22276
+ }
22277
+ let issuerUrl;
22278
+ try {
22279
+ issuerUrl = new URL(payload.iss);
22280
+ } catch {
22281
+ throw createCliError6(
22282
+ "CLI_PAIR_CONFIRM_TICKET_INVALID",
22283
+ "Pairing ticket is invalid"
22284
+ );
22285
+ }
22286
+ if (issuerUrl.protocol !== "https:" && issuerUrl.protocol !== "http:") {
22287
+ throw createCliError6(
22288
+ "CLI_PAIR_CONFIRM_TICKET_INVALID",
22289
+ "Pairing ticket is invalid"
22290
+ );
22291
+ }
22292
+ return issuerUrl.origin;
22293
+ }
22294
+ function parsePeerAlias2(value) {
22295
+ if (value.length === 0 || value.length > 128) {
22296
+ throw createCliError6(
22297
+ "CLI_PAIR_PEER_ALIAS_INVALID",
22298
+ "Generated peer alias is invalid"
22299
+ );
22300
+ }
22301
+ if (!PEER_ALIAS_PATTERN2.test(value)) {
22302
+ throw createCliError6(
22303
+ "CLI_PAIR_PEER_ALIAS_INVALID",
22304
+ "Generated peer alias is invalid"
22305
+ );
22306
+ }
22307
+ return value;
22308
+ }
22309
+ function derivePeerAliasBase(peerDid) {
22310
+ try {
22311
+ const parsed = parseDid(peerDid);
22312
+ if (parsed.kind === "agent") {
22313
+ return parsePeerAlias2(`peer-${parsed.ulid.slice(-8).toLowerCase()}`);
22314
+ }
22315
+ } catch {
22316
+ }
22317
+ return "peer";
22318
+ }
22319
+ function resolvePeerAlias(input) {
22320
+ for (const [alias, entry] of Object.entries(input.peers)) {
22321
+ if (entry.did === input.peerDid) {
22322
+ return alias;
22323
+ }
22324
+ }
22325
+ const baseAlias = derivePeerAliasBase(input.peerDid);
22326
+ if (input.peers[baseAlias] === void 0) {
22327
+ return baseAlias;
22328
+ }
22329
+ let index = 2;
22330
+ while (input.peers[`${baseAlias}-${index}`] !== void 0) {
22331
+ index += 1;
22332
+ }
22333
+ return `${baseAlias}-${index}`;
22334
+ }
22335
+ function resolvePeersConfigPath(getConfigDirImpl) {
22336
+ return join7(getConfigDirImpl(), PEERS_FILE_NAME2);
22337
+ }
22338
+ function parsePeerEntry(value) {
22339
+ if (!isRecord8(value)) {
22340
+ throw createCliError6(
22341
+ "CLI_PAIR_PEERS_CONFIG_INVALID",
22342
+ "Peer entry must be an object"
22343
+ );
22344
+ }
22345
+ const did = parseNonEmptyString8(value.did);
22346
+ const proxyUrl = parseNonEmptyString8(value.proxyUrl);
22347
+ if (did.length === 0 || proxyUrl.length === 0) {
22348
+ throw createCliError6(
22349
+ "CLI_PAIR_PEERS_CONFIG_INVALID",
22350
+ "Peer entry is invalid"
22351
+ );
22352
+ }
22353
+ return {
22354
+ did,
22355
+ proxyUrl
22356
+ };
22357
+ }
22358
+ async function loadPeersConfig2(input) {
22359
+ const peersPath = resolvePeersConfigPath(input.getConfigDirImpl);
22360
+ let raw;
22361
+ try {
22362
+ raw = await input.readFileImpl(peersPath, "utf8");
22363
+ } catch (error48) {
22364
+ const nodeError = error48;
22365
+ if (nodeError.code === "ENOENT") {
22366
+ return { peers: {} };
22367
+ }
22368
+ throw error48;
22369
+ }
22370
+ let parsed;
22371
+ try {
22372
+ parsed = JSON.parse(raw);
22373
+ } catch {
22374
+ throw createCliError6(
22375
+ "CLI_PAIR_PEERS_CONFIG_INVALID",
22376
+ "Peer config is not valid JSON"
22377
+ );
22378
+ }
22379
+ if (!isRecord8(parsed)) {
22380
+ throw createCliError6(
22381
+ "CLI_PAIR_PEERS_CONFIG_INVALID",
22382
+ "Peer config must be a JSON object"
22383
+ );
22384
+ }
22385
+ if (parsed.peers === void 0) {
22386
+ return { peers: {} };
22387
+ }
22388
+ if (!isRecord8(parsed.peers)) {
22389
+ throw createCliError6(
22390
+ "CLI_PAIR_PEERS_CONFIG_INVALID",
22391
+ "Peer config peers field must be an object"
22392
+ );
22393
+ }
22394
+ const peers = {};
22395
+ for (const [alias, value] of Object.entries(parsed.peers)) {
22396
+ peers[parsePeerAlias2(alias)] = parsePeerEntry(value);
22397
+ }
22398
+ return { peers };
22399
+ }
22400
+ async function savePeersConfig2(input) {
22401
+ const peersPath = resolvePeersConfigPath(input.getConfigDirImpl);
22402
+ await input.mkdirImpl(dirname5(peersPath), { recursive: true });
22403
+ await input.writeFileImpl(
22404
+ peersPath,
22405
+ `${JSON.stringify(input.config, null, 2)}
22406
+ `,
22407
+ "utf8"
22408
+ );
22409
+ await input.chmodImpl(peersPath, FILE_MODE4);
22410
+ }
22233
22411
  function parseTtlSeconds(value) {
22234
22412
  const raw = parseNonEmptyString8(value);
22235
22413
  if (raw.length === 0) {
@@ -22244,14 +22422,7 @@ function parseTtlSeconds(value) {
22244
22422
  }
22245
22423
  return parsed;
22246
22424
  }
22247
- function resolveProxyUrl(overrideProxyUrl) {
22248
- const candidate = parseNonEmptyString8(overrideProxyUrl) || parseNonEmptyString8(process.env.CLAWDENTITY_PROXY_URL);
22249
- if (candidate.length === 0) {
22250
- throw createCliError6(
22251
- "CLI_PAIR_PROXY_URL_REQUIRED",
22252
- "Proxy URL is required. Pass --proxy-url <url> or set CLAWDENTITY_PROXY_URL."
22253
- );
22254
- }
22425
+ function parseProxyUrl2(candidate) {
22255
22426
  try {
22256
22427
  const parsed = new URL(candidate);
22257
22428
  if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
@@ -22262,6 +22433,33 @@ function resolveProxyUrl(overrideProxyUrl) {
22262
22433
  throw createCliError6("CLI_PAIR_INVALID_PROXY_URL", "Proxy URL is invalid");
22263
22434
  }
22264
22435
  }
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);
22442
+ if (fromEnv.length > 0) {
22443
+ return [parseProxyUrl2(fromEnv)];
22444
+ }
22445
+ const fromConfig = parseNonEmptyString8(input.config.proxyUrl);
22446
+ if (fromConfig.length > 0) {
22447
+ return [parseProxyUrl2(fromConfig)];
22448
+ }
22449
+ const derivedFromRegistry = deriveProxyUrlFromRegistryUrl(
22450
+ input.config.registryUrl || DEFAULT_REGISTRY_URL
22451
+ );
22452
+ if (typeof derivedFromRegistry === "string" && derivedFromRegistry.length > 0) {
22453
+ return [parseProxyUrl2(derivedFromRegistry)];
22454
+ }
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>`."
22458
+ );
22459
+ }
22460
+ function resolveProxyUrl(input) {
22461
+ return resolveProxyUrlCandidates(input)[0];
22462
+ }
22265
22463
  function toProxyRequestUrl(proxyUrl, path) {
22266
22464
  const normalizedBase = proxyUrl.endsWith("/") ? proxyUrl : `${proxyUrl}/`;
22267
22465
  return new URL(path.slice(1), normalizedBase).toString();
@@ -22585,14 +22783,46 @@ function resolveConfirmTicketSource(options) {
22585
22783
  "Pairing ticket is required. Pass --ticket <clwpair1_...> or --qr-file <path>."
22586
22784
  );
22587
22785
  }
22786
+ async function persistPairedPeer(input) {
22787
+ const getConfigDirImpl = input.dependencies.getConfigDirImpl ?? getConfigDir;
22788
+ const readFileImpl = input.dependencies.readFileImpl ?? readFile5;
22789
+ const mkdirImpl = input.dependencies.mkdirImpl ?? mkdir6;
22790
+ const writeFileImpl = input.dependencies.writeFileImpl ?? writeFile6;
22791
+ const chmodImpl = input.dependencies.chmodImpl ?? chmod4;
22792
+ const issuerOrigin = parsePairingTicketIssuerOrigin(input.ticket);
22793
+ const peerProxyUrl = new URL("/hooks/agent", `${issuerOrigin}/`).toString();
22794
+ const peersConfig = await loadPeersConfig2({
22795
+ getConfigDirImpl,
22796
+ readFileImpl
22797
+ });
22798
+ const alias = resolvePeerAlias({
22799
+ peers: peersConfig.peers,
22800
+ peerDid: input.peerDid
22801
+ });
22802
+ peersConfig.peers[alias] = {
22803
+ did: input.peerDid,
22804
+ proxyUrl: peerProxyUrl
22805
+ };
22806
+ await savePeersConfig2({
22807
+ config: peersConfig,
22808
+ getConfigDirImpl,
22809
+ mkdirImpl,
22810
+ writeFileImpl,
22811
+ chmodImpl
22812
+ });
22813
+ return alias;
22814
+ }
22588
22815
  async function startPairing(agentName, options, dependencies = {}) {
22589
22816
  const fetchImpl = dependencies.fetchImpl ?? fetch;
22590
22817
  const resolveConfigImpl = dependencies.resolveConfigImpl ?? resolveConfig;
22591
22818
  const nowSecondsImpl = dependencies.nowSecondsImpl ?? (() => Math.floor(Date.now() / 1e3));
22592
22819
  const nonceFactoryImpl = dependencies.nonceFactoryImpl ?? (() => randomBytes4(NONCE_SIZE2).toString("base64url"));
22593
22820
  const ttlSeconds = parseTtlSeconds(options.ttlSeconds);
22594
- const proxyUrl = resolveProxyUrl(options.proxyUrl);
22595
22821
  const config2 = await resolveConfigImpl();
22822
+ const proxyUrl = resolveProxyUrl({
22823
+ overrideProxyUrl: options.proxyUrl,
22824
+ config: config2
22825
+ });
22596
22826
  const ownerPat = resolveOwnerPat({
22597
22827
  explicitOwnerPat: options.ownerPat,
22598
22828
  config: config2
@@ -22655,12 +22885,17 @@ async function startPairing(agentName, options, dependencies = {}) {
22655
22885
  }
22656
22886
  async function confirmPairing(agentName, options, dependencies = {}) {
22657
22887
  const fetchImpl = dependencies.fetchImpl ?? fetch;
22888
+ const resolveConfigImpl = dependencies.resolveConfigImpl ?? resolveConfig;
22658
22889
  const nowSecondsImpl = dependencies.nowSecondsImpl ?? (() => Math.floor(Date.now() / 1e3));
22659
22890
  const nonceFactoryImpl = dependencies.nonceFactoryImpl ?? (() => randomBytes4(NONCE_SIZE2).toString("base64url"));
22660
22891
  const readFileImpl = dependencies.readFileImpl ?? readFile5;
22661
22892
  const qrDecodeImpl = dependencies.qrDecodeImpl ?? decodeTicketFromPng;
22893
+ const config2 = await resolveConfigImpl();
22662
22894
  const ticketSource = resolveConfirmTicketSource(options);
22663
- const proxyUrl = resolveProxyUrl(options.proxyUrl);
22895
+ const proxyUrl = resolveProxyUrl({
22896
+ overrideProxyUrl: options.proxyUrl,
22897
+ config: config2
22898
+ });
22664
22899
  let ticket = ticketSource.ticket;
22665
22900
  if (ticketSource.source === "qr-file") {
22666
22901
  if (!ticketSource.qrFilePath) {
@@ -22722,6 +22957,11 @@ async function confirmPairing(agentName, options, dependencies = {}) {
22722
22957
  );
22723
22958
  }
22724
22959
  const parsed = parsePairConfirmResponse(responseBody);
22960
+ const peerAlias = await persistPairedPeer({
22961
+ ticket,
22962
+ peerDid: parsed.initiatorAgentDid,
22963
+ dependencies
22964
+ });
22725
22965
  if (ticketSource.source === "qr-file" && ticketSource.qrFilePath) {
22726
22966
  const unlinkImpl = dependencies.unlinkImpl ?? unlink2;
22727
22967
  await unlinkImpl(ticketSource.qrFilePath).catch((error48) => {
@@ -22737,17 +22977,15 @@ async function confirmPairing(agentName, options, dependencies = {}) {
22737
22977
  }
22738
22978
  return {
22739
22979
  ...parsed,
22740
- proxyUrl
22980
+ proxyUrl,
22981
+ peerAlias
22741
22982
  };
22742
22983
  }
22743
22984
  var createPairCommand = (dependencies = {}) => {
22744
22985
  const pairCommand = new Command8("pair").description(
22745
22986
  "Manage proxy trust pairing between agents"
22746
22987
  );
22747
- pairCommand.command("start <agentName>").description("Start pairing and issue one-time pairing ticket").option(
22748
- "--proxy-url <url>",
22749
- "Initiator proxy base URL (or set CLAWDENTITY_PROXY_URL)"
22750
- ).option(
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(
22751
22989
  "--owner-pat <token>",
22752
22990
  "Owner PAT override (defaults to configured API key)"
22753
22991
  ).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(
@@ -22771,10 +23009,7 @@ var createPairCommand = (dependencies = {}) => {
22771
23009
  }
22772
23010
  )
22773
23011
  );
22774
- 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(
22775
- "--proxy-url <url>",
22776
- "Responder proxy base URL (or set CLAWDENTITY_PROXY_URL)"
22777
- ).action(
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(
22778
23013
  withErrorHandling(
22779
23014
  "pair confirm",
22780
23015
  async (agentName, options) => {
@@ -22782,12 +23017,16 @@ var createPairCommand = (dependencies = {}) => {
22782
23017
  logger9.info("cli.pair_confirmed", {
22783
23018
  initiatorAgentDid: result.initiatorAgentDid,
22784
23019
  responderAgentDid: result.responderAgentDid,
22785
- proxyUrl: result.proxyUrl
23020
+ proxyUrl: result.proxyUrl,
23021
+ peerAlias: result.peerAlias
22786
23022
  });
22787
23023
  writeStdoutLine("Pairing confirmed");
22788
23024
  writeStdoutLine(`Initiator Agent DID: ${result.initiatorAgentDid}`);
22789
23025
  writeStdoutLine(`Responder Agent DID: ${result.responderAgentDid}`);
22790
23026
  writeStdoutLine(`Paired: ${result.paired ? "true" : "false"}`);
23027
+ if (result.peerAlias) {
23028
+ writeStdoutLine(`Peer alias saved: ${result.peerAlias}`);
23029
+ }
22791
23030
  }
22792
23031
  )
22793
23032
  );
@@ -22827,11 +23066,11 @@ var toRegistryUrl = (registryUrl, path) => {
22827
23066
  var toExpectedIssuer = (registryUrl) => {
22828
23067
  try {
22829
23068
  const hostname3 = new URL(registryUrl).hostname;
22830
- if (hostname3 === "api.clawdentity.com") {
22831
- return "https://api.clawdentity.com";
23069
+ if (hostname3 === "registry.clawdentity.com") {
23070
+ return "https://registry.clawdentity.com";
22832
23071
  }
22833
- if (hostname3 === "dev.api.clawdentity.com") {
22834
- return "https://dev.api.clawdentity.com";
23072
+ if (hostname3 === "dev.registry.clawdentity.com") {
23073
+ return "https://dev.registry.clawdentity.com";
22835
23074
  }
22836
23075
  return void 0;
22837
23076
  } catch {