@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
@@ -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.18.1";
3717
3717
 
3718
3718
  // src/http.ts
3719
3719
  var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
@@ -6574,29 +6574,29 @@ function loadEnrolment(paths) {
6574
6574
  [paths.machineDevice, "machine"],
6575
6575
  [paths.userDevice, "user"]
6576
6576
  ];
6577
- for (const [path2, scope] of candidates) {
6578
- if (!(0, import_fs6.existsSync)(path2)) continue;
6577
+ for (const [path3, scope] of candidates) {
6578
+ if (!(0, import_fs6.existsSync)(path3)) continue;
6579
6579
  try {
6580
- const parsed = JSON.parse((0, import_fs6.readFileSync)(path2, "utf-8"));
6580
+ const parsed = JSON.parse((0, import_fs6.readFileSync)(path3, "utf-8"));
6581
6581
  if (isEnrolment(parsed)) return { enrolment: parsed, scope };
6582
6582
  } catch {
6583
6583
  }
6584
- return { corrupt: true, path: path2 };
6584
+ return { corrupt: true, path: path3 };
6585
6585
  }
6586
6586
  return null;
6587
6587
  }
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 });
6588
+ function writeFileMode(path3, content, mode) {
6589
+ (0, import_fs6.mkdirSync)((0, import_path5.dirname)(path3), { recursive: true, mode: 448 });
6590
+ (0, import_fs6.writeFileSync)(path3, content, { encoding: "utf-8", mode });
6591
6591
  try {
6592
- (0, import_fs6.chmodSync)(path2, mode);
6592
+ (0, import_fs6.chmodSync)(path3, mode);
6593
6593
  } catch {
6594
6594
  }
6595
6595
  }
6596
6596
  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;
6597
+ const path3 = scope === "machine" ? paths.machineDevice : paths.userDevice;
6598
+ writeFileMode(path3, JSON.stringify(enrolment, null, 2), scope === "machine" ? 420 : 384);
6599
+ return path3;
6600
6600
  }
6601
6601
  function readCachedPolicies(paths) {
6602
6602
  const out = [];
@@ -6918,12 +6918,12 @@ var MANAGED_SETTINGS_LOCATIONS = {
6918
6918
  win32: "%ProgramData%\\OpenAI\\Codex\\requirements.toml"
6919
6919
  }
6920
6920
  };
