@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
@@ -24,9 +24,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/cli/guard.ts
27
- var fs = __toESM(require("fs"));
28
- var os = __toESM(require("os"));
29
- var path = __toESM(require("path"));
27
+ var fs2 = __toESM(require("fs"));
28
+ var os2 = __toESM(require("os"));
29
+ var path2 = __toESM(require("path"));
30
30
  var readline = __toESM(require("readline"));
31
31
 
32
32
  // ../../node_modules/js-yaml/dist/js-yaml.mjs
@@ -3129,115 +3129,115 @@ function validatePolicy(policy) {
3129
3129
  }
3130
3130
  return { valid: errors.length === 0, errors };
3131
3131
  }
3132
- function validatePurpose(purpose, path2) {
3132
+ function validatePurpose(purpose, path3) {
3133
3133
  const errors = [];
3134
3134
  if (!purpose || typeof purpose !== "object") {
3135
- return [{ path: path2, message: "purpose must be an object" }];
3135
+ return [{ path: path3, message: "purpose must be an object" }];
3136
3136
  }
3137
3137
  const p = purpose;
3138
3138
  if (typeof p.id !== "string" || !p.id) {
3139
- errors.push({ path: `${path2}.id`, message: "id is required and must be a non-empty string" });
3139
+ errors.push({ path: `${path3}.id`, message: "id is required and must be a non-empty string" });
3140
3140
  }
3141
3141
  if (typeof p.allowed !== "boolean") {
3142
- errors.push({ path: `${path2}.allowed`, message: "allowed is required and must be a boolean" });
3142
+ errors.push({ path: `${path3}.allowed`, message: "allowed is required and must be a boolean" });
3143
3143
  }
3144
3144
  if (p.targets !== void 0 && !isStringArray(p.targets)) {
3145
- errors.push({ path: `${path2}.targets`, message: "targets must be an array of strings" });
3145
+ errors.push({ path: `${path3}.targets`, message: "targets must be an array of strings" });
3146
3146
  }
3147
3147
  if (p.blockedPatterns !== void 0 && !isStringArray(p.blockedPatterns)) {
3148
3148
  errors.push({
3149
- path: `${path2}.blockedPatterns`,
3149
+ path: `${path3}.blockedPatterns`,
3150
3150
  message: "blockedPatterns must be an array of strings"
3151
3151
  });
3152
3152
  }
3153
3153
  if (p.requiresApproval !== void 0 && typeof p.requiresApproval !== "boolean") {
3154
3154
  errors.push({
3155
- path: `${path2}.requiresApproval`,
3155
+ path: `${path3}.requiresApproval`,
3156
3156
  message: "requiresApproval must be a boolean"
3157
3157
  });
3158
3158
  }
3159
3159
  return errors;
3160
3160
  }
3161
- function validateScope(scope, path2) {
3161
+ function validateScope(scope, path3) {
3162
3162
  const errors = [];
3163
3163
  if (typeof scope !== "object" || scope === null) {
3164
- return [{ path: path2, message: "scope must be an object" }];
3164
+ return [{ path: path3, message: "scope must be an object" }];
3165
3165
  }
3166
3166
  const s = scope;
3167
3167
  for (const field of ["allowedDomains", "blockedDomains", "blockedResources"]) {
3168
3168
  if (s[field] !== void 0 && !isStringArray(s[field])) {
3169
- errors.push({ path: `${path2}.${field}`, message: `${field} must be an array of strings` });
3169
+ errors.push({ path: `${path3}.${field}`, message: `${field} must be an array of strings` });
3170
3170
  }
3171
3171
  }
3172
3172
  return errors;
3173
3173
  }
3174
- function validateLimits(limits, path2) {
3174
+ function validateLimits(limits, path3) {
3175
3175
  const errors = [];
3176
3176
  if (typeof limits !== "object" || limits === null) {
3177
- return [{ path: path2, message: "limits must be an object" }];
3177
+ return [{ path: path3, message: "limits must be an object" }];
3178
3178
  }
3179
3179
  const l = limits;
3180
3180
  if (l.autonomousThreshold !== void 0 && typeof l.autonomousThreshold !== "number") {
3181
3181
  errors.push({
3182
- path: `${path2}.autonomousThreshold`,
3182
+ path: `${path3}.autonomousThreshold`,
3183
3183
  message: "autonomousThreshold must be a number"
3184
3184
  });
3185
3185
  }
3186
3186
  if (l.maxTransactionAmount !== void 0 && typeof l.maxTransactionAmount !== "number") {
3187
3187
  errors.push({
3188
- path: `${path2}.maxTransactionAmount`,
3188
+ path: `${path3}.maxTransactionAmount`,
3189
3189
  message: "maxTransactionAmount must be a number"
3190
3190
  });
3191
3191
  }
3192
3192
  if (typeof l.autonomousThreshold === "number" && typeof l.maxTransactionAmount === "number" && l.autonomousThreshold > l.maxTransactionAmount) {
3193
3193
  errors.push({
3194
- path: `${path2}.autonomousThreshold`,
3194
+ path: `${path3}.autonomousThreshold`,
3195
3195
  message: "autonomousThreshold (Autonomous Limit) must be <= maxTransactionAmount (Hard Limit)"
3196
3196
  });
3197
3197
  }
3198
3198
  if (l.maxRequestsPerHour !== void 0 && typeof l.maxRequestsPerHour !== "number") {
3199
3199
  errors.push({
3200
- path: `${path2}.maxRequestsPerHour`,
3200
+ path: `${path3}.maxRequestsPerHour`,
3201
3201
  message: "maxRequestsPerHour must be a number"
3202
3202
  });
3203
3203
  }
3204
3204
  if (l.currency !== void 0 && typeof l.currency !== "string") {
3205
- errors.push({ path: `${path2}.currency`, message: "currency must be a string" });
3205
+ errors.push({ path: `${path3}.currency`, message: "currency must be a string" });
3206
3206
  }
3207
3207
  return errors;
3208
3208
  }
3209
- function validateRiskThresholds(thresholds, path2) {
3209
+ function validateRiskThresholds(thresholds, path3) {
3210
3210
  const errors = [];
3211
3211
  if (typeof thresholds !== "object" || thresholds === null) {
3212
- return [{ path: path2, message: "riskThresholds must be an object" }];
3212
+ return [{ path: path3, message: "riskThresholds must be an object" }];
3213
3213
  }
3214
3214
  const t = thresholds;
3215
3215
  for (const range of ["autoAllow", "requireApproval", "autoBlock"]) {
3216
3216
  if (!t[range] || typeof t[range] !== "object") {
3217
3217
  errors.push({
3218
- path: `${path2}.${range}`,
3218
+ path: `${path3}.${range}`,
3219
3219
  message: `${range} is required and must be an object with min and max`
3220
3220
  });
3221
3221
  continue;
3222
3222
  }
3223
3223
  const r = t[range];
3224
3224
  if (typeof r.min !== "number" || typeof r.max !== "number") {
3225
- errors.push({ path: `${path2}.${range}`, message: `${range} must have numeric min and max` });
3225
+ errors.push({ path: `${path3}.${range}`, message: `${range} must have numeric min and max` });
3226
3226
  } else if (r.min > r.max) {
3227
- errors.push({ path: `${path2}.${range}`, message: `${range}.min must be <= ${range}.max` });
3227
+ errors.push({ path: `${path3}.${range}`, message: `${range}.min must be <= ${range}.max` });
3228
3228
  }
3229
3229
  }
3230
3230
  if (isValidThresholdShape(t)) {
3231
3231
  const thresholdObj = t;
3232
3232
  if (thresholdObj.autoAllow.max + 1 !== thresholdObj.requireApproval.min) {
3233
3233
  errors.push({
3234
- path: `${path2}`,
3234
+ path: `${path3}`,
3235
3235
  message: `Gap between autoAllow.max (${thresholdObj.autoAllow.max}) and requireApproval.min (${thresholdObj.requireApproval.min}). Ranges must be contiguous.`
3236
3236
  });
3237
3237
  }
3238
3238
  if (thresholdObj.requireApproval.max + 1 !== thresholdObj.autoBlock.min) {
3239
3239
  errors.push({
3240
- path: `${path2}`,
3240
+ path: `${path3}`,
3241
3241
  message: `Gap between requireApproval.max (${thresholdObj.requireApproval.max}) and autoBlock.min (${thresholdObj.autoBlock.min}). Ranges must be contiguous.`
3242
3242
  });
3243
3243
  }
@@ -3597,8 +3597,8 @@ ${category.label}
3597
3597
  async runAndSave(outputPath) {
3598
3598
  const policy = await this.run();
3599
3599
  const yaml2 = toYaml(policy);
3600
- const fs2 = await import("fs");
3601
- fs2.writeFileSync(outputPath, yaml2, "utf-8");
3600
+ const fs3 = await import("fs");
3601
+ fs3.writeFileSync(outputPath, yaml2, "utf-8");
3602
3602
  this.print(`
3603
3603
  Policy saved to ${outputPath}`);
3604
3604
  this.print(" AstraSync Local Guard is now protecting your agent.\n");
@@ -3713,7 +3713,7 @@ Policy saved to ${outputPath}`);
3713
3713
  };
3714
3714
 
3715
3715
  // src/version.ts
3716
- var SDK_VERSION = "5.18.0";
3716
+ var SDK_VERSION = "5.19.0";
3717
3717
 
3718
3718
  // src/http.ts
3719
3719
  var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
@@ -4772,7 +4772,12 @@ function getCacheKey(request2, counterpartyId) {
4772
4772
  // its start_checkout quote was served the cached quote-leg grant (no
4773
4773
  // settlementOutcome) and never reached the backend. Same F-A1-07 bug class.
4774
4774
  request2.commercePhase || "",
4775
- request2.checkoutSessionId || ""
4775
+ request2.checkoutSessionId || "",
4776
+ // 5.19.0: a route_config purpose claim is checked server-side against
4777
+ // the stored rule for this method + path, so it changes the verdict.
4778
+ request2.purposeSource || "",
4779
+ request2.requestMethod || "",
4780
+ request2.requestPath || ""
4776
4781
  ].join("|");
4777
4782
  }
4778
4783
  function getCachedResult(request2, counterpartyId) {
@@ -4946,6 +4951,11 @@ async function callVerifyAccessAPI(config, request2) {
4946
4951
  if (requestData.buyerEmail) body.buyerEmail = requestData.buyerEmail;
4947
4952
  if (requestData.commerceArtifacts) body.commerceArtifacts = requestData.commerceArtifacts;
4948
4953
  if (requestData.attemptId) body.attemptId = requestData.attemptId;
4954
+ if (requestData.purposeSource) body.purposeSource = requestData.purposeSource;
4955
+ if (requestData.agentArtifacts) body.agentArtifacts = requestData.agentArtifacts;
4956
+ if (requestData.metadata) body.metadata = requestData.metadata;
4957
+ if (requestData.requestMethod) body.requestMethod = requestData.requestMethod;
4958
+ if (requestData.requestPath) body.requestPath = requestData.requestPath;
4949
4959
  if (requestData.considerationSet) body.considerationSet = requestData.considerationSet;
4950
4960
  body.sdkVersion = SDK_VERSION;
4951
4961
  if (requestData.callerMetadata || requestData.clientIp || requestData.userAgent) {
@@ -5135,9 +5145,18 @@ async function verify(config, request2, options) {
5135
5145
  if (!apiResponse.access?.allowed) {
5136
5146
  const aggregatedFailures = apiResponse.access?.failures;
5137
5147
  const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
5148
+ const denyAgent = idVerifiedFromBackend && apiResponse.agent ? {
5149
+ astraId: apiResponse.agent.astraId,
5150
+ name: apiResponse.agent.name,
5151
+ trustScore: apiResponse.agent.trustScore,
5152
+ trustLevel: getTrustLevel(apiResponse.agent.trustScore),
5153
+ blockchainAnchored: apiResponse.agent.blockchainStatus === "anchored",
5154
+ status: apiResponse.agent.agentStatus
5155
+ } : void 0;
5138
5156
  const result2 = {
5139
5157
  identityVerified: idVerifiedFromBackend,
5140
5158
  policyAllowed: false,
5159
+ ...denyAgent && { agent: denyAgent },
5141
5160
  // 3.6.0: the server now emits top-level denialReasons on every deny
5142
5161
  // path — prefer it verbatim; the failure-message reconstruction remains
5143
5162
  // as the fallback for older backends.
@@ -5283,6 +5302,9 @@ var MAX_TOTAL_BYTES = 16 * 1024;
5283
5302
  // src/adapters/commerce-detect.ts
5284
5303
  var MAX_BODY_BYTES = 64 * 1024;
5285
5304
 
5305
+ // src/adapters/routes-cache.ts
5306
+ var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
5307
+
5286
5308
  // src/edge-config.ts
5287
5309
  var DEFAULT_EDGE_CONFIG = Object.freeze({
5288
5310
  version: 1,
@@ -5524,9 +5546,6 @@ var BeaconBuffer = class {
5524
5546
  };
5525
5547
  var beaconBuffer = new BeaconBuffer();
5526
5548
 
5527
- // src/adapters/express.ts
5528
- var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
5529
-
5530
5549
  // src/adapters/nextjs.ts
5531
5550
  var DEFAULT_ROUTES_REFRESH_MS2 = 5 * 60 * 1e3;
5532
5551
 
@@ -5612,6 +5631,25 @@ var import_jose2 = require("jose");
5612
5631
  // src/idempotency/index.ts
5613
5632
  var DEFAULT_IDEMPOTENCY_TTL_MS = 24 * 60 * 60 * 1e3;
5614
5633
 
5634
+ // src/ucp/approval-events.ts
5635
+ var MemorySeenEvents = class {
5636
+ constructor(max = 1e4) {
5637
+ this.max = max;
5638
+ this.ids = /* @__PURE__ */ new Set();
5639
+ }
5640
+ async has(eventId) {
5641
+ return this.ids.has(eventId);
5642
+ }
5643
+ async add(eventId) {
5644
+ if (this.ids.size >= this.max) {
5645
+ const first = this.ids.values().next().value;
5646
+ if (first !== void 0) this.ids.delete(first);
5647
+ }
5648
+ this.ids.add(eventId);
5649
+ }
5650
+ };
5651
+ var defaultSeen = new MemorySeenEvents();
5652
+
5615
5653
  // src/payments/kyapay.ts
5616
5654
  var import_jose4 = require("jose");
5617
5655
 
@@ -5651,7 +5689,9 @@ function toDecision(result) {
5651
5689
  if (result.identityVerified && result.policyAllowed) {
5652
5690
  return {
5653
5691
  recommendation: "ALLOW",
5654
- reason: result.agent?.trustScore ? `Verified (trust score ${result.agent.trustScore})` : "Verified",
5692
+ // 5.19.0: the score is on `trustScore` for the merchant; `reason` is
5693
+ // text a merchant may relay to the agent, so it never embeds the number.
5694
+ reason: "Verified",
5655
5695
  trustScore: result.agent?.trustScore,
5656
5696
  sessionId: "sessionId" in result ? result.sessionId : void 0
5657
5697
  };
@@ -6574,29 +6614,29 @@ function loadEnrolment(paths) {
6574
6614
  [paths.machineDevice, "machine"],
6575
6615
  [paths.userDevice, "user"]
6576
6616
  ];
6577
- for (const [path2, scope] of candidates) {
6578
- if (!(0, import_fs6.existsSync)(path2)) continue;
6617
+ for (const [path3, scope] of candidates) {
6618
+ if (!(0, import_fs6.existsSync)(path3)) continue;
6579
6619
  try {
6580
- const parsed = JSON.parse((0, import_fs6.readFileSync)(path2, "utf-8"));
6620
+ const parsed = JSON.parse((0, import_fs6.readFileSync)(path3, "utf-8"));
6581
6621
  if (isEnrolment(parsed)) return { enrolment: parsed, scope };
6582
6622
  } catch {
6583
6623
  }
6584
- return { corrupt: true, path: path2 };
6624
+ return { corrupt: true, path: path3 };
6585
6625
  }
6586
6626
  return null;
6587
6627
  }
6588
- function writeFileMode(path2, content, mode) {
6589
- (0, import_fs6.mkdirSync)((0, import_path5.dirname)(path2), { recursive: true, mode: 448 });
6590
- (0, import_fs6.writeFileSync)(path2, content, { encoding: "utf-8", mode });
6628
+ function writeFileMode(path3, content, mode) {
6629
+ (0, import_fs6.mkdirSync)((0, import_path5.dirname)(path3), { recursive: true, mode: 448 });
6630
+ (0, import_fs6.writeFileSync)(path3, content, { encoding: "utf-8", mode });
6591
6631
  try {
6592
- (0, import_fs6.chmodSync)(path2, mode);
6632
+ (0, import_fs6.chmodSync)(path3, mode);
6593
6633
  } catch {
6594
6634
  }
6595
6635
  }
6596
6636
  function saveEnrolment(paths, enrolment, scope) {
6597
- const path2 = scope === "machine" ? paths.machineDevice : paths.userDevice;
6598
- writeFileMode(path2, JSON.stringify(enrolment, null, 2), scope === "machine" ? 420 : 384);
6599
- return path2;
6637
+ const path3 = scope === "machine" ? paths.machineDevice : paths.userDevice;
6638
+ writeFileMode(path3, JSON.stringify(enrolment, null, 2), scope === "machine" ? 420 : 384);
6639
+ return path3;
6600
6640
  }
6601
6641
  function readCachedPolicies(paths) {
6602
6642
  const out = [];
@@ -6918,12 +6958,12 @@ var MANAGED_SETTINGS_LOCATIONS = {
6918
6958
  win32: "%ProgramData%\\OpenAI\\Codex\\requirements.toml"
6919
6959
  }
6920
6960
  };
6921
- function managedHookCommand(target, os2) {
6961
+ function managedHookCommand(target, os3) {
6922
6962
  const bin = target === "claude" ? "astrasync-claude-hook" : "astrasync-codex-hook";
6923
- if (os2 === "win32") return `"C:\\Program Files\\nodejs\\${bin}.cmd"`;
6963
+ if (os3 === "win32") return `"C:\\Program Files\\nodejs\\${bin}.cmd"`;
6924
6964
  return `/usr/local/bin/${bin}`;
6925
6965
  }
6926
- function buildClaudeManagedSettings(os2) {
6966
+ function buildClaudeManagedSettings(os3) {
6927
6967
  return {
6928
6968
  allowManagedHooksOnly: true,
6929
6969
  env: { ASTRASYNC_FLEET_REQUIRED: "1" },
@@ -6931,7 +6971,7 @@ function buildClaudeManagedSettings(os2) {
6931
6971
  PreToolUse: [
6932
6972
  {
6933
6973
  matcher: HOOK_MATCHER,
6934
- hooks: [{ type: "command", command: managedHookCommand("claude", os2), timeout: 10 }]
6974
+ hooks: [{ type: "command", command: managedHookCommand("claude", os3), timeout: 10 }]
6935
6975
  }
6936
6976
  ]
6937
6977
  },
@@ -6941,11 +6981,11 @@ function buildClaudeManagedSettings(os2) {
6941
6981
  function tomlString(value) {
6942
6982
  return JSON.stringify(value);
6943
6983
  }
6944
- function buildCodexRequirementsToml(os2) {
6945
- const command = os2 === "win32" ? `set ASTRASYNC_FLEET_REQUIRED=1&& ${managedHookCommand("codex", os2)}` : `ASTRASYNC_FLEET_REQUIRED=1 ${managedHookCommand("codex", os2)}`;
6984
+ function buildCodexRequirementsToml(os3) {
6985
+ const command = os3 === "win32" ? `set ASTRASYNC_FLEET_REQUIRED=1&& ${managedHookCommand("codex", os3)}` : `ASTRASYNC_FLEET_REQUIRED=1 ${managedHookCommand("codex", os3)}`;
6946
6986
  return [
6947
6987
  "# AstraSync Local Guard \u2014 managed hook (generated by `astrasync guard managed-settings --codex`)",
6948
- `# Install at: ${MANAGED_SETTINGS_LOCATIONS.codex[os2]}`,
6988
+ `# Install at: ${MANAGED_SETTINGS_LOCATIONS.codex[os3]}`,
6949
6989
  "allow_managed_hooks_only = true",
6950
6990
  "",
6951
6991
  "[features]",
@@ -6965,14 +7005,14 @@ function buildCodexRequirementsToml(os2) {
6965
7005
  function buildManagedSettingsFiles(targets) {
6966
7006
  const out = [];
6967
7007
  for (const target of targets) {
6968
- for (const os2 of ["darwin", "linux", "win32"]) {
6969
- const content = target === "claude" ? `${JSON.stringify(buildClaudeManagedSettings(os2), null, 2)}
6970
- ` : buildCodexRequirementsToml(os2);
7008
+ for (const os3 of ["darwin", "linux", "win32"]) {
7009
+ const content = target === "claude" ? `${JSON.stringify(buildClaudeManagedSettings(os3), null, 2)}
7010
+ ` : buildCodexRequirementsToml(os3);
6971
7011
  out.push({
6972
7012
  target,
6973
- os: os2,
6974
- installPath: MANAGED_SETTINGS_LOCATIONS[target][os2],
6975
- fileName: `${target}-${os2}-${target === "claude" ? "managed-settings.json" : "requirements.toml"}`,
7013
+ os: os3,
7014
+ installPath: MANAGED_SETTINGS_LOCATIONS[target][os3],
7015
+ fileName: `${target}-${os3}-${target === "claude" ? "managed-settings.json" : "requirements.toml"}`,
6976
7016
  content
6977
7017
  });
6978
7018
  }
@@ -7151,10 +7191,170 @@ function createLocalCert(hosts, days = 30) {
7151
7191
  ${b64}
7152
7192
  -----END CERTIFICATE-----
7153
7193
  `,
7154
- key: privateKey.export({ type: "pkcs8", format: "pem" }).toString()
7194
+ key: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
7195
+ spkiSha256: import_node_crypto5.default.createHash("sha256").update(spki).digest("base64")
7155
7196
  };
7156
7197
  }
7157
7198
 
7199
+ // src/fleet/open-browser.ts
7200
+ var import_node_child_process = require("child_process");
7201
+ var import_node_fs = __toESM(require("fs"));
7202
+ var import_node_os = __toESM(require("os"));
7203
+ var import_node_path = __toESM(require("path"));
7204
+ function isWsl(env = process.env, platform2 = process.platform, readVersion = () => import_node_fs.default.readFileSync("/proc/version", "utf8")) {
7205
+ if (platform2 !== "linux") return false;
7206
+ if (env.WSL_DISTRO_NAME) return true;
7207
+ try {
7208
+ return /microsoft/i.test(readVersion());
7209
+ } catch {
7210
+ return false;
7211
+ }
7212
+ }
7213
+ var MAC_PATHS = [
7214
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
7215
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
7216
+ "/Applications/Chromium.app/Contents/MacOS/Chromium"
7217
+ ];
7218
+ var LINUX_NAMES = [
7219
+ "google-chrome",
7220
+ "google-chrome-stable",
7221
+ "chromium",
7222
+ "chromium-browser",
7223
+ "microsoft-edge"
7224
+ ];
7225
+ var WSL_WINDOWS_PATHS = [
7226
+ "/mnt/c/Program Files/Google/Chrome/Application/chrome.exe",
7227
+ "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe",
7228
+ "/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe",
7229
+ "/mnt/c/Program Files/Microsoft/Edge/Application/msedge.exe"
7230
+ ];
7231
+ 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)) {
7232
+ let candidates;
7233
+ if (platform2 === "darwin") {
7234
+ candidates = MAC_PATHS;
7235
+ } else if (platform2 === "win32") {
7236
+ const bases = [
7237
+ env.ProgramFiles,
7238
+ env["ProgramFiles(x86)"],
7239
+ env.LOCALAPPDATA ?? env.LocalAppData
7240
+ ].filter((b) => !!b);
7241
+ candidates = [
7242
+ ...bases.map((b) => import_node_path.default.win32.join(b, "Google", "Chrome", "Application", "chrome.exe")),
7243
+ ...bases.map((b) => import_node_path.default.win32.join(b, "Microsoft", "Edge", "Application", "msedge.exe"))
7244
+ ];
7245
+ } else {
7246
+ const dirs = (env.PATH ?? "").split(":").filter(Boolean);
7247
+ candidates = LINUX_NAMES.flatMap((n) => dirs.map((d) => import_node_path.default.posix.join(d, n)));
7248
+ const cache2 = env.PLAYWRIGHT_BROWSERS_PATH ?? (env.HOME ? `${env.HOME}/.cache/ms-playwright` : "");
7249
+ if (cache2) {
7250
+ let builds = [];
7251
+ try {
7252
+ builds = listDir(cache2).filter((d) => /^chromium-\d+$/.test(d));
7253
+ } catch {
7254
+ }
7255
+ builds.sort((a, b) => Number(b.split("-")[1]) - Number(a.split("-")[1]));
7256
+ for (const b of builds)
7257
+ candidates.push(
7258
+ import_node_path.default.posix.join(cache2, b, "chrome-linux64", "chrome"),
7259
+ import_node_path.default.posix.join(cache2, b, "chrome-linux", "chrome")
7260
+ );
7261
+ }
7262
+ if (wsl) candidates = [...candidates, ...WSL_WINDOWS_PATHS];
7263
+ }
7264
+ return candidates.find((c) => exists(c)) ?? null;
7265
+ }
7266
+ function isWindowsExeFromWsl(browserPath) {
7267
+ return /^\/mnt\/[a-z]\//i.test(browserPath) && /\.exe$/i.test(browserPath);
7268
+ }
7269
+ function browserName(browserPath) {
7270
+ const b = browserPath.toLowerCase();
7271
+ if (b.includes("edge")) return "Edge";
7272
+ if (b.includes("chromium")) return "Chromium";
7273
+ return "Chrome";
7274
+ }
7275
+ function profileDirFor(proxyPort) {
7276
+ return import_node_path.default.join(import_node_os.default.tmpdir(), `astrasync-agent-browser-${proxyPort}`);
7277
+ }
7278
+ function windowsProfileDir(profileDir) {
7279
+ if (/^\/mnt\/[a-z]\//i.test(profileDir)) {
7280
+ try {
7281
+ return (0, import_node_child_process.execFileSync)("wslpath", ["-w", profileDir], { encoding: "utf8" }).trim();
7282
+ } catch {
7283
+ }
7284
+ }
7285
+ return `C:\\Temp\\${import_node_path.default.basename(profileDir)}`;
7286
+ }
7287
+ function certFlag(certSpki) {
7288
+ return `--ignore-certificate-errors-spki-list=${certSpki}`;
7289
+ }
7290
+ var 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.";
7291
+ function launchBrowser(browserPath, proxyPort, url, certSpki, profileDir = profileDirFor(proxyPort), onError) {
7292
+ const dir = isWindowsExeFromWsl(browserPath) ? windowsProfileDir(profileDir) : profileDir;
7293
+ const child = (0, import_node_child_process.spawn)(
7294
+ browserPath,
7295
+ [
7296
+ `--proxy-server=http://127.0.0.1:${proxyPort}`,
7297
+ certFlag(certSpki),
7298
+ `--user-data-dir=${dir}`,
7299
+ "--no-first-run",
7300
+ "--new-window",
7301
+ url
7302
+ ],
7303
+ { detached: true, stdio: "ignore" }
7304
+ );
7305
+ child.on("error", (err) => onError?.(err));
7306
+ child.unref();
7307
+ return child;
7308
+ }
7309
+ function browserCommand(platform2, port, url, certSpki, wsl = false) {
7310
+ const proxy = `--proxy-server="http://127.0.0.1:${port}" ${certFlag(certSpki)}`;
7311
+ if (platform2 === "win32" || wsl) {
7312
+ return `& "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" ${proxy} --user-data-dir="$env:TEMP\\astrasync-agent-${port}" '${url}'`;
7313
+ }
7314
+ const exe = platform2 === "darwin" ? '"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"' : "google-chrome";
7315
+ return `${exe} ${proxy} --user-data-dir="\${TMPDIR:-/tmp}/astrasync-agent-${port}" '${url}'`;
7316
+ }
7317
+ function browserCommandLines(platform2, port, url, certSpki, wsl = isWsl(process.env, platform2)) {
7318
+ if (wsl) {
7319
+ return [
7320
+ " In PowerShell (Windows):",
7321
+ ` ${browserCommand("win32", port, url, certSpki)}`,
7322
+ " Or inside WSL (with Chromium installed):",
7323
+ ` ${browserCommand("linux", port, url, certSpki)}`,
7324
+ ` ${CERT_FLAG_NOTE}`
7325
+ ];
7326
+ }
7327
+ return [` ${browserCommand(platform2, port, url, certSpki)}`, ` ${CERT_FLAG_NOTE}`];
7328
+ }
7329
+ function openThroughProxy(opts) {
7330
+ const log = opts.log ?? ((l) => console.log(l));
7331
+ const platform2 = opts.platform ?? process.platform;
7332
+ const env = opts.env ?? process.env;
7333
+ const wsl = opts.wsl ?? isWsl(env, platform2);
7334
+ const found = opts.find ? opts.find() : findChromium(env, platform2, void 0, wsl);
7335
+ if (!found) {
7336
+ log("No Chrome/Edge/Chromium found. Start one through the proxy with:");
7337
+ for (const l of browserCommandLines(platform2, opts.port, opts.url, opts.certSpki, wsl)) log(l);
7338
+ return false;
7339
+ }
7340
+ (opts.launch ?? launchBrowser)(
7341
+ found,
7342
+ opts.port,
7343
+ opts.url,
7344
+ opts.certSpki,
7345
+ profileDirFor(opts.port),
7346
+ (err) => log(`Couldn't start ${browserName(found)}: ${err.message}`)
7347
+ );
7348
+ log(`Opened ${browserName(found)} through the proxy.`);
7349
+ log(CERT_FLAG_NOTE);
7350
+ if (wsl && isWindowsExeFromWsl(found)) {
7351
+ log(
7352
+ "If the Windows browser can't reach the proxy, install Chromium inside WSL (e.g. npx playwright install chromium)."
7353
+ );
7354
+ }
7355
+ return true;
7356
+ }
7357
+
7158
7358
  // src/fleet/browse-proxy.ts
7159
7359
  var HOP_BY_HOP = /* @__PURE__ */ new Set([
7160
7360
  "connection",
@@ -7171,10 +7371,33 @@ function cleanHeaders(h) {
7171
7371
  for (const [k, v] of Object.entries(h)) if (!HOP_BY_HOP.has(k) && v !== void 0) out[k] = v;
7172
7372
  return out;
7173
7373
  }
7374
+ function helpPage(port, certSpki) {
7375
+ const proxy = `http://127.0.0.1:${port}`;
7376
+ return `<!doctype html>
7377
+ <html lang="en"><head><meta charset="utf-8"><title>AstraSync device proxy</title>
7378
+ <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>
7379
+ </head><body>
7380
+ <h1>AstraSync device proxy</h1>
7381
+ <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>
7382
+ <h2>Open the sign-in link</h2>
7383
+ <ul>
7384
+ <li>Run <code>astrasync guard browse --open</code> &mdash; it opens a browser that goes through this proxy.</li>
7385
+ <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>
7386
+ </ul>
7387
+ <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>
7388
+ <p>Keep <code>astrasync guard browse</code> running while you use the session.</p>
7389
+ </body></html>
7390
+ `;
7391
+ }
7174
7392
  async function startBrowseProxy(opts) {
7175
7393
  const hosts = new Set(opts.hosts.map((h) => h.toLowerCase()));
7176
7394
  const deviceHeader = `Device ${opts.deviceCredential}`;
7177
- const { cert, key } = createLocalCert([...hosts]);
7395
+ const { cert, key, spkiSha256 } = createLocalCert([...hosts]);
7396
+ const sockets = /* @__PURE__ */ new Set();
7397
+ const track = (s) => {
7398
+ sockets.add(s);
7399
+ s.once("close", () => sockets.delete(s));
7400
+ };
7178
7401
  function forward(req, res, target) {
7179
7402
  const headers = cleanHeaders(req.headers);
7180
7403
  headers["x-astra-device"] = deviceHeader;
@@ -7226,6 +7449,7 @@ async function startBrowseProxy(opts) {
7226
7449
  },
7227
7450
  onConnect
7228
7451
  ) : import_node_net.default.connect(target.port, target.host, onConnect);
7452
+ track(upstream);
7229
7453
  upstream.on("error", () => socket.destroy());
7230
7454
  socket.on("error", () => upstream.destroy());
7231
7455
  }
@@ -7257,6 +7481,16 @@ async function startBrowseProxy(opts) {
7257
7481
  });
7258
7482
  });
7259
7483
  const server = import_node_http.default.createServer((req, res) => {
7484
+ if ((req.url ?? "").startsWith("/")) {
7485
+ if (req.url === "/favicon.ico") {
7486
+ res.writeHead(404).end();
7487
+ return;
7488
+ }
7489
+ const listening = server.address()?.port ?? 0;
7490
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
7491
+ res.end(helpPage(listening, spkiSha256));
7492
+ return;
7493
+ }
7260
7494
  let url;
7261
7495
  try {
7262
7496
  url = new URL(req.url ?? "");
@@ -7349,18 +7583,22 @@ async function startBrowseProxy(opts) {
7349
7583
  upstream.pipe(socket);
7350
7584
  socket.pipe(upstream);
7351
7585
  });
7586
+ track(upstream);
7352
7587
  upstream.on("error", () => socket.destroy());
7353
7588
  socket.on("error", () => upstream.destroy());
7354
7589
  });
7590
+ server.on("connection", track);
7355
7591
  await new Promise((resolve) => server.listen(opts.port ?? 0, "127.0.0.1", resolve));
7356
7592
  const port = server.address().port;
7357
7593
  return {
7358
7594
  port,
7595
+ certSpki: spkiSha256,
7359
7596
  close: () => new Promise((resolve) => {
7360
7597
  inner.close();
7361
7598
  plainInner.close();
7362
7599
  server.close(() => resolve());
7363
7600
  server.closeAllConnections?.();
7601
+ for (const s of sockets) s.destroy();
7364
7602
  })
7365
7603
  };
7366
7604
  }
@@ -7447,6 +7685,18 @@ async function runBrowse(paths, opts) {
7447
7685
  log(
7448
7686
  `Device proxy: http://127.0.0.1:${proxy.port} (adds this machine's credential for ${hosts.join(", ")})`
7449
7687
  );
7688
+ if (!opts.open) {
7689
+ log("Open it in a browser through the proxy:");
7690
+ for (const l of browserCommandLines(
7691
+ opts.platform ?? process.platform,
7692
+ proxy.port,
7693
+ openUrl,
7694
+ proxy.certSpki,
7695
+ opts.wsl
7696
+ ))
7697
+ log(l);
7698
+ log(" (or re-run with --open)");
7699
+ }
7450
7700
  log("Playwright:");
7451
7701
  log(
7452
7702
  ` chromium.launch({ proxy: { server: 'http://127.0.0.1:${proxy.port}' } }) + newContext({ ignoreHTTPSErrors: true })`
@@ -7486,7 +7736,7 @@ async function cmdSetup() {
7486
7736
  }
7487
7737
  async function cmdUpgrade() {
7488
7738
  const policyPath = process.argv[4] || DEFAULT_POLICY_PATH;
7489
- if (!fs.existsSync(policyPath)) {
7739
+ if (!fs2.existsSync(policyPath)) {
7490
7740
  console.error(`No policy file found at ${policyPath}`);
7491
7741
  console.error('Run "astrasync guard setup" first to create a policy.');
7492
7742
  process.exit(1);
@@ -7514,13 +7764,13 @@ async function cmdUpgrade() {
7514
7764
  prompt: (message) => prompt.question(message),
7515
7765
  print: (message) => console.log(message),
7516
7766
  writeConfig: async (filePath, content) => {
7517
- const dir = path.dirname(filePath);
7518
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
7519
- fs.writeFileSync(filePath, content, "utf-8");
7767
+ const dir = path2.dirname(filePath);
7768
+ if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
7769
+ fs2.writeFileSync(filePath, content, "utf-8");
7520
7770
  },
7521
7771
  readConfig: async (filePath) => {
7522
7772
  try {
7523
- return fs.readFileSync(filePath, "utf-8");
7773
+ return fs2.readFileSync(filePath, "utf-8");
7524
7774
  } catch {
7525
7775
  return null;
7526
7776
  }
@@ -7541,13 +7791,13 @@ async function cmdStart() {
7541
7791
  let gatewayConfig;
7542
7792
  const upgraded = await isUpgraded(DEFAULT_CONFIG_DIR, async (p) => {
7543
7793
  try {
7544
- return fs.readFileSync(p, "utf-8");
7794
+ return fs2.readFileSync(p, "utf-8");
7545
7795
  } catch {
7546
7796
  return null;
7547
7797
  }
7548
7798
  });
7549
7799
  if (upgraded) {
7550
- const config = JSON.parse(fs.readFileSync(`${DEFAULT_CONFIG_DIR}/config`, "utf-8"));
7800
+ const config = JSON.parse(fs2.readFileSync(`${DEFAULT_CONFIG_DIR}/config`, "utf-8"));
7551
7801
  gatewayConfig = {
7552
7802
  mode: config.mode,
7553
7803
  apiBaseUrl: config.platformUrl ? `${config.platformUrl}/api/v1` : void 0,
@@ -7555,7 +7805,7 @@ async function cmdStart() {
7555
7805
  };
7556
7806
  console.log(`[AstraSync] Starting in ${config.mode} mode (agent: ${config.agentId})`);
7557
7807
  } else {
7558
- if (!fs.existsSync(policyPath)) {
7808
+ if (!fs2.existsSync(policyPath)) {
7559
7809
  console.error(`No policy file found at ${policyPath}`);
7560
7810
  console.error('Run "astrasync guard setup" first.');
7561
7811
  process.exit(1);
@@ -7592,7 +7842,7 @@ async function cmdStatus() {
7592
7842
  const policyPath = process.argv[4] || DEFAULT_POLICY_PATH;
7593
7843
  const upgraded = await isUpgraded(DEFAULT_CONFIG_DIR, async (p) => {
7594
7844
  try {
7595
- return fs.readFileSync(p, "utf-8");
7845
+ return fs2.readFileSync(p, "utf-8");
7596
7846
  } catch {
7597
7847
  return null;
7598
7848
  }
@@ -7602,7 +7852,7 @@ async function cmdStatus() {
7602
7852
  console.log(` ${line}`);
7603
7853
  }
7604
7854
  if (upgraded) {
7605
- const config = JSON.parse(fs.readFileSync(`${DEFAULT_CONFIG_DIR}/config`, "utf-8"));
7855
+ const config = JSON.parse(fs2.readFileSync(`${DEFAULT_CONFIG_DIR}/config`, "utf-8"));
7606
7856
  console.log(` Mode: ${config.mode}`);
7607
7857
  console.log(` Agent: ${config.agentId}`);
7608
7858
  console.log(` Platform: ${config.platformUrl}`);
@@ -7610,7 +7860,7 @@ async function cmdStatus() {
7610
7860
  } else {
7611
7861
  console.log(" Mode: local (free tier)");
7612
7862
  }
7613
- if (fs.existsSync(policyPath)) {
7863
+ if (fs2.existsSync(policyPath)) {
7614
7864
  const parsed = parseFile(policyPath);
7615
7865
  if (parsed.validation.valid && parsed.policy) {
7616
7866
  const p = parsed.policy;
@@ -7635,7 +7885,7 @@ async function cmdCheck() {
7635
7885
  (a) => !a.startsWith("--") && a !== "guard" && a !== "check" && a !== process.argv[0] && a !== process.argv[1]
7636
7886
  ) || DEFAULT_POLICY_PATH;
7637
7887
  const trigger = process.argv.find((a) => a.startsWith("--trigger="))?.split("=")[1] || "pre-push";
7638
- if (!fs.existsSync(policyPath)) {
7888
+ if (!fs2.existsSync(policyPath)) {
7639
7889
  console.log("[AstraSync] No policy file \u2014 skipping check");
7640
7890
  process.exit(0);
7641
7891
  }
@@ -7647,9 +7897,9 @@ async function cmdCheck() {
7647
7897
  }
7648
7898
  let enforcement = "warn";
7649
7899
  const configPath = `${DEFAULT_CONFIG_DIR}/config`;
7650
- if (fs.existsSync(configPath)) {
7900
+ if (fs2.existsSync(configPath)) {
7651
7901
  try {
7652
- const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
7902
+ const config = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
7653
7903
  enforcement = config.gitTriggerEnforcement || "warn";
7654
7904
  } catch {
7655
7905
  }
@@ -7712,8 +7962,8 @@ async function cmdUninstallHooks() {
7712
7962
  }
7713
7963
  }
7714
7964
  function resolveClaudeHookCommand() {
7715
- const sibling = path.join(__dirname, "astrasync-claude-hook.js");
7716
- if (fs.existsSync(sibling)) {
7965
+ const sibling = path2.join(__dirname, "astrasync-claude-hook.js");
7966
+ if (fs2.existsSync(sibling)) {
7717
7967
  return `"${process.execPath}" "${sibling}"`;
7718
7968
  }
7719
7969
  return "astrasync-claude-hook";
@@ -7729,19 +7979,19 @@ function parseClaudeCodeFlags() {
7729
7979
  };
7730
7980
  }
7731
7981
  function claudeCodePaths(project) {
7732
- const root = project ? process.cwd() : os.homedir();
7982
+ const root = project ? process.cwd() : os2.homedir();
7733
7983
  return {
7734
- settingsPath: path.join(root, ".claude", "settings.json"),
7735
- astrasyncDir: path.join(root, ".astrasync")
7984
+ settingsPath: path2.join(root, ".claude", "settings.json"),
7985
+ astrasyncDir: path2.join(root, ".astrasync")
7736
7986
  };
7737
7987
  }
7738
7988
  async function cmdInstallClaudeCode() {
7739
7989
  const flags = parseClaudeCodeFlags();
7740
7990
  const { settingsPath, astrasyncDir } = claudeCodePaths(flags.project);
7741
- const policyPath = flags.policyOverride || path.join(astrasyncDir, "policy.yaml");
7742
- const guardJsonPath = path.join(astrasyncDir, "guard.json");
7991
+ const policyPath = flags.policyOverride || path2.join(astrasyncDir, "policy.yaml");
7992
+ const guardJsonPath = path2.join(astrasyncDir, "guard.json");
7743
7993
  console.log("\nAstraSync Local Guard for Claude Code \u2014 install\n");
7744
- if (fs.existsSync(policyPath)) {
7994
+ if (fs2.existsSync(policyPath)) {
7745
7995
  console.log(` Policy: using existing ${policyPath}`);
7746
7996
  if (ensureGuardSelfProtection(policyPath)) {
7747
7997
  console.log(" added guard self-protection patterns to file.write");
@@ -7749,7 +7999,7 @@ async function cmdInstallClaudeCode() {
7749
7999
  } else if (flags.wizard) {
7750
8000
  const prompt = createPrompt();
7751
8001
  try {
7752
- fs.mkdirSync(path.dirname(policyPath), { recursive: true });
8002
+ fs2.mkdirSync(path2.dirname(policyPath), { recursive: true });
7753
8003
  const wizard = new SetupWizard(prompt);
7754
8004
  await wizard.runAndSave(policyPath);
7755
8005
  } finally {
@@ -7758,17 +8008,17 @@ async function cmdInstallClaudeCode() {
7758
8008
  ensureGuardSelfProtection(policyPath);
7759
8009
  console.log(` Policy: created via wizard at ${policyPath}`);
7760
8010
  } else {
7761
- fs.mkdirSync(path.dirname(policyPath), { recursive: true });
7762
- fs.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
8011
+ fs2.mkdirSync(path2.dirname(policyPath), { recursive: true });
8012
+ fs2.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
7763
8013
  console.log(` Policy: default coding-agent policy written to ${policyPath}`);
7764
8014
  console.log(" (re-run with --wizard for the interactive setup instead)");
7765
8015
  }
7766
- if (!fs.existsSync(guardJsonPath)) {
8016
+ if (!fs2.existsSync(guardJsonPath)) {
7767
8017
  const guardSettings = {
7768
8018
  posture: flags.active ? "active" : "passive",
7769
8019
  governMcp: false
7770
8020
  };
7771
- fs.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
8021
+ fs2.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
7772
8022
  `, "utf-8");
7773
8023
  console.log(
7774
8024
  ` Guard: ${guardJsonPath} (posture: ${guardSettings.posture}, governMcp: false)`
@@ -7798,7 +8048,7 @@ async function cmdInstallClaudeCode() {
7798
8048
  }
7799
8049
  console.log("\n Next steps:");
7800
8050
  console.log(" 1. Start a NEW Claude Code session (settings load at startup).");
7801
- console.log(` 2. Watch decisions: tail -f ${path.join(astrasyncDir, "traces")}/*.jsonl`);
8051
+ console.log(` 2. Watch decisions: tail -f ${path2.join(astrasyncDir, "traces")}/*.jsonl`);
7802
8052
  console.log(` 3. When the traces look right, set "posture": "active" in ${guardJsonPath}`);
7803
8053
  console.log(" to enforce (denies block, approvals use Claude Code's own prompt).");
7804
8054
  console.log(' 4. To govern MCP tools too, set "governMcp": true and add');
@@ -7828,26 +8078,26 @@ async function cmdUninstallClaudeCode() {
7828
8078
  );
7829
8079
  }
7830
8080
  function resolveCodexHookCommand() {
7831
- const sibling = path.join(__dirname, "astrasync-codex-hook.js");
7832
- if (fs.existsSync(sibling)) {
8081
+ const sibling = path2.join(__dirname, "astrasync-codex-hook.js");
8082
+ if (fs2.existsSync(sibling)) {
7833
8083
  return `"${process.execPath}" "${sibling}"`;
7834
8084
  }
7835
8085
  return "astrasync-codex-hook";
7836
8086
  }
7837
8087
  function codexPaths(project) {
7838
- const root = project ? process.cwd() : os.homedir();
8088
+ const root = project ? process.cwd() : os2.homedir();
7839
8089
  return {
7840
- hooksPath: path.join(root, ".codex", "hooks.json"),
7841
- astrasyncDir: path.join(root, ".astrasync")
8090
+ hooksPath: path2.join(root, ".codex", "hooks.json"),
8091
+ astrasyncDir: path2.join(root, ".astrasync")
7842
8092
  };
7843
8093
  }
7844
8094
  async function cmdInstallCodex() {
7845
8095
  const flags = parseClaudeCodeFlags();
7846
8096
  const { hooksPath, astrasyncDir } = codexPaths(flags.project);
7847
- const policyPath = flags.policyOverride || path.join(astrasyncDir, "policy.yaml");
7848
- const guardJsonPath = path.join(astrasyncDir, "guard.json");
8097
+ const policyPath = flags.policyOverride || path2.join(astrasyncDir, "policy.yaml");
8098
+ const guardJsonPath = path2.join(astrasyncDir, "guard.json");
7849
8099
  console.log("\nAstraSync Local Guard for Codex CLI \u2014 install\n");
7850
- if (fs.existsSync(policyPath)) {
8100
+ if (fs2.existsSync(policyPath)) {
7851
8101
  console.log(` Policy: using existing ${policyPath} (shared across adapters)`);
7852
8102
  if (ensureGuardSelfProtection(policyPath)) {
7853
8103
  console.log(" added guard self-protection patterns to file.write");
@@ -7855,7 +8105,7 @@ async function cmdInstallCodex() {
7855
8105
  } else if (flags.wizard) {
7856
8106
  const prompt = createPrompt();
7857
8107
  try {
7858
- fs.mkdirSync(path.dirname(policyPath), { recursive: true });
8108
+ fs2.mkdirSync(path2.dirname(policyPath), { recursive: true });
7859
8109
  const wizard = new SetupWizard(prompt);
7860
8110
  await wizard.runAndSave(policyPath);
7861
8111
  } finally {
@@ -7864,17 +8114,17 @@ async function cmdInstallCodex() {
7864
8114
  ensureGuardSelfProtection(policyPath);
7865
8115
  console.log(` Policy: created via wizard at ${policyPath}`);
7866
8116
  } else {
7867
- fs.mkdirSync(path.dirname(policyPath), { recursive: true });
7868
- fs.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
8117
+ fs2.mkdirSync(path2.dirname(policyPath), { recursive: true });
8118
+ fs2.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
7869
8119
  console.log(` Policy: default coding-agent policy written to ${policyPath}`);
7870
8120
  console.log(" (re-run with --wizard for the interactive setup instead)");
7871
8121
  }
7872
- if (!fs.existsSync(guardJsonPath)) {
8122
+ if (!fs2.existsSync(guardJsonPath)) {
7873
8123
  const guardSettings = {
7874
8124
  posture: flags.active ? "active" : "passive",
7875
8125
  governMcp: false
7876
8126
  };
7877
- fs.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
8127
+ fs2.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
7878
8128
  `, "utf-8");
7879
8129
  console.log(
7880
8130
  ` Guard: ${guardJsonPath} (posture: ${guardSettings.posture}, governMcp: false)`
@@ -7893,7 +8143,7 @@ async function cmdInstallCodex() {
7893
8143
  }
7894
8144
  console.log("\n Next steps:");
7895
8145
  console.log(" 1. Start a NEW Codex session (hooks load at startup; /hooks to inspect).");
7896
- console.log(` 2. Watch decisions: tail -f ${path.join(astrasyncDir, "traces")}/*.jsonl`);
8146
+ console.log(` 2. Watch decisions: tail -f ${path2.join(astrasyncDir, "traces")}/*.jsonl`);
7897
8147
  console.log(` 3. When the traces look right, set "posture": "active" in ${guardJsonPath}.`);
7898
8148
  console.log(" Denies block; approvals fall through to Codex's own prompt \u2014");
7899
8149
  console.log(" except in dontAsk/bypassPermissions modes, where they deny.");
@@ -7918,13 +8168,13 @@ function printCodexStatus() {
7918
8168
  const { hooksPath, astrasyncDir } = codexPaths(scope === "project");
7919
8169
  let hookInstalled = false;
7920
8170
  try {
7921
- const content = fs.readFileSync(hooksPath, "utf-8");
8171
+ const content = fs2.readFileSync(hooksPath, "utf-8");
7922
8172
  hookInstalled = content.includes(ASTRASYNC_CODEX_HOOK_MARKER);
7923
8173
  } catch {
7924
8174
  }
7925
8175
  if (hookInstalled) {
7926
- const policyPath = path.join(astrasyncDir, "policy.yaml");
7927
- const policy = fs.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
8176
+ const policyPath = path2.join(astrasyncDir, "policy.yaml");
8177
+ const policy = fs2.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
7928
8178
  console.log(` Codex CLI (${scope}): hook installed, ${policy}`);
7929
8179
  }
7930
8180
  }
@@ -7934,24 +8184,24 @@ function printClaudeCodeStatus() {
7934
8184
  const { settingsPath, astrasyncDir } = claudeCodePaths(scope === "project");
7935
8185
  let hookInstalled = false;
7936
8186
  try {
7937
- const content = fs.readFileSync(settingsPath, "utf-8");
8187
+ const content = fs2.readFileSync(settingsPath, "utf-8");
7938
8188
  hookInstalled = content.includes(ASTRASYNC_HOOK_MARKER);
7939
8189
  } catch {
7940
8190
  }
7941
- const policyPath = path.join(astrasyncDir, "policy.yaml");
7942
- const guardJsonPath = path.join(astrasyncDir, "guard.json");
8191
+ const policyPath = path2.join(astrasyncDir, "policy.yaml");
8192
+ const guardJsonPath = path2.join(astrasyncDir, "guard.json");
7943
8193
  let posture = "";
7944
- if (fs.existsSync(guardJsonPath)) {
8194
+ if (fs2.existsSync(guardJsonPath)) {
7945
8195
  try {
7946
- const settings = JSON.parse(fs.readFileSync(guardJsonPath, "utf-8"));
8196
+ const settings = JSON.parse(fs2.readFileSync(guardJsonPath, "utf-8"));
7947
8197
  posture = settings.posture ? `, posture: ${settings.posture}` : "";
7948
8198
  } catch {
7949
8199
  posture = ", guard.json unreadable";
7950
8200
  }
7951
8201
  }
7952
- if (hookInstalled || fs.existsSync(policyPath)) {
8202
+ if (hookInstalled || fs2.existsSync(policyPath)) {
7953
8203
  const hook = hookInstalled ? "hook installed" : "hook NOT installed";
7954
- const policy = fs.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
8204
+ const policy = fs2.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
7955
8205
  console.log(` Claude Code (${scope}): ${hook}, ${policy}${posture}`);
7956
8206
  }
7957
8207
  }
@@ -7997,14 +8247,21 @@ async function cmdFleetStatus() {
7997
8247
  async function cmdBrowse() {
7998
8248
  try {
7999
8249
  const port = flagValue("--port");
8000
- const { proxy } = await runBrowse(fleetPaths(), {
8250
+ const open = process.argv.includes("--open");
8251
+ const { proxy, openUrl } = await runBrowse(fleetPaths(), {
8001
8252
  checkpoint: flagValue("--checkpoint") ?? "",
8002
8253
  slug: flagValue("--slug") ?? "",
8003
8254
  upstream: flagValue("--upstream"),
8004
8255
  port: port ? Number(port) : void 0,
8005
- extraHosts: flagValue("--host") ? [flagValue("--host")] : []
8256
+ extraHosts: flagValue("--host") ? [flagValue("--host")] : [],
8257
+ open
8006
8258
  });
8259
+ if (open) openThroughProxy({ port: proxy.port, url: openUrl, certSpki: proxy.certSpki });
8260
+ let stopping = false;
8007
8261
  const stop = async () => {
8262
+ if (stopping) process.exit(0);
8263
+ stopping = true;
8264
+ setTimeout(() => process.exit(0), 2e3).unref();
8008
8265
  await proxy.close();
8009
8266
  process.exit(0);
8010
8267
  };
@@ -8039,7 +8296,7 @@ async function cmdGitRoute() {
8039
8296
  }
8040
8297
  async function cmdGitCredential() {
8041
8298
  const action = process.argv[process.argv.indexOf("git-credential") + 1] ?? "";
8042
- const input = fs.readFileSync(0, "utf-8");
8299
+ const input = fs2.readFileSync(0, "utf-8");
8043
8300
  process.stdout.write(runGitCredential(action, input));
8044
8301
  }
8045
8302
  async function cmdManagedSettings() {
@@ -8143,8 +8400,9 @@ async function main() {
8143
8400
  " browse Sign an agent in to your dashboard from this machine and"
8144
8401
  );
8145
8402
  console.log(
8146
- " run its device proxy (--checkpoint <url> --slug <agent>)"
8403
+ " run its device proxy (--checkpoint <url> --slug <agent>;"
8147
8404
  );
8405
+ console.log(" --open to start a browser through the proxy)");
8148
8406
  console.log(" enroll <token> Enrol this machine in your organisation fleet");
8149
8407
  console.log(" (--api-base-url <url>, --system for machine scope)");
8150
8408
  console.log(" fleet-status Show fleet enrolment, signed policy and heartbeat");