@astrasyncai/verification-gateway 5.18.0 → 5.19.0

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.
Files changed (115) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/README.md +168 -4
  3. package/dist/adapters/express.d.mts +9 -1
  4. package/dist/adapters/express.d.ts +9 -1
  5. package/dist/adapters/express.js +96 -37
  6. package/dist/adapters/express.js.map +1 -1
  7. package/dist/adapters/express.mjs +96 -37
  8. package/dist/adapters/express.mjs.map +1 -1
  9. package/dist/adapters/http-pdlss.d.mts +7 -1
  10. package/dist/adapters/http-pdlss.d.ts +7 -1
  11. package/dist/adapters/http-pdlss.js +18 -2
  12. package/dist/adapters/http-pdlss.js.map +1 -1
  13. package/dist/adapters/http-pdlss.mjs +16 -1
  14. package/dist/adapters/http-pdlss.mjs.map +1 -1
  15. package/dist/adapters/kit-express.d.mts +93 -1
  16. package/dist/adapters/kit-express.d.ts +93 -1
  17. package/dist/adapters/kit-express.js +181 -2
  18. package/dist/adapters/kit-express.js.map +1 -1
  19. package/dist/adapters/kit-express.mjs +180 -2
  20. package/dist/adapters/kit-express.mjs.map +1 -1
  21. package/dist/adapters/mcp.d.mts +80 -9
  22. package/dist/adapters/mcp.d.ts +80 -9
  23. package/dist/adapters/mcp.js +153 -5
  24. package/dist/adapters/mcp.js.map +1 -1
  25. package/dist/adapters/mcp.mjs +151 -5
  26. package/dist/adapters/mcp.mjs.map +1 -1
  27. package/dist/adapters/nextjs.d.mts +1 -1
  28. package/dist/adapters/nextjs.d.ts +1 -1
  29. package/dist/adapters/nextjs.js +41 -4
  30. package/dist/adapters/nextjs.js.map +1 -1
  31. package/dist/adapters/nextjs.mjs +41 -4
  32. package/dist/adapters/nextjs.mjs.map +1 -1
  33. package/dist/adapters/sdk.d.mts +44 -1
  34. package/dist/adapters/sdk.d.ts +44 -1
  35. package/dist/adapters/sdk.js +76 -2
  36. package/dist/adapters/sdk.js.map +1 -1
  37. package/dist/adapters/sdk.mjs +76 -2
  38. package/dist/adapters/sdk.mjs.map +1 -1
  39. package/dist/agent/index.js +1 -1
  40. package/dist/agent/index.js.map +1 -1
  41. package/dist/agent/index.mjs +1 -1
  42. package/dist/agent/index.mjs.map +1 -1
  43. package/dist/bin/astrasync-claude-hook.js +46 -6
  44. package/dist/bin/astrasync-codex-hook.js +46 -6
  45. package/dist/bin/astrasync-guard.js +375 -117
  46. package/dist/bin/astrasync.js +417 -117
  47. package/dist/browser/background.js +46 -6
  48. package/dist/browser/background.js.map +1 -1
  49. package/dist/browser/background.mjs +46 -6
  50. package/dist/browser/background.mjs.map +1 -1
  51. package/dist/cli/index.js +1 -1
  52. package/dist/cli/index.js.map +1 -1
  53. package/dist/cli/index.mjs +1 -1
  54. package/dist/cli/index.mjs.map +1 -1
  55. package/dist/codex/index.js +46 -6
  56. package/dist/codex/index.js.map +1 -1
  57. package/dist/codex/index.mjs +46 -6
  58. package/dist/codex/index.mjs.map +1 -1
  59. package/dist/cursor/extension.js +46 -6
  60. package/dist/cursor/extension.js.map +1 -1
  61. package/dist/cursor/extension.mjs +46 -6
  62. package/dist/cursor/extension.mjs.map +1 -1
  63. package/dist/edge-config.d.mts +1 -1
  64. package/dist/edge-config.d.ts +1 -1
  65. package/dist/edge-config.js +1 -1
  66. package/dist/edge-config.js.map +1 -1
  67. package/dist/edge-config.mjs +1 -1
  68. package/dist/edge-config.mjs.map +1 -1
  69. package/dist/edge-core/index.d.mts +1 -1
  70. package/dist/edge-core/index.d.ts +1 -1
  71. package/dist/edge-core/index.js +21 -2
  72. package/dist/edge-core/index.js.map +1 -1
  73. package/dist/edge-core/index.mjs +21 -2
  74. package/dist/edge-core/index.mjs.map +1 -1
  75. package/dist/gateway/gateway.js +46 -6
  76. package/dist/gateway/gateway.js.map +1 -1
  77. package/dist/gateway/gateway.mjs +46 -6
  78. package/dist/gateway/gateway.mjs.map +1 -1
  79. package/dist/git-trigger/git-hooks.d.mts +1 -1
  80. package/dist/git-trigger/git-hooks.d.ts +1 -1
  81. package/dist/index.d.mts +542 -30
  82. package/dist/index.d.ts +542 -30
  83. package/dist/index.js +1171 -183
  84. package/dist/index.js.map +1 -1
  85. package/dist/index.mjs +1157 -173
  86. package/dist/index.mjs.map +1 -1
  87. package/dist/registration/index.d.mts +1 -1
  88. package/dist/registration/index.d.ts +1 -1
  89. package/dist/registration/index.js +1 -1
  90. package/dist/registration/index.js.map +1 -1
  91. package/dist/registration/index.mjs +1 -1
  92. package/dist/registration/index.mjs.map +1 -1
  93. package/dist/transport/index.js +3 -2
  94. package/dist/transport/index.js.map +1 -1
  95. package/dist/transport/index.mjs +3 -2
  96. package/dist/transport/index.mjs.map +1 -1
  97. package/dist/{types-3ObBpsnf.d.mts → types-BoGjyon0.d.mts} +81 -2
  98. package/dist/{types-Cns4l8gX.d.mts → types-DqWPkiYl.d.mts} +90 -12
  99. package/dist/{types-Cns4l8gX.d.ts → types-DqWPkiYl.d.ts} +90 -12
  100. package/dist/{types-BoxZEYPS.d.ts → types-eWyZuz-3.d.ts} +81 -2
  101. package/dist/ui/index.d.mts +1 -1
  102. package/dist/ui/index.d.ts +1 -1
  103. package/dist/verify.d.mts +23 -2
  104. package/dist/verify.d.ts +23 -2
  105. package/dist/verify.js +23 -4
  106. package/dist/verify.js.map +1 -1
  107. package/dist/verify.mjs +23 -4
  108. package/dist/verify.mjs.map +1 -1
  109. package/dist/webhooks.d.mts +16 -3
  110. package/dist/webhooks.d.ts +16 -3
  111. package/dist/webhooks.js +24 -13
  112. package/dist/webhooks.js.map +1 -1
  113. package/dist/webhooks.mjs +24 -13
  114. package/dist/webhooks.mjs.map +1 -1
  115. package/package.json +1 -1
@@ -36,7 +36,7 @@ var SDK_VERSION;
36
36
  var init_version = __esm({
37
37
  "src/version.ts"() {
38
38
  "use strict";
39
- SDK_VERSION = "5.18.0";
39
+ SDK_VERSION = "5.19.0";
40
40
  }
41
41
  });
42
42
 
@@ -3474,115 +3474,115 @@ function validatePolicy(policy) {
3474
3474
  }
3475
3475
  return { valid: errors.length === 0, errors };
3476
3476
  }