6921
- function managedHookCommand(target, os2) {
6921
+ function managedHookCommand(target, os3) {
6922
6922
  const bin = target === "claude" ? "astrasync-claude-hook" : "astrasync-codex-hook";
6923
- if (os2 === "win32") return `"C:\\Program Files\\nodejs\\${bin}.cmd"`;
6923
+ if (os3 === "win32") return `"C:\\Program Files\\nodejs\\${bin}.cmd"`;
6924
6924
  return `/usr/local/bin/${bin}`;
6925
6925
  }
6926
- function buildClaudeManagedSettings(os2) {
6926
+ function buildClaudeManagedSettings(os3) {
6927
6927
  return {
6928
6928
  allowManagedHooksOnly: true,
6929
6929
  env: { ASTRASYNC_FLEET_REQUIRED: "1" },
@@ -6931,7 +6931,7 @@ function buildClaudeManagedSettings(os2) {
6931
6931
  PreToolUse: [
6932
6932
  {
6933
6933
  matcher: HOOK_MATCHER,
6934
- hooks: [{ type: "command", command: managedHookCommand("claude", os2), timeout: 10 }]
6934
+ hooks: [{ type: "command", command: managedHookCommand("claude", os3), timeout: 10 }]
6935
6935
  }
6936
6936
  ]
6937
6937
  },
@@ -6941,11 +6941,11 @@ function buildClaudeManagedSettings(os2) {
6941
6941
  function tomlString(value) {
6942
6942
  return JSON.stringify(value);
6943
6943
  }
6944
- function buildCodexRequirementsToml(os2) {
6945
- const command = os2 === "win32" ? `set ASTRASYNC_FLEET_REQUIRED=1&& ${managedHookCommand("codex", os2)}` : `ASTRASYNC_FLEET_REQUIRED=1 ${managedHookCommand("codex", os2)}`;
6944
+ function buildCodexRequirementsToml(os3) {
6945
+ const command = os3 === "win32" ? `set ASTRASYNC_FLEET_REQUIRED=1&& ${managedHookCommand("codex", os3)}` : `ASTRASYNC_FLEET_REQUIRED=1 ${managedHookCommand("codex", os3)}`;
6946
6946
  return [
6947
6947
  "# AstraSync Local Guard \u2014 managed hook (generated by `astrasync guard managed-settings --codex`)",
6948
- `# Install at: ${MANAGED_SETTINGS_LOCATIONS.codex[os2]}`,
6948
+ `# Install at: ${MANAGED_SETTINGS_LOCATIONS.codex[os3]}`,
6949
6949
  "allow_managed_hooks_only = true",
6950
6950
  "",
6951
6951
  "[features]",
@@ -6965,14 +6965,14 @@ function buildCodexRequirementsToml(os2) {
6965
6965
  function buildManagedSettingsFiles(targets) {
6966
6966
  const out = [];
6967
6967
  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);
6968
+ for (const os3 of ["darwin", "linux", "win32"]) {
6969
+ const content = target === "claude" ? `${JSON.stringify(buildClaudeManagedSettings(os3), null, 2)}
6970
+ ` : buildCodexRequirementsToml(os3);
6971
6971
  out.push({
6972
6972
  target,
6973
- os: os2,
6974
- installPath: MANAGED_SETTINGS_LOCATIONS[target][os2],
6975
- fileName: `${target}-${os2}-${target === "claude" ? "managed-settings.json" : "requirements.toml"}`,
6973
+ os: os3,
6974
+ installPath: MANAGED_SETTINGS_LOCATIONS[target][os3],
6975
+ fileName: `${target}-${os3}-${target === "claude" ? "managed-settings.json" : "requirements.toml"}`,
6976
6976
  content
6977
6977
  });
6978
6978
  }
@@ -7151,10 +7151,170 @@ function createLocalCert(hosts, days = 30) {
7151
7151
  ${b64}
7152
7152
  -----END CERTIFICATE-----
7153
7153
  `,
7154
- key: privateKey.export({ type: "pkcs8", format: "pem" }).toString()
7154
+ key: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
7155
+ spkiSha256: import_node_crypto5.default.createHash("sha256").update(spki).digest("base64")
7155
7156
  };
7156
7157
  }
7157
7158
 
7159
+ // src/fleet/open-browser.ts
7160
+ var import_node_child_process = require("child_process");
7161
+ var import_node_fs = __toESM(require("fs"));
7162
+ var import_node_os = __toESM(require("os"));
7163
+ var import_node_path = __toESM(require("path"));
7164
+ function isWsl(env = process.env, platform2 = process.platform, readVersion = () => import_node_fs.default.readFileSync("/proc/version", "utf8")) {
7165
+ if (platform2 !== "linux") return false;
7166
+ if (env.WSL_DISTRO_NAME) return true;
7167
+ try {
7168
+ return /microsoft/i.test(readVersion());
7169
+ } catch {
7170
+ return false;
7171
+ }
7172
+ }
7173
+ var MAC_PATHS = [
7174
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
7175
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
7176
+ "/Applications/Chromium.app/Contents/MacOS/Chromium"
7177
+ ];
7178
+ var LINUX_NAMES = [
7179
+ "google-chrome",
7180
+ "google-chrome-stable",
7181
+ "chromium",
7182
+ "chromium-browser",
7183
+ "microsoft-edge"
7184
+ ];
7185
+ var WSL_WINDOWS_PATHS = [
7186
+ "/mnt/c/Program Files/Google/Chrome/Application/chrome.exe",
7187
+ "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe",
7188
+ "/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe",
7189
+ "/mnt/c/Program Files/Microsoft/Edge/Application/msedge.exe"
7190
+ ];
7191
+ 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)) {
7192
+ let candidates;
7193
+ if (platform2 === "darwin") {
7194
+ candidates = MAC_PATHS;
7195
+ } else if (platform2 === "win32") {
7196
+ const bases = [
7197
+ env.ProgramFiles,
7198
+ env["ProgramFiles(x86)"],
7199
+ env.LOCALAPPDATA ?? env.LocalAppData
7200
+ ].filter((b) => !!b);
7201
+ candidates = [
7202
+ ...bases.map((b) => import_node_path.default.win32.join(b, "Google", "Chrome", "Application", "chrome.exe")),
7203
+ ...bases.map((b) => import_node_path.default.win32.join(b, "Microsoft", "Edge", "Application", "msedge.exe"))
7204
+ ];
7205
+ } else {
7206
+ const dirs = (env.PATH ?? "").split(":").filter(Boolean);
7207
+ candidates = LINUX_NAMES.flatMap((n) => dirs.map((d) => import_node_path.default.posix.join(d, n)));
7208
+ const cache2 = env.PLAYWRIGHT_BROWSERS_PATH ?? (env.HOME ? `${env.HOME}/.cache/ms-playwright` : "");
7209
+ if (cache2) {
7210
+ let builds = [];
7211
+ try {
7212
+ builds = listDir(cache2).filter((d) => /^chromium-\d+$/.test(d));
7213
+ } catch {
7214
+ }
7215
+ builds.sort((a, b) => Number(b.split("-")[1]) - Number(a.split("-")[1]));
7216
+ for (const b of builds)
7217
+ candidates.push(
7218
+ import_node_path.default.posix.join(cache2, b, "chrome-linux64", "chrome"),
7219
+ import_node_path.default.posix.join(cache2, b, "chrome-linux", "chrome")
7220
+ );
7221
+ }
7222
+ if (wsl) candidates = [...candidates, ...WSL_WINDOWS_PATHS];
7223
+ }
7224
+ return candidates.find((c) => exists(c)) ?? null;
7225
+ }
7226
+ function isWindowsExeFromWsl(browserPath) {
7227
+ return /^\/mnt\/[a-z]\//i.test(browserPath) && /\.exe$/i.test(browserPath);
7228
+ }
7229
+ function browserName(browserPath) {
7230
+ const b = browserPath.toLowerCase();
7231
+ if (b.includes("edge")) return "Edge";
7232
+ if (b.includes("chromium")) return "Chromium";
7233
+ return "Chrome";
7234
+ }
7235
+ function profileDirFor(proxyPort) {
7236
+ return import_node_path.default.join(import_node_os.default.tmpdir(), `astrasync-agent-browser-${proxyPort}`);
7237
+ }
7238
+ function windowsProfileDir(profileDir) {
7239
+ if (/^\/mnt\/[a-z]\//i.test(profileDir)) {
7240
+ try {
7241
+ return (0, import_node_child_process.execFileSync)("wslpath", ["-w", profileDir], { encoding: "utf8" }).trim();
7242
+ } catch {
7243
+ }
7244
+ }
7245
+ return `C:\\Temp\\${import_node_path.default.basename(profileDir)}`;
7246
+ }
7247
+ function certFlag(certSpki) {
7248
+ return `--ignore-certificate-errors-spki-list=${certSpki}`;
7249
+ }
7250
+ 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.";
7251
+ function launchBrowser(browserPath, proxyPort, url, certSpki, profileDir = profileDirFor(proxyPort), onError) {
7252
+ const dir = isWindowsExeFromWsl(browserPath) ? windowsProfileDir(profileDir) : profileDir;
7253
+ const child = (0, import_node_child_process.spawn)(
7254
+ browserPath,
7255
+ [
7256
+ `--proxy-server=http://127.0.0.1:${proxyPort}`,
7257
+ certFlag(certSpki),
7258
+ `--user-data-dir=${dir}`,
7259
+ "--no-first-run",
7260
+ "--new-window",
7261
+ url
7262
+ ],
7263
+ { detached: true, stdio: "ignore" }
7264
+ );
7265
+ child.on("error", (err) => onError?.(err));
7266
+ child.unref();
7267
+ return child;
7268
+ }
7269
+ function browserCommand(platform2, port, url, certSpki, wsl = false) {
7270
+ const proxy = `--proxy-server="http://127.0.0.1:${port}" ${certFlag(certSpki)}`;
7271
+ if (platform2 === "win32" || wsl) {
7272
+ return `& "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" ${proxy} --user-data-dir="$env:TEMP\\astrasync-agent-${port}" '${url}'`;
7273
+ }
7274
+ const exe = platform2 === "darwin" ? '"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"' : "google-chrome";
7275
+ return `${exe} ${proxy} --user-data-dir="\${TMPDIR:-/tmp}/astrasync-agent-${port}" '${url}'`;
7276
+ }
7277
+ function browserCommandLines(platform2, port, url, certSpki, wsl = isWsl(process.env, platform2)) {
7278
+ if (wsl) {
7279
+ return [
7280
+ " In PowerShell (Windows):",
7281
+ ` ${browserCommand("win32", port, url, certSpki)}`,
7282
+ " Or inside WSL (with Chromium installed):",
7283
+ ` ${browserCommand("linux", port, url, certSpki)}`,
7284
+ ` ${CERT_FLAG_NOTE}`
7285
+ ];
7286
+ }
7287
+ return [` ${browserCommand(platform2, port, url, certSpki)}`, ` ${CERT_FLAG_NOTE}`];
7288
+ }
7289
+ function openThroughProxy(opts) {
7290
+ const log = opts.log ?? ((l) => console.log(l));
7291
+ const platform2 = opts.platform ?? process.platform;
7292
+ const env = opts.env ?? process.env;
7293
+ const wsl = opts.wsl ?? isWsl(env, platform2);
7294
+ const found = opts.find ? opts.find() : findChromium(env, platform2, void 0, wsl);
7295
+ if (!found) {
7296
+ log("No Chrome/Edge/Chromium found. Start one through the proxy with:");
7297
+ for (const l of browserCommandLines(platform2, opts.port, opts.url, opts.certSpki, wsl)) log(l);
7298
+ return false;
7299
+ }
7300
+ (opts.launch ?? launchBrowser)(
7301
+ found,
7302
+ opts.port,
7303
+ opts.url,
7304
+ opts.certSpki,
7305
+ profileDirFor(opts.port),
7306
+ (err) => log(`Couldn't start ${browserName(found)}: ${err.message}`)
7307
+ );
7308
+ log(`Opened ${browserName(found)} through the proxy.`);
7309
+ log(CERT_FLAG_NOTE);
7310
+ if (wsl && isWindowsExeFromWsl(found)) {
7311
+ log(
7312
+ "If the Windows browser can't reach the proxy, install Chromium inside WSL (e.g. npx playwright install chromium)."
7313
+ );
7314
+ }
7315
+ return true;
7316
+ }
7317
+
7158
7318
  // src/fleet/browse-proxy.ts
7159
7319
  var HOP_BY_HOP = /* @__PURE__ */ new Set([
7160
7320
  "connection",
@@ -7171,10 +7331,33 @@ function cleanHeaders(h) {
7171
7331
  for (const [k, v] of Object.entries(h)) if (!HOP_BY_HOP.has(k) && v !== void 0) out[k] = v;
7172
7332
  return out;
7173
7333
  }
7334
+ function helpPage(port, certSpki) {
7335
+ const proxy = `http://127.0.0.1:${port}`;
7336
+ return `<!doctype html>
7337
+ <html lang="en"><head><meta charset="utf-8"><title>AstraSync device proxy</title>
7338
+ <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>
7339
+ </head><body>
7340
+ <h1>AstraSync device proxy</h1>
7341
+ <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>
7342
+ <h2>Open the sign-in link</h2>
7343
+ <ul>
7344
+ <li>Run <code>astrasync guard browse --open</code> &mdash; it opens a browser that goes through this proxy.</li>
7345
+ <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>
7346
+ </ul>
7347
+ <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>
7348
+ <p>Keep <code>astrasync guard browse</code> running while you use the session.</p>
7349
+ </body></html>
7350
+ `;
7351
+ }
7174
7352
  async function startBrowseProxy(opts) {
7175
7353
  const hosts = new Set(opts.hosts.map((h) => h.toLowerCase()));
7176
7354
  const deviceHeader = `Device ${opts.deviceCredential}`;
7177
- const { cert, key } = createLocalCert([...hosts]);
7355
+ const { cert, key, spkiSha256 } = createLocalCert([...hosts]);
7356
+ const sockets = /* @__PURE__ */ new Set();
7357
+ const track = (s) => {
7358
+ sockets.add(s);
7359
+ s.once("close", () => sockets.delete(s));
7360
+ };
7178
7361
  function forward(req, res, target) {
7179
7362
  const headers = cleanHeaders(req.headers);
7180
7363
  headers["x-astra-device"] = deviceHeader;
@@ -7226,6 +7409,7 @@ async function startBrowseProxy(opts) {
7226
7409
  },
7227
7410
  onConnect
7228
7411
  ) : import_node_net.default.connect(target.port, target.host, onConnect);
7412
+ track(upstream);
7229
7413
  upstream.on("error", () => socket.destroy());
7230
7414
  socket.on("error", () => upstream.destroy());
7231
7415
  }
@@ -7257,6 +7441,16 @@ async function startBrowseProxy(opts) {
7257
7441
  });
7258
7442
  });
7259
7443
  const server = import_node_http.default.createServer((req, res) => {
7444
+ if ((req.url ?? "").startsWith("/")) {
7445
+ if (req.url === "/favicon.ico") {
7446
+ res.writeHead(404).end();
7447
+ return;
7448
+ }
7449
+ const listening = server.address()?.port ?? 0;
7450
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
7451
+ res.end(helpPage(listening, spkiSha256));
7452
+ return;
7453
+ }
7260
7454
  let url;
7261
7455
  try {
7262
7456
  url = new URL(req.url ?? "");
@@ -7349,18 +7543,22 @@ async function startBrowseProxy(opts) {
7349
7543
  upstream.pipe(socket);
7350
7544
  socket.pipe(upstream);
7351
7545
  });
7546
+ track(upstream);
7352
7547
  upstream.on("error", () => socket.destroy());
7353
7548
  socket.on("error", () => upstream.destroy());
7354
7549
  });
7550
+ server.on("connection", track);
7355
7551
  await new Promise((resolve) => server.listen(opts.port ?? 0, "127.0.0.1", resolve));
7356
7552
  const port = server.address().port;
7357
7553
  return {
7358
7554
  port,
7555
+ certSpki: spkiSha256,
7359
7556
  close: () => new Promise((resolve) => {
7360
7557
  inner.close();
7361
7558
  plainInner.close();
7362
7559
  server.close(() => resolve());
7363
7560
  server.closeAllConnections?.();
7561
+ for (const s of sockets) s.destroy();
7364
7562
  })
7365
7563
  };
7366
7564
  }
@@ -7447,6 +7645,18 @@ async function runBrowse(paths, opts) {
7447
7645
  log(
7448
7646
  `Device proxy: http://127.0.0.1:${proxy.port} (adds this machine's credential for ${hosts.join(", ")})`
7449
7647
  );
7648
+ if (!opts.open) {
7649
+ log("Open it in a browser through the proxy:");
7650
+ for (const l of browserCommandLines(
7651
+ opts.platform ?? process.platform,
7652
+ proxy.port,
7653
+ openUrl,
7654
+ proxy.certSpki,
7655
+ opts.wsl
7656
+ ))
7657
+ log(l);
7658
+ log(" (or re-run with --open)");
7659
+ }
7450
7660
  log("Playwright:");
7451
7661
  log(
7452
7662
  ` chromium.launch({ proxy: { server: 'http://127.0.0.1:${proxy.port}' } }) + newContext({ ignoreHTTPSErrors: true })`
@@ -7486,7 +7696,7 @@ async function cmdSetup() {
7486
7696
  }
7487
7697
  async function cmdUpgrade() {
7488
7698
  const policyPath = process.argv[4] || DEFAULT_POLICY_PATH;
7489
- if (!fs.existsSync(policyPath)) {
7699
+ if (!fs2.existsSync(policyPath)) {
7490
7700
  console.error(`No policy file found at ${policyPath}`);
7491
7701
  console.error('Run "astrasync guard setup" first to create a policy.');
7492
7702
  process.exit(1);
@@ -7514,13 +7724,13 @@ async function cmdUpgrade() {
7514
7724
  prompt: (message) => prompt.question(message),
7515
7725
  print: (message) => console.log(message),
7516
7726
  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");
7727
+ const dir = path2.dirname(filePath);
7728
+ if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
7729
+ fs2.writeFileSync(filePath, content, "utf-8");
7520
7730
  },
7521
7731
  readConfig: async (filePath) => {
7522
7732
  try {
7523
- return fs.readFileSync(filePath, "utf-8");
7733
+ return fs2.readFileSync(filePath, "utf-8");
7524
7734
  } catch {
7525
7735
  return null;
7526
7736
  }
@@ -7541,13 +7751,13 @@ async function cmdStart() {
7541
7751
  let gatewayConfig;
7542
7752
  const upgraded = await isUpgraded(DEFAULT_CONFIG_DIR, async (p) => {
7543
7753
  try {
7544
- return fs.readFileSync(p, "utf-8");
7754
+ return fs2.readFileSync(p, "utf-8");
7545
7755
  } catch {
7546
7756
  return null;
7547
7757
  }
7548
7758
  });
7549
7759
  if (upgraded) {
7550
- const config = JSON.parse(fs.readFileSync(`${DEFAULT_CONFIG_DIR}/config`, "utf-8"));
7760
+ const config = JSON.parse(fs2.readFileSync(`${DEFAULT_CONFIG_DIR}/config`, "utf-8"));
7551
7761
  gatewayConfig = {
7552
7762
  mode: config.mode,
7553
7763
  apiBaseUrl: config.platformUrl ? `${config.platformUrl}/api/v1` : void 0,
@@ -7555,7 +7765,7 @@ async function cmdStart() {
7555
7765
  };
7556
7766
  console.log(`[AstraSync] Starting in ${config.mode} mode (agent: ${config.agentId})`);
7557
7767
  } else {
7558
- if (!fs.existsSync(policyPath)) {
7768
+ if (!fs2.existsSync(policyPath)) {
7559
7769
  console.error(`No policy file found at ${policyPath}`);
7560
7770
  console.error('Run "astrasync guard setup" first.');
7561
7771
  process.exit(1);
@@ -7592,7 +7802,7 @@ async function cmdStatus() {
7592
7802
  const policyPath = process.argv[4] || DEFAULT_POLICY_PATH;
7593
7803
  const upgraded = await isUpgraded(DEFAULT_CONFIG_DIR, async (p) => {
7594
7804
  try {
7595
- return fs.readFileSync(p, "utf-8");
7805
+ return fs2.readFileSync(p, "utf-8");
7596
7806
  } catch {
7597
7807
  return null;
7598
7808
  }
@@ -7602,7 +7812,7 @@ async function cmdStatus() {
7602
7812
  console.log(` ${line}`);
7603
7813
  }
7604
7814
  if (upgraded) {
7605
- const config = JSON.parse(fs.readFileSync(`${DEFAULT_CONFIG_DIR}/config`, "utf-8"));
7815
+ const config = JSON.parse(fs2.readFileSync(`${DEFAULT_CONFIG_DIR}/config`, "utf-8"));
7606
7816
  console.log(` Mode: ${config.mode}`);
7607
7817
  console.log(` Agent: ${config.agentId}`);
7608
7818
  console.log(` Platform: ${config.platformUrl}`);
@@ -7610,7 +7820,7 @@ async function cmdStatus() {
7610
7820
  } else {
7611
7821
  console.log(" Mode: local (free tier)");
7612
7822
  }
7613
- if (fs.existsSync(policyPath)) {
7823
+ if (fs2.existsSync(policyPath)) {
7614
7824
  const parsed = parseFile(policyPath);
7615
7825
  if (parsed.validation.valid && parsed.policy) {
7616
7826
  const p = parsed.policy;
@@ -7635,7 +7845,7 @@ async function cmdCheck() {
7635
7845
  (a) => !a.startsWith("--") && a !== "guard" && a !== "check" && a !== process.argv[0] && a !== process.argv[1]
7636
7846
  ) || DEFAULT_POLICY_PATH;
7637
7847
  const trigger = process.argv.find((a) => a.startsWith("--trigger="))?.split("=")[1] || "pre-push";
7638
- if (!fs.existsSync(policyPath)) {
7848
+ if (!fs2.existsSync(policyPath)) {
7639
7849
  console.log("[AstraSync] No policy file \u2014 skipping check");
7640
7850
  process.exit(0);
7641
7851
  }
@@ -7647,9 +7857,9 @@ async function cmdCheck() {
7647
7857
  }
7648
7858
  let enforcement = "warn";
7649
7859
  const configPath = `${DEFAULT_CONFIG_DIR}/config`;
7650
- if (fs.existsSync(configPath)) {
7860
+ if (fs2.existsSync(configPath)) {
7651
7861
  try {
7652
- const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
7862
+ const config = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
7653
7863
  enforcement = config.gitTriggerEnforcement || "warn";
7654
7864
  } catch {
7655
7865
  }
@@ -7712,8 +7922,8 @@ async function cmdUninstallHooks() {
7712
7922
  }
7713
7923
  }
7714
7924
  function resolveClaudeHookCommand() {
7715
- const sibling = path.join(__dirname, "astrasync-claude-hook.js");
7716
- if (fs.existsSync(sibling)) {
7925
+ const sibling = path2.join(__dirname, "astrasync-claude-hook.js");
7926
+ if (fs2.existsSync(sibling)) {
7717
7927
  return `"${process.execPath}" "${sibling}"`;
7718
7928
  }
7719
7929
  return "astrasync-claude-hook";
@@ -7729,19 +7939,19 @@ function parseClaudeCodeFlags() {
7729
7939
  };
7730
7940
  }
7731
7941
  function claudeCodePaths(project) {
7732
- const root = project ? process.cwd() : os.homedir();
7942
+ const root = project ? process.cwd() : os2.homedir();
7733
7943
  return {
7734
- settingsPath: path.join(root, ".claude", "settings.json"),
7735
- astrasyncDir: path.join(root, ".astrasync")
7944
+ settingsPath: path2.join(root, ".claude", "settings.json"),
7945
+ astrasyncDir: path2.join(root, ".astrasync")
7736
7946
  };
7737
7947
  }
7738
7948
  async function cmdInstallClaudeCode() {
7739
7949
  const flags = parseClaudeCodeFlags();
7740
7950
  const { settingsPath, astrasyncDir } = claudeCodePaths(flags.project);
7741
- const policyPath = flags.policyOverride || path.join(astrasyncDir, "policy.yaml");
7742
- const guardJsonPath = path.join(astrasyncDir, "guard.json");
7951
+ const policyPath = flags.policyOverride || path2.join(astrasyncDir, "policy.yaml");
7952
+ const guardJsonPath = path2.join(astrasyncDir, "guard.json");
7743
7953
  console.log("\nAstraSync Local Guard for Claude Code \u2014 install\n");
7744
- if (fs.existsSync(policyPath)) {
7954
+ if (fs2.existsSync(policyPath)) {
7745
7955
  console.log(` Policy: using existing ${policyPath}`);
7746
7956
  if (ensureGuardSelfProtection(policyPath)) {
7747
7957
  console.log(" added guard self-protection patterns to file.write");
@@ -7749,7 +7959,7 @@ async function cmdInstallClaudeCode() {
7749
7959
  } else if (flags.wizard) {
7750
7960
  const prompt = createPrompt();
7751
7961
  try {
7752
- fs.mkdirSync(path.dirname(policyPath), { recursive: true });
7962
+ fs2.mkdirSync(path2.dirname(policyPath), { recursive: true });
7753
7963
  const wizard = new SetupWizard(prompt);
7754
7964
  await wizard.runAndSave(policyPath);
7755
7965
  } finally {
@@ -7758,17 +7968,17 @@ async function cmdInstallClaudeCode() {
7758
7968
  ensureGuardSelfProtection(policyPath);
7759
7969
  console.log(` Policy: created via wizard at ${policyPath}`);
7760
7970
  } else {
7761
- fs.mkdirSync(path.dirname(policyPath), { recursive: true });
7762
- fs.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
7971
+ fs2.mkdirSync(path2.dirname(policyPath), { recursive: true });
7972
+ fs2.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
7763
7973
  console.log(` Policy: default coding-agent policy written to ${policyPath}`);
7764
7974
  console.log(" (re-run with --wizard for the interactive setup instead)");
7765
7975
  }
7766
- if (!fs.existsSync(guardJsonPath)) {
7976
+ if (!fs2.existsSync(guardJsonPath)) {
7767
7977
  const guardSettings = {
7768
7978
  posture: flags.active ? "active" : "passive",
7769
7979
  governMcp: false
7770
7980
  };
7771
- fs.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
7981
+ fs2.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
7772
7982
  `, "utf-8");
7773
7983
  console.log(
7774
7984
  ` Guard: ${guardJsonPath} (posture: ${guardSettings.posture}, governMcp: false)`
@@ -7798,7 +8008,7 @@ async function cmdInstallClaudeCode() {
7798
8008
  }
7799
8009
  console.log("\n Next steps:");
7800
8010
  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`);
8011
+ console.log(` 2. Watch decisions: tail -f ${path2.join(astrasyncDir, "traces")}/*.jsonl`);
7802
8012
  console.log(` 3. When the traces look right, set "posture": "active" in ${guardJsonPath}`);
7803
8013
  console.log(" to enforce (denies block, approvals use Claude Code's own prompt).");
7804
8014
  console.log(' 4. To govern MCP tools too, set "governMcp": true and add');
@@ -7828,26 +8038,26 @@ async function cmdUninstallClaudeCode() {
7828
8038
  );
7829
8039
  }
7830
8040
  function resolveCodexHookCommand() {
7831
- const sibling = path.join(__dirname, "astrasync-codex-hook.js");
7832
- if (fs.existsSync(sibling)) {
8041
+ const sibling = path2.join(__dirname, "astrasync-codex-hook.js");
8042
+ if (fs2.existsSync(sibling)) {
7833
8043
  return `"${process.execPath}" "${sibling}"`;
7834
8044
  }
7835
8045
  return "astrasync-codex-hook";
7836
8046
  }
7837
8047
  function codexPaths(project) {
7838
- const root = project ? process.cwd() : os.homedir();
8048
+ const root = project ? process.cwd() : os2.homedir();
7839
8049
  return {
7840
- hooksPath: path.join(root, ".codex", "hooks.json"),
7841
- astrasyncDir: path.join(root, ".astrasync")
8050
+ hooksPath: path2.join(root, ".codex", "hooks.json"),
8051
+ astrasyncDir: path2.join(root, ".astrasync")
7842
8052
  };
7843
8053
  }
7844
8054
  async function cmdInstallCodex() {
7845
8055
  const flags = parseClaudeCodeFlags();
7846
8056
  const { hooksPath, astrasyncDir } = codexPaths(flags.project);
7847
- const policyPath = flags.policyOverride || path.join(astrasyncDir, "policy.yaml");
7848
- const guardJsonPath = path.join(astrasyncDir, "guard.json");
8057
+ const policyPath = flags.policyOverride || path2.join(astrasyncDir, "policy.yaml");
8058
+ const guardJsonPath = path2.join(astrasyncDir, "guard.json");
7849
8059
  console.log("\nAstraSync Local Guard for Codex CLI \u2014 install\n");
7850
- if (fs.existsSync(policyPath)) {
8060
+ if (fs2.existsSync(policyPath)) {
7851
8061
  console.log(` Policy: using existing ${policyPath} (shared across adapters)`);
7852
8062
  if (ensureGuardSelfProtection(policyPath)) {
7853
8063
  console.log(" added guard self-protection patterns to file.write");
@@ -7855,7 +8065,7 @@ async function cmdInstallCodex() {
7855
8065
  } else if (flags.wizard) {
7856
8066
  const prompt = createPrompt();
7857
8067
  try {
7858
- fs.mkdirSync(path.dirname(policyPath), { recursive: true });
8068
+ fs2.mkdirSync(path2.dirname(policyPath), { recursive: true });
7859
8069
  const wizard = new SetupWizard(prompt);
7860
8070
  await wizard.runAndSave(policyPath);
7861
8071
  } finally {
@@ -7864,17 +8074,17 @@ async function cmdInstallCodex() {
7864
8074
  ensureGuardSelfProtection(policyPath);
7865
8075
  console.log(` Policy: created via wizard at ${policyPath}`);
7866
8076
  } else {
7867
- fs.mkdirSync(path.dirname(policyPath), { recursive: true });
7868
- fs.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
8077
+ fs2.mkdirSync(path2.dirname(policyPath), { recursive: true });
8078
+ fs2.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
7869
8079
  console.log(` Policy: default coding-agent policy written to ${policyPath}`);
7870
8080
  console.log(" (re-run with --wizard for the interactive setup instead)");
7871
8081
  }
7872
- if (!fs.existsSync(guardJsonPath)) {
8082
+ if (!fs2.existsSync(guardJsonPath)) {
7873
8083
  const guardSettings = {
7874
8084
  posture: flags.active ? "active" : "passive",
7875
8085
  governMcp: false
7876
8086
  };
7877
- fs.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
8087
+ fs2.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
7878
8088
  `, "utf-8");
7879
8089
  console.log(
7880
8090
  ` Guard: ${guardJsonPath} (posture: ${guardSettings.posture}, governMcp: false)`
@@ -7893,7 +8103,7 @@ async function cmdInstallCodex() {
7893
8103
  }
7894
8104
  console.log("\n Next steps:");
7895
8105
  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`);
8106
+ console.log(` 2. Watch decisions: tail -f ${path2.join(astrasyncDir, "traces")}/*.jsonl`);
7897
8107
  console.log(` 3. When the traces look right, set "posture": "active" in ${guardJsonPath}.`);
7898
8108
  console.log(" Denies block; approvals fall through to Codex's own prompt \u2014");
7899
8109
  console.log(" except in dontAsk/bypassPermissions modes, where they deny.");
@@ -7918,13 +8128,13 @@ function printCodexStatus() {
7918
8128
  const { hooksPath, astrasyncDir } = codexPaths(scope === "project");
7919
8129
  let hookInstalled = false;
7920
8130
  try {
7921
- const content = fs.readFileSync(hooksPath, "utf-8");
8131
+ const content = fs2.readFileSync(hooksPath, "utf-8");
7922
8132
  hookInstalled = content.includes(ASTRASYNC_CODEX_HOOK_MARKER);
7923
8133
  } catch {
7924
8134
  }
7925
8135
  if (hookInstalled) {
7926
- const policyPath = path.join(astrasyncDir, "policy.yaml");
7927
- const policy = fs.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
8136
+ const policyPath = path2.join(astrasyncDir, "policy.yaml");
8137
+ const policy = fs2.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
7928
8138
  console.log(` Codex CLI (${scope}): hook installed, ${policy}`);
7929
8139
  }
7930
8140
  }
@@ -7934,24 +8144,24 @@ function printClaudeCodeStatus() {
7934
8144
  const { settingsPath, astrasyncDir } = claudeCodePaths(scope === "project");
7935
8145
  let hookInstalled = false;
7936
8146
  try {
7937
- const content = fs.readFileSync(settingsPath, "utf-8");
8147
+ const content = fs2.readFileSync(settingsPath, "utf-8");
7938
8148
  hookInstalled = content.includes(ASTRASYNC_HOOK_MARKER);
7939
8149
  } catch {
7940
8150
  }
7941
- const policyPath = path.join(astrasyncDir, "policy.yaml");
7942
- const guardJsonPath = path.join(astrasyncDir, "guard.json");
8151
+ const policyPath = path2.join(astrasyncDir, "policy.yaml");
8152
+ const guardJsonPath = path2.join(astrasyncDir, "guard.json");
7943
8153
  let posture = "";
7944
- if (fs.existsSync(guardJsonPath)) {
8154
+ if (fs2.existsSync(guardJsonPath)) {
7945
8155
  try {
7946
- const settings = JSON.parse(fs.readFileSync(guardJsonPath, "utf-8"));
8156
+ const settings = JSON.parse(fs2.readFileSync(guardJsonPath, "utf-8"));
7947
8157
  posture = settings.posture ? `, posture: ${settings.posture}` : "";
7948
8158
  } catch {
7949
8159
  posture = ", guard.json unreadable";
7950
8160
  }
7951
8161
  }
7952
- if (hookInstalled || fs.existsSync(policyPath)) {
8162
+ if (hookInstalled || fs2.existsSync(policyPath)) {
7953
8163
  const hook = hookInstalled ? "hook installed" : "hook NOT installed";
7954
- const policy = fs.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
8164
+ const policy = fs2.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
7955
8165
  console.log(` Claude Code (${scope}): ${hook}, ${policy}${posture}`);
7956
8166
  }
7957
8167
  }
@@ -7997,14 +8207,21 @@ async function cmdFleetStatus() {
7997
8207
  async function cmdBrowse() {
7998
8208
  try {
7999
8209
  const port = flagValue("--port");
8000
- const { proxy } = await runBrowse(fleetPaths(), {
8210
+ const open = process.argv.includes("--open");
8211
+ const { proxy, openUrl } = await runBrowse(fleetPaths(), {
8001
8212
  checkpoint: flagValue("--checkpoint") ?? "",
8002
8213
  slug: flagValue("--slug") ?? "",
8003
8214
  upstream: flagValue("--upstream"),
8004
8215
  port: port ? Number(port) : void 0,
8005
- extraHosts: flagValue("--host") ? [flagValue("--host")] : []
8216
+ extraHosts: flagValue("--host") ? [flagValue("--host")] : [],
8217
+ open
8006
8218
  });
8219
+ if (open) openThroughProxy({ port: proxy.port, url: openUrl, certSpki: proxy.certSpki });
8220
+ let stopping = false;
8007
8221
  const stop = async () => {
8222
+ if (stopping) process.exit(0);
8223
+ stopping = true;
8224
+ setTimeout(() => process.exit(0), 2e3).unref();
8008
8225
  await proxy.close();
8009
8226
  process.exit(0);
8010
8227
  };
@@ -8039,7 +8256,7 @@ async function cmdGitRoute() {
8039
8256
  }
8040
8257
  async function cmdGitCredential() {
8041
8258
  const action = process.argv[process.argv.indexOf("git-credential") + 1] ?? "";
8042
- const input = fs.readFileSync(0, "utf-8");
8259
+ const input = fs2.readFileSync(0, "utf-8");
8043
8260
  process.stdout.write(runGitCredential(action, input));
8044
8261
  }
8045
8262
  async function cmdManagedSettings() {
@@ -8143,8 +8360,9 @@ async function main() {
8143
8360
  " browse Sign an agent in to your dashboard from this machine and"
8144
8361
  );
8145
8362
  console.log(
8146
- " run its device proxy (--checkpoint <url> --slug <agent>)"
8363
+ " run its device proxy (--checkpoint <url> --slug <agent>;"
8147
8364
  );
8365
+ console.log(" --open to start a browser through the proxy)");
8148
8366
  console.log(" enroll <token> Enrol this machine in your organisation fleet");
8149
8367
  console.log(" (--api-base-url <url>, --system for machine scope)");
8150
8368
  console.log(" fleet-status Show fleet enrolment, signed policy and heartbeat");