@astrasyncai/verification-gateway 5.18.0 → 5.18.1

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 (74) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/adapters/express.js +1 -1
  3. package/dist/adapters/express.js.map +1 -1
  4. package/dist/adapters/express.mjs +1 -1
  5. package/dist/adapters/express.mjs.map +1 -1
  6. package/dist/adapters/mcp.js +1 -1
  7. package/dist/adapters/mcp.js.map +1 -1
  8. package/dist/adapters/mcp.mjs +1 -1
  9. package/dist/adapters/mcp.mjs.map +1 -1
  10. package/dist/adapters/nextjs.js +1 -1
  11. package/dist/adapters/nextjs.js.map +1 -1
  12. package/dist/adapters/nextjs.mjs +1 -1
  13. package/dist/adapters/nextjs.mjs.map +1 -1
  14. package/dist/adapters/sdk.js +1 -1
  15. package/dist/adapters/sdk.js.map +1 -1
  16. package/dist/adapters/sdk.mjs +1 -1
  17. package/dist/adapters/sdk.mjs.map +1 -1
  18. package/dist/agent/index.js +1 -1
  19. package/dist/agent/index.js.map +1 -1
  20. package/dist/agent/index.mjs +1 -1
  21. package/dist/agent/index.mjs.map +1 -1
  22. package/dist/bin/astrasync-claude-hook.js +1 -1
  23. package/dist/bin/astrasync-codex-hook.js +1 -1
  24. package/dist/bin/astrasync-guard.js +330 -112
  25. package/dist/bin/astrasync.js +341 -113
  26. package/dist/browser/background.js +1 -1
  27. package/dist/browser/background.js.map +1 -1
  28. package/dist/browser/background.mjs +1 -1
  29. package/dist/browser/background.mjs.map +1 -1
  30. package/dist/cli/index.js +1 -1
  31. package/dist/cli/index.js.map +1 -1
  32. package/dist/cli/index.mjs +1 -1
  33. package/dist/cli/index.mjs.map +1 -1
  34. package/dist/codex/index.js +1 -1
  35. package/dist/codex/index.js.map +1 -1
  36. package/dist/codex/index.mjs +1 -1
  37. package/dist/codex/index.mjs.map +1 -1
  38. package/dist/cursor/extension.js +1 -1
  39. package/dist/cursor/extension.js.map +1 -1
  40. package/dist/cursor/extension.mjs +1 -1
  41. package/dist/cursor/extension.mjs.map +1 -1
  42. package/dist/edge-config.js +1 -1
  43. package/dist/edge-config.js.map +1 -1
  44. package/dist/edge-config.mjs +1 -1
  45. package/dist/edge-config.mjs.map +1 -1
  46. package/dist/edge-core/index.js +1 -1
  47. package/dist/edge-core/index.js.map +1 -1
  48. package/dist/edge-core/index.mjs +1 -1
  49. package/dist/edge-core/index.mjs.map +1 -1
  50. package/dist/gateway/gateway.js +1 -1
  51. package/dist/gateway/gateway.js.map +1 -1
  52. package/dist/gateway/gateway.mjs +1 -1
  53. package/dist/gateway/gateway.mjs.map +1 -1
  54. package/dist/index.d.mts +3 -3
  55. package/dist/index.d.ts +3 -3
  56. package/dist/index.js +1 -1
  57. package/dist/index.js.map +1 -1
  58. package/dist/index.mjs +1 -1
  59. package/dist/index.mjs.map +1 -1
  60. package/dist/registration/index.d.mts +1 -1
  61. package/dist/registration/index.d.ts +1 -1
  62. package/dist/registration/index.js +1 -1
  63. package/dist/registration/index.js.map +1 -1
  64. package/dist/registration/index.mjs +1 -1
  65. package/dist/registration/index.mjs.map +1 -1
  66. package/dist/transport/index.js +1 -1
  67. package/dist/transport/index.js.map +1 -1
  68. package/dist/transport/index.mjs +1 -1
  69. package/dist/transport/index.mjs.map +1 -1
  70. package/dist/verify.js +1 -1
  71. package/dist/verify.js.map +1 -1
  72. package/dist/verify.mjs +1 -1
  73. package/dist/verify.mjs.map +1 -1
  74. 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.18.1";
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");
@@ -7825,29 +7825,29 @@ function loadEnrolment(paths) {
7825
7825
  [paths.machineDevice, "machine"],
7826
7826
  [paths.userDevice, "user"]
7827
7827
  ];
7828
- for (const [path2, scope] of candidates) {
7829
- if (!(0, import_fs6.existsSync)(path2)) continue;
7828
+ for (const [path3, scope] of candidates) {
7829
+ if (!(0, import_fs6.existsSync)(path3)) continue;
7830
7830
  try {
7831
- const parsed = JSON.parse((0, import_fs6.readFileSync)(path2, "utf-8"));
7831
+ const parsed = JSON.parse((0, import_fs6.readFileSync)(path3, "utf-8"));
7832
7832
  if (isEnrolment(parsed)) return { enrolment: parsed, scope };
7833
7833
  } catch {
7834
7834
  }
7835
- return { corrupt: true, path: path2 };
7835
+ return { corrupt: true, path: path3 };
7836
7836
  }
7837
7837
  return null;
7838
7838
  }
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 });
7839
+ function writeFileMode(path3, content, mode) {
7840
+ (0, import_fs6.mkdirSync)((0, import_path5.dirname)(path3), { recursive: true, mode: 448 });
7841
+ (0, import_fs6.writeFileSync)(path3, content, { encoding: "utf-8", mode });
7842
7842
  try {
7843
- (0, import_fs6.chmodSync)(path2, mode);
7843
+ (0, import_fs6.chmodSync)(path3, mode);
7844
7844
  } catch {
7845
7845
  }
7846
7846
  }
7847
7847
  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;
7848
+ const path3 = scope === "machine" ? paths.machineDevice : paths.userDevice;
7849
+ writeFileMode(path3, JSON.stringify(enrolment, null, 2), scope === "machine" ? 420 : 384);
7850
+ return path3;
7851
7851
  }
7852
7852
  function readCachedPolicies(paths) {
7853
7853
  const out = [];
@@ -8175,12 +8175,12 @@ function formatFleetStatus(status) {
8175
8175
  if (status.lastError) lines.push(` Last sync error: ${status.lastError}`);
8176
8176
  return lines;
8177
8177
  }
8178
- function managedHookCommand(target, os2) {
8178
+ function managedHookCommand(target, os3) {
8179
8179
  const bin = target === "claude" ? "astrasync-claude-hook" : "astrasync-codex-hook";
8180
- if (os2 === "win32") return `"C:\\Program Files\\nodejs\\${bin}.cmd"`;
8180
+ if (os3 === "win32") return `"C:\\Program Files\\nodejs\\${bin}.cmd"`;
8181
8181
  return `/usr/local/bin/${bin}`;
8182
8182
  }
8183
- function buildClaudeManagedSettings(os2) {
8183
+ function buildClaudeManagedSettings(os3) {
8184
8184
  return {
8185
8185
  allowManagedHooksOnly: true,
8186
8186
  env: { ASTRASYNC_FLEET_REQUIRED: "1" },
@@ -8188,7 +8188,7 @@ function buildClaudeManagedSettings(os2) {
8188
8188
  PreToolUse: [
8189
8189
  {
8190
8190
  matcher: HOOK_MATCHER,
8191
- hooks: [{ type: "command", command: managedHookCommand("claude", os2), timeout: 10 }]
8191
+ hooks: [{ type: "command", command: managedHookCommand("claude", os3), timeout: 10 }]
8192
8192
  }
8193
8193
  ]
8194
8194
  },
@@ -8198,11 +8198,11 @@ function buildClaudeManagedSettings(os2) {
8198
8198
  function tomlString(value) {
8199
8199
  return JSON.stringify(value);
8200
8200
  }
8201
- function buildCodexRequirementsToml(os2) {
8202
- const command = os2 === "win32" ? `set ASTRASYNC_FLEET_REQUIRED=1&& ${managedHookCommand("codex", os2)}` : `ASTRASYNC_FLEET_REQUIRED=1 ${managedHookCommand("codex", os2)}`;
8201
+ function buildCodexRequirementsToml(os3) {
8202
+ const command = os3 === "win32" ? `set ASTRASYNC_FLEET_REQUIRED=1&& ${managedHookCommand("codex", os3)}` : `ASTRASYNC_FLEET_REQUIRED=1 ${managedHookCommand("codex", os3)}`;
8203
8203
  return [
8204
8204
  "# AstraSync Local Guard \u2014 managed hook (generated by `astrasync guard managed-settings --codex`)",
8205
- `# Install at: ${MANAGED_SETTINGS_LOCATIONS.codex[os2]}`,
8205
+ `# Install at: ${MANAGED_SETTINGS_LOCATIONS.codex[os3]}`,
8206
8206
  "allow_managed_hooks_only = true",
8207
8207
  "",
8208
8208
  "[features]",
@@ -8222,14 +8222,14 @@ function buildCodexRequirementsToml(os2) {
8222
8222
  function buildManagedSettingsFiles(targets) {
8223
8223
  const out = [];
8224
8224
  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);
8225
+ for (const os3 of ["darwin", "linux", "win32"]) {
8226
+ const content = target === "claude" ? `${JSON.stringify(buildClaudeManagedSettings(os3), null, 2)}
8227
+ ` : buildCodexRequirementsToml(os3);
8228
8228
  out.push({
8229
8229
  target,
8230
- os: os2,
8231
- installPath: MANAGED_SETTINGS_LOCATIONS[target][os2],
8232
- fileName: `${target}-${os2}-${target === "claude" ? "managed-settings.json" : "requirements.toml"}`,
8230
+ os: os3,
8231
+ installPath: MANAGED_SETTINGS_LOCATIONS[target][os3],
8232
+ fileName: `${target}-${os3}-${target === "claude" ? "managed-settings.json" : "requirements.toml"}`,
8233
8233
  content
8234
8234
  });
8235
8235
  }
@@ -8435,7 +8435,8 @@ function createLocalCert(hosts, days = 30) {
8435
8435
  ${b64}
8436
8436
  -----END CERTIFICATE-----
8437
8437
  `,
8438
- key: privateKey.export({ type: "pkcs8", format: "pem" }).toString()
8438
+ key: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
8439
+ spkiSha256: import_node_crypto5.default.createHash("sha256").update(spki).digest("base64")
8439
8440
  };
8440
8441
  }
8441
8442
  var import_node_crypto5, tlv, seq2, set2, ctx, ECDSA_SHA256;
@@ -8451,16 +8452,204 @@ var init_local_cert = __esm({
8451
8452
  }
8452
8453
  });
8453
8454
 
8455
+ // src/fleet/open-browser.ts
8456
+ function isWsl(env = process.env, platform2 = process.platform, readVersion = () => import_node_fs.default.readFileSync("/proc/version", "utf8")) {
8457
+ if (platform2 !== "linux") return false;
8458
+ if (env.WSL_DISTRO_NAME) return true;
8459
+ try {
8460
+ return /microsoft/i.test(readVersion());
8461
+ } catch {
8462
+ return false;
8463
+ }
8464
+ }
8465
+ 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)) {
8466
+ let candidates;
8467
+ if (platform2 === "darwin") {
8468
+ candidates = MAC_PATHS;
8469
+ } else if (platform2 === "win32") {
8470
+ const bases = [
8471
+ env.ProgramFiles,
8472
+ env["ProgramFiles(x86)"],
8473
+ env.LOCALAPPDATA ?? env.LocalAppData
8474
+ ].filter((b) => !!b);
8475
+ candidates = [
8476
+ ...bases.map((b) => import_node_path.default.win32.join(b, "Google", "Chrome", "Application", "chrome.exe")),
8477
+ ...bases.map((b) => import_node_path.default.win32.join(b, "Microsoft", "Edge", "Application", "msedge.exe"))
8478
+ ];
8479
+ } else {
8480
+ const dirs = (env.PATH ?? "").split(":").filter(Boolean);
8481
+ candidates = LINUX_NAMES.flatMap((n) => dirs.map((d) => import_node_path.default.posix.join(d, n)));
8482
+ const cache2 = env.PLAYWRIGHT_BROWSERS_PATH ?? (env.HOME ? `${env.HOME}/.cache/ms-playwright` : "");
8483
+ if (cache2) {
8484
+ let builds = [];
8485
+ try {
8486
+ builds = listDir(cache2).filter((d) => /^chromium-\d+$/.test(d));
8487
+ } catch {
8488
+ }
8489
+ builds.sort((a, b) => Number(b.split("-")[1]) - Number(a.split("-")[1]));
8490
+ for (const b of builds)
8491
+ candidates.push(
8492
+ import_node_path.default.posix.join(cache2, b, "chrome-linux64", "chrome"),
8493
+ import_node_path.default.posix.join(cache2, b, "chrome-linux", "chrome")
8494
+ );
8495
+ }
8496
+ if (wsl) candidates = [...candidates, ...WSL_WINDOWS_PATHS];
8497
+ }
8498
+ return candidates.find((c) => exists(c)) ?? null;
8499
+ }
8500
+ function isWindowsExeFromWsl(browserPath) {
8501
+ return /^\/mnt\/[a-z]\//i.test(browserPath) && /\.exe$/i.test(browserPath);
8502
+ }
8503
+ function browserName(browserPath) {
8504
+ const b = browserPath.toLowerCase();
8505
+ if (b.includes("edge")) return "Edge";
8506
+ if (b.includes("chromium")) return "Chromium";
8507
+ return "Chrome";
8508
+ }
8509
+ function profileDirFor(proxyPort) {
8510
+ return import_node_path.default.join(import_node_os.default.tmpdir(), `astrasync-agent-browser-${proxyPort}`);
8511
+ }
8512
+ function windowsProfileDir(profileDir) {
8513
+ if (/^\/mnt\/[a-z]\//i.test(profileDir)) {
8514
+ try {
8515
+ return (0, import_node_child_process.execFileSync)("wslpath", ["-w", profileDir], { encoding: "utf8" }).trim();
8516
+ } catch {
8517
+ }
8518
+ }
8519
+ return `C:\\Temp\\${import_node_path.default.basename(profileDir)}`;
8520
+ }
8521
+ function certFlag(certSpki) {
8522
+ return `--ignore-certificate-errors-spki-list=${certSpki}`;
8523
+ }
8524
+ function launchBrowser(browserPath, proxyPort, url, certSpki, profileDir = profileDirFor(proxyPort), onError) {
8525
+ const dir = isWindowsExeFromWsl(browserPath) ? windowsProfileDir(profileDir) : profileDir;
8526
+ const child = (0, import_node_child_process.spawn)(
8527
+ browserPath,
8528
+ [
8529
+ `--proxy-server=http://127.0.0.1:${proxyPort}`,
8530
+ certFlag(certSpki),
8531
+ `--user-data-dir=${dir}`,
8532
+ "--no-first-run",
8533
+ "--new-window",
8534
+ url
8535
+ ],
8536
+ { detached: true, stdio: "ignore" }
8537
+ );
8538
+ child.on("error", (err) => onError?.(err));
8539
+ child.unref();
8540
+ return child;
8541
+ }
8542
+ function browserCommand(platform2, port, url, certSpki, wsl = false) {
8543
+ const proxy = `--proxy-server="http://127.0.0.1:${port}" ${certFlag(certSpki)}`;
8544
+ if (platform2 === "win32" || wsl) {
8545
+ return `& "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" ${proxy} --user-data-dir="$env:TEMP\\astrasync-agent-${port}" '${url}'`;
8546
+ }
8547
+ const exe = platform2 === "darwin" ? '"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"' : "google-chrome";
8548
+ return `${exe} ${proxy} --user-data-dir="\${TMPDIR:-/tmp}/astrasync-agent-${port}" '${url}'`;
8549
+ }
8550
+ function browserCommandLines(platform2, port, url, certSpki, wsl = isWsl(process.env, platform2)) {
8551
+ if (wsl) {
8552
+ return [
8553
+ " In PowerShell (Windows):",
8554
+ ` ${browserCommand("win32", port, url, certSpki)}`,
8555
+ " Or inside WSL (with Chromium installed):",
8556
+ ` ${browserCommand("linux", port, url, certSpki)}`,
8557
+ ` ${CERT_FLAG_NOTE}`
8558
+ ];
8559
+ }
8560
+ return [` ${browserCommand(platform2, port, url, certSpki)}`, ` ${CERT_FLAG_NOTE}`];
8561
+ }
8562
+ function openThroughProxy(opts) {
8563
+ const log = opts.log ?? ((l) => console.log(l));
8564
+ const platform2 = opts.platform ?? process.platform;
8565
+ const env = opts.env ?? process.env;
8566
+ const wsl = opts.wsl ?? isWsl(env, platform2);
8567
+ const found = opts.find ? opts.find() : findChromium(env, platform2, void 0, wsl);
8568
+ if (!found) {
8569
+ log("No Chrome/Edge/Chromium found. Start one through the proxy with:");
8570
+ for (const l of browserCommandLines(platform2, opts.port, opts.url, opts.certSpki, wsl)) log(l);
8571
+ return false;
8572
+ }
8573
+ (opts.launch ?? launchBrowser)(
8574
+ found,
8575
+ opts.port,
8576
+ opts.url,
8577
+ opts.certSpki,
8578
+ profileDirFor(opts.port),
8579
+ (err) => log(`Couldn't start ${browserName(found)}: ${err.message}`)
8580
+ );
8581
+ log(`Opened ${browserName(found)} through the proxy.`);
8582
+ log(CERT_FLAG_NOTE);
8583
+ if (wsl && isWindowsExeFromWsl(found)) {
8584
+ log(
8585
+ "If the Windows browser can't reach the proxy, install Chromium inside WSL (e.g. npx playwright install chromium)."
8586
+ );
8587
+ }
8588
+ return true;
8589
+ }
8590
+ var import_node_child_process, import_node_fs, import_node_os, import_node_path, MAC_PATHS, LINUX_NAMES, WSL_WINDOWS_PATHS, CERT_FLAG_NOTE;
8591
+ var init_open_browser = __esm({
8592
+ "src/fleet/open-browser.ts"() {
8593
+ "use strict";
8594
+ import_node_child_process = require("child_process");
8595
+ import_node_fs = __toESM(require("fs"));
8596
+ import_node_os = __toESM(require("os"));
8597
+ import_node_path = __toESM(require("path"));
8598
+ MAC_PATHS = [
8599
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
8600
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
8601
+ "/Applications/Chromium.app/Contents/MacOS/Chromium"
8602
+ ];
8603
+ LINUX_NAMES = [
8604
+ "google-chrome",
8605
+ "google-chrome-stable",
8606
+ "chromium",
8607
+ "chromium-browser",
8608
+ "microsoft-edge"
8609
+ ];
8610
+ WSL_WINDOWS_PATHS = [
8611
+ "/mnt/c/Program Files/Google/Chrome/Application/chrome.exe",
8612
+ "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe",
8613
+ "/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe",
8614
+ "/mnt/c/Program Files/Microsoft/Edge/Application/msedge.exe"
8615
+ ];
8616
+ 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.";
8617
+ }
8618
+ });
8619
+
8454
8620
  // src/fleet/browse-proxy.ts
8455
8621
  function cleanHeaders(h) {
8456
8622
  const out = {};
8457
8623
  for (const [k, v] of Object.entries(h)) if (!HOP_BY_HOP.has(k) && v !== void 0) out[k] = v;
8458
8624
  return out;
8459
8625
  }
8626
+ function helpPage(port, certSpki) {
8627
+ const proxy = `http://127.0.0.1:${port}`;
8628
+ return `<!doctype html>
8629
+ <html lang="en"><head><meta charset="utf-8"><title>AstraSync device proxy</title>
8630
+ <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>
8631
+ </head><body>
8632
+ <h1>AstraSync device proxy</h1>
8633
+ <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>
8634
+ <h2>Open the sign-in link</h2>
8635
+ <ul>
8636
+ <li>Run <code>astrasync guard browse --open</code> &mdash; it opens a browser that goes through this proxy.</li>
8637
+ <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>
8638
+ </ul>
8639
+ <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>
8640
+ <p>Keep <code>astrasync guard browse</code> running while you use the session.</p>
8641
+ </body></html>
8642
+ `;
8643
+ }
8460
8644
  async function startBrowseProxy(opts) {
8461
8645
  const hosts = new Set(opts.hosts.map((h) => h.toLowerCase()));
8462
8646
  const deviceHeader = `Device ${opts.deviceCredential}`;
8463
- const { cert, key } = createLocalCert([...hosts]);
8647
+ const { cert, key, spkiSha256 } = createLocalCert([...hosts]);
8648
+ const sockets = /* @__PURE__ */ new Set();
8649
+ const track = (s) => {
8650
+ sockets.add(s);
8651
+ s.once("close", () => sockets.delete(s));
8652
+ };
8464
8653
  function forward(req, res, target) {
8465
8654
  const headers = cleanHeaders(req.headers);
8466
8655
  headers["x-astra-device"] = deviceHeader;
@@ -8512,6 +8701,7 @@ async function startBrowseProxy(opts) {
8512
8701
  },
8513
8702
  onConnect
8514
8703
  ) : import_node_net.default.connect(target.port, target.host, onConnect);
8704
+ track(upstream);
8515
8705
  upstream.on("error", () => socket.destroy());
8516
8706
  socket.on("error", () => upstream.destroy());
8517
8707
  }
@@ -8543,6 +8733,16 @@ async function startBrowseProxy(opts) {
8543
8733
  });
8544
8734
  });
8545
8735
  const server = import_node_http.default.createServer((req, res) => {
8736
+ if ((req.url ?? "").startsWith("/")) {
8737
+ if (req.url === "/favicon.ico") {
8738
+ res.writeHead(404).end();
8739
+ return;
8740
+ }
8741
+ const listening = server.address()?.port ?? 0;
8742
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
8743
+ res.end(helpPage(listening, spkiSha256));
8744
+ return;
8745
+ }
8546
8746
  let url;
8547
8747
  try {
8548
8748
  url = new URL(req.url ?? "");
@@ -8635,18 +8835,22 @@ async function startBrowseProxy(opts) {
8635
8835
  upstream.pipe(socket);
8636
8836
  socket.pipe(upstream);
8637
8837
  });
8838
+ track(upstream);
8638
8839
  upstream.on("error", () => socket.destroy());
8639
8840
  socket.on("error", () => upstream.destroy());
8640
8841
  });
8842
+ server.on("connection", track);
8641
8843
  await new Promise((resolve) => server.listen(opts.port ?? 0, "127.0.0.1", resolve));
8642
8844
  const port = server.address().port;
8643
8845
  return {
8644
8846
  port,
8847
+ certSpki: spkiSha256,
8645
8848
  close: () => new Promise((resolve) => {
8646
8849
  inner.close();
8647
8850
  plainInner.close();
8648
8851
  server.close(() => resolve());
8649
8852
  server.closeAllConnections?.();
8853
+ for (const s of sockets) s.destroy();
8650
8854
  })
8651
8855
  };
8652
8856
  }
@@ -8659,6 +8863,7 @@ var init_browse_proxy = __esm({
8659
8863
  import_node_net = __toESM(require("net"));
8660
8864
  import_node_tls = __toESM(require("tls"));
8661
8865
  init_local_cert();
8866
+ init_open_browser();
8662
8867
  HOP_BY_HOP = /* @__PURE__ */ new Set([
8663
8868
  "connection",
8664
8869
  "proxy-connection",
@@ -8754,6 +8959,18 @@ async function runBrowse(paths, opts) {
8754
8959
  log(
8755
8960
  `Device proxy: http://127.0.0.1:${proxy.port} (adds this machine's credential for ${hosts.join(", ")})`
8756
8961
  );
8962
+ if (!opts.open) {
8963
+ log("Open it in a browser through the proxy:");
8964
+ for (const l of browserCommandLines(
8965
+ opts.platform ?? process.platform,
8966
+ proxy.port,
8967
+ openUrl,
8968
+ proxy.certSpki,
8969
+ opts.wsl
8970
+ ))
8971
+ log(l);
8972
+ log(" (or re-run with --open)");
8973
+ }
8757
8974
  log("Playwright:");
8758
8975
  log(
8759
8976
  ` chromium.launch({ proxy: { server: 'http://127.0.0.1:${proxy.port}' } }) + newContext({ ignoreHTTPSErrors: true })`
@@ -8770,6 +8987,7 @@ var init_browse = __esm({
8770
8987
  "use strict";
8771
8988
  init_enrolment_store();
8772
8989
  init_browse_proxy();
8990
+ init_open_browser();
8773
8991
  }
8774
8992
  });
8775
8993
 
@@ -8799,7 +9017,7 @@ async function cmdSetup() {
8799
9017
  }
8800
9018
  async function cmdUpgrade() {
8801
9019
  const policyPath = process.argv[4] || DEFAULT_POLICY_PATH;
8802
- if (!fs.existsSync(policyPath)) {
9020
+ if (!fs2.existsSync(policyPath)) {
8803
9021
  console.error(`No policy file found at ${policyPath}`);
8804
9022
  console.error('Run "astrasync guard setup" first to create a policy.');
8805
9023
  process.exit(1);
@@ -8827,13 +9045,13 @@ async function cmdUpgrade() {
8827
9045
  prompt: (message) => prompt.question(message),
8828
9046
  print: (message) => console.log(message),
8829
9047
  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");
9048
+ const dir = path2.dirname(filePath);
9049
+ if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
9050
+ fs2.writeFileSync(filePath, content, "utf-8");
8833
9051
  },
8834
9052
  readConfig: async (filePath) => {
8835
9053
  try {
8836
- return fs.readFileSync(filePath, "utf-8");
9054
+ return fs2.readFileSync(filePath, "utf-8");
8837
9055
  } catch {
8838
9056
  return null;
8839
9057
  }
@@ -8854,13 +9072,13 @@ async function cmdStart() {
8854
9072
  let gatewayConfig;
8855
9073
  const upgraded = await isUpgraded(DEFAULT_CONFIG_DIR, async (p) => {
8856
9074
  try {
8857
- return fs.readFileSync(p, "utf-8");
9075
+ return fs2.readFileSync(p, "utf-8");
8858
9076
  } catch {
8859
9077
  return null;
8860
9078
  }
8861
9079
  });
8862
9080
  if (upgraded) {
8863
- const config = JSON.parse(fs.readFileSync(`${DEFAULT_CONFIG_DIR}/config`, "utf-8"));
9081
+ const config = JSON.parse(fs2.readFileSync(`${DEFAULT_CONFIG_DIR}/config`, "utf-8"));
8864
9082
  gatewayConfig = {
8865
9083
  mode: config.mode,
8866
9084
  apiBaseUrl: config.platformUrl ? `${config.platformUrl}/api/v1` : void 0,
@@ -8868,7 +9086,7 @@ async function cmdStart() {
8868
9086
  };
8869
9087
  console.log(`[AstraSync] Starting in ${config.mode} mode (agent: ${config.agentId})`);
8870
9088
  } else {
8871
- if (!fs.existsSync(policyPath)) {
9089
+ if (!fs2.existsSync(policyPath)) {
8872
9090
  console.error(`No policy file found at ${policyPath}`);
8873
9091
  console.error('Run "astrasync guard setup" first.');
8874
9092
  process.exit(1);
@@ -8905,7 +9123,7 @@ async function cmdStatus() {
8905
9123
  const policyPath = process.argv[4] || DEFAULT_POLICY_PATH;
8906
9124
  const upgraded = await isUpgraded(DEFAULT_CONFIG_DIR, async (p) => {
8907
9125
  try {
8908
- return fs.readFileSync(p, "utf-8");
9126
+ return fs2.readFileSync(p, "utf-8");
8909
9127
  } catch {
8910
9128
  return null;
8911
9129
  }
@@ -8915,7 +9133,7 @@ async function cmdStatus() {
8915
9133
  console.log(` ${line}`);
8916
9134
  }
8917
9135
  if (upgraded) {
8918
- const config = JSON.parse(fs.readFileSync(`${DEFAULT_CONFIG_DIR}/config`, "utf-8"));
9136
+ const config = JSON.parse(fs2.readFileSync(`${DEFAULT_CONFIG_DIR}/config`, "utf-8"));
8919
9137
  console.log(` Mode: ${config.mode}`);
8920
9138
  console.log(` Agent: ${config.agentId}`);
8921
9139
  console.log(` Platform: ${config.platformUrl}`);
@@ -8923,7 +9141,7 @@ async function cmdStatus() {
8923
9141
  } else {
8924
9142
  console.log(" Mode: local (free tier)");
8925
9143
  }
8926
- if (fs.existsSync(policyPath)) {
9144
+ if (fs2.existsSync(policyPath)) {
8927
9145
  const parsed = parseFile(policyPath);
8928
9146
  if (parsed.validation.valid && parsed.policy) {
8929
9147
  const p = parsed.policy;
@@ -8948,7 +9166,7 @@ async function cmdCheck() {
8948
9166
  (a) => !a.startsWith("--") && a !== "guard" && a !== "check" && a !== process.argv[0] && a !== process.argv[1]
8949
9167
  ) || DEFAULT_POLICY_PATH;
8950
9168
  const trigger = process.argv.find((a) => a.startsWith("--trigger="))?.split("=")[1] || "pre-push";
8951
- if (!fs.existsSync(policyPath)) {
9169
+ if (!fs2.existsSync(policyPath)) {
8952
9170
  console.log("[AstraSync] No policy file \u2014 skipping check");
8953
9171
  process.exit(0);
8954
9172
  }
@@ -8960,9 +9178,9 @@ async function cmdCheck() {
8960
9178
  }
8961
9179
  let enforcement = "warn";
8962
9180
  const configPath = `${DEFAULT_CONFIG_DIR}/config`;
8963
- if (fs.existsSync(configPath)) {
9181
+ if (fs2.existsSync(configPath)) {
8964
9182
  try {
8965
- const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
9183
+ const config = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
8966
9184
  enforcement = config.gitTriggerEnforcement || "warn";
8967
9185
  } catch {
8968
9186
  }
@@ -9025,8 +9243,8 @@ async function cmdUninstallHooks() {
9025
9243
  }
9026
9244
  }
9027
9245
  function resolveClaudeHookCommand() {
9028
- const sibling = path.join(__dirname, "astrasync-claude-hook.js");
9029
- if (fs.existsSync(sibling)) {
9246
+ const sibling = path2.join(__dirname, "astrasync-claude-hook.js");
9247
+ if (fs2.existsSync(sibling)) {
9030
9248
  return `"${process.execPath}" "${sibling}"`;
9031
9249
  }
9032
9250
  return "astrasync-claude-hook";
@@ -9042,19 +9260,19 @@ function parseClaudeCodeFlags() {
9042
9260
  };
9043
9261
  }
9044
9262
  function claudeCodePaths(project) {
9045
- const root = project ? process.cwd() : os.homedir();
9263
+ const root = project ? process.cwd() : os2.homedir();
9046
9264
  return {
9047
- settingsPath: path.join(root, ".claude", "settings.json"),
9048
- astrasyncDir: path.join(root, ".astrasync")
9265
+ settingsPath: path2.join(root, ".claude", "settings.json"),
9266
+ astrasyncDir: path2.join(root, ".astrasync")
9049
9267
  };
9050
9268
  }
9051
9269
  async function cmdInstallClaudeCode() {
9052
9270
  const flags = parseClaudeCodeFlags();
9053
9271
  const { settingsPath, astrasyncDir } = claudeCodePaths(flags.project);
9054
- const policyPath = flags.policyOverride || path.join(astrasyncDir, "policy.yaml");
9055
- const guardJsonPath = path.join(astrasyncDir, "guard.json");
9272
+ const policyPath = flags.policyOverride || path2.join(astrasyncDir, "policy.yaml");
9273
+ const guardJsonPath = path2.join(astrasyncDir, "guard.json");
9056
9274
  console.log("\nAstraSync Local Guard for Claude Code \u2014 install\n");
9057
- if (fs.existsSync(policyPath)) {
9275
+ if (fs2.existsSync(policyPath)) {
9058
9276
  console.log(` Policy: using existing ${policyPath}`);
9059
9277
  if (ensureGuardSelfProtection(policyPath)) {
9060
9278
  console.log(" added guard self-protection patterns to file.write");
@@ -9062,7 +9280,7 @@ async function cmdInstallClaudeCode() {
9062
9280
  } else if (flags.wizard) {
9063
9281
  const prompt = createPrompt();
9064
9282
  try {
9065
- fs.mkdirSync(path.dirname(policyPath), { recursive: true });
9283
+ fs2.mkdirSync(path2.dirname(policyPath), { recursive: true });
9066
9284
  const wizard = new SetupWizard(prompt);
9067
9285
  await wizard.runAndSave(policyPath);
9068
9286
  } finally {
@@ -9071,17 +9289,17 @@ async function cmdInstallClaudeCode() {
9071
9289
  ensureGuardSelfProtection(policyPath);
9072
9290
  console.log(` Policy: created via wizard at ${policyPath}`);
9073
9291
  } else {
9074
- fs.mkdirSync(path.dirname(policyPath), { recursive: true });
9075
- fs.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
9292
+ fs2.mkdirSync(path2.dirname(policyPath), { recursive: true });
9293
+ fs2.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
9076
9294
  console.log(` Policy: default coding-agent policy written to ${policyPath}`);
9077
9295
  console.log(" (re-run with --wizard for the interactive setup instead)");
9078
9296
  }
9079
- if (!fs.existsSync(guardJsonPath)) {
9297
+ if (!fs2.existsSync(guardJsonPath)) {
9080
9298
  const guardSettings = {
9081
9299
  posture: flags.active ? "active" : "passive",
9082
9300
  governMcp: false
9083
9301
  };
9084
- fs.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
9302
+ fs2.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
9085
9303
  `, "utf-8");
9086
9304
  console.log(
9087
9305
  ` Guard: ${guardJsonPath} (posture: ${guardSettings.posture}, governMcp: false)`
@@ -9111,7 +9329,7 @@ async function cmdInstallClaudeCode() {
9111
9329
  }
9112
9330
  console.log("\n Next steps:");
9113
9331
  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`);
9332
+ console.log(` 2. Watch decisions: tail -f ${path2.join(astrasyncDir, "traces")}/*.jsonl`);
9115
9333
  console.log(` 3. When the traces look right, set "posture": "active" in ${guardJsonPath}`);
9116
9334
  console.log(" to enforce (denies block, approvals use Claude Code's own prompt).");
9117
9335
  console.log(' 4. To govern MCP tools too, set "governMcp": true and add');
@@ -9141,26 +9359,26 @@ async function cmdUninstallClaudeCode() {
9141
9359
  );
9142
9360
  }
9143
9361
  function resolveCodexHookCommand() {
9144
- const sibling = path.join(__dirname, "astrasync-codex-hook.js");
9145
- if (fs.existsSync(sibling)) {
9362
+ const sibling = path2.join(__dirname, "astrasync-codex-hook.js");
9363
+ if (fs2.existsSync(sibling)) {
9146
9364
  return `"${process.execPath}" "${sibling}"`;
9147
9365
  }
9148
9366
  return "astrasync-codex-hook";
9149
9367
  }
9150
9368
  function codexPaths(project) {
9151
- const root = project ? process.cwd() : os.homedir();
9369
+ const root = project ? process.cwd() : os2.homedir();
9152
9370
  return {
9153
- hooksPath: path.join(root, ".codex", "hooks.json"),
9154
- astrasyncDir: path.join(root, ".astrasync")
9371
+ hooksPath: path2.join(root, ".codex", "hooks.json"),
9372
+ astrasyncDir: path2.join(root, ".astrasync")
9155
9373
  };
9156
9374
  }
9157
9375
  async function cmdInstallCodex() {
9158
9376
  const flags = parseClaudeCodeFlags();
9159
9377
  const { hooksPath, astrasyncDir } = codexPaths(flags.project);
9160
- const policyPath = flags.policyOverride || path.join(astrasyncDir, "policy.yaml");
9161
- const guardJsonPath = path.join(astrasyncDir, "guard.json");
9378
+ const policyPath = flags.policyOverride || path2.join(astrasyncDir, "policy.yaml");
9379
+ const guardJsonPath = path2.join(astrasyncDir, "guard.json");
9162
9380
  console.log("\nAstraSync Local Guard for Codex CLI \u2014 install\n");
9163
- if (fs.existsSync(policyPath)) {
9381
+ if (fs2.existsSync(policyPath)) {
9164
9382
  console.log(` Policy: using existing ${policyPath} (shared across adapters)`);
9165
9383
  if (ensureGuardSelfProtection(policyPath)) {
9166
9384
  console.log(" added guard self-protection patterns to file.write");
@@ -9168,7 +9386,7 @@ async function cmdInstallCodex() {
9168
9386
  } else if (flags.wizard) {
9169
9387
  const prompt = createPrompt();
9170
9388
  try {
9171
- fs.mkdirSync(path.dirname(policyPath), { recursive: true });
9389
+ fs2.mkdirSync(path2.dirname(policyPath), { recursive: true });
9172
9390
  const wizard = new SetupWizard(prompt);
9173
9391
  await wizard.runAndSave(policyPath);
9174
9392
  } finally {
@@ -9177,17 +9395,17 @@ async function cmdInstallCodex() {
9177
9395
  ensureGuardSelfProtection(policyPath);
9178
9396
  console.log(` Policy: created via wizard at ${policyPath}`);
9179
9397
  } else {
9180
- fs.mkdirSync(path.dirname(policyPath), { recursive: true });
9181
- fs.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
9398
+ fs2.mkdirSync(path2.dirname(policyPath), { recursive: true });
9399
+ fs2.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
9182
9400
  console.log(` Policy: default coding-agent policy written to ${policyPath}`);
9183
9401
  console.log(" (re-run with --wizard for the interactive setup instead)");
9184
9402
  }
9185
- if (!fs.existsSync(guardJsonPath)) {
9403
+ if (!fs2.existsSync(guardJsonPath)) {
9186
9404
  const guardSettings = {
9187
9405
  posture: flags.active ? "active" : "passive",
9188
9406
  governMcp: false
9189
9407
  };
9190
- fs.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
9408
+ fs2.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
9191
9409
  `, "utf-8");
9192
9410
  console.log(
9193
9411
  ` Guard: ${guardJsonPath} (posture: ${guardSettings.posture}, governMcp: false)`
@@ -9206,7 +9424,7 @@ async function cmdInstallCodex() {
9206
9424
  }
9207
9425
  console.log("\n Next steps:");
9208
9426
  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`);
9427
+ console.log(` 2. Watch decisions: tail -f ${path2.join(astrasyncDir, "traces")}/*.jsonl`);
9210
9428
  console.log(` 3. When the traces look right, set "posture": "active" in ${guardJsonPath}.`);
9211
9429
  console.log(" Denies block; approvals fall through to Codex's own prompt \u2014");
9212
9430
  console.log(" except in dontAsk/bypassPermissions modes, where they deny.");
@@ -9231,13 +9449,13 @@ function printCodexStatus() {
9231
9449
  const { hooksPath, astrasyncDir } = codexPaths(scope === "project");
9232
9450
  let hookInstalled = false;
9233
9451
  try {
9234
- const content = fs.readFileSync(hooksPath, "utf-8");
9452
+ const content = fs2.readFileSync(hooksPath, "utf-8");
9235
9453
  hookInstalled = content.includes(ASTRASYNC_CODEX_HOOK_MARKER);
9236
9454
  } catch {
9237
9455
  }
9238
9456
  if (hookInstalled) {
9239
- const policyPath = path.join(astrasyncDir, "policy.yaml");
9240
- const policy = fs.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
9457
+ const policyPath = path2.join(astrasyncDir, "policy.yaml");
9458
+ const policy = fs2.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
9241
9459
  console.log(` Codex CLI (${scope}): hook installed, ${policy}`);
9242
9460
  }
9243
9461
  }
@@ -9247,24 +9465,24 @@ function printClaudeCodeStatus() {
9247
9465
  const { settingsPath, astrasyncDir } = claudeCodePaths(scope === "project");
9248
9466
  let hookInstalled = false;
9249
9467
  try {
9250
- const content = fs.readFileSync(settingsPath, "utf-8");
9468
+ const content = fs2.readFileSync(settingsPath, "utf-8");
9251
9469
  hookInstalled = content.includes(ASTRASYNC_HOOK_MARKER);
9252
9470
  } catch {
9253
9471
  }
9254
- const policyPath = path.join(astrasyncDir, "policy.yaml");
9255
- const guardJsonPath = path.join(astrasyncDir, "guard.json");
9472
+ const policyPath = path2.join(astrasyncDir, "policy.yaml");
9473
+ const guardJsonPath = path2.join(astrasyncDir, "guard.json");
9256
9474
  let posture = "";
9257
- if (fs.existsSync(guardJsonPath)) {
9475
+ if (fs2.existsSync(guardJsonPath)) {
9258
9476
  try {
9259
- const settings = JSON.parse(fs.readFileSync(guardJsonPath, "utf-8"));
9477
+ const settings = JSON.parse(fs2.readFileSync(guardJsonPath, "utf-8"));
9260
9478
  posture = settings.posture ? `, posture: ${settings.posture}` : "";
9261
9479
  } catch {
9262
9480
  posture = ", guard.json unreadable";
9263
9481
  }
9264
9482
  }
9265
- if (hookInstalled || fs.existsSync(policyPath)) {
9483
+ if (hookInstalled || fs2.existsSync(policyPath)) {
9266
9484
  const hook = hookInstalled ? "hook installed" : "hook NOT installed";
9267
- const policy = fs.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
9485
+ const policy = fs2.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
9268
9486
  console.log(` Claude Code (${scope}): ${hook}, ${policy}${posture}`);
9269
9487
  }
9270
9488
  }
@@ -9310,14 +9528,21 @@ async function cmdFleetStatus() {
9310
9528
  async function cmdBrowse() {
9311
9529
  try {
9312
9530
  const port = flagValue("--port");
9313
- const { proxy } = await runBrowse(fleetPaths(), {
9531
+ const open = process.argv.includes("--open");
9532
+ const { proxy, openUrl } = await runBrowse(fleetPaths(), {
9314
9533
  checkpoint: flagValue("--checkpoint") ?? "",
9315
9534
  slug: flagValue("--slug") ?? "",
9316
9535
  upstream: flagValue("--upstream"),
9317
9536
  port: port ? Number(port) : void 0,
9318
- extraHosts: flagValue("--host") ? [flagValue("--host")] : []
9537
+ extraHosts: flagValue("--host") ? [flagValue("--host")] : [],
9538
+ open
9319
9539
  });
9540
+ if (open) openThroughProxy({ port: proxy.port, url: openUrl, certSpki: proxy.certSpki });
9541
+ let stopping = false;
9320
9542
  const stop = async () => {
9543
+ if (stopping) process.exit(0);
9544
+ stopping = true;
9545
+ setTimeout(() => process.exit(0), 2e3).unref();
9321
9546
  await proxy.close();
9322
9547
  process.exit(0);
9323
9548
  };
@@ -9352,7 +9577,7 @@ async function cmdGitRoute() {
9352
9577
  }
9353
9578
  async function cmdGitCredential() {
9354
9579
  const action = process.argv[process.argv.indexOf("git-credential") + 1] ?? "";
9355
- const input = fs.readFileSync(0, "utf-8");
9580
+ const input = fs2.readFileSync(0, "utf-8");
9356
9581
  process.stdout.write(runGitCredential(action, input));
9357
9582
  }
9358
9583
  async function cmdManagedSettings() {
@@ -9456,8 +9681,9 @@ async function main() {
9456
9681
  " browse Sign an agent in to your dashboard from this machine and"
9457
9682
  );
9458
9683
  console.log(
9459
- " run its device proxy (--checkpoint <url> --slug <agent>)"
9684
+ " run its device proxy (--checkpoint <url> --slug <agent>;"
9460
9685
  );
9686
+ console.log(" --open to start a browser through the proxy)");
9461
9687
  console.log(" enroll <token> Enrol this machine in your organisation fleet");
9462
9688
  console.log(" (--api-base-url <url>, --system for machine scope)");
9463
9689
  console.log(" fleet-status Show fleet enrolment, signed policy and heartbeat");
@@ -9471,13 +9697,13 @@ async function main() {
9471
9697
  process.exit(command ? 1 : 0);
9472
9698
  }
9473
9699
  }
9474
- var fs, os, path, readline, DEFAULT_POLICY_PATH, DEFAULT_CONFIG_DIR;
9700
+ var fs2, os2, path2, readline, DEFAULT_POLICY_PATH, DEFAULT_CONFIG_DIR;
9475
9701
  var init_guard = __esm({
9476
9702
  "src/cli/guard.ts"() {
9477
9703
  "use strict";
9478
- fs = __toESM(require("fs"));
9479
- os = __toESM(require("os"));
9480
- path = __toESM(require("path"));
9704
+ fs2 = __toESM(require("fs"));
9705
+ os2 = __toESM(require("os"));
9706
+ path2 = __toESM(require("path"));
9481
9707
  readline = __toESM(require("readline"));
9482
9708
  init_wizard();
9483
9709
  init_upgrade();
@@ -9491,6 +9717,7 @@ var init_guard = __esm({
9491
9717
  init_enrolment_store();
9492
9718
  init_git_route();
9493
9719
  init_browse();
9720
+ init_open_browser();
9494
9721
  DEFAULT_POLICY_PATH = "./local-policy.yaml";
9495
9722
  DEFAULT_CONFIG_DIR = ".astrasync";
9496
9723
  main().catch((err) => {
@@ -9527,6 +9754,7 @@ COMMANDS:
9527
9754
  (--api-base-url <url>, --system for machine scope)
9528
9755
  guard fleet-status Fleet enrolment, signed policy and heartbeat state
9529
9756
  guard browse Sign an agent in to your dashboard via this machine's device proxy
9757
+ (--checkpoint <url> --slug <agent>; --open starts a browser through it)
9530
9758
  guard managed-settings MDM templates for Claude Code / Codex (--claude, --codex, --out <dir>)
9531
9759
 
9532
9760
  REGISTER OPTIONS: