clawdentity 0.0.13 → 0.0.14

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
@@ -22471,6 +22471,7 @@ var PAIRING_QR_DIR_NAME = "pairing";
22471
22471
  var PEERS_FILE_NAME2 = "peers.json";
22472
22472
  var PAIR_START_PATH = "/pair/start";
22473
22473
  var PAIR_CONFIRM_PATH = "/pair/confirm";
22474
+ var PAIR_STATUS_PATH = "/pair/status";
22474
22475
  var OWNER_PAT_HEADER = "x-claw-owner-pat";
22475
22476
  var NONCE_SIZE2 = 24;
22476
22477
  var PAIRING_TICKET_PREFIX = "clwpair1_";
@@ -22478,6 +22479,8 @@ var PAIRING_QR_MAX_AGE_SECONDS = 900;
22478
22479
  var PAIRING_QR_FILENAME_PATTERN = /-pair-(\d+)\.png$/;
22479
22480
  var FILE_MODE4 = 384;
22480
22481
  var PEER_ALIAS_PATTERN2 = /^[a-zA-Z0-9._-]+$/;
22482
+ var DEFAULT_STATUS_WAIT_SECONDS = 300;
22483
+ var DEFAULT_STATUS_POLL_INTERVAL_SECONDS = 3;
22481
22484
  var isRecord9 = (value) => {
22482
22485
  return typeof value === "object" && value !== null;
22483
22486
  };
@@ -22553,6 +22556,52 @@ function parsePairingTicketIssuerOrigin(ticket) {
22553
22556
  }
22554
22557
  return issuerUrl.origin;
22555
22558
  }
22559
+ function parseAitAgentDid(ait) {
22560
+ const parts = ait.split(".");
22561
+ if (parts.length < 2) {
22562
+ throw createCliError7(
22563
+ "CLI_PAIR_AGENT_NOT_FOUND",
22564
+ "Agent AIT is invalid. Recreate the agent before pairing."
22565
+ );
22566
+ }
22567
+ let payloadRaw;
22568
+ try {
22569
+ payloadRaw = new TextDecoder().decode(decodeBase64url(parts[1] ?? ""));
22570
+ } catch {
22571
+ throw createCliError7(
22572
+ "CLI_PAIR_AGENT_NOT_FOUND",
22573
+ "Agent AIT is invalid. Recreate the agent before pairing."
22574
+ );
22575
+ }
22576
+ let payload;
22577
+ try {
22578
+ payload = JSON.parse(payloadRaw);
22579
+ } catch {
22580
+ throw createCliError7(
22581
+ "CLI_PAIR_AGENT_NOT_FOUND",
22582
+ "Agent AIT is invalid. Recreate the agent before pairing."
22583
+ );
22584
+ }
22585
+ if (!isRecord9(payload) || typeof payload.sub !== "string") {
22586
+ throw createCliError7(
22587
+ "CLI_PAIR_AGENT_NOT_FOUND",
22588
+ "Agent AIT is invalid. Recreate the agent before pairing."
22589
+ );
22590
+ }
22591
+ const candidate = payload.sub.trim();
22592
+ try {
22593
+ const parsed = parseDid(candidate);
22594
+ if (parsed.kind !== "agent") {
22595
+ throw new Error("invalid kind");
22596
+ }
22597
+ } catch {
22598
+ throw createCliError7(
22599
+ "CLI_PAIR_AGENT_NOT_FOUND",
22600
+ "Agent AIT is invalid. Recreate the agent before pairing."
22601
+ );
22602
+ }
22603
+ return candidate;
22604
+ }
22556
22605
  function parsePeerAlias2(value) {
22557
22606
  if (value.length === 0 || value.length > 128) {
22558
22607
  throw createCliError7(
@@ -22684,6 +22733,20 @@ function parseTtlSeconds(value) {
22684
22733
  }
22685
22734
  return parsed;
22686
22735
  }
22736
+ function parsePositiveIntegerOption(input) {
22737
+ const raw = parseNonEmptyString9(input.value);
22738
+ if (raw.length === 0) {
22739
+ return input.defaultValue;
22740
+ }
22741
+ const parsed = Number.parseInt(raw, 10);
22742
+ if (!Number.isInteger(parsed) || parsed < 1) {
22743
+ throw createCliError7(
22744
+ "CLI_PAIR_STATUS_WAIT_INVALID",
22745
+ `${input.optionName} must be a positive integer`
22746
+ );
22747
+ }
22748
+ return parsed;
22749
+ }
22687
22750
  function parseProxyUrl2(candidate) {
22688
22751
  try {
22689
22752
  const parsed = new URL(candidate);
@@ -22804,6 +22867,29 @@ function mapConfirmPairError(status, payload) {
22804
22867
  }
22805
22868
  return `Pair confirm failed (${status})`;
22806
22869
  }
22870
+ function mapStatusPairError(status, payload) {
22871
+ const code = extractErrorCode(payload);
22872
+ const message2 = extractErrorMessage(payload);
22873
+ if (code === "PROXY_PAIR_TICKET_NOT_FOUND" || status === 404) {
22874
+ return "Pairing ticket not found";
22875
+ }
22876
+ if (code === "PROXY_PAIR_TICKET_EXPIRED" || status === 410) {
22877
+ return "Pairing ticket has expired";
22878
+ }
22879
+ if (code === "PROXY_PAIR_STATUS_FORBIDDEN" || status === 403) {
22880
+ return message2 ? `Pair status request is forbidden (403): ${message2}` : "Pair status request is forbidden (403).";
22881
+ }
22882
+ if (status === 400) {
22883
+ return message2 ? `Pair status request is invalid (400): ${message2}` : "Pair status request is invalid (400).";
22884
+ }
22885
+ if (status >= 500) {
22886
+ return `Proxy pairing service is unavailable (${status}).`;
22887
+ }
22888
+ if (message2) {
22889
+ return `Pair status failed (${status}): ${message2}`;
22890
+ }
22891
+ return `Pair status failed (${status})`;
22892
+ }
22807
22893
  function parsePairStartResponse(payload) {
22808
22894
  if (!isRecord9(payload)) {
22809
22895
  throw createCliError7(
@@ -22848,6 +22934,44 @@ function parsePairConfirmResponse(payload) {
22848
22934
  responderAgentDid
22849
22935
  };
22850
22936
  }
22937
+ function parsePairStatusResponse(payload) {
22938
+ if (!isRecord9(payload)) {
22939
+ throw createCliError7(
22940
+ "CLI_PAIR_STATUS_INVALID_RESPONSE",
22941
+ "Pair status response is invalid"
22942
+ );
22943
+ }
22944
+ const statusRaw = parseNonEmptyString9(payload.status);
22945
+ if (statusRaw !== "pending" && statusRaw !== "confirmed") {
22946
+ throw createCliError7(
22947
+ "CLI_PAIR_STATUS_INVALID_RESPONSE",
22948
+ "Pair status response is invalid"
22949
+ );
22950
+ }
22951
+ const initiatorAgentDid = parseNonEmptyString9(payload.initiatorAgentDid);
22952
+ const responderAgentDid = parseNonEmptyString9(payload.responderAgentDid);
22953
+ const expiresAt = parseNonEmptyString9(payload.expiresAt);
22954
+ const confirmedAt = parseNonEmptyString9(payload.confirmedAt);
22955
+ if (initiatorAgentDid.length === 0 || expiresAt.length === 0) {
22956
+ throw createCliError7(
22957
+ "CLI_PAIR_STATUS_INVALID_RESPONSE",
22958
+ "Pair status response is invalid"
22959
+ );
22960
+ }
22961
+ if (statusRaw === "confirmed" && responderAgentDid.length === 0) {
22962
+ throw createCliError7(
22963
+ "CLI_PAIR_STATUS_INVALID_RESPONSE",
22964
+ "Pair status response is invalid"
22965
+ );
22966
+ }
22967
+ return {
22968
+ status: statusRaw,
22969
+ initiatorAgentDid,
22970
+ responderAgentDid: responderAgentDid.length > 0 ? responderAgentDid : void 0,
22971
+ expiresAt,
22972
+ confirmedAt: confirmedAt.length > 0 ? confirmedAt : void 0
22973
+ };
22974
+ }
22851
22975
  async function readAgentProofMaterial(agentName, dependencies) {
22852
22976
  const readFileImpl = dependencies.readFileImpl ?? readFile5;
22853
22977
  const getConfigDirImpl = dependencies.getConfigDirImpl ?? getConfigDir;
@@ -23238,6 +23362,147 @@ async function confirmPairing(agentName, options, dependencies = {}) {
23238
23362
  peerAlias
23239
23363
  };
23240
23364
  }
23365
+ async function getPairingStatusOnce(agentName, options, dependencies = {}) {
23366
+ const fetchImpl = dependencies.fetchImpl ?? fetch;
23367
+ const resolveConfigImpl = dependencies.resolveConfigImpl ?? resolveConfig;
23368
+ const nowSecondsImpl = dependencies.nowSecondsImpl ?? (() => Math.floor(Date.now() / 1e3));
23369
+ const nonceFactoryImpl = dependencies.nonceFactoryImpl ?? (() => randomBytes4(NONCE_SIZE2).toString("base64url"));
23370
+ const config2 = await resolveConfigImpl();
23371
+ const proxyUrl = await resolveProxyUrl({
23372
+ config: config2,
23373
+ fetchImpl
23374
+ });
23375
+ const ticket = parsePairingTicket(options.ticket);
23376
+ const { ait, secretKey } = await readAgentProofMaterial(
23377
+ agentName,
23378
+ dependencies
23379
+ );
23380
+ const callerAgentDid = parseAitAgentDid(ait);
23381
+ const requestUrl = toProxyRequestUrl(proxyUrl, PAIR_STATUS_PATH);
23382
+ const requestBody = JSON.stringify({ ticket });
23383
+ const bodyBytes = new TextEncoder().encode(requestBody);
23384
+ const timestampSeconds = nowSecondsImpl();
23385
+ const nonce = nonceFactoryImpl();
23386
+ const signedHeaders = await buildSignedHeaders({
23387
+ method: "POST",
23388
+ requestUrl,
23389
+ bodyBytes,
23390
+ secretKey,
23391
+ timestampSeconds,
23392
+ nonce
23393
+ });
23394
+ const response = await executePairRequest({
23395
+ fetchImpl,
23396
+ url: requestUrl,
23397
+ init: {
23398
+ method: "POST",
23399
+ headers: {
23400
+ authorization: `Claw ${ait}`,
23401
+ "content-type": "application/json",
23402
+ ...signedHeaders
23403
+ },
23404
+ body: requestBody
23405
+ }
23406
+ });
23407
+ const responseBody = await parseJsonResponse6(response);
23408
+ if (!response.ok) {
23409
+ throw createCliError7(
23410
+ "CLI_PAIR_STATUS_FAILED",
23411
+ mapStatusPairError(response.status, responseBody)
23412
+ );
23413
+ }
23414
+ const parsed = parsePairStatusResponse(responseBody);
23415
+ let peerAlias;
23416
+ if (parsed.status === "confirmed") {
23417
+ const responderAgentDid = parsed.responderAgentDid;
23418
+ if (!responderAgentDid) {
23419
+ throw createCliError7(
23420
+ "CLI_PAIR_STATUS_INVALID_RESPONSE",
23421
+ "Pair status response is invalid"
23422
+ );
23423
+ }
23424
+ const peerDid = callerAgentDid === parsed.initiatorAgentDid ? responderAgentDid : callerAgentDid === responderAgentDid ? parsed.initiatorAgentDid : void 0;
23425
+ if (!peerDid) {
23426
+ throw createCliError7(
23427
+ "CLI_PAIR_STATUS_FORBIDDEN",
23428
+ "Local agent is not a participant in the pairing ticket"
23429
+ );
23430
+ }
23431
+ peerAlias = await persistPairedPeer({
23432
+ ticket,
23433
+ peerDid,
23434
+ dependencies
23435
+ });
23436
+ }
23437
+ return {
23438
+ ...parsed,
23439
+ proxyUrl,
23440
+ peerAlias
23441
+ };
23442
+ }
23443
+ async function waitForPairingStatus(input) {
23444
+ const nowSecondsImpl = input.dependencies.nowSecondsImpl ?? (() => Math.floor(Date.now() / 1e3));
23445
+ const sleepImpl = input.dependencies.sleepImpl ?? (async (ms) => {
23446
+ await new Promise((resolve2) => {
23447
+ setTimeout(resolve2, ms);
23448
+ });
23449
+ });
23450
+ const deadlineSeconds = nowSecondsImpl() + input.waitSeconds;
23451
+ while (true) {
23452
+ const status = await getPairingStatusOnce(
23453
+ input.agentName,
23454
+ { ticket: input.ticket },
23455
+ input.dependencies
23456
+ );
23457
+ if (status.status === "confirmed") {
23458
+ return status;
23459
+ }
23460
+ const nowSeconds = nowSecondsImpl();
23461
+ if (nowSeconds >= deadlineSeconds) {
23462
+ throw createCliError7(
23463
+ "CLI_PAIR_STATUS_WAIT_TIMEOUT",
23464
+ `Pairing is still pending after ${input.waitSeconds} seconds`
23465
+ );
23466
+ }
23467
+ const remainingSeconds = Math.max(0, deadlineSeconds - nowSeconds);
23468
+ const sleepSeconds = Math.min(input.pollIntervalSeconds, remainingSeconds);
23469
+ await sleepImpl(sleepSeconds * 1e3);
23470
+ }
23471
+ }
23472
+ async function getPairingStatus(agentName, options, dependencies = {}) {
23473
+ const ticketRaw = parseNonEmptyString9(options.ticket);
23474
+ if (ticketRaw.length === 0) {
23475
+ throw createCliError7(
23476
+ "CLI_PAIR_STATUS_TICKET_REQUIRED",
23477
+ "Pair status requires --ticket <clwpair1_...>"
23478
+ );
23479
+ }
23480
+ const ticket = parsePairingTicket(ticketRaw);
23481
+ if (options.wait !== true) {
23482
+ return getPairingStatusOnce(
23483
+ agentName,
23484
+ { ticket },
23485
+ dependencies
23486
+ );
23487
+ }
23488
+ const waitSeconds = parsePositiveIntegerOption({
23489
+ value: options.waitSeconds,
23490
+ optionName: "waitSeconds",
23491
+ defaultValue: DEFAULT_STATUS_WAIT_SECONDS
23492
+ });
23493
+ const pollIntervalSeconds = parsePositiveIntegerOption({
23494
+ value: options.pollIntervalSeconds,
23495
+ optionName: "pollIntervalSeconds",
23496
+ defaultValue: DEFAULT_STATUS_POLL_INTERVAL_SECONDS
23497
+ });
23498
+ return waitForPairingStatus({
23499
+ agentName,
23500
+ ticket,
23501
+ waitSeconds,
23502
+ pollIntervalSeconds,
23503
+ dependencies
23504
+ });
23505
+ }
23241
23506
  var createPairCommand = (dependencies = {}) => {
23242
23507
  const pairCommand = new Command8("pair").description(
23243
23508
  "Manage proxy trust pairing between agents"
@@ -23245,7 +23510,16 @@ var createPairCommand = (dependencies = {}) => {
23245
23510
  pairCommand.command("start <agentName>").description("Start pairing and issue one-time pairing ticket").option(
23246
23511
  "--owner-pat <token>",
23247
23512
  "Owner PAT override (defaults to configured API key)"
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(
23513
+ ).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").option(
23514
+ "--wait",
23515
+ "Wait for responder confirmation and auto-save peer on initiator"
23516
+ ).option(
23517
+ "--wait-seconds <seconds>",
23518
+ "Max seconds to poll for confirmation (default: 300)"
23519
+ ).option(
23520
+ "--poll-interval-seconds <seconds>",
23521
+ "Polling interval in seconds while waiting (default: 3)"
23522
+ ).action(
23249
23523
  withErrorHandling(
23250
23524
  "pair start",
23251
23525
  async (agentName, options) => {
@@ -23263,6 +23537,44 @@ var createPairCommand = (dependencies = {}) => {
23263
23537
  if (result.qrPath) {
23264
23538
  writeStdoutLine(`QR File: ${result.qrPath}`);
23265
23539
  }
23540
+ if (options.wait === true) {
23541
+ const waitSeconds = parsePositiveIntegerOption({
23542
+ value: options.waitSeconds,
23543
+ optionName: "waitSeconds",
23544
+ defaultValue: DEFAULT_STATUS_WAIT_SECONDS
23545
+ });
23546
+ const pollIntervalSeconds = parsePositiveIntegerOption({
23547
+ value: options.pollIntervalSeconds,
23548
+ optionName: "pollIntervalSeconds",
23549
+ defaultValue: DEFAULT_STATUS_POLL_INTERVAL_SECONDS
23550
+ });
23551
+ writeStdoutLine(
23552
+ `Waiting for confirmation (timeout=${waitSeconds}s, interval=${pollIntervalSeconds}s) ...`
23553
+ );
23554
+ const status = await waitForPairingStatus({
23555
+ agentName,
23556
+ ticket: result.ticket,
23557
+ waitSeconds,
23558
+ pollIntervalSeconds,
23559
+ dependencies
23560
+ });
23561
+ logger9.info("cli.pair_status_confirmed_after_start", {
23562
+ initiatorAgentDid: status.initiatorAgentDid,
23563
+ responderAgentDid: status.responderAgentDid,
23564
+ peerAlias: status.peerAlias
23565
+ });
23566
+ writeStdoutLine("Pairing confirmed");
23567
+ writeStdoutLine(`Status: ${status.status}`);
23568
+ if (status.initiatorAgentDid) {
23569
+ writeStdoutLine(`Initiator Agent DID: ${status.initiatorAgentDid}`);
23570
+ }
23571
+ if (status.responderAgentDid) {
23572
+ writeStdoutLine(`Responder Agent DID: ${status.responderAgentDid}`);
23573
+ }
23574
+ if (status.peerAlias) {
23575
+ writeStdoutLine(`Peer alias saved: ${status.peerAlias}`);
23576
+ }
23577
+ }
23266
23578
  }
23267
23579
  )
23268
23580
  );
@@ -23287,6 +23599,39 @@ var createPairCommand = (dependencies = {}) => {
23287
23599
  }
23288
23600
  )
23289
23601
  );
23602
+ pairCommand.command("status <agentName>").description("Check pairing ticket status and sync local peer on confirm").option("--ticket <ticket>", "One-time pairing ticket (clwpair1_...)").option("--wait", "Poll until ticket is confirmed or timeout is reached").option(
23603
+ "--wait-seconds <seconds>",
23604
+ "Max seconds to poll for confirmation (default: 300)"
23605
+ ).option(
23606
+ "--poll-interval-seconds <seconds>",
23607
+ "Polling interval in seconds while waiting (default: 3)"
23608
+ ).action(
23609
+ withErrorHandling(
23610
+ "pair status",
23611
+ async (agentName, options) => {
23612
+ const result = await getPairingStatus(agentName, options, dependencies);
23613
+ logger9.info("cli.pair_status", {
23614
+ initiatorAgentDid: result.initiatorAgentDid,
23615
+ responderAgentDid: result.responderAgentDid,
23616
+ status: result.status,
23617
+ proxyUrl: result.proxyUrl,
23618
+ peerAlias: result.peerAlias
23619
+ });
23620
+ writeStdoutLine(`Status: ${result.status}`);
23621
+ writeStdoutLine(`Initiator Agent DID: ${result.initiatorAgentDid}`);
23622
+ if (result.responderAgentDid) {
23623
+ writeStdoutLine(`Responder Agent DID: ${result.responderAgentDid}`);
23624
+ }
23625
+ writeStdoutLine(`Expires At: ${result.expiresAt}`);
23626
+ if (result.confirmedAt) {
23627
+ writeStdoutLine(`Confirmed At: ${result.confirmedAt}`);
23628
+ }
23629
+ if (result.peerAlias) {
23630
+ writeStdoutLine(`Peer alias saved: ${result.peerAlias}`);
23631
+ }
23632
+ }
23633
+ )
23634
+ );
23290
23635
  return pairCommand;
23291
23636
  };
23292
23637
 
package/dist/index.js CHANGED
@@ -22471,6 +22471,7 @@ var PAIRING_QR_DIR_NAME = "pairing";
22471
22471
  var PEERS_FILE_NAME2 = "peers.json";
22472
22472
  var PAIR_START_PATH = "/pair/start";
22473
22473
  var PAIR_CONFIRM_PATH = "/pair/confirm";
22474
+ var PAIR_STATUS_PATH = "/pair/status";
22474
22475
  var OWNER_PAT_HEADER = "x-claw-owner-pat";
22475
22476
  var NONCE_SIZE2 = 24;
22476
22477
  var PAIRING_TICKET_PREFIX = "clwpair1_";
@@ -22478,6 +22479,8 @@ var PAIRING_QR_MAX_AGE_SECONDS = 900;
22478
22479
  var PAIRING_QR_FILENAME_PATTERN = /-pair-(\d+)\.png$/;
22479
22480
  var FILE_MODE4 = 384;
22480
22481
  var PEER_ALIAS_PATTERN2 = /^[a-zA-Z0-9._-]+$/;
22482
+ var DEFAULT_STATUS_WAIT_SECONDS = 300;
22483
+ var DEFAULT_STATUS_POLL_INTERVAL_SECONDS = 3;
22481
22484
  var isRecord9 = (value) => {
22482
22485
  return typeof value === "object" && value !== null;
22483
22486
  };
@@ -22553,6 +22556,52 @@ function parsePairingTicketIssuerOrigin(ticket) {
22553
22556
  }
22554
22557
  return issuerUrl.origin;
22555
22558
  }
22559
+ function parseAitAgentDid(ait) {
22560
+ const parts = ait.split(".");
22561
+ if (parts.length < 2) {
22562
+ throw createCliError7(
22563
+ "CLI_PAIR_AGENT_NOT_FOUND",
22564
+ "Agent AIT is invalid. Recreate the agent before pairing."
22565
+ );
22566
+ }
22567
+ let payloadRaw;
22568
+ try {
22569
+ payloadRaw = new TextDecoder().decode(decodeBase64url(parts[1] ?? ""));
22570
+ } catch {
22571
+ throw createCliError7(
22572
+ "CLI_PAIR_AGENT_NOT_FOUND",
22573
+ "Agent AIT is invalid. Recreate the agent before pairing."
22574
+ );
22575
+ }
22576
+ let payload;
22577
+ try {
22578
+ payload = JSON.parse(payloadRaw);
22579
+ } catch {
22580
+ throw createCliError7(
22581
+ "CLI_PAIR_AGENT_NOT_FOUND",
22582
+ "Agent AIT is invalid. Recreate the agent before pairing."
22583
+ );
22584
+ }
22585
+ if (!isRecord9(payload) || typeof payload.sub !== "string") {
22586
+ throw createCliError7(
22587
+ "CLI_PAIR_AGENT_NOT_FOUND",
22588
+ "Agent AIT is invalid. Recreate the agent before pairing."
22589
+ );
22590
+ }
22591
+ const candidate = payload.sub.trim();
22592
+ try {
22593
+ const parsed = parseDid(candidate);
22594
+ if (parsed.kind !== "agent") {
22595
+ throw new Error("invalid kind");
22596
+ }
22597
+ } catch {
22598
+ throw createCliError7(
22599
+ "CLI_PAIR_AGENT_NOT_FOUND",
22600
+ "Agent AIT is invalid. Recreate the agent before pairing."
22601
+ );
22602
+ }
22603
+ return candidate;
22604
+ }
22556
22605
  function parsePeerAlias2(value) {
22557
22606
  if (value.length === 0 || value.length > 128) {
22558
22607
  throw createCliError7(
@@ -22684,6 +22733,20 @@ function parseTtlSeconds(value) {
22684
22733
  }
22685
22734
  return parsed;
22686
22735
  }
22736
+ function parsePositiveIntegerOption(input) {
22737
+ const raw = parseNonEmptyString9(input.value);
22738
+ if (raw.length === 0) {
22739
+ return input.defaultValue;
22740
+ }
22741
+ const parsed = Number.parseInt(raw, 10);
22742
+ if (!Number.isInteger(parsed) || parsed < 1) {
22743
+ throw createCliError7(
22744
+ "CLI_PAIR_STATUS_WAIT_INVALID",
22745
+ `${input.optionName} must be a positive integer`
22746
+ );
22747
+ }
22748
+ return parsed;
22749
+ }
22687
22750
  function parseProxyUrl2(candidate) {
22688
22751
  try {
22689
22752
  const parsed = new URL(candidate);
@@ -22804,6 +22867,29 @@ function mapConfirmPairError(status, payload) {
22804
22867
  }
22805
22868
  return `Pair confirm failed (${status})`;
22806
22869
  }
22870
+ function mapStatusPairError(status, payload) {
22871
+ const code = extractErrorCode(payload);
22872
+ const message2 = extractErrorMessage(payload);
22873
+ if (code === "PROXY_PAIR_TICKET_NOT_FOUND" || status === 404) {
22874
+ return "Pairing ticket not found";
22875
+ }
22876
+ if (code === "PROXY_PAIR_TICKET_EXPIRED" || status === 410) {
22877
+ return "Pairing ticket has expired";
22878
+ }
22879
+ if (code === "PROXY_PAIR_STATUS_FORBIDDEN" || status === 403) {
22880
+ return message2 ? `Pair status request is forbidden (403): ${message2}` : "Pair status request is forbidden (403).";
22881
+ }
22882
+ if (status === 400) {
22883
+ return message2 ? `Pair status request is invalid (400): ${message2}` : "Pair status request is invalid (400).";
22884
+ }
22885
+ if (status >= 500) {
22886
+ return `Proxy pairing service is unavailable (${status}).`;
22887
+ }
22888
+ if (message2) {
22889
+ return `Pair status failed (${status}): ${message2}`;
22890
+ }
22891
+ return `Pair status failed (${status})`;
22892
+ }
22807
22893
  function parsePairStartResponse(payload) {
22808
22894
  if (!isRecord9(payload)) {
22809
22895
  throw createCliError7(
@@ -22848,6 +22934,44 @@ function parsePairConfirmResponse(payload) {
22848
22934
  responderAgentDid
22849
22935
  };
22850
22936
  }
22937
+ function parsePairStatusResponse(payload) {
22938
+ if (!isRecord9(payload)) {
22939
+ throw createCliError7(
22940
+ "CLI_PAIR_STATUS_INVALID_RESPONSE",
22941
+ "Pair status response is invalid"
22942
+ );
22943
+ }
22944
+ const statusRaw = parseNonEmptyString9(payload.status);
22945
+ if (statusRaw !== "pending" && statusRaw !== "confirmed") {
22946
+ throw createCliError7(
22947
+ "CLI_PAIR_STATUS_INVALID_RESPONSE",
22948
+ "Pair status response is invalid"
22949
+ );
22950
+ }
22951
+ const initiatorAgentDid = parseNonEmptyString9(payload.initiatorAgentDid);
22952
+ const responderAgentDid = parseNonEmptyString9(payload.responderAgentDid);
22953
+ const expiresAt = parseNonEmptyString9(payload.expiresAt);
22954
+ const confirmedAt = parseNonEmptyString9(payload.confirmedAt);
22955
+ if (initiatorAgentDid.length === 0 || expiresAt.length === 0) {
22956
+ throw createCliError7(
22957
+ "CLI_PAIR_STATUS_INVALID_RESPONSE",
22958
+ "Pair status response is invalid"
22959
+ );
22960
+ }
22961
+ if (statusRaw === "confirmed" && responderAgentDid.length === 0) {
22962
+ throw createCliError7(
22963
+ "CLI_PAIR_STATUS_INVALID_RESPONSE",
22964
+ "Pair status response is invalid"
22965
+ );
22966
+ }
22967
+ return {
22968
+ status: statusRaw,
22969
+ initiatorAgentDid,
22970
+ responderAgentDid: responderAgentDid.length > 0 ? responderAgentDid : void 0,
22971
+ expiresAt,
22972
+ confirmedAt: confirmedAt.length > 0 ? confirmedAt : void 0
22973
+ };
22974
+ }
22851
22975
  async function readAgentProofMaterial(agentName, dependencies) {
22852
22976
  const readFileImpl = dependencies.readFileImpl ?? readFile5;
22853
22977
  const getConfigDirImpl = dependencies.getConfigDirImpl ?? getConfigDir;
@@ -23238,6 +23362,147 @@ async function confirmPairing(agentName, options, dependencies = {}) {
23238
23362
  peerAlias
23239
23363
  };
23240
23364
  }
23365
+ async function getPairingStatusOnce(agentName, options, dependencies = {}) {
23366
+ const fetchImpl = dependencies.fetchImpl ?? fetch;
23367
+ const resolveConfigImpl = dependencies.resolveConfigImpl ?? resolveConfig;
23368
+ const nowSecondsImpl = dependencies.nowSecondsImpl ?? (() => Math.floor(Date.now() / 1e3));
23369
+ const nonceFactoryImpl = dependencies.nonceFactoryImpl ?? (() => randomBytes4(NONCE_SIZE2).toString("base64url"));
23370
+ const config2 = await resolveConfigImpl();
23371
+ const proxyUrl = await resolveProxyUrl({
23372
+ config: config2,
23373
+ fetchImpl
23374
+ });
23375
+ const ticket = parsePairingTicket(options.ticket);
23376
+ const { ait, secretKey } = await readAgentProofMaterial(
23377
+ agentName,
23378
+ dependencies
23379
+ );
23380
+ const callerAgentDid = parseAitAgentDid(ait);
23381
+ const requestUrl = toProxyRequestUrl(proxyUrl, PAIR_STATUS_PATH);
23382
+ const requestBody = JSON.stringify({ ticket });
23383
+ const bodyBytes = new TextEncoder().encode(requestBody);
23384
+ const timestampSeconds = nowSecondsImpl();
23385
+ const nonce = nonceFactoryImpl();
23386
+ const signedHeaders = await buildSignedHeaders({
23387
+ method: "POST",
23388
+ requestUrl,
23389
+ bodyBytes,
23390
+ secretKey,
23391
+ timestampSeconds,
23392
+ nonce
23393
+ });
23394
+ const response = await executePairRequest({
23395
+ fetchImpl,
23396
+ url: requestUrl,
23397
+ init: {
23398
+ method: "POST",
23399
+ headers: {
23400
+ authorization: `Claw ${ait}`,
23401
+ "content-type": "application/json",
23402
+ ...signedHeaders
23403
+ },
23404
+ body: requestBody
23405
+ }
23406
+ });
23407
+ const responseBody = await parseJsonResponse6(response);
23408
+ if (!response.ok) {
23409
+ throw createCliError7(
23410
+ "CLI_PAIR_STATUS_FAILED",
23411
+ mapStatusPairError(response.status, responseBody)
23412
+ );
23413
+ }
23414
+ const parsed = parsePairStatusResponse(responseBody);
23415
+ let peerAlias;
23416
+ if (parsed.status === "confirmed") {
23417
+ const responderAgentDid = parsed.responderAgentDid;
23418
+ if (!responderAgentDid) {
23419
+ throw createCliError7(
23420
+ "CLI_PAIR_STATUS_INVALID_RESPONSE",
23421
+ "Pair status response is invalid"
23422
+ );
23423
+ }
23424
+ const peerDid = callerAgentDid === parsed.initiatorAgentDid ? responderAgentDid : callerAgentDid === responderAgentDid ? parsed.initiatorAgentDid : void 0;
23425
+ if (!peerDid) {
23426
+ throw createCliError7(
23427
+ "CLI_PAIR_STATUS_FORBIDDEN",
23428
+ "Local agent is not a participant in the pairing ticket"
23429
+ );
23430
+ }
23431
+ peerAlias = await persistPairedPeer({
23432
+ ticket,
23433
+ peerDid,
23434
+ dependencies
23435
+ });
23436
+ }
23437
+ return {
23438
+ ...parsed,
23439
+ proxyUrl,
23440
+ peerAlias
23441
+ };
23442
+ }
23443
+ async function waitForPairingStatus(input) {
23444
+ const nowSecondsImpl = input.dependencies.nowSecondsImpl ?? (() => Math.floor(Date.now() / 1e3));
23445
+ const sleepImpl = input.dependencies.sleepImpl ?? (async (ms) => {
23446
+ await new Promise((resolve2) => {
23447
+ setTimeout(resolve2, ms);
23448
+ });
23449
+ });
23450
+ const deadlineSeconds = nowSecondsImpl() + input.waitSeconds;
23451
+ while (true) {
23452
+ const status = await getPairingStatusOnce(
23453
+ input.agentName,
23454
+ { ticket: input.ticket },
23455
+ input.dependencies
23456
+ );
23457
+ if (status.status === "confirmed") {
23458
+ return status;
23459
+ }
23460
+ const nowSeconds = nowSecondsImpl();
23461
+ if (nowSeconds >= deadlineSeconds) {
23462
+ throw createCliError7(
23463
+ "CLI_PAIR_STATUS_WAIT_TIMEOUT",
23464
+ `Pairing is still pending after ${input.waitSeconds} seconds`
23465
+ );
23466
+ }
23467
+ const remainingSeconds = Math.max(0, deadlineSeconds - nowSeconds);
23468
+ const sleepSeconds = Math.min(input.pollIntervalSeconds, remainingSeconds);
23469
+ await sleepImpl(sleepSeconds * 1e3);
23470
+ }
23471
+ }
23472
+ async function getPairingStatus(agentName, options, dependencies = {}) {
23473
+ const ticketRaw = parseNonEmptyString9(options.ticket);
23474
+ if (ticketRaw.length === 0) {
23475
+ throw createCliError7(
23476
+ "CLI_PAIR_STATUS_TICKET_REQUIRED",
23477
+ "Pair status requires --ticket <clwpair1_...>"
23478
+ );
23479
+ }
23480
+ const ticket = parsePairingTicket(ticketRaw);
23481
+ if (options.wait !== true) {
23482
+ return getPairingStatusOnce(
23483
+ agentName,
23484
+ { ticket },
23485
+ dependencies
23486
+ );
23487
+ }
23488
+ const waitSeconds = parsePositiveIntegerOption({
23489
+ value: options.waitSeconds,
23490
+ optionName: "waitSeconds",
23491
+ defaultValue: DEFAULT_STATUS_WAIT_SECONDS
23492
+ });
23493
+ const pollIntervalSeconds = parsePositiveIntegerOption({
23494
+ value: options.pollIntervalSeconds,
23495
+ optionName: "pollIntervalSeconds",
23496
+ defaultValue: DEFAULT_STATUS_POLL_INTERVAL_SECONDS
23497
+ });
23498
+ return waitForPairingStatus({
23499
+ agentName,
23500
+ ticket,
23501
+ waitSeconds,
23502
+ pollIntervalSeconds,
23503
+ dependencies
23504
+ });
23505
+ }
23241
23506
  var createPairCommand = (dependencies = {}) => {
23242
23507
  const pairCommand = new Command8("pair").description(
23243
23508
  "Manage proxy trust pairing between agents"
@@ -23245,7 +23510,16 @@ var createPairCommand = (dependencies = {}) => {
23245
23510
  pairCommand.command("start <agentName>").description("Start pairing and issue one-time pairing ticket").option(
23246
23511
  "--owner-pat <token>",
23247
23512
  "Owner PAT override (defaults to configured API key)"
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(
23513
+ ).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").option(
23514
+ "--wait",
23515
+ "Wait for responder confirmation and auto-save peer on initiator"
23516
+ ).option(
23517
+ "--wait-seconds <seconds>",
23518
+ "Max seconds to poll for confirmation (default: 300)"
23519
+ ).option(
23520
+ "--poll-interval-seconds <seconds>",
23521
+ "Polling interval in seconds while waiting (default: 3)"
23522
+ ).action(
23249
23523
  withErrorHandling(
23250
23524
  "pair start",
23251
23525
  async (agentName, options) => {
@@ -23263,6 +23537,44 @@ var createPairCommand = (dependencies = {}) => {
23263
23537
  if (result.qrPath) {
23264
23538
  writeStdoutLine(`QR File: ${result.qrPath}`);
23265
23539
  }
23540
+ if (options.wait === true) {
23541
+ const waitSeconds = parsePositiveIntegerOption({
23542
+ value: options.waitSeconds,
23543
+ optionName: "waitSeconds",
23544
+ defaultValue: DEFAULT_STATUS_WAIT_SECONDS
23545
+ });
23546
+ const pollIntervalSeconds = parsePositiveIntegerOption({
23547
+ value: options.pollIntervalSeconds,
23548
+ optionName: "pollIntervalSeconds",
23549
+ defaultValue: DEFAULT_STATUS_POLL_INTERVAL_SECONDS
23550
+ });
23551
+ writeStdoutLine(
23552
+ `Waiting for confirmation (timeout=${waitSeconds}s, interval=${pollIntervalSeconds}s) ...`
23553
+ );
23554
+ const status = await waitForPairingStatus({
23555
+ agentName,
23556
+ ticket: result.ticket,
23557
+ waitSeconds,
23558
+ pollIntervalSeconds,
23559
+ dependencies
23560
+ });
23561
+ logger9.info("cli.pair_status_confirmed_after_start", {
23562
+ initiatorAgentDid: status.initiatorAgentDid,
23563
+ responderAgentDid: status.responderAgentDid,
23564
+ peerAlias: status.peerAlias
23565
+ });
23566
+ writeStdoutLine("Pairing confirmed");
23567
+ writeStdoutLine(`Status: ${status.status}`);
23568
+ if (status.initiatorAgentDid) {
23569
+ writeStdoutLine(`Initiator Agent DID: ${status.initiatorAgentDid}`);
23570
+ }
23571
+ if (status.responderAgentDid) {
23572
+ writeStdoutLine(`Responder Agent DID: ${status.responderAgentDid}`);
23573
+ }
23574
+ if (status.peerAlias) {
23575
+ writeStdoutLine(`Peer alias saved: ${status.peerAlias}`);
23576
+ }
23577
+ }
23266
23578
  }
23267
23579
  )
23268
23580
  );
@@ -23287,6 +23599,39 @@ var createPairCommand = (dependencies = {}) => {
23287
23599
  }
23288
23600
  )
23289
23601
  );
23602
+ pairCommand.command("status <agentName>").description("Check pairing ticket status and sync local peer on confirm").option("--ticket <ticket>", "One-time pairing ticket (clwpair1_...)").option("--wait", "Poll until ticket is confirmed or timeout is reached").option(
23603
+ "--wait-seconds <seconds>",
23604
+ "Max seconds to poll for confirmation (default: 300)"
23605
+ ).option(
23606
+ "--poll-interval-seconds <seconds>",
23607
+ "Polling interval in seconds while waiting (default: 3)"
23608
+ ).action(
23609
+ withErrorHandling(
23610
+ "pair status",
23611
+ async (agentName, options) => {
23612
+ const result = await getPairingStatus(agentName, options, dependencies);
23613
+ logger9.info("cli.pair_status", {
23614
+ initiatorAgentDid: result.initiatorAgentDid,
23615
+ responderAgentDid: result.responderAgentDid,
23616
+ status: result.status,
23617
+ proxyUrl: result.proxyUrl,
23618
+ peerAlias: result.peerAlias
23619
+ });
23620
+ writeStdoutLine(`Status: ${result.status}`);
23621
+ writeStdoutLine(`Initiator Agent DID: ${result.initiatorAgentDid}`);
23622
+ if (result.responderAgentDid) {
23623
+ writeStdoutLine(`Responder Agent DID: ${result.responderAgentDid}`);
23624
+ }
23625
+ writeStdoutLine(`Expires At: ${result.expiresAt}`);
23626
+ if (result.confirmedAt) {
23627
+ writeStdoutLine(`Confirmed At: ${result.confirmedAt}`);
23628
+ }
23629
+ if (result.peerAlias) {
23630
+ writeStdoutLine(`Peer alias saved: ${result.peerAlias}`);
23631
+ }
23632
+ }
23633
+ )
23634
+ );
23290
23635
  return pairCommand;
23291
23636
  };
23292
23637
 
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawdentity",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -21,6 +21,17 @@
21
21
  "postinstall.mjs",
22
22
  "skill-bundle"
23
23
  ],
24
+ "scripts": {
25
+ "build": "pnpm -F @clawdentity/openclaw-skill build && pnpm run sync:skill-bundle && pnpm run verify:skill-bundle && tsup",
26
+ "format": "biome format .",
27
+ "lint": "biome lint .",
28
+ "prepack": "pnpm run build",
29
+ "postinstall": "node ./postinstall.mjs",
30
+ "sync:skill-bundle": "node ./scripts/sync-skill-bundle.mjs",
31
+ "verify:skill-bundle": "node ./scripts/verify-skill-bundle.mjs",
32
+ "test": "vitest run",
33
+ "typecheck": "tsc --noEmit"
34
+ },
24
35
  "dependencies": {
25
36
  "commander": "^13.1.0",
26
37
  "jsqr": "^1.4.0",
@@ -29,21 +40,11 @@
29
40
  "ws": "^8.19.0"
30
41
  },
31
42
  "devDependencies": {
43
+ "@clawdentity/connector": "workspace:*",
44
+ "@clawdentity/protocol": "workspace:*",
45
+ "@clawdentity/sdk": "workspace:*",
32
46
  "@types/node": "^22.18.11",
33
47
  "@types/pngjs": "^6.0.5",
34
- "@types/qrcode": "^1.5.6",
35
- "@clawdentity/connector": "0.0.0",
36
- "@clawdentity/protocol": "0.0.0",
37
- "@clawdentity/sdk": "0.0.0"
38
- },
39
- "scripts": {
40
- "build": "pnpm -F @clawdentity/openclaw-skill build && pnpm run sync:skill-bundle && pnpm run verify:skill-bundle && tsup",
41
- "format": "biome format .",
42
- "lint": "biome lint .",
43
- "postinstall": "node ./postinstall.mjs",
44
- "sync:skill-bundle": "node ./scripts/sync-skill-bundle.mjs",
45
- "verify:skill-bundle": "node ./scripts/verify-skill-bundle.mjs",
46
- "test": "vitest run",
47
- "typecheck": "tsc --noEmit"
48
+ "@types/qrcode": "^1.5.6"
48
49
  }
49
- }
50
+ }