3477
- function validatePurpose(purpose, path2) {
3477
+ function validatePurpose(purpose, path3) {
3478
3478
  const errors = [];
3479
3479
  if (!purpose || typeof purpose !== "object") {
3480
- return [{ path: path2, message: "purpose must be an object" }];
3480
+ return [{ path: path3, message: "purpose must be an object" }];
3481
3481
  }
3482
3482
  const p = purpose;
3483
3483
  if (typeof p.id !== "string" || !p.id) {
3484
- errors.push({ path: `${path2}.id`, message: "id is required and must be a non-empty string" });
3484
+ errors.push({ path: `${path3}.id`, message: "id is required and must be a non-empty string" });
3485
3485
  }
3486
3486
  if (typeof p.allowed !== "boolean") {
3487
- errors.push({ path: `${path2}.allowed`, message: "allowed is required and must be a boolean" });
3487
+ errors.push({ path: `${path3}.allowed`, message: "allowed is required and must be a boolean" });
3488
3488
  }
3489
3489
  if (p.targets !== void 0 && !isStringArray(p.targets)) {
3490
- errors.push({ path: `${path2}.targets`, message: "targets must be an array of strings" });
3490
+ errors.push({ path: `${path3}.targets`, message: "targets must be an array of strings" });
3491
3491
  }
3492
3492
  if (p.blockedPatterns !== void 0 && !isStringArray(p.blockedPatterns)) {
3493
3493
  errors.push({
3494
- path: `${path2}.blockedPatterns`,
3494
+ path: `${path3}.blockedPatterns`,
3495
3495
  message: "blockedPatterns must be an array of strings"
3496
3496
  });
3497
3497
  }
3498
3498
  if (p.requiresApproval !== void 0 && typeof p.requiresApproval !== "boolean") {
3499
3499
  errors.push({
3500
- path: `${path2}.requiresApproval`,
3500
+ path: `${path3}.requiresApproval`,
3501
3501
  message: "requiresApproval must be a boolean"
3502
3502
  });
3503
3503
  }
3504
3504
  return errors;
3505
3505
  }
3506
- function validateScope(scope, path2) {
3506
+ function validateScope(scope, path3) {
3507
3507
  const errors = [];
3508
3508
  if (typeof scope !== "object" || scope === null) {
3509
- return [{ path: path2, message: "scope must be an object" }];
3509
+ return [{ path: path3, message: "scope must be an object" }];
3510
3510
  }
3511
3511
  const s = scope;
3512
3512
  for (const field of ["allowedDomains", "blockedDomains", "blockedResources"]) {
3513
3513
  if (s[field] !== void 0 && !isStringArray(s[field])) {
3514
- errors.push({ path: `${path2}.${field}`, message: `${field} must be an array of strings` });
3514
+ errors.push({ path: `${path3}.${field}`, message: `${field} must be an array of strings` });
3515
3515
  }
3516
3516
  }
3517
3517
  return errors;
3518
3518
  }
3519
- function validateLimits(limits, path2) {
3519
+ function validateLimits(limits, path3) {
3520
3520
  const errors = [];
3521
3521
  if (typeof limits !== "object" || limits === null) {
3522
- return [{ path: path2, message: "limits must be an object" }];
3522
+ return [{ path: path3, message: "limits must be an object" }];
3523
3523
  }
3524
3524
  const l = limits;
3525
3525
  if (l.autonomousThreshold !== void 0 && typeof l.autonomousThreshold !== "number") {
3526
3526
  errors.push({
3527
- path: `${path2}.autonomousThreshold`,
3527
+ path: `${path3}.autonomousThreshold`,
3528
3528
  message: "autonomousThreshold must be a number"
3529
3529
  });
3530
3530
  }
3531
3531
  if (l.maxTransactionAmount !== void 0 && typeof l.maxTransactionAmount !== "number") {
3532
3532
  errors.push({
3533
- path: `${path2}.maxTransactionAmount`,
3533
+ path: `${path3}.maxTransactionAmount`,
3534
3534
  message: "maxTransactionAmount must be a number"
3535
3535
  });
3536
3536
  }
3537
3537
  if (typeof l.autonomousThreshold === "number" && typeof l.maxTransactionAmount === "number" && l.autonomousThreshold > l.maxTransactionAmount) {
3538
3538
  errors.push({
3539
- path: `${path2}.autonomousThreshold`,
3539
+ path: `${path3}.autonomousThreshold`,
3540
3540
  message: "autonomousThreshold (Autonomous Limit) must be <= maxTransactionAmount (Hard Limit)"
3541
3541
  });
3542
3542
  }
3543
3543
  if (l.maxRequestsPerHour !== void 0 && typeof l.maxRequestsPerHour !== "number") {
3544
3544
  errors.push({
3545
- path: `${path2}.maxRequestsPerHour`,
3545
+ path: `${path3}.maxRequestsPerHour`,
3546
3546
  message: "maxRequestsPerHour must be a number"
3547
3547
  });
3548
3548
  }
3549
3549
  if (l.currency !== void 0 && typeof l.currency !== "string") {
3550
- errors.push({ path: `${path2}.currency`, message: "currency must be a string" });
3550
+ errors.push({ path: `${path3}.currency`, message: "currency must be a string" });
3551
3551
  }
3552
3552
  return errors;
3553
3553
  }
3554
- function validateRiskThresholds(thresholds, path2) {
3554
+ function validateRiskThresholds(thresholds, path3) {
3555
3555
  const errors = [];
3556
3556
  if (typeof thresholds !== "object" || thresholds === null) {
3557
- return [{ path: path2, message: "riskThresholds must be an object" }];
3557
+ return [{ path: path3, message: "riskThresholds must be an object" }];
3558
3558
  }
3559
3559
  const t = thresholds;
3560
3560
  for (const range of ["autoAllow", "requireApproval", "autoBlock"]) {
3561
3561
  if (!t[range] || typeof t[range] !== "object") {
3562
3562
  errors.push({
3563
- path: `${path2}.${range}`,
3563
+ path: `${path3}.${range}`,
3564
3564
  message: `${range} is required and must be an object with min and max`
3565
3565
  });
3566
3566
  continue;
3567
3567
  }
3568
3568
  const r = t[range];
3569
3569
  if (typeof r.min !== "number" || typeof r.max !== "number") {
3570
- errors.push({ path: `${path2}.${range}`, message: `${range} must have numeric min and max` });
3570
+ errors.push({ path: `${path3}.${range}`, message: `${range} must have numeric min and max` });
3571
3571
  } else if (r.min > r.max) {
3572
- errors.push({ path: `${path2}.${range}`, message: `${range}.min must be <= ${range}.max` });
3572
+ errors.push({ path: `${path3}.${range}`, message: `${range}.min must be <= ${range}.max` });
3573
3573
  }
3574
3574
  }
3575
3575
  if (isValidThresholdShape(t)) {
3576
3576
  const thresholdObj = t;
3577
3577
  if (thresholdObj.autoAllow.max + 1 !== thresholdObj.requireApproval.min) {
3578
3578
  errors.push({
3579
- path: `${path2}`,
3579
+ path: `${path3}`,
3580
3580
  message: `Gap between autoAllow.max (${thresholdObj.autoAllow.max}) and requireApproval.min (${thresholdObj.requireApproval.min}). Ranges must be contiguous.`
3581
3581
  });
3582
3582
  }
3583
3583
  if (thresholdObj.requireApproval.max + 1 !== thresholdObj.autoBlock.min) {
3584
3584
  errors.push({
3585
- path: `${path2}`,
3585
+ path: `${path3}`,
3586
3586
  message: `Gap between requireApproval.max (${thresholdObj.requireApproval.max}) and autoBlock.min (${thresholdObj.autoBlock.min}). Ranges must be contiguous.`
3587
3587
  });
3588
3588
  }
@@ -3962,8 +3962,8 @@ ${category.label}
3962
3962
  async runAndSave(outputPath) {
3963
3963
  const policy = await this.run();
3964
3964
  const yaml2 = toYaml(policy);
3965
- const fs2 = await import("fs");
3966
- fs2.writeFileSync(outputPath, yaml2, "utf-8");
3965
+ const fs3 = await import("fs");
3966
+ fs3.writeFileSync(outputPath, yaml2, "utf-8");
3967
3967
  this.print(`
3968
3968
  Policy saved to ${outputPath}`);
3969
3969
  this.print(" AstraSync Local Guard is now protecting your agent.\n");
@@ -5186,7 +5186,12 @@ function getCacheKey(request2, counterpartyId) {
5186
5186
  // its start_checkout quote was served the cached quote-leg grant (no
5187
5187
  // settlementOutcome) and never reached the backend. Same F-A1-07 bug class.
5188
5188
  request2.commercePhase || "",
5189
- request2.checkoutSessionId || ""
5189
+ request2.checkoutSessionId || "",
5190
+ // 5.19.0: a route_config purpose claim is checked server-side against
5191
+ // the stored rule for this method + path, so it changes the verdict.
5192
+ request2.purposeSource || "",
5193
+ request2.requestMethod || "",
5194
+ request2.requestPath || ""
5190
5195
  ].join("|");
5191
5196
  }
5192
5197
  function getCachedResult(request2, counterpartyId) {
@@ -5358,6 +5363,11 @@ async function callVerifyAccessAPI(config, request2) {
5358
5363
  if (requestData.buyerEmail) body.buyerEmail = requestData.buyerEmail;
5359
5364
  if (requestData.commerceArtifacts) body.commerceArtifacts = requestData.commerceArtifacts;
5360
5365
  if (requestData.attemptId) body.attemptId = requestData.attemptId;
5366
+ if (requestData.purposeSource) body.purposeSource = requestData.purposeSource;
5367
+ if (requestData.agentArtifacts) body.agentArtifacts = requestData.agentArtifacts;
5368
+ if (requestData.metadata) body.metadata = requestData.metadata;
5369
+ if (requestData.requestMethod) body.requestMethod = requestData.requestMethod;
5370
+ if (requestData.requestPath) body.requestPath = requestData.requestPath;
5361
5371
  if (requestData.considerationSet) body.considerationSet = requestData.considerationSet;
5362
5372
  body.sdkVersion = SDK_VERSION;
5363
5373
  if (requestData.callerMetadata || requestData.clientIp || requestData.userAgent) {
@@ -5547,9 +5557,18 @@ async function verify(config, request2, options) {
5547
5557
  if (!apiResponse.access?.allowed) {
5548
5558
  const aggregatedFailures = apiResponse.access?.failures;
5549
5559
  const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
5560
+ const denyAgent = idVerifiedFromBackend && apiResponse.agent ? {
5561
+ astraId: apiResponse.agent.astraId,
5562
+ name: apiResponse.agent.name,
5563
+ trustScore: apiResponse.agent.trustScore,
5564
+ trustLevel: getTrustLevel(apiResponse.agent.trustScore),
5565
+ blockchainAnchored: apiResponse.agent.blockchainStatus === "anchored",
5566
+ status: apiResponse.agent.agentStatus
5567
+ } : void 0;
5550
5568
  const result2 = {
5551
5569
  identityVerified: idVerifiedFromBackend,
5552
5570
  policyAllowed: false,
5571
+ ...denyAgent && { agent: denyAgent },
5553
5572
  // 3.6.0: the server now emits top-level denialReasons on every deny
5554
5573
  // path — prefer it verbatim; the failure-message reconstruction remains
5555
5574
  // as the fallback for older backends.
@@ -5772,6 +5791,16 @@ var init_commerce_detect = __esm({
5772
5791
  }
5773
5792
  });
5774
5793
 
5794
+ // src/adapters/routes-cache.ts
5795
+ var DEFAULT_ROUTES_REFRESH_MS;
5796
+ var init_routes_cache = __esm({
5797
+ "src/adapters/routes-cache.ts"() {
5798
+ "use strict";
5799
+ init_verify();
5800
+ DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
5801
+ }
5802
+ });
5803
+
5775
5804
  // src/transport/http.ts
5776
5805
  var init_http2 = __esm({
5777
5806
  "src/transport/http.ts"() {
@@ -6101,7 +6130,6 @@ var init_anonymous_beacon = __esm({
6101
6130
  });
6102
6131
 
6103
6132
  // src/adapters/express.ts
6104
- var DEFAULT_ROUTES_REFRESH_MS;
6105
6133
  var init_express = __esm({
6106
6134
  "src/adapters/express.ts"() {
6107
6135
  "use strict";
@@ -6109,6 +6137,7 @@ var init_express = __esm({
6109
6137
  init_types2();
6110
6138
  init_stepup_poll();
6111
6139
  init_verify();
6140
+ init_routes_cache();
6112
6141
  init_http2();
6113
6142
  init_verified_hop();
6114
6143
  init_http_pdlss();
@@ -6119,7 +6148,6 @@ var init_express = __esm({
6119
6148
  init_anonymous_beacon();
6120
6149
  init_failure_posture();
6121
6150
  init_route_pattern();
6122
- DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
6123
6151
  }
6124
6152
  });
6125
6153
 
@@ -6460,6 +6488,7 @@ var init_mcp2 = __esm({
6460
6488
  init_approval_gate();
6461
6489
  init_stepup_poll();
6462
6490
  init_well_known();
6491
+ init_routes_cache();
6463
6492
  init_failure_posture();
6464
6493
  init_mcp_server();
6465
6494
  }
@@ -6648,6 +6677,7 @@ var init_report_amount = __esm({
6648
6677
  var init_kit_shared = __esm({
6649
6678
  "src/ucp/kit-shared.ts"() {
6650
6679
  "use strict";
6680
+ init_sign();
6651
6681
  }
6652
6682
  });
6653
6683
 
@@ -6667,6 +6697,41 @@ var init_checkout_kit = __esm({
6667
6697
  init_signatures();
6668
6698
  init_report_amount();
6669
6699
  init_kit_shared();
6700
+ init_errors();
6701
+ init_stepup_poll();
6702
+ }
6703
+ });
6704
+
6705
+ // src/webhooks.ts
6706
+ var init_webhooks = __esm({
6707
+ "src/webhooks.ts"() {
6708
+ "use strict";
6709
+ }
6710
+ });
6711
+
6712
+ // src/ucp/approval-events.ts
6713
+ var MemorySeenEvents, defaultSeen;
6714
+ var init_approval_events = __esm({
6715
+ "src/ucp/approval-events.ts"() {
6716
+ "use strict";
6717
+ init_webhooks();
6718
+ MemorySeenEvents = class {
6719
+ constructor(max = 1e4) {
6720
+ this.max = max;
6721
+ this.ids = /* @__PURE__ */ new Set();
6722
+ }
6723
+ async has(eventId) {
6724
+ return this.ids.has(eventId);
6725
+ }
6726
+ async add(eventId) {
6727
+ if (this.ids.size >= this.max) {
6728
+ const first = this.ids.values().next().value;
6729
+ if (first !== void 0) this.ids.delete(first);
6730
+ }
6731
+ this.ids.add(eventId);
6732
+ }
6733
+ };
6734
+ defaultSeen = new MemorySeenEvents();
6670
6735
  }
6671
6736
  });
6672
6737
 
@@ -6681,6 +6746,7 @@ var init_ucp2 = __esm({
6681
6746
  init_signatures();
6682
6747
  init_checkout_kit();
6683
6748
  init_jcs();
6749
+ init_approval_events();
6684
6750
  }
6685
6751
  });
6686
6752
 
@@ -6742,6 +6808,8 @@ var init_checkout_kit2 = __esm({
6742
6808
  init_idempotency();
6743
6809
  init_report_amount();
6744
6810
  init_kit_shared();
6811
+ init_errors();
6812
+ init_stepup_poll();
6745
6813
  }
6746
6814
  });
6747
6815
 
@@ -6750,6 +6818,7 @@ var init_acp2 = __esm({
6750
6818
  "src/acp/index.ts"() {
6751
6819
  "use strict";
6752
6820
  init_checkout_kit2();
6821
+ init_approval_events();
6753
6822
  }
6754
6823
  });
6755
6824
 
@@ -6758,6 +6827,7 @@ var init_kit_express = __esm({
6758
6827
  "src/adapters/kit-express.ts"() {
6759
6828
  "use strict";
6760
6829
  init_kit_shared();
6830
+ init_approval_events();
6761
6831
  }
6762
6832
  });
6763
6833
 
@@ -6846,7 +6916,9 @@ function toDecision(result) {
6846
6916
  if (result.identityVerified && result.policyAllowed) {
6847
6917
  return {
6848
6918
  recommendation: "ALLOW",
6849
- reason: result.agent?.trustScore ? `Verified (trust score ${result.agent.trustScore})` : "Verified",
6919
+ // 5.19.0: the score is on `trustScore` for the merchant; `reason` is
6920
+ // text a merchant may relay to the agent, so it never embeds the number.
6921
+ reason: "Verified",
6850
6922
  trustScore: result.agent?.trustScore,
6851
6923
  sessionId: "sessionId" in result ? result.sessionId : void 0
6852
6924
  };
@@ -7825,29 +7897,29 @@ function loadEnrolment(paths) {
7825
7897
  [paths.machineDevice, "machine"],
7826
7898
  [paths.userDevice, "user"]
7827
7899
  ];
7828
- for (const [path2, scope] of candidates) {
7829
- if (!(0, import_fs6.existsSync)(path2)) continue;
7900
+ for (const [path3, scope] of candidates) {
7901
+ if (!(0, import_fs6.existsSync)(path3)) continue;
7830
7902
  try {
7831
- const parsed = JSON.parse((0, import_fs6.readFileSync)(path2, "utf-8"));
7903
+ const parsed = JSON.parse((0, import_fs6.readFileSync)(path3, "utf-8"));
7832
7904
  if (isEnrolment(parsed)) return { enrolment: parsed, scope };
7833
7905
  } catch {
7834
7906
  }
7835
- return { corrupt: true, path: path2 };
7907
+ return { corrupt: true, path: path3 };
7836
7908
  }
7837
7909
  return null;
7838
7910
  }
7839
- function writeFileMode(path2, content, mode) {
7840
- (0, import_fs6.mkdirSync)((0, import_path5.dirname)(path2), { recursive: true, mode: 448 });
7841
- (0, import_fs6.writeFileSync)(path2, content, { encoding: "utf-8", mode });
7911
+ function writeFileMode(path3, content, mode) {
7912
+ (0, import_fs6.mkdirSync)((0, import_path5.dirname)(path3), { recursive: true, mode: 448 });
7913
+ (0, import_fs6.writeFileSync)(path3, content, { encoding: "utf-8", mode });
7842
7914
  try {
7843
- (0, import_fs6.chmodSync)(path2, mode);
7915
+ (0, import_fs6.chmodSync)(path3, mode);
7844
7916
  } catch {
7845
7917
  }
7846
7918
  }
7847
7919
  function saveEnrolment(paths, enrolment, scope) {
7848
- const path2 = scope === "machine" ? paths.machineDevice : paths.userDevice;
7849
- writeFileMode(path2, JSON.stringify(enrolment, null, 2), scope === "machine" ? 420 : 384);
7850
- return path2;
7920
+ const path3 = scope === "machine" ? paths.machineDevice : paths.userDevice;
7921
+ writeFileMode(path3, JSON.stringify(enrolment, null, 2), scope === "machine" ? 420 : 384);
7922
+ return path3;
7851
7923
  }
7852
7924
  function readCachedPolicies(paths) {
7853
7925
  const out = [];
@@ -8175,12 +8247,12 @@ function formatFleetStatus(status) {
8175
8247
  if (status.lastError) lines.push(` Last sync error: ${status.lastError}`);
8176
8248
  return lines;
8177
8249
  }
8178
- function managedHookCommand(target, os2) {
8250
+ function managedHookCommand(target, os3) {
8179
8251
  const bin = target === "claude" ? "astrasync-claude-hook" : "astrasync-codex-hook";
8180
- if (os2 === "win32") return `"C:\\Program Files\\nodejs\\${bin}.cmd"`;
8252
+ if (os3 === "win32") return `"C:\\Program Files\\nodejs\\${bin}.cmd"`;
8181
8253
  return `/usr/local/bin/${bin}`;
8182
8254
  }
8183
- function buildClaudeManagedSettings(os2) {
8255
+ function buildClaudeManagedSettings(os3) {
8184
8256
  return {
8185
8257
  allowManagedHooksOnly: true,
8186
8258
  env: { ASTRASYNC_FLEET_REQUIRED: "1" },
@@ -8188,7 +8260,7 @@ function buildClaudeManagedSettings(os2) {
8188
8260
  PreToolUse: [
8189
8261
  {
8190
8262
  matcher: HOOK_MATCHER,
8191
- hooks: [{ type: "command", command: managedHookCommand("claude", os2), timeout: 10 }]
8263
+ hooks: [{ type: "command", command: managedHookCommand("claude", os3), timeout: 10 }]
8192
8264
  }
8193
8265
  ]
8194
8266
  },
@@ -8198,11 +8270,11 @@ function buildClaudeManagedSettings(os2) {
8198
8270
  function tomlString(value) {
8199
8271
  return JSON.stringify(value);
8200
8272
  }
8201
- function buildCodexRequirementsToml(os2) {
8202
- const command = os2 === "win32" ? `set ASTRASYNC_FLEET_REQUIRED=1&& ${managedHookCommand("codex", os2)}` : `ASTRASYNC_FLEET_REQUIRED=1 ${managedHookCommand("codex", os2)}`;
8273
+ function buildCodexRequirementsToml(os3) {
8274
+ const command = os3 === "win32" ? `set ASTRASYNC_FLEET_REQUIRED=1&& ${managedHookCommand("codex", os3)}` : `ASTRASYNC_FLEET_REQUIRED=1 ${managedHookCommand("codex", os3)}`;
8203
8275
  return [
8204
8276
  "# AstraSync Local Guard \u2014 managed hook (generated by `astrasync guard managed-settings --codex`)",
8205
- `# Install at: ${MANAGED_SETTINGS_LOCATIONS.codex[os2]}`,
8277
+ `# Install at: ${MANAGED_SETTINGS_LOCATIONS.codex[os3]}`,
8206
8278
  "allow_managed_hooks_only = true",
8207
8279
  "",
8208
8280
  "[features]",
@@ -8222,14 +8294,14 @@ function buildCodexRequirementsToml(os2) {
8222
8294
  function buildManagedSettingsFiles(targets) {
8223
8295
  const out = [];
8224
8296
  for (const target of targets) {
8225
- for (const os2 of ["darwin", "linux", "win32"]) {
8226
- const content = target === "claude" ? `${JSON.stringify(buildClaudeManagedSettings(os2), null, 2)}
8227
- ` : buildCodexRequirementsToml(os2);
8297
+ for (const os3 of ["darwin", "linux", "win32"]) {
8298
+ const content = target === "claude" ? `${JSON.stringify(buildClaudeManagedSettings(os3), null, 2)}
8299
+ ` : buildCodexRequirementsToml(os3);
8228
8300
  out.push({
8229
8301
  target,
8230
- os: os2,
8231
- installPath: MANAGED_SETTINGS_LOCATIONS[target][os2],
8232
- fileName: `${target}-${os2}-${target === "claude" ? "managed-settings.json" : "requirements.toml"}`,
8302
+ os: os3,
8303
+ installPath: MANAGED_SETTINGS_LOCATIONS[target][os3],
8304
+ fileName: `${target}-${os3}-${target === "claude" ? "managed-settings.json" : "requirements.toml"}`,
8233
8305
  content
8234
8306
  });
8235
8307
  }
@@ -8435,7 +8507,8 @@ function createLocalCert(hosts, days = 30) {
8435
8507
  ${b64}
8436
8508
  -----END CERTIFICATE-----
8437
8509
  `,
8438
- key: privateKey.export({ type: "pkcs8", format: "pem" }).toString()
8510
+ key: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
8511
+ spkiSha256: import_node_crypto5.default.createHash("sha256").update(spki).digest("base64")
8439
8512
  };
8440
8513
  }
8441
8514
  var import_node_crypto5, tlv, seq2, set2, ctx, ECDSA_SHA256;
@@ -8451,16 +8524,204 @@ var init_local_cert = __esm({
8451
8524
  }
8452
8525
  });
8453
8526
 
8527
+ // src/fleet/open-browser.ts
8528
+ function isWsl(env = process.env, platform2 = process.platform, readVersion = () => import_node_fs.default.readFileSync("/proc/version", "utf8")) {
8529
+ if (platform2 !== "linux") return false;
8530
+ if (env.WSL_DISTRO_NAME) return true;
8531
+ try {
8532
+ return /microsoft/i.test(readVersion());
8533
+ } catch {
8534
+ return false;
8535
+ }
8536
+ }
8537
+ function findChromium(env = process.env, platform2 = process.platform, exists = (p) => import_node_fs.default.existsSync(p), wsl = isWsl(env, platform2), listDir = (dir) => import_node_fs.default.readdirSync(dir)) {
8538
+ let candidates;
8539
+ if (platform2 === "darwin") {
8540
+ candidates = MAC_PATHS;
8541
+ } else if (platform2 === "win32") {
8542
+ const bases = [
8543
+ env.ProgramFiles,
8544
+ env["ProgramFiles(x86)"],
8545
+ env.LOCALAPPDATA ?? env.LocalAppData
8546
+ ].filter((b) => !!b);
8547
+ candidates = [
8548
+ ...bases.map((b) => import_node_path.default.win32.join(b, "Google", "Chrome", "Application", "chrome.exe")),
8549
+ ...bases.map((b) => import_node_path.default.win32.join(b, "Microsoft", "Edge", "Application", "msedge.exe"))
8550
+ ];
8551
+ } else {
8552
+ const dirs = (env.PATH ?? "").split(":").filter(Boolean);
8553
+ candidates = LINUX_NAMES.flatMap((n) => dirs.map((d) => import_node_path.default.posix.join(d, n)));
8554
+ const cache2 = env.PLAYWRIGHT_BROWSERS_PATH ?? (env.HOME ? `${env.HOME}/.cache/ms-playwright` : "");
8555
+ if (cache2) {
8556
+ let builds = [];
8557
+ try {
8558
+ builds = listDir(cache2).filter((d) => /^chromium-\d+$/.test(d));
8559
+ } catch {
8560
+ }
8561
+ builds.sort((a, b) => Number(b.split("-")[1]) - Number(a.split("-")[1]));
8562
+ for (const b of builds)
8563
+ candidates.push(
8564
+ import_node_path.default.posix.join(cache2, b, "chrome-linux64", "chrome"),
8565
+ import_node_path.default.posix.join(cache2, b, "chrome-linux", "chrome")
8566
+ );
8567
+ }
8568
+ if (wsl) candidates = [...candidates, ...WSL_WINDOWS_PATHS];
8569
+ }
8570
+ return candidates.find((c) => exists(c)) ?? null;
8571
+ }
8572
+ function isWindowsExeFromWsl(browserPath) {
8573
+ return /^\/mnt\/[a-z]\//i.test(browserPath) && /\.exe$/i.test(browserPath);
8574
+ }
8575
+ function browserName(browserPath) {
8576
+ const b = browserPath.toLowerCase();
8577
+ if (b.includes("edge")) return "Edge";
8578
+ if (b.includes("chromium")) return "Chromium";
8579
+ return "Chrome";
8580
+ }
8581
+ function profileDirFor(proxyPort) {
8582
+ return import_node_path.default.join(import_node_os.default.tmpdir(), `astrasync-agent-browser-${proxyPort}`);
8583
+ }
8584
+ function windowsProfileDir(profileDir) {
8585
+ if (/^\/mnt\/[a-z]\//i.test(profileDir)) {
8586
+ try {
8587
+ return (0, import_node_child_process.execFileSync)("wslpath", ["-w", profileDir], { encoding: "utf8" }).trim();
8588
+ } catch {
8589
+ }
8590
+ }
8591
+ return `C:\\Temp\\${import_node_path.default.basename(profileDir)}`;
8592
+ }
8593
+ function certFlag(certSpki) {
8594
+ return `--ignore-certificate-errors-spki-list=${certSpki}`;
8595
+ }
8596
+ function launchBrowser(browserPath, proxyPort, url, certSpki, profileDir = profileDirFor(proxyPort), onError) {
8597
+ const dir = isWindowsExeFromWsl(browserPath) ? windowsProfileDir(profileDir) : profileDir;
8598
+ const child = (0, import_node_child_process.spawn)(
8599
+ browserPath,
8600
+ [
8601
+ `--proxy-server=http://127.0.0.1:${proxyPort}`,
8602
+ certFlag(certSpki),
8603
+ `--user-data-dir=${dir}`,
8604
+ "--no-first-run",
8605
+ "--new-window",
8606
+ url
8607
+ ],
8608
+ { detached: true, stdio: "ignore" }
8609
+ );
8610
+ child.on("error", (err) => onError?.(err));
8611
+ child.unref();
8612
+ return child;
8613
+ }
8614
+ function browserCommand(platform2, port, url, certSpki, wsl = false) {
8615
+ const proxy = `--proxy-server="http://127.0.0.1:${port}" ${certFlag(certSpki)}`;
8616
+ if (platform2 === "win32" || wsl) {
8617
+ return `& "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" ${proxy} --user-data-dir="$env:TEMP\\astrasync-agent-${port}" '${url}'`;
8618
+ }
8619
+ const exe = platform2 === "darwin" ? '"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"' : "google-chrome";
8620
+ return `${exe} ${proxy} --user-data-dir="\${TMPDIR:-/tmp}/astrasync-agent-${port}" '${url}'`;
8621
+ }
8622
+ function browserCommandLines(platform2, port, url, certSpki, wsl = isWsl(process.env, platform2)) {
8623
+ if (wsl) {
8624
+ return [
8625
+ " In PowerShell (Windows):",
8626
+ ` ${browserCommand("win32", port, url, certSpki)}`,
8627
+ " Or inside WSL (with Chromium installed):",
8628
+ ` ${browserCommand("linux", port, url, certSpki)}`,
8629
+ ` ${CERT_FLAG_NOTE}`
8630
+ ];
8631
+ }
8632
+ return [` ${browserCommand(platform2, port, url, certSpki)}`, ` ${CERT_FLAG_NOTE}`];
8633
+ }
8634
+ function openThroughProxy(opts) {
8635
+ const log = opts.log ?? ((l) => console.log(l));
8636
+ const platform2 = opts.platform ?? process.platform;
8637
+ const env = opts.env ?? process.env;
8638
+ const wsl = opts.wsl ?? isWsl(env, platform2);
8639
+ const found = opts.find ? opts.find() : findChromium(env, platform2, void 0, wsl);
8640
+ if (!found) {
8641
+ log("No Chrome/Edge/Chromium found. Start one through the proxy with:");
8642
+ for (const l of browserCommandLines(platform2, opts.port, opts.url, opts.certSpki, wsl)) log(l);
8643
+ return false;
8644
+ }
8645
+ (opts.launch ?? launchBrowser)(
8646
+ found,
8647
+ opts.port,
8648
+ opts.url,
8649
+ opts.certSpki,
8650
+ profileDirFor(opts.port),
8651
+ (err) => log(`Couldn't start ${browserName(found)}: ${err.message}`)
8652
+ );
8653
+ log(`Opened ${browserName(found)} through the proxy.`);
8654
+ log(CERT_FLAG_NOTE);
8655
+ if (wsl && isWindowsExeFromWsl(found)) {
8656
+ log(
8657
+ "If the Windows browser can't reach the proxy, install Chromium inside WSL (e.g. npx playwright install chromium)."
8658
+ );
8659
+ }
8660
+ return true;
8661
+ }
8662
+ var import_node_child_process, import_node_fs, import_node_os, import_node_path, MAC_PATHS, LINUX_NAMES, WSL_WINDOWS_PATHS, CERT_FLAG_NOTE;
8663
+ var init_open_browser = __esm({
8664
+ "src/fleet/open-browser.ts"() {
8665
+ "use strict";
8666
+ import_node_child_process = require("child_process");
8667
+ import_node_fs = __toESM(require("fs"));
8668
+ import_node_os = __toESM(require("os"));
8669
+ import_node_path = __toESM(require("path"));
8670
+ MAC_PATHS = [
8671
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
8672
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
8673
+ "/Applications/Chromium.app/Contents/MacOS/Chromium"
8674
+ ];
8675
+ LINUX_NAMES = [
8676
+ "google-chrome",
8677
+ "google-chrome-stable",
8678
+ "chromium",
8679
+ "chromium-browser",
8680
+ "microsoft-edge"
8681
+ ];
8682
+ WSL_WINDOWS_PATHS = [
8683
+ "/mnt/c/Program Files/Google/Chrome/Application/chrome.exe",
8684
+ "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe",
8685
+ "/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe",
8686
+ "/mnt/c/Program Files/Microsoft/Edge/Application/msedge.exe"
8687
+ ];
8688
+ CERT_FLAG_NOTE = "Chrome shows a warning bar about the --ignore-certificate-errors-spki-list flag. That is expected (it trusts only this proxy's certificate); close the bar.";
8689
+ }
8690
+ });
8691
+
8454
8692
  // src/fleet/browse-proxy.ts
8455
8693
  function cleanHeaders(h) {
8456
8694
  const out = {};
8457
8695
  for (const [k, v] of Object.entries(h)) if (!HOP_BY_HOP.has(k) && v !== void 0) out[k] = v;
8458
8696
  return out;
8459
8697
  }
8698
+ function helpPage(port, certSpki) {
8699
+ const proxy = `http://127.0.0.1:${port}`;
8700
+ return `<!doctype html>
8701
+ <html lang="en"><head><meta charset="utf-8"><title>AstraSync device proxy</title>
8702
+ <style>body{font:16px/1.5 system-ui,sans-serif;max-width:40rem;margin:2rem auto;padding:0 1rem}code{background:#f2f2f2;padding:0 .25rem}</style>
8703
+ </head><body>
8704
+ <h1>AstraSync device proxy</h1>
8705
+ <p>This is AstraSync's Local Guard device proxy. It only works as a browser proxy &mdash; it isn't a page to open.</p>
8706
+ <h2>Open the sign-in link</h2>
8707
+ <ul>
8708
+ <li>Run <code>astrasync guard browse --open</code> &mdash; it opens a browser that goes through this proxy.</li>
8709
+ <li>Or start Chrome, Edge or Chromium with <code>--proxy-server=${proxy} ${certFlag(certSpki)}</code> and a separate profile (<code>--user-data-dir=&lt;an empty folder&gt;</code>), then open the sign-in link in it.</li>
8710
+ </ul>
8711
+ <p>Chrome shows a warning bar about that flag. That is expected &mdash; it trusts only this proxy&apos;s certificate &mdash; so close the bar.</p>
8712
+ <p>Keep <code>astrasync guard browse</code> running while you use the session.</p>
8713
+ </body></html>
8714
+ `;
8715
+ }
8460
8716
  async function startBrowseProxy(opts) {
8461
8717
  const hosts = new Set(opts.hosts.map((h) => h.toLowerCase()));
8462
8718
  const deviceHeader = `Device ${opts.deviceCredential}`;
8463
- const { cert, key } = createLocalCert([...hosts]);
8719
+ const { cert, key, spkiSha256 } = createLocalCert([...hosts]);
8720
+ const sockets = /* @__PURE__ */ new Set();
8721
+ const track = (s) => {
8722
+ sockets.add(s);
8723
+ s.once("close", () => sockets.delete(s));
8724
+ };
8464
8725
  function forward(req, res, target) {
8465
8726
  const headers = cleanHeaders(req.headers);
8466
8727
  headers["x-astra-device"] = deviceHeader;
@@ -8512,6 +8773,7 @@ async function startBrowseProxy(opts) {
8512
8773
  },
8513
8774
  onConnect
8514
8775
  ) : import_node_net.default.connect(target.port, target.host, onConnect);
8776
+ track(upstream);
8515
8777
  upstream.on("error", () => socket.destroy());
8516
8778
  socket.on("error", () => upstream.destroy());
8517
8779
  }
@@ -8543,6 +8805,16 @@ async function startBrowseProxy(opts) {
8543
8805
  });
8544
8806
  });
8545
8807
  const server = import_node_http.default.createServer((req, res) => {
8808
+ if ((req.url ?? "").startsWith("/")) {
8809
+ if (req.url === "/favicon.ico") {
8810
+ res.writeHead(404).end();
8811
+ return;
8812
+ }
8813
+ const listening = server.address()?.port ?? 0;
8814
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
8815
+ res.end(helpPage(listening, spkiSha256));
8816
+ return;
8817
+ }
8546
8818
  let url;
8547
8819
  try {
8548
8820
  url = new URL(req.url ?? "");
@@ -8635,18 +8907,22 @@ async function startBrowseProxy(opts) {
8635
8907
  upstream.pipe(socket);
8636
8908
  socket.pipe(upstream);
8637
8909
  });
8910
+ track(upstream);
8638
8911
  upstream.on("error", () => socket.destroy());
8639
8912
  socket.on("error", () => upstream.destroy());
8640
8913
  });
8914
+ server.on("connection", track);
8641
8915
  await new Promise((resolve) => server.listen(opts.port ?? 0, "127.0.0.1", resolve));
8642
8916
  const port = server.address().port;
8643
8917
  return {
8644
8918
  port,
8919
+ certSpki: spkiSha256,
8645
8920
  close: () => new Promise((resolve) => {
8646
8921
  inner.close();
8647
8922
  plainInner.close();
8648
8923
  server.close(() => resolve());
8649
8924
  server.closeAllConnections?.();
8925
+ for (const s of sockets) s.destroy();
8650
8926
  })
8651
8927
  };
8652
8928
  }
@@ -8659,6 +8935,7 @@ var init_browse_proxy = __esm({
8659
8935
  import_node_net = __toESM(require("net"));
8660
8936
  import_node_tls = __toESM(require("tls"));
8661
8937
  init_local_cert();
8938
+ init_open_browser();
8662
8939
  HOP_BY_HOP = /* @__PURE__ */ new Set([
8663
8940
  "connection",
8664
8941
  "proxy-connection",
@@ -8754,6 +9031,18 @@ async function runBrowse(paths, opts) {
8754
9031
  log(
8755
9032
  `Device proxy: http://127.0.0.1:${proxy.port} (adds this machine's credential for ${hosts.join(", ")})`
8756
9033
  );
9034
+ if (!opts.open) {
9035
+ log("Open it in a browser through the proxy:");
9036
+ for (const l of browserCommandLines(
9037
+ opts.platform ?? process.platform,
9038
+ proxy.port,
9039
+ openUrl,
9040
+ proxy.certSpki,
9041
+ opts.wsl
9042
+ ))
9043
+ log(l);
9044
+ log(" (or re-run with --open)");
9045
+ }
8757
9046
  log("Playwright:");
8758
9047
  log(
8759
9048
  ` chromium.launch({ proxy: { server: 'http://127.0.0.1:${proxy.port}' } }) + newContext({ ignoreHTTPSErrors: true })`
@@ -8770,6 +9059,7 @@ var init_browse = __esm({
8770
9059
  "use strict";
8771
9060
  init_enrolment_store();
8772
9061
  init_browse_proxy();
9062
+ init_open_browser();
8773
9063
  }
8774
9064
  });
8775
9065
 
@@ -8799,7 +9089,7 @@ async function cmdSetup() {
8799
9089
  }
8800
9090
  async function cmdUpgrade() {
8801
9091
  const policyPath = process.argv[4] || DEFAULT_POLICY_PATH;
8802
- if (!fs.existsSync(policyPath)) {
9092
+ if (!fs2.existsSync(policyPath)) {
8803
9093
  console.error(`No policy file found at ${policyPath}`);
8804
9094
  console.error('Run "astrasync guard setup" first to create a policy.');
8805
9095
  process.exit(1);
@@ -8827,13 +9117,13 @@ async function cmdUpgrade() {
8827
9117
  prompt: (message) => prompt.question(message),
8828
9118
  print: (message) => console.log(message),
8829
9119
  writeConfig: async (filePath, content) => {
8830
- const dir = path.dirname(filePath);
8831
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
8832
- fs.writeFileSync(filePath, content, "utf-8");
9120
+ const dir = path2.dirname(filePath);
9121
+ if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
9122
+ fs2.writeFileSync(filePath, content, "utf-8");
8833
9123
  },
8834
9124
  readConfig: async (filePath) => {
8835
9125
  try {
8836
- return fs.readFileSync(filePath, "utf-8");
9126
+ return fs2.readFileSync(filePath, "utf-8");
8837
9127
  } catch {
8838
9128
  return null;
8839
9129
  }
@@ -8854,13 +9144,13 @@ async function cmdStart() {
8854
9144
  let gatewayConfig;
8855
9145
  const upgraded = await isUpgraded(DEFAULT_CONFIG_DIR, async (p) => {
8856
9146
  try {
8857
- return fs.readFileSync(p, "utf-8");
9147
+ return fs2.readFileSync(p, "utf-8");
8858
9148
  } catch {
8859
9149
  return null;
8860
9150
  }
8861
9151
  });
8862
9152
  if (upgraded) {
8863
- const config = JSON.parse(fs.readFileSync(`${DEFAULT_CONFIG_DIR}/config`, "utf-8"));
9153
+ const config = JSON.parse(fs2.readFileSync(`${DEFAULT_CONFIG_DIR}/config`, "utf-8"));
8864
9154
  gatewayConfig = {
8865
9155
  mode: config.mode,
8866
9156
  apiBaseUrl: config.platformUrl ? `${config.platformUrl}/api/v1` : void 0,
@@ -8868,7 +9158,7 @@ async function cmdStart() {
8868
9158
  };
8869
9159
  console.log(`[AstraSync] Starting in ${config.mode} mode (agent: ${config.agentId})`);
8870
9160
  } else {
8871
- if (!fs.existsSync(policyPath)) {
9161
+ if (!fs2.existsSync(policyPath)) {
8872
9162
  console.error(`No policy file found at ${policyPath}`);
8873
9163
  console.error('Run "astrasync guard setup" first.');
8874
9164
  process.exit(1);
@@ -8905,7 +9195,7 @@ async function cmdStatus() {
8905
9195
  const policyPath = process.argv[4] || DEFAULT_POLICY_PATH;
8906
9196
  const upgraded = await isUpgraded(DEFAULT_CONFIG_DIR, async (p) => {
8907
9197
  try {
8908
- return fs.readFileSync(p, "utf-8");
9198
+ return fs2.readFileSync(p, "utf-8");
8909
9199
  } catch {
8910
9200
  return null;
8911
9201
  }
@@ -8915,7 +9205,7 @@ async function cmdStatus() {
8915
9205
  console.log(` ${line}`);
8916
9206
  }
8917
9207
  if (upgraded) {
8918
- const config = JSON.parse(fs.readFileSync(`${DEFAULT_CONFIG_DIR}/config`, "utf-8"));
9208
+ const config = JSON.parse(fs2.readFileSync(`${DEFAULT_CONFIG_DIR}/config`, "utf-8"));
8919
9209
  console.log(` Mode: ${config.mode}`);
8920
9210
  console.log(` Agent: ${config.agentId}`);
8921
9211
  console.log(` Platform: ${config.platformUrl}`);
@@ -8923,7 +9213,7 @@ async function cmdStatus() {
8923
9213
  } else {
8924
9214
  console.log(" Mode: local (free tier)");
8925
9215
  }
8926
- if (fs.existsSync(policyPath)) {
9216
+ if (fs2.existsSync(policyPath)) {
8927
9217
  const parsed = parseFile(policyPath);
8928
9218
  if (parsed.validation.valid && parsed.policy) {
8929
9219
  const p = parsed.policy;
@@ -8948,7 +9238,7 @@ async function cmdCheck() {
8948
9238
  (a) => !a.startsWith("--") && a !== "guard" && a !== "check" && a !== process.argv[0] && a !== process.argv[1]
8949
9239
  ) || DEFAULT_POLICY_PATH;
8950
9240
  const trigger = process.argv.find((a) => a.startsWith("--trigger="))?.split("=")[1] || "pre-push";
8951
- if (!fs.existsSync(policyPath)) {
9241
+ if (!fs2.existsSync(policyPath)) {
8952
9242
  console.log("[AstraSync] No policy file \u2014 skipping check");
8953
9243
  process.exit(0);
8954
9244
  }
@@ -8960,9 +9250,9 @@ async function cmdCheck() {
8960
9250
  }
8961
9251
  let enforcement = "warn";
8962
9252
  const configPath = `${DEFAULT_CONFIG_DIR}/config`;
8963
- if (fs.existsSync(configPath)) {
9253
+ if (fs2.existsSync(configPath)) {
8964
9254
  try {
8965
- const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
9255
+ const config = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
8966
9256
  enforcement = config.gitTriggerEnforcement || "warn";
8967
9257
  } catch {
8968
9258
  }
@@ -9025,8 +9315,8 @@ async function cmdUninstallHooks() {
9025
9315
  }
9026
9316
  }
9027
9317
  function resolveClaudeHookCommand() {
9028
- const sibling = path.join(__dirname, "astrasync-claude-hook.js");
9029
- if (fs.existsSync(sibling)) {
9318
+ const sibling = path2.join(__dirname, "astrasync-claude-hook.js");
9319
+ if (fs2.existsSync(sibling)) {
9030
9320
  return `"${process.execPath}" "${sibling}"`;
9031
9321
  }
9032
9322
  return "astrasync-claude-hook";
@@ -9042,19 +9332,19 @@ function parseClaudeCodeFlags() {
9042
9332
  };
9043
9333
  }
9044
9334
  function claudeCodePaths(project) {
9045
- const root = project ? process.cwd() : os.homedir();
9335
+ const root = project ? process.cwd() : os2.homedir();
9046
9336
  return {
9047
- settingsPath: path.join(root, ".claude", "settings.json"),
9048
- astrasyncDir: path.join(root, ".astrasync")
9337
+ settingsPath: path2.join(root, ".claude", "settings.json"),
9338
+ astrasyncDir: path2.join(root, ".astrasync")
9049
9339
  };
9050
9340
  }
9051
9341
  async function cmdInstallClaudeCode() {
9052
9342
  const flags = parseClaudeCodeFlags();
9053
9343
  const { settingsPath, astrasyncDir } = claudeCodePaths(flags.project);
9054
- const policyPath = flags.policyOverride || path.join(astrasyncDir, "policy.yaml");
9055
- const guardJsonPath = path.join(astrasyncDir, "guard.json");
9344
+ const policyPath = flags.policyOverride || path2.join(astrasyncDir, "policy.yaml");
9345
+ const guardJsonPath = path2.join(astrasyncDir, "guard.json");
9056
9346
  console.log("\nAstraSync Local Guard for Claude Code \u2014 install\n");
9057
- if (fs.existsSync(policyPath)) {
9347
+ if (fs2.existsSync(policyPath)) {
9058
9348
  console.log(` Policy: using existing ${policyPath}`);
9059
9349
  if (ensureGuardSelfProtection(policyPath)) {
9060
9350
  console.log(" added guard self-protection patterns to file.write");
@@ -9062,7 +9352,7 @@ async function cmdInstallClaudeCode() {
9062
9352
  } else if (flags.wizard) {
9063
9353
  const prompt = createPrompt();
9064
9354
  try {
9065
- fs.mkdirSync(path.dirname(policyPath), { recursive: true });
9355
+ fs2.mkdirSync(path2.dirname(policyPath), { recursive: true });
9066
9356
  const wizard = new SetupWizard(prompt);
9067
9357
  await wizard.runAndSave(policyPath);
9068
9358
  } finally {
@@ -9071,17 +9361,17 @@ async function cmdInstallClaudeCode() {
9071
9361
  ensureGuardSelfProtection(policyPath);
9072
9362
  console.log(` Policy: created via wizard at ${policyPath}`);
9073
9363
  } else {
9074
- fs.mkdirSync(path.dirname(policyPath), { recursive: true });
9075
- fs.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
9364
+ fs2.mkdirSync(path2.dirname(policyPath), { recursive: true });
9365
+ fs2.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
9076
9366
  console.log(` Policy: default coding-agent policy written to ${policyPath}`);
9077
9367
  console.log(" (re-run with --wizard for the interactive setup instead)");
9078
9368
  }
9079
- if (!fs.existsSync(guardJsonPath)) {
9369
+ if (!fs2.existsSync(guardJsonPath)) {
9080
9370
  const guardSettings = {
9081
9371
  posture: flags.active ? "active" : "passive",
9082
9372
  governMcp: false
9083
9373
  };
9084
- fs.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
9374
+ fs2.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
9085
9375
  `, "utf-8");
9086
9376
  console.log(
9087
9377
  ` Guard: ${guardJsonPath} (posture: ${guardSettings.posture}, governMcp: false)`
@@ -9111,7 +9401,7 @@ async function cmdInstallClaudeCode() {
9111
9401
  }
9112
9402
  console.log("\n Next steps:");
9113
9403
  console.log(" 1. Start a NEW Claude Code session (settings load at startup).");
9114
- console.log(` 2. Watch decisions: tail -f ${path.join(astrasyncDir, "traces")}/*.jsonl`);
9404
+ console.log(` 2. Watch decisions: tail -f ${path2.join(astrasyncDir, "traces")}/*.jsonl`);
9115
9405
  console.log(` 3. When the traces look right, set "posture": "active" in ${guardJsonPath}`);
9116
9406
  console.log(" to enforce (denies block, approvals use Claude Code's own prompt).");
9117
9407
  console.log(' 4. To govern MCP tools too, set "governMcp": true and add');
@@ -9141,26 +9431,26 @@ async function cmdUninstallClaudeCode() {
9141
9431
  );
9142
9432
  }
9143
9433
  function resolveCodexHookCommand() {
9144
- const sibling = path.join(__dirname, "astrasync-codex-hook.js");
9145
- if (fs.existsSync(sibling)) {
9434
+ const sibling = path2.join(__dirname, "astrasync-codex-hook.js");
9435
+ if (fs2.existsSync(sibling)) {
9146
9436
  return `"${process.execPath}" "${sibling}"`;
9147
9437
  }
9148
9438
  return "astrasync-codex-hook";
9149
9439
  }
9150
9440
  function codexPaths(project) {
9151
- const root = project ? process.cwd() : os.homedir();
9441
+ const root = project ? process.cwd() : os2.homedir();
9152
9442
  return {
9153
- hooksPath: path.join(root, ".codex", "hooks.json"),
9154
- astrasyncDir: path.join(root, ".astrasync")
9443
+ hooksPath: path2.join(root, ".codex", "hooks.json"),
9444
+ astrasyncDir: path2.join(root, ".astrasync")
9155
9445
  };
9156
9446
  }
9157
9447
  async function cmdInstallCodex() {
9158
9448
  const flags = parseClaudeCodeFlags();
9159
9449
  const { hooksPath, astrasyncDir } = codexPaths(flags.project);
9160
- const policyPath = flags.policyOverride || path.join(astrasyncDir, "policy.yaml");
9161
- const guardJsonPath = path.join(astrasyncDir, "guard.json");
9450
+ const policyPath = flags.policyOverride || path2.join(astrasyncDir, "policy.yaml");
9451
+ const guardJsonPath = path2.join(astrasyncDir, "guard.json");
9162
9452
  console.log("\nAstraSync Local Guard for Codex CLI \u2014 install\n");
9163
- if (fs.existsSync(policyPath)) {
9453
+ if (fs2.existsSync(policyPath)) {
9164
9454
  console.log(` Policy: using existing ${policyPath} (shared across adapters)`);
9165
9455
  if (ensureGuardSelfProtection(policyPath)) {
9166
9456
  console.log(" added guard self-protection patterns to file.write");
@@ -9168,7 +9458,7 @@ async function cmdInstallCodex() {
9168
9458
  } else if (flags.wizard) {
9169
9459
  const prompt = createPrompt();
9170
9460
  try {
9171
- fs.mkdirSync(path.dirname(policyPath), { recursive: true });
9461
+ fs2.mkdirSync(path2.dirname(policyPath), { recursive: true });
9172
9462
  const wizard = new SetupWizard(prompt);
9173
9463
  await wizard.runAndSave(policyPath);
9174
9464
  } finally {
@@ -9177,17 +9467,17 @@ async function cmdInstallCodex() {
9177
9467
  ensureGuardSelfProtection(policyPath);
9178
9468
  console.log(` Policy: created via wizard at ${policyPath}`);
9179
9469
  } else {
9180
- fs.mkdirSync(path.dirname(policyPath), { recursive: true });
9181
- fs.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
9470
+ fs2.mkdirSync(path2.dirname(policyPath), { recursive: true });
9471
+ fs2.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
9182
9472
  console.log(` Policy: default coding-agent policy written to ${policyPath}`);
9183
9473
  console.log(" (re-run with --wizard for the interactive setup instead)");
9184
9474
  }
9185
- if (!fs.existsSync(guardJsonPath)) {
9475
+ if (!fs2.existsSync(guardJsonPath)) {
9186
9476
  const guardSettings = {
9187
9477
  posture: flags.active ? "active" : "passive",
9188
9478
  governMcp: false
9189
9479
  };
9190
- fs.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
9480
+ fs2.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
9191
9481
  `, "utf-8");
9192
9482
  console.log(
9193
9483
  ` Guard: ${guardJsonPath} (posture: ${guardSettings.posture}, governMcp: false)`
@@ -9206,7 +9496,7 @@ async function cmdInstallCodex() {
9206
9496
  }
9207
9497
  console.log("\n Next steps:");
9208
9498
  console.log(" 1. Start a NEW Codex session (hooks load at startup; /hooks to inspect).");
9209
- console.log(` 2. Watch decisions: tail -f ${path.join(astrasyncDir, "traces")}/*.jsonl`);
9499
+ console.log(` 2. Watch decisions: tail -f ${path2.join(astrasyncDir, "traces")}/*.jsonl`);
9210
9500
  console.log(` 3. When the traces look right, set "posture": "active" in ${guardJsonPath}.`);
9211
9501
  console.log(" Denies block; approvals fall through to Codex's own prompt \u2014");
9212
9502
  console.log(" except in dontAsk/bypassPermissions modes, where they deny.");
@@ -9231,13 +9521,13 @@ function printCodexStatus() {
9231
9521
  const { hooksPath, astrasyncDir } = codexPaths(scope === "project");
9232
9522
  let hookInstalled = false;
9233
9523
  try {
9234
- const content = fs.readFileSync(hooksPath, "utf-8");
9524
+ const content = fs2.readFileSync(hooksPath, "utf-8");
9235
9525
  hookInstalled = content.includes(ASTRASYNC_CODEX_HOOK_MARKER);
9236
9526
  } catch {
9237
9527
  }
9238
9528
  if (hookInstalled) {
9239
- const policyPath = path.join(astrasyncDir, "policy.yaml");
9240
- const policy = fs.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
9529
+ const policyPath = path2.join(astrasyncDir, "policy.yaml");
9530
+ const policy = fs2.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
9241
9531
  console.log(` Codex CLI (${scope}): hook installed, ${policy}`);
9242
9532
  }
9243
9533
  }
@@ -9247,24 +9537,24 @@ function printClaudeCodeStatus() {
9247
9537
  const { settingsPath, astrasyncDir } = claudeCodePaths(scope === "project");
9248
9538
  let hookInstalled = false;
9249
9539
  try {
9250
- const content = fs.readFileSync(settingsPath, "utf-8");
9540
+ const content = fs2.readFileSync(settingsPath, "utf-8");
9251
9541
  hookInstalled = content.includes(ASTRASYNC_HOOK_MARKER);
9252
9542
  } catch {
9253
9543
  }
9254
- const policyPath = path.join(astrasyncDir, "policy.yaml");
9255
- const guardJsonPath = path.join(astrasyncDir, "guard.json");
9544
+ const policyPath = path2.join(astrasyncDir, "policy.yaml");
9545
+ const guardJsonPath = path2.join(astrasyncDir, "guard.json");
9256
9546
  let posture = "";
9257
- if (fs.existsSync(guardJsonPath)) {
9547
+ if (fs2.existsSync(guardJsonPath)) {
9258
9548
  try {
9259
- const settings = JSON.parse(fs.readFileSync(guardJsonPath, "utf-8"));
9549
+ const settings = JSON.parse(fs2.readFileSync(guardJsonPath, "utf-8"));
9260
9550
  posture = settings.posture ? `, posture: ${settings.posture}` : "";
9261
9551
  } catch {
9262
9552
  posture = ", guard.json unreadable";
9263
9553
  }
9264
9554
  }
9265
- if (hookInstalled || fs.existsSync(policyPath)) {
9555
+ if (hookInstalled || fs2.existsSync(policyPath)) {
9266
9556
  const hook = hookInstalled ? "hook installed" : "hook NOT installed";
9267
- const policy = fs.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
9557
+ const policy = fs2.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
9268
9558
  console.log(` Claude Code (${scope}): ${hook}, ${policy}${posture}`);
9269
9559
  }
9270
9560
  }
@@ -9310,14 +9600,21 @@ async function cmdFleetStatus() {
9310
9600
  async function cmdBrowse() {
9311
9601
  try {
9312
9602
  const port = flagValue("--port");
9313
- const { proxy } = await runBrowse(fleetPaths(), {
9603
+ const open = process.argv.includes("--open");
9604
+ const { proxy, openUrl } = await runBrowse(fleetPaths(), {
9314
9605
  checkpoint: flagValue("--checkpoint") ?? "",
9315
9606
  slug: flagValue("--slug") ?? "",
9316
9607
  upstream: flagValue("--upstream"),
9317
9608
  port: port ? Number(port) : void 0,
9318
- extraHosts: flagValue("--host") ? [flagValue("--host")] : []
9609
+ extraHosts: flagValue("--host") ? [flagValue("--host")] : [],
9610
+ open
9319
9611
  });
9612
+ if (open) openThroughProxy({ port: proxy.port, url: openUrl, certSpki: proxy.certSpki });
9613
+ let stopping = false;
9320
9614
  const stop = async () => {
9615
+ if (stopping) process.exit(0);
9616
+ stopping = true;
9617
+ setTimeout(() => process.exit(0), 2e3).unref();
9321
9618
  await proxy.close();
9322
9619
  process.exit(0);
9323
9620
  };
@@ -9352,7 +9649,7 @@ async function cmdGitRoute() {
9352
9649
  }
9353
9650
  async function cmdGitCredential() {
9354
9651
  const action = process.argv[process.argv.indexOf("git-credential") + 1] ?? "";
9355
- const input = fs.readFileSync(0, "utf-8");
9652
+ const input = fs2.readFileSync(0, "utf-8");
9356
9653
  process.stdout.write(runGitCredential(action, input));
9357
9654
  }
9358
9655
  async function cmdManagedSettings() {
@@ -9456,8 +9753,9 @@ async function main() {
9456
9753
  " browse Sign an agent in to your dashboard from this machine and"
9457
9754
  );
9458
9755
  console.log(
9459
- " run its device proxy (--checkpoint <url> --slug <agent>)"
9756
+ " run its device proxy (--checkpoint <url> --slug <agent>;"
9460
9757
  );
9758
+ console.log(" --open to start a browser through the proxy)");
9461
9759
  console.log(" enroll <token> Enrol this machine in your organisation fleet");
9462
9760
  console.log(" (--api-base-url <url>, --system for machine scope)");
9463
9761
  console.log(" fleet-status Show fleet enrolment, signed policy and heartbeat");
@@ -9471,13 +9769,13 @@ async function main() {
9471
9769
  process.exit(command ? 1 : 0);
9472
9770
  }
9473
9771
  }
9474
- var fs, os, path, readline, DEFAULT_POLICY_PATH, DEFAULT_CONFIG_DIR;
9772
+ var fs2, os2, path2, readline, DEFAULT_POLICY_PATH, DEFAULT_CONFIG_DIR;
9475
9773
  var init_guard = __esm({
9476
9774
  "src/cli/guard.ts"() {
9477
9775
  "use strict";
9478
- fs = __toESM(require("fs"));
9479
- os = __toESM(require("os"));
9480
- path = __toESM(require("path"));
9776
+ fs2 = __toESM(require("fs"));
9777
+ os2 = __toESM(require("os"));
9778
+ path2 = __toESM(require("path"));
9481
9779
  readline = __toESM(require("readline"));
9482
9780
  init_wizard();
9483
9781
  init_upgrade();
@@ -9491,6 +9789,7 @@ var init_guard = __esm({
9491
9789
  init_enrolment_store();
9492
9790
  init_git_route();
9493
9791
  init_browse();
9792
+ init_open_browser();
9494
9793
  DEFAULT_POLICY_PATH = "./local-policy.yaml";
9495
9794
  DEFAULT_CONFIG_DIR = ".astrasync";
9496
9795
  main().catch((err) => {
@@ -9527,6 +9826,7 @@ COMMANDS:
9527
9826
  (--api-base-url <url>, --system for machine scope)
9528
9827
  guard fleet-status Fleet enrolment, signed policy and heartbeat state
9529
9828
  guard browse Sign an agent in to your dashboard via this machine's device proxy
9829
+ (--checkpoint <url> --slug <agent>; --open starts a browser through it)
9530
9830
  guard managed-settings MDM templates for Claude Code / Codex (--claude, --codex, --out <dir>)
9531
9831
 
9532
9832
  REGISTER OPTIONS: