@tryarcanist/cli 0.1.380 → 0.1.381

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 (3) hide show
  1. package/README.md +14 -114
  2. package/dist/index.js +651 -1778
  3. package/package.json +4 -5
package/dist/index.js CHANGED
@@ -3773,7 +3773,7 @@ ${pointer}
3773
3773
  var require_resolve_props = __commonJS({
3774
3774
  "../../node_modules/yaml/dist/compose/resolve-props.js"(exports) {
3775
3775
  "use strict";
3776
- function resolveProps(tokens2, { flow, indicator, next, offset, onError, parentIndent, startOnNewline }) {
3776
+ function resolveProps(tokens, { flow, indicator, next, offset, onError, parentIndent, startOnNewline }) {
3777
3777
  let spaceBefore = false;
3778
3778
  let atNewline = startOnNewline;
3779
3779
  let hasSpace = startOnNewline;
@@ -3788,7 +3788,7 @@ var require_resolve_props = __commonJS({
3788
3788
  let comma = null;
3789
3789
  let found = null;
3790
3790
  let start = null;
3791
- for (const token of tokens2) {
3791
+ for (const token of tokens) {
3792
3792
  if (reqSpace) {
3793
3793
  if (token.type !== "space" && token.type !== "newline" && token.type !== "comma")
3794
3794
  onError(token.offset, "MISSING_CHAR", "Tags and anchors must be separated from the next token by white space");
@@ -3879,7 +3879,7 @@ var require_resolve_props = __commonJS({
3879
3879
  hasSpace = false;
3880
3880
  }
3881
3881
  }
3882
- const last = tokens2[tokens2.length - 1];
3882
+ const last = tokens[tokens.length - 1];
3883
3883
  const end = last ? last.offset + last.source.length : offset;
3884
3884
  if (reqSpace && next && next.type !== "space" && next.type !== "newline" && next.type !== "comma" && (next.type !== "scalar" || next.source !== "")) {
3885
3885
  onError(next.offset, "MISSING_CHAR", "Tags and anchors must be separated from the next token by white space");
@@ -5218,8 +5218,8 @@ ${cb}` : comment;
5218
5218
  * @param forceDoc - If the stream contains no document, still emit a final document including any comments and directives that would be applied to a subsequent document.
5219
5219
  * @param endOffset - Should be set if `forceDoc` is also set, to set the document range end and to indicate errors correctly.
5220
5220
  */
5221
- *compose(tokens2, forceDoc = false, endOffset = -1) {
5222
- for (const token of tokens2)
5221
+ *compose(tokens, forceDoc = false, endOffset = -1) {
5222
+ for (const token of tokens)
5223
5223
  yield* this.next(token);
5224
5224
  yield* this.end(forceDoc, endOffset);
5225
5225
  }
@@ -7409,8 +7409,8 @@ function isTerminalPhase(phase, _sessionKind) {
7409
7409
  // package.json
7410
7410
  var package_default = {
7411
7411
  name: "@tryarcanist/cli",
7412
- version: "0.1.380",
7413
- description: "Command-line interface for Arcanist: inspect sessions, prepare Zeus environments, and trigger reviews",
7412
+ version: "0.1.381",
7413
+ description: "Command-line interface for Arcanist: inspect sessions, prepare Arcanist environments, and trigger reviews",
7414
7414
  type: "module",
7415
7415
  bin: {
7416
7416
  arcanist: "dist/index.js"
@@ -7423,9 +7423,8 @@ var package_default = {
7423
7423
  ],
7424
7424
  scripts: {
7425
7425
  build: "tsup",
7426
- typecheck: "tsc --noEmit",
7427
- prepublishOnly: "npm run build",
7428
- "bundle:sandbox": "tsup --config tsup.sandbox.config.ts"
7426
+ typecheck: "node ../../node_modules/typescript-7/bin/tsc --noEmit",
7427
+ prepublishOnly: "npm run build"
7429
7428
  },
7430
7429
  publishConfig: {
7431
7430
  access: "public"
@@ -7513,20 +7512,6 @@ function parseApiErrorBody(body) {
7513
7512
  return null;
7514
7513
  }
7515
7514
  }
7516
- function parseConflictBody(err) {
7517
- if (!(err instanceof ApiError) || err.status !== 409) return null;
7518
- try {
7519
- const parsed = JSON.parse(err.body);
7520
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
7521
- const body = parsed;
7522
- return {
7523
- ...typeof body.error === "string" ? { error: body.error } : {},
7524
- ...typeof body.reason === "string" ? { reason: body.reason } : {}
7525
- };
7526
- } catch {
7527
- return null;
7528
- }
7529
- }
7530
7515
 
7531
7516
  // src/errors.ts
7532
7517
  var EXIT_CODE_INTERRUPTED = 130;
@@ -7600,7 +7585,6 @@ function messageForApiError(status, body, parsed = parseApiErrorBody(body)) {
7600
7585
  function hintForHttpStatus(status, resourceNoun, parsed) {
7601
7586
  if (status === 401 || status === 403) return "Run `arcanist auth login` or set `ARCANIST_TOKEN`.";
7602
7587
  if (status === 404) {
7603
- if (resourceNoun === "token") return "List tokens with `arcanist tokens list`.";
7604
7588
  if (resourceNoun === "repo") return "Check accessible repositories with `arcanist repos list`.";
7605
7589
  return "List sessions with `arcanist sessions list`.";
7606
7590
  }
@@ -7701,7 +7685,6 @@ async function apiRequest(connection, path, init, read) {
7701
7685
  }
7702
7686
  }
7703
7687
  function resourceNounForPath(path) {
7704
- if (path.startsWith("/api/cli-tokens")) return "token";
7705
7688
  if (path.startsWith("/api/repos")) return "repo";
7706
7689
  if (path.startsWith("/api/sessions")) return "session";
7707
7690
  return void 0;
@@ -7709,9 +7692,6 @@ function resourceNounForPath(path) {
7709
7692
  async function apiFetch(config2, path, init) {
7710
7693
  return apiRequest(config2, path, init, (res) => res.json());
7711
7694
  }
7712
- async function publicApiFetch(apiUrl, path, init) {
7713
- return apiRequest({ apiUrl }, path, init, (res) => res.json());
7714
- }
7715
7695
  async function apiFetchText(config2, path, init) {
7716
7696
  return apiRequest(config2, path, init, (res) => res.text());
7717
7697
  }
@@ -7733,8 +7713,6 @@ var CONFIG_DIR = join(homedir(), ".arcanist");
7733
7713
  var CONFIG_FILE = join(CONFIG_DIR, "config.json");
7734
7714
  var PROJECT_CONFIG_FILE = ".arcanist-cli.json";
7735
7715
  var DEFAULT_API_URL = "https://app.tryarcanist.com";
7736
- var SANDBOX_CLI_AUTH_WAIT_TIMEOUT_MS = 1e4;
7737
- var SANDBOX_CLI_AUTH_WAIT_INTERVAL_MS = 100;
7738
7716
  function loadFileConfig() {
7739
7717
  try {
7740
7718
  return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
@@ -7747,7 +7725,7 @@ function loadConfig(overrides = {}) {
7747
7725
  if (completeEnvOrFlagConfig) return validateAndNormalizeConfig(completeEnvOrFlagConfig);
7748
7726
  const projectConfig = loadProjectConfig();
7749
7727
  const envOrFlagConfig = resolveEnvOrFlagConfig(overrides, projectConfig !== null);
7750
- const fileConfig = !projectConfig && shouldWaitForSandboxCliAuth() ? waitForSandboxCliAuthConfig() : loadFileConfig();
7728
+ const fileConfig = loadFileConfig();
7751
7729
  const apiUrl = envOrFlagConfig?.apiUrl ?? projectConfig?.apiUrl ?? fileConfig?.apiUrl;
7752
7730
  const token = envOrFlagConfig?.token ?? projectConfig?.token ?? fileConfig?.token;
7753
7731
  if (!apiUrl || !token) return null;
@@ -7834,24 +7812,6 @@ function validateAndNormalizeConfig(config2) {
7834
7812
  if (urlError) throw new CliError("user", urlError);
7835
7813
  return { apiUrl: normalizeBaseUrl(config2.apiUrl), token: config2.token };
7836
7814
  }
7837
- function shouldWaitForSandboxCliAuth() {
7838
- const pendingPath = process.env.ARCANIST_CLI_AUTH_PENDING_PATH;
7839
- return Boolean(pendingPath && existsSync(pendingPath));
7840
- }
7841
- function waitForSandboxCliAuthConfig() {
7842
- const readyPath = process.env.ARCANIST_CLI_AUTH_READY_PATH;
7843
- const failedPath = process.env.ARCANIST_CLI_AUTH_FAILED_PATH;
7844
- const deadline = Date.now() + SANDBOX_CLI_AUTH_WAIT_TIMEOUT_MS;
7845
- while (Date.now() <= deadline) {
7846
- if (readyPath && existsSync(readyPath)) return loadFileConfig();
7847
- if (failedPath && existsSync(failedPath)) return null;
7848
- sleepSync(SANDBOX_CLI_AUTH_WAIT_INTERVAL_MS);
7849
- }
7850
- return readyPath && existsSync(readyPath) ? loadFileConfig() : null;
7851
- }
7852
- function sleepSync(ms) {
7853
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
7854
- }
7855
7815
  function findGitRoot(cwd) {
7856
7816
  let current = cwd;
7857
7817
  while (true) {
@@ -7917,7 +7877,6 @@ function isLoopbackHost(parsed) {
7917
7877
 
7918
7878
  // src/runtime.ts
7919
7879
  import { randomUUID } from "crypto";
7920
- import { createInterface } from "readline/promises";
7921
7880
  function getRuntimeOptions(command, options = {}) {
7922
7881
  const globals = command?.optsWithGlobals?.();
7923
7882
  const merged = { ...globals, ...options };
@@ -7951,20 +7910,6 @@ async function readStdin() {
7951
7910
  async function readStdinTrimmed() {
7952
7911
  return (await readStdin()).trim();
7953
7912
  }
7954
- async function confirmOrThrow(message) {
7955
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
7956
- throw new CliError("user", "Confirmation required. Re-run with --yes in non-interactive environments.");
7957
- }
7958
- const rl = createInterface({ input: process.stdin, output: process.stdout });
7959
- try {
7960
- const answer = (await rl.question(`${message} [y/N] `)).trim().toLowerCase();
7961
- if (answer !== "y" && answer !== "yes") {
7962
- throw new CliError("user", "Aborted.");
7963
- }
7964
- } finally {
7965
- rl.close();
7966
- }
7967
- }
7968
7913
  function applyColorEnvironment(options) {
7969
7914
  if (options.noColor === true || !process.stdout.isTTY) {
7970
7915
  process.env.NO_COLOR = "1";
@@ -7973,17 +7918,6 @@ function applyColorEnvironment(options) {
7973
7918
  function randomIdempotencyKey() {
7974
7919
  return randomUUID();
7975
7920
  }
7976
- function assertArcanistSessionMutationAllowed(subcommand) {
7977
- const role = process.env.ARCANIST_AGENT_ROLE?.trim();
7978
- if (role !== "verification" && role !== "review") return;
7979
- throw new CliError(
7980
- "user",
7981
- `\`arcanist ${subcommand}\` is disabled inside ${role} sessions because nested Arcanist work is not observable to read-only agents.`,
7982
- {
7983
- hint: "Use direct local/sandbox evidence instead, or report a verification gap rather than spawning another Arcanist session."
7984
- }
7985
- );
7986
- }
7987
7921
 
7988
7922
  // src/utils/terminal-text.ts
7989
7923
  function sanitizeTerminalText(value) {
@@ -27026,7 +26960,6 @@ var INTEGRATION_REGISTRY = {
27026
26960
  };
27027
26961
 
27028
26962
  // ../../shared/constants/onboarding.ts
27029
- var MAX_ENVIRONMENT_SETUP_REPOSITORIES = 20;
27030
26963
  var CREDENTIAL_VALIDATION_STATUS = {
27031
26964
  VALIDATED: "validated",
27032
26965
  SAVED_UNVERIFIED: "saved_unverified",
@@ -27275,191 +27208,11 @@ async function codexStatusCommand(options, command) {
27275
27208
  if (status) console.log(`Status: ${status}`);
27276
27209
  });
27277
27210
  }
27278
- async function codexLogoutCommand(options, command) {
27279
- const { config: config2 } = resolveBusinessContext(command, options);
27280
- await setCodexSubscriptionEnabled(config2, false).catch(() => {
27281
- });
27282
- const payload = parseCodexResponse(
27283
- codexSubscriptionCredentialStateSchema,
27284
- await apiFetch(config2, CODEX_SUBSCRIPTION_AUTH_JSON_PATH, { method: "DELETE" })
27285
- );
27286
- emit(command, options, payload, () => console.log("Codex subscription auth deactivated and cleared."));
27287
- }
27288
-
27289
- // src/commands/egress.ts
27290
- import { readFileSync as readFileSync2 } from "fs";
27291
-
27292
- // ../../shared/types/business-egress-policy.ts
27293
- var MAX_BUSINESS_EGRESS_DOMAINS = 100;
27294
- var MAX_DOMAIN_LENGTH = 253;
27295
- function normalizeEgressDomains(entries, key = "egressAllowlist") {
27296
- const unique = [...new Set(entries.map((entry) => entry.trim().toLowerCase()).filter(Boolean))].sort();
27297
- if (unique.length > MAX_BUSINESS_EGRESS_DOMAINS) {
27298
- throw new Error(`${key} must contain at most ${MAX_BUSINESS_EGRESS_DOMAINS} domain names`);
27299
- }
27300
- for (const entry of unique) {
27301
- assertValidEgressDomain(entry, key);
27302
- }
27303
- return unique;
27304
- }
27305
- function assertValidEgressDomain(value, key = "egressAllowlist") {
27306
- if (value.includes("*") || value.startsWith(".") || value.endsWith(".") || value.includes("/") || value.includes(":")) {
27307
- throw new Error(`${key} entries must be exact domain names`);
27308
- }
27309
- if (value.length > MAX_DOMAIN_LENGTH) {
27310
- throw new Error(`${key} entries must be exact domain names`);
27311
- }
27312
- const labels = value.split(".");
27313
- if (labels.length === 4 && labels.every((label) => /^\d+$/.test(label) && Number(label) >= 0 && Number(label) <= 255)) {
27314
- throw new Error(`${key} entries must be exact domain names`);
27315
- }
27316
- if (labels.length < 2 || labels.some(
27317
- (label) => label.length < 1 || label.length > 63 || !/^[a-z0-9-]+$/.test(label) || label.startsWith("-") || label.endsWith("-")
27318
- )) {
27319
- throw new Error(`${key} entries must be exact domain names`);
27320
- }
27321
- }
27322
-
27323
- // ../../shared/egress-allowlist/parser.ts
27324
- var EGRESS_ALLOWLIST_SOURCE_PATH = ".arcanist/egress-allowlist.txt";
27325
- function parseEgressAllowlistSourceFile(content, key = EGRESS_ALLOWLIST_SOURCE_PATH) {
27326
- const domains = content.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
27327
- return normalizeEgressDomains(domains, key);
27328
- }
27329
-
27330
- // ../../shared/github/repo-url.ts
27331
- var REPO_SEGMENT_PATTERN = /^[A-Za-z0-9_.][A-Za-z0-9_.-]*$/;
27332
- var MAX_REPO_SEGMENT_LENGTH = 100;
27333
- function isValidGithubRepoSegment(value) {
27334
- if (typeof value !== "string") return false;
27335
- if (!value || value.length > MAX_REPO_SEGMENT_LENGTH) return false;
27336
- if (value === "." || value === "..") return false;
27337
- return REPO_SEGMENT_PATTERN.test(value);
27338
- }
27339
- function parseGithubRepoCoordinate(owner, repo) {
27340
- return isValidGithubRepoSegment(owner) && isValidGithubRepoSegment(repo) ? { owner, repo } : null;
27341
- }
27342
- function parseGithubRepoFullName(value) {
27343
- const match = value.match(/^([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)$/) ?? value.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/) ?? value.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
27344
- return match ? parseGithubRepoCoordinate(match[1], match[2]) : null;
27345
- }
27346
-
27347
- // src/git.ts
27348
- import { execFileSync } from "child_process";
27349
- function git(args) {
27350
- try {
27351
- return execFileSync("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
27352
- } catch (err) {
27353
- throw new CliError("user", `git ${args.join(" ")} failed: ${stringifyError(err)}`);
27354
- }
27355
- }
27356
- function currentRepo() {
27357
- const parsed = parseGithubRepoFullName(git(["remote", "get-url", "origin"]));
27358
- if (!parsed) throw new CliError("user", "origin remote must point at a GitHub repository");
27359
- return { owner: parsed.owner, repo: parsed.repo };
27360
- }
27361
-
27362
- // src/utils/repo-arg.ts
27363
- function parseRepoArg(value, argName = "repo") {
27364
- const parsed = parseGithubRepoFullName(value);
27365
- if (!parsed) throw new CliError("user", `${argName} must be owner/name or a GitHub URL`);
27366
- return { owner: parsed.owner, repo: parsed.repo.replace(/\.git$/, "") };
27367
- }
27368
- function parseRepoArgOrCurrent(value, argName = "repo") {
27369
- if (value === void 0) return currentRepo();
27370
- return parseRepoArg(value, argName);
27371
- }
27372
-
27373
- // src/commands/egress.ts
27374
- async function egressSourceSetCommand(sourceRepoArg, options = {}, command) {
27375
- const { config: config2 } = resolveBusinessContext(command, options);
27376
- const businessId = await resolveBusinessId(config2, options);
27377
- const source = parseRepoArg(sourceRepoArg);
27378
- const payload = await apiFetch(config2, `/api/businesses/${encodeURIComponent(businessId)}/egress-allowlist/source`, {
27379
- method: "PUT",
27380
- body: JSON.stringify({ sourceRepoOwner: source.owner, sourceRepoName: source.repo })
27381
- });
27382
- emit(
27383
- command,
27384
- options,
27385
- payload,
27386
- () => console.log(`Set egress allowlist source to ${source.owner}/${source.repo}:${EGRESS_ALLOWLIST_SOURCE_PATH}.`)
27387
- );
27388
- }
27389
- async function egressSourceGetCommand(options = {}, command) {
27390
- const { config: config2 } = resolveBusinessContext(command, options);
27391
- const businessId = await resolveBusinessId(config2, options);
27392
- const payload = await apiFetch(
27393
- config2,
27394
- `/api/businesses/${encodeURIComponent(businessId)}/egress-allowlist/source`
27395
- );
27396
- emit(command, options, payload, (sourcePayload) => {
27397
- if (!sourcePayload.source) {
27398
- console.log("No egress allowlist source configured.");
27399
- return;
27400
- }
27401
- console.log(
27402
- `${sourcePayload.source.sourceRepoOwner}/${sourcePayload.source.sourceRepoName}:${sourcePayload.path} (${sourcePayload.defaultBranch ?? "default branch"})`
27403
- );
27404
- console.log(`${sourcePayload.domains.length} ${sourcePayload.domains.length === 1 ? "domain" : "domains"}.`);
27405
- });
27406
- }
27407
- async function egressAddCommand(domains, options = {}, command) {
27408
- const { config: config2 } = resolveBusinessContext(command, options);
27409
- const businessId = await resolveBusinessId(config2, options);
27410
- const normalized = normalizeEgressDomains(domains, "domains");
27411
- const payload = await apiFetch(
27412
- config2,
27413
- `/api/businesses/${encodeURIComponent(businessId)}/egress-allowlist/pull-request`,
27414
- {
27415
- method: "POST",
27416
- body: JSON.stringify({ domains: normalized, ...options.reason ? { reason: options.reason } : {} })
27417
- }
27418
- );
27419
- emit(command, options, payload, (addPayload) => {
27420
- if (addPayload.status === "unchanged") {
27421
- console.log("All requested domains are already present in the source file.");
27422
- return;
27423
- }
27424
- console.log(`Egress allowlist PR ${addPayload.status}: ${addPayload.prUrl}`);
27425
- console.log(`Added: ${addPayload.addedDomains.join(", ")}`);
27426
- });
27427
- }
27428
- async function egressSyncCommand(options = {}, command) {
27429
- const { config: config2 } = resolveBusinessContext(command, options);
27430
- const businessId = await resolveBusinessId(config2, options);
27431
- const payload = await apiFetch(
27432
- config2,
27433
- `/api/businesses/${encodeURIComponent(businessId)}/egress-allowlist/sync`,
27434
- { method: "POST" }
27435
- );
27436
- emit(
27437
- command,
27438
- options,
27439
- payload,
27440
- (syncPayload) => console.log(
27441
- `Applied ${syncPayload.egressAllowlist.length} egress allowlist domains from ${syncPayload.source.sourceRepoOwner}/${syncPayload.source.sourceRepoName}:${syncPayload.path}.`
27442
- )
27443
- );
27444
- }
27445
- async function egressValidateCommand(path = EGRESS_ALLOWLIST_SOURCE_PATH, options = {}, command) {
27446
- const content = readFileSync2(path, "utf8");
27447
- const domains = parseEgressAllowlistSourceFile(content, path);
27448
- emit(command, options, { ok: true, path, domains }, (payload) => {
27449
- console.log(`Valid egress allowlist: ${payload.path}`);
27450
- console.log(`${payload.domains.length} ${payload.domains.length === 1 ? "domain" : "domains"}.`);
27451
- });
27452
- }
27453
27211
 
27454
27212
  // ../../shared/utils/type-guards.ts
27455
27213
  function isRecord(value) {
27456
27214
  return !!value && typeof value === "object" && !Array.isArray(value);
27457
27215
  }
27458
- function asNonEmptyString(value) {
27459
- if (typeof value !== "string") return void 0;
27460
- const trimmed = value.trim();
27461
- return trimmed.length > 0 ? trimmed : void 0;
27462
- }
27463
27216
 
27464
27217
  // ../../shared/browser-state/storage-state.ts
27465
27218
  var MAX_BROWSER_STATE_BYTES = 1024 * 1024;
@@ -27709,7 +27462,7 @@ async function captureBrowserLogin(opts) {
27709
27462
  }
27710
27463
 
27711
27464
  // src/utils/prompt-line.ts
27712
- import { createInterface as createInterface2 } from "readline/promises";
27465
+ import { createInterface } from "readline/promises";
27713
27466
  async function promptLine(question, options = {}) {
27714
27467
  if (!process.stdin.isTTY || !process.stderr.isTTY) {
27715
27468
  throw new CliError(
@@ -27719,7 +27472,7 @@ async function promptLine(question, options = {}) {
27719
27472
  }
27720
27473
  const { signal } = options;
27721
27474
  if (signal?.aborted) throw new CliError("user", "Cancelled.");
27722
- const rl = createInterface2({ input: process.stdin, output: process.stderr });
27475
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
27723
27476
  const onAbort = () => rl.close();
27724
27477
  signal?.addEventListener("abort", onAbort, { once: true });
27725
27478
  try {
@@ -27734,6 +27487,49 @@ async function promptLine(question, options = {}) {
27734
27487
  }
27735
27488
  }
27736
27489
 
27490
+ // ../../shared/github/repo-url.ts
27491
+ var REPO_SEGMENT_PATTERN = /^[A-Za-z0-9_.][A-Za-z0-9_.-]*$/;
27492
+ var MAX_REPO_SEGMENT_LENGTH = 100;
27493
+ function isValidGithubRepoSegment(value) {
27494
+ if (typeof value !== "string") return false;
27495
+ if (!value || value.length > MAX_REPO_SEGMENT_LENGTH) return false;
27496
+ if (value === "." || value === "..") return false;
27497
+ return REPO_SEGMENT_PATTERN.test(value);
27498
+ }
27499
+ function parseGithubRepoCoordinate(owner, repo) {
27500
+ return isValidGithubRepoSegment(owner) && isValidGithubRepoSegment(repo) ? { owner, repo } : null;
27501
+ }
27502
+ function parseGithubRepoFullName(value) {
27503
+ const match = value.match(/^([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)$/) ?? value.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/) ?? value.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
27504
+ return match ? parseGithubRepoCoordinate(match[1], match[2]) : null;
27505
+ }
27506
+
27507
+ // src/git.ts
27508
+ import { execFileSync } from "child_process";
27509
+ function git(args) {
27510
+ try {
27511
+ return execFileSync("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
27512
+ } catch (err) {
27513
+ throw new CliError("user", `git ${args.join(" ")} failed: ${stringifyError(err)}`);
27514
+ }
27515
+ }
27516
+ function currentRepo() {
27517
+ const parsed = parseGithubRepoFullName(git(["remote", "get-url", "origin"]));
27518
+ if (!parsed) throw new CliError("user", "origin remote must point at a GitHub repository");
27519
+ return { owner: parsed.owner, repo: parsed.repo };
27520
+ }
27521
+
27522
+ // src/utils/repo-arg.ts
27523
+ function parseRepoArg(value, argName = "repo") {
27524
+ const parsed = parseGithubRepoFullName(value);
27525
+ if (!parsed) throw new CliError("user", `${argName} must be owner/name or a GitHub URL`);
27526
+ return { owner: parsed.owner, repo: parsed.repo.replace(/\.git$/, "") };
27527
+ }
27528
+ function parseRepoArgOrCurrent(value, argName = "repo") {
27529
+ if (value === void 0) return currentRepo();
27530
+ return parseRepoArg(value, argName);
27531
+ }
27532
+
27737
27533
  // src/commands/environment-login.ts
27738
27534
  var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
27739
27535
  async function resolveEnvironment(config2, target) {
@@ -27954,11 +27750,6 @@ async function pollUntilSettled(opts) {
27954
27750
  }
27955
27751
  throw new CliError("server", opts.timeoutMessage);
27956
27752
  }
27957
- function assertSameAttempt(triggeredSessionId, settledSessionId, label) {
27958
- if (triggeredSessionId && settledSessionId !== triggeredSessionId) {
27959
- throw new CliError("conflict", `${label} completed for a different attempt; retry the command.`);
27960
- }
27961
- }
27962
27753
 
27963
27754
  // src/constants/watch.ts
27964
27755
  var MIN_WATCH_POLL_INTERVAL_MS = 250;
@@ -28156,8 +27947,6 @@ async function environmentRetryCommand(repoArg, options = {}, command) {
28156
27947
  }
28157
27948
 
28158
27949
  // src/commands/login.ts
28159
- var DEVICE_AUTH_START_PATH = "/api/auth/cli/device";
28160
- var DEVICE_AUTH_TOKEN_PATH = "/api/auth/cli/device/token";
28161
27950
  async function loginCommand(options, command) {
28162
27951
  const runtime = getRuntimeOptions(command, options);
28163
27952
  const apiUrl = resolveLoginApiUrl(runtime.apiUrl);
@@ -28167,11 +27956,11 @@ async function loginCommand(options, command) {
28167
27956
  token = await readStdinTrimmed();
28168
27957
  } else if (envOrFlag?.token) {
28169
27958
  token = envOrFlag.token;
28170
- } else {
28171
- token = await loginWithDeviceAuthorization(apiUrl, runtime);
28172
27959
  }
28173
27960
  if (!token) {
28174
- throw new CliError("user", "No token provided.");
27961
+ throw new CliError("user", "No token provided.", {
27962
+ hint: "Pass --token-stdin or set ARCANIST_TOKEN. Mint a token in Settings > CLI."
27963
+ });
28175
27964
  }
28176
27965
  if (!token.startsWith("arc_")) {
28177
27966
  throw new CliError("user", "Invalid token format. Token must start with 'arc_'.");
@@ -28194,640 +27983,90 @@ async function loginCommand(options, command) {
28194
27983
  throw err;
28195
27984
  }
28196
27985
  }
28197
- async function loginWithDeviceAuthorization(apiUrl, runtime) {
28198
- const authorization = await publicApiFetch(apiUrl, DEVICE_AUTH_START_PATH, {
28199
- method: "POST",
28200
- body: "{}"
27986
+
27987
+ // src/commands/repos.ts
27988
+ async function reposListCommand(options = {}, command) {
27989
+ const { config: config2 } = resolveBusinessContext(command, options);
27990
+ const payload = await apiFetch(config2, "/api/repos");
27991
+ emit(command, options, { repos: payload.repos, ssoOrgs: payload.ssoOrgs }, (data) => {
27992
+ for (const repo of data.repos) {
27993
+ console.log(`${String(repo.fullName ?? repo.name ?? repo.repo ?? "")} ${String(repo.defaultBranch ?? "")}`);
27994
+ }
27995
+ if (data.ssoOrgs.length > 0) console.log(`SSO withheld orgs: ${data.ssoOrgs.length}`);
28201
27996
  });
28202
- emitAction(runtime, { url: authorization.verificationUrl, userCode: authorization.userCode });
28203
- if (!runtime.json && !runtime.quiet) console.error("Waiting for approval...");
28204
- const deadline = Date.now() + authorization.expiresIn * 1e3;
28205
- const pollIntervalMs = Math.max(1, authorization.pollInterval) * 1e3;
28206
- while (Date.now() < deadline) {
28207
- const response = await publicApiFetch(apiUrl, DEVICE_AUTH_TOKEN_PATH, {
28208
- method: "POST",
28209
- body: JSON.stringify({ deviceCode: authorization.deviceCode })
28210
- });
28211
- if ("accessToken" in response) return response.accessToken;
28212
- await sleep(pollIntervalMs);
27997
+ }
27998
+
27999
+ // src/commands/review.ts
28000
+ var PR_REVIEW_POLL_INTERVAL_MS = 1e4;
28001
+ var PR_REVIEW_WAIT_TIMEOUT_MS = 30 * 60 * 1e3;
28002
+ var SANDBOX_RUNTIME_BACKENDS = {
28003
+ e2b: "e2b_cloud",
28004
+ modal: "modal_cloud"
28005
+ };
28006
+ function isSandboxRuntimeOption(runtime) {
28007
+ return Object.hasOwn(SANDBOX_RUNTIME_BACKENDS, runtime);
28008
+ }
28009
+ function resolveSandboxRuntimeBackend(runtime) {
28010
+ if (runtime === void 0) return null;
28011
+ if (!isSandboxRuntimeOption(runtime)) {
28012
+ throw new CliError("user", "Sandbox runtime must be modal or e2b.");
28213
28013
  }
28214
- throw new CliError("auth", "CLI authorization expired.", { hint: "Run `arcanist auth login` again." });
28014
+ return SANDBOX_RUNTIME_BACKENDS[runtime];
28215
28015
  }
28216
- function emitAction(runtime, action) {
28217
- if (runtime.json) {
28218
- process.stderr.write(
28219
- `${JSON.stringify({ type: "action_required", action: { kind: "authorize_cli", ...action } })}
28220
- `
28221
- );
28016
+ async function reviewCommand(prUrl, options = {}, command) {
28017
+ const sandboxRuntimeBackend = resolveSandboxRuntimeBackend(options.sandboxRuntime);
28018
+ const pollIntervalMs = options.wait ? parsePollInterval(options.pollInterval, { defaultMs: PR_REVIEW_POLL_INTERVAL_MS, maxMs: null }) : null;
28019
+ const { config: config2 } = resolveBusinessContext(command, options);
28020
+ const trigger = await apiFetch(config2, "/api/pr-reviews", {
28021
+ method: "POST",
28022
+ body: JSON.stringify({
28023
+ prUrl,
28024
+ ...sandboxRuntimeBackend !== null ? { sandboxRuntimeBackend } : {}
28025
+ })
28026
+ });
28027
+ if (!options.wait || trigger.cached) {
28028
+ emit(command, options, trigger, (payload) => {
28029
+ if (payload.cached) console.log(`Arcanist review already completed: ${payload.verdict ?? "unknown"}.`);
28030
+ else console.log(`Started Arcanist review${payload.requestId ? ` (${payload.requestId})` : ""}.`);
28031
+ });
28222
28032
  return;
28223
28033
  }
28224
- process.stderr.write(`Authorize Arcanist in your browser:
28225
- ${action.url}
28226
- Code: ${action.userCode}
28227
- `);
28228
- }
28229
-
28230
- // ../../shared/agent/agent-runtime-backend.ts
28231
- var CODEX_AGENT_RUNTIME_BACKEND = "codex";
28232
- var AGENT_RUNTIME_BACKENDS = [CODEX_AGENT_RUNTIME_BACKEND];
28233
-
28234
- // ../../shared/constants/models.ts
28235
- var OpenAIModel = {
28236
- GPT6Astra: "gpt-6-astra",
28237
- GPT56: "gpt-5.6",
28238
- GPT56Sol: "gpt-5.6-sol",
28239
- GPT56Terra: "gpt-5.6-terra",
28240
- GPT56Luna: "gpt-5.6-luna",
28241
- GPT55: "gpt-5.5",
28242
- GPT54: "gpt-5.4",
28243
- GPT54Pro: "gpt-5.4-pro",
28244
- GPT54Mini: "gpt-5.4-mini",
28245
- GPT54Nano: "gpt-5.4-nano",
28246
- GPT53CodexSpark: "gpt-5.3-codex-spark",
28247
- GPT53Codex: "gpt-5.3-codex",
28248
- GPT52: "gpt-5.2",
28249
- GPT52ChatLatest: "gpt-5.2-chat-latest",
28250
- GPT52Codex: "gpt-5.2-codex"
28251
- };
28252
- var MODEL_PROVIDERS_SET = /* @__PURE__ */ new Set(["openai", "anthropic"]);
28253
- var BACKEND_DESKTOP_IMAGE_FEEDBACK_CONFIGS = {
28254
- [CODEX_AGENT_RUNTIME_BACKEND]: {
28255
- backend: CODEX_AGENT_RUNTIME_BACKEND,
28256
- fixture: "known_image_fixture",
28257
- // Codex 0.146.1 code mode flattens tool-result images to text, so the bridge steers the image
28258
- // into the active turn as a user message; DEFAULT_CODEX_IMAGE_FEEDBACK_CAPABILITY mirrors this.
28259
- deliveryPath: "steer_image_message",
28260
- verifiedAt: "2026-09-14"
28034
+ const requestId = trigger.requestId?.trim() ?? "";
28035
+ if (requestId.length === 0) {
28036
+ throw new CliError("server", "Arcanist review start did not return a requestId.");
28261
28037
  }
28262
- };
28263
- var MODEL_REGISTRY = [
28264
- {
28265
- id: OpenAIModel.GPT54,
28266
- name: "GPT-5.4",
28267
- provider: "openai",
28268
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
28269
- codexProviderRoute: "openai",
28270
- capabilities: { codexToolSearch: true },
28271
- contextWindow: 1e6,
28272
- // Default codex model after the gpt-5.5 downgrade; carries gpt-5.5's prior
28273
- // "medium" default so default sessions keep the same reasoning effort.
28274
- reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: "medium" },
28275
- // No cacheWritePerMillion: the pricing page shows a dash in the cache-writes
28276
- // column for gpt-5.4 and gpt-5.5, so the absence is deliberate, not an
28277
- // oversight (checked 2026-07-30). The 5.6 family does publish that rate.
28278
- pricing: {
28279
- inputPerMillion: 2.5,
28280
- outputPerMillion: 15,
28281
- cacheReadPerMillion: 0.25,
28282
- flex: { inputPerMillion: 1.25, outputPerMillion: 7.5, cacheReadPerMillion: 0.13 },
28283
- longContext: {
28284
- thresholdTokens: 272e3,
28285
- flex: { inputPerMillion: 2.5, outputPerMillion: 11.25, cacheReadPerMillion: 0.25 },
28286
- inputPerMillion: 5,
28287
- outputPerMillion: 22.5,
28288
- cacheReadPerMillion: 0.5
28289
- }
28290
- },
28291
- sessionStart: { eligible: true, isDefault: true }
28292
- },
28293
- {
28294
- id: OpenAIModel.GPT6Astra,
28295
- name: "GPT-6 Astra",
28296
- provider: "openai",
28297
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
28298
- codexProviderRoute: "openai",
28299
- capabilities: { codexToolSearch: true },
28300
- contextWindow: 105e4,
28301
- reasoning: { efforts: ["low", "medium", "high", "xhigh", "max"], default: "medium" },
28302
- // Standard and Flex rates: https://developers.openai.com/api/docs/models/gpt-6-astra (2026-09-15).
28303
- pricing: {
28304
- inputPerMillion: 10,
28305
- outputPerMillion: 50,
28306
- cacheReadPerMillion: 1,
28307
- cacheWritePerMillion: 12.5,
28308
- flex: { inputPerMillion: 5, outputPerMillion: 25, cacheReadPerMillion: 0.5, cacheWritePerMillion: 6.25 },
28309
- longContext: {
28310
- thresholdTokens: 272e3,
28311
- inputPerMillion: 20,
28312
- outputPerMillion: 75,
28313
- cacheReadPerMillion: 2,
28314
- cacheWritePerMillion: 25,
28315
- flex: { inputPerMillion: 10, outputPerMillion: 37.5, cacheReadPerMillion: 1, cacheWritePerMillion: 12.5 }
28316
- }
28317
- },
28318
- sessionStart: { eligible: true }
28319
- },
28320
- {
28321
- id: OpenAIModel.GPT56,
28322
- name: "GPT-5.6",
28323
- provider: "openai",
28324
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
28325
- codexProviderRoute: "openai",
28326
- capabilities: { codexToolSearch: true },
28327
- contextWindow: 105e4,
28328
- // Verified 2026-07-09 against OpenAI's GPT-5.6 migration guide and model
28329
- // catalog: the alias routes to gpt-5.6-sol and supports max effort. Pricing
28330
- // mirrors gpt-5.6-sol since the alias resolves to it. Cache-write rates
28331
- // verified 2026-07-30 against the Standard tier at
28332
- // developers.openai.com/api/docs/pricing.
28333
- reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
28334
- pricing: {
28335
- inputPerMillion: 5,
28336
- outputPerMillion: 30,
28337
- cacheReadPerMillion: 0.5,
28338
- cacheWritePerMillion: 6.25,
28339
- flex: { inputPerMillion: 2, outputPerMillion: 10, cacheReadPerMillion: 0.2, cacheWritePerMillion: 2.5 },
28340
- longContext: {
28341
- thresholdTokens: 272e3,
28342
- flex: { inputPerMillion: 4, outputPerMillion: 15, cacheReadPerMillion: 0.4, cacheWritePerMillion: 5 },
28343
- inputPerMillion: 10,
28344
- outputPerMillion: 45,
28345
- cacheReadPerMillion: 1,
28346
- cacheWritePerMillion: 12.5
28038
+ const result = await pollUntilSettled({
28039
+ pollIntervalMs: pollIntervalMs ?? PR_REVIEW_POLL_INTERVAL_MS,
28040
+ timeoutMs: PR_REVIEW_WAIT_TIMEOUT_MS,
28041
+ timeoutMessage: "Timed out waiting for Arcanist review results.",
28042
+ fetchStatus: async () => {
28043
+ const status = await apiFetch(
28044
+ config2,
28045
+ `/api/pr-reviews/requests/${encodeURIComponent(requestId)}`
28046
+ );
28047
+ if (status.status === "cancelled") {
28048
+ throw new CliError(
28049
+ "conflict",
28050
+ status.terminalReason ? `Arcanist review was cancelled: ${status.terminalReason}` : "Arcanist review was cancelled."
28051
+ );
28347
28052
  }
28348
- },
28349
- sessionStart: { eligible: true }
28350
- },
28351
- {
28352
- id: OpenAIModel.GPT56Sol,
28353
- name: "GPT-5.6 Sol",
28354
- provider: "openai",
28355
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
28356
- codexProviderRoute: "openai",
28357
- capabilities: { codexToolSearch: true },
28358
- contextWindow: 105e4,
28359
- reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
28360
- // Cache-write rates verified 2026-07-30 against the Standard tier at
28361
- // developers.openai.com/api/docs/pricing.
28362
- pricing: {
28363
- inputPerMillion: 5,
28364
- outputPerMillion: 30,
28365
- cacheReadPerMillion: 0.5,
28366
- cacheWritePerMillion: 6.25,
28367
- flex: { inputPerMillion: 2, outputPerMillion: 10, cacheReadPerMillion: 0.2, cacheWritePerMillion: 2.5 },
28368
- longContext: {
28369
- thresholdTokens: 272e3,
28370
- flex: { inputPerMillion: 4, outputPerMillion: 15, cacheReadPerMillion: 0.4, cacheWritePerMillion: 5 },
28371
- inputPerMillion: 10,
28372
- outputPerMillion: 45,
28373
- cacheReadPerMillion: 1,
28374
- cacheWritePerMillion: 12.5
28375
- }
28376
- },
28377
- sessionStart: { eligible: true }
28378
- },
28379
- {
28380
- id: OpenAIModel.GPT56Terra,
28381
- name: "GPT-5.6 Terra",
28382
- provider: "openai",
28383
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
28384
- codexProviderRoute: "openai",
28385
- capabilities: { codexToolSearch: true },
28386
- contextWindow: 105e4,
28387
- reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
28388
- // Verified 2026-07-30 against the Standard tier at
28389
- // developers.openai.com/api/docs/pricing: short and long context input,
28390
- // cached input, cache writes, and output.
28391
- pricing: {
28392
- inputPerMillion: 2,
28393
- outputPerMillion: 12,
28394
- cacheReadPerMillion: 0.2,
28395
- cacheWritePerMillion: 2.5,
28396
- flex: { inputPerMillion: 1, outputPerMillion: 6, cacheReadPerMillion: 0.1, cacheWritePerMillion: 1.25 },
28397
- longContext: {
28398
- thresholdTokens: 272e3,
28399
- flex: { inputPerMillion: 2, outputPerMillion: 9, cacheReadPerMillion: 0.2, cacheWritePerMillion: 2.5 },
28400
- inputPerMillion: 4,
28401
- outputPerMillion: 18,
28402
- cacheReadPerMillion: 0.4,
28403
- cacheWritePerMillion: 5
28404
- }
28405
- },
28406
- sessionStart: { eligible: true }
28407
- },
28408
- {
28409
- id: OpenAIModel.GPT56Luna,
28410
- name: "GPT-5.6 Luna",
28411
- provider: "openai",
28412
- overloadFallback: OpenAIModel.GPT56Terra,
28413
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
28414
- codexProviderRoute: "openai",
28415
- capabilities: { codexToolSearch: true },
28416
- contextWindow: 105e4,
28417
- reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
28418
- // Verified 2026-07-30 against the Standard tier at
28419
- // developers.openai.com/api/docs/pricing: short and long context input,
28420
- // cached input, cache writes, and output.
28421
- pricing: {
28422
- inputPerMillion: 0.2,
28423
- outputPerMillion: 1.2,
28424
- cacheReadPerMillion: 0.02,
28425
- cacheWritePerMillion: 0.25,
28426
- flex: { inputPerMillion: 0.1, outputPerMillion: 0.6, cacheReadPerMillion: 0.01, cacheWritePerMillion: 0.125 },
28427
- longContext: {
28428
- thresholdTokens: 272e3,
28429
- flex: { inputPerMillion: 0.2, outputPerMillion: 0.9, cacheReadPerMillion: 0.02, cacheWritePerMillion: 0.25 },
28430
- inputPerMillion: 0.4,
28431
- outputPerMillion: 1.8,
28432
- cacheReadPerMillion: 0.04,
28433
- cacheWritePerMillion: 0.5
28434
- }
28435
- },
28436
- sessionStart: { eligible: true }
28437
- },
28438
- {
28439
- id: OpenAIModel.GPT55,
28440
- name: "GPT-5.5",
28441
- provider: "openai",
28442
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
28443
- codexProviderRoute: "openai",
28444
- capabilities: { codexToolSearch: true },
28445
- contextWindow: 1e6,
28446
- reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: "medium" },
28447
- pricing: {
28448
- inputPerMillion: 5,
28449
- outputPerMillion: 30,
28450
- cacheReadPerMillion: 0.5,
28451
- flex: { inputPerMillion: 2.5, outputPerMillion: 15, cacheReadPerMillion: 0.25 },
28452
- longContext: {
28453
- thresholdTokens: 272e3,
28454
- flex: { inputPerMillion: 5, outputPerMillion: 22.5, cacheReadPerMillion: 0.5 },
28455
- inputPerMillion: 10,
28456
- outputPerMillion: 45,
28457
- cacheReadPerMillion: 1
28458
- }
28459
- },
28460
- sessionStart: { eligible: true }
28461
- },
28462
- {
28463
- id: OpenAIModel.GPT54Mini,
28464
- name: "GPT-5.4 Mini",
28465
- provider: "openai",
28466
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
28467
- codexProviderRoute: "openai",
28468
- capabilities: { codexToolSearch: true },
28469
- contextWindow: 4e5,
28470
- reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: void 0 },
28471
- pricing: {
28472
- inputPerMillion: 0.75,
28473
- outputPerMillion: 4.5,
28474
- cacheReadPerMillion: 0.075,
28475
- // Flex bills at Batch rates: a flat 50% off standard across input,
28476
- // cached input, and output (verified against the OpenAI pricing page,
28477
- // June 2026). Bridge cost estimates deliberately strip this gateway-only
28478
- // axis when deriving MODEL_PRICING.
28479
- flex: { inputPerMillion: 0.375, outputPerMillion: 2.25, cacheReadPerMillion: 0.0375 }
28480
- },
28481
- sessionStart: { eligible: true }
28482
- },
28483
- {
28484
- id: OpenAIModel.GPT54Nano,
28485
- name: "GPT-5.4 Nano",
28486
- provider: "openai",
28487
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
28488
- codexProviderRoute: "openai",
28489
- capabilities: { codexToolSearch: false },
28490
- contextWindow: 4e5,
28491
- reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: void 0 },
28492
- pricing: { inputPerMillion: 0.2, outputPerMillion: 1.25, cacheReadPerMillion: 0.02 },
28493
- sessionStart: { eligible: true }
28494
- },
28495
- {
28496
- id: OpenAIModel.GPT53CodexSpark,
28497
- name: "GPT-5.3 Codex Spark",
28498
- provider: "openai",
28499
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
28500
- codexProviderRoute: "openai",
28501
- capabilities: { codexToolSearch: true },
28502
- contextWindow: 128e3,
28503
- reasoning: { efforts: ["low", "medium", "high", "xhigh"], default: "high" },
28504
- costTracked: false,
28505
- requiresCodexSubscriptionAuth: true,
28506
- sessionStart: { eligible: true },
28507
- visibility: "internal_probe"
28508
- },
28509
- {
28510
- id: OpenAIModel.GPT53Codex,
28511
- name: "GPT-5.3 Codex",
28512
- provider: "openai",
28513
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
28514
- codexProviderRoute: "openai",
28515
- capabilities: { codexToolSearch: true },
28516
- contextWindow: 4e5,
28517
- reasoning: { efforts: ["low", "medium", "high", "xhigh"], default: "high" },
28518
- pricing: { inputPerMillion: 1.75, outputPerMillion: 14, cacheReadPerMillion: 0.175 }
28519
- },
28520
- {
28521
- id: OpenAIModel.GPT52,
28522
- name: "GPT-5.2",
28523
- provider: "openai",
28524
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
28525
- codexProviderRoute: "openai",
28526
- capabilities: { codexToolSearch: true },
28527
- contextWindow: 4e5,
28528
- reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: void 0 },
28529
- pricing: {
28530
- inputPerMillion: 1.75,
28531
- outputPerMillion: 14,
28532
- cacheReadPerMillion: 0.175,
28533
- flex: { inputPerMillion: 0.875, outputPerMillion: 7, cacheReadPerMillion: 0.0875 }
28534
- }
28535
- },
28536
- {
28537
- id: OpenAIModel.GPT52ChatLatest,
28538
- name: "GPT-5.2 Chat",
28539
- provider: "openai",
28540
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
28541
- codexProviderRoute: "openai",
28542
- capabilities: { codexToolSearch: true },
28543
- contextWindow: 128e3,
28544
- reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: void 0 },
28545
- pricing: { inputPerMillion: 1.75, outputPerMillion: 14, cacheReadPerMillion: 0.175 }
28546
- },
28547
- {
28548
- id: OpenAIModel.GPT52Codex,
28549
- name: "GPT-5.2 Codex",
28550
- provider: "openai",
28551
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
28552
- codexProviderRoute: "openai",
28553
- capabilities: { codexToolSearch: true },
28554
- contextWindow: 4e5,
28555
- reasoning: { efforts: ["low", "medium", "high", "xhigh"], default: "high" },
28556
- pricing: { inputPerMillion: 1.75, outputPerMillion: 14, cacheReadPerMillion: 0.175 }
28557
- }
28558
- ];
28559
- var MODEL_PROVIDER_NAMES = {
28560
- openai: "OpenAI",
28561
- anthropic: "Anthropic"
28562
- };
28563
- function buildSessionStartModelIdsByBackend() {
28564
- const byBackend = Object.fromEntries(AGENT_RUNTIME_BACKENDS.map((backend) => [backend, []]));
28565
- for (const model of MODEL_REGISTRY) {
28566
- if (!model.sessionStart?.eligible) continue;
28567
- for (const backend of model.backends) {
28568
- if (!modelSupportsRequiredSessionStartCapabilities(model, backend)) continue;
28569
- byBackend[backend].push(model.id);
28570
- }
28571
- }
28572
- return byBackend;
28573
- }
28574
- function modelSupportsRequiredSessionStartCapabilities(model, backend) {
28575
- if (backend !== CODEX_AGENT_RUNTIME_BACKEND) return true;
28576
- return model.capabilities?.codexToolSearch === true;
28577
- }
28578
- function buildDefaultSessionStartModelIdByBackend() {
28579
- const defaults = {};
28580
- for (const backend of AGENT_RUNTIME_BACKENDS) {
28581
- const backendDefaults = MODEL_REGISTRY.filter(
28582
- (model) => model.sessionStart?.eligible && model.sessionStart.isDefault === true && model.backends.includes(backend)
28583
- );
28584
- if (backendDefaults.length !== 1) {
28585
- throw new Error(`Expected exactly one default session-start model for ${backend}, got ${backendDefaults.length}`);
28586
- }
28587
- defaults[backend] = backendDefaults[0].id;
28588
- }
28589
- return defaults;
28590
- }
28591
- var SESSION_START_MODEL_IDS_BY_BACKEND = buildSessionStartModelIdsByBackend();
28592
- var DEFAULT_SESSION_START_MODEL_ID_BY_BACKEND = buildDefaultSessionStartModelIdByBackend();
28593
- var DEFAULT_SESSION_START_MODEL_ID = DEFAULT_SESSION_START_MODEL_ID_BY_BACKEND[CODEX_AGENT_RUNTIME_BACKEND];
28594
- var VALID_MODEL_IDS = new Set(MODEL_REGISTRY.map((model) => model.id));
28595
- var VALID_SESSION_START_MODEL_IDS_BY_BACKEND = {
28596
- [CODEX_AGENT_RUNTIME_BACKEND]: new Set(SESSION_START_MODEL_IDS_BY_BACKEND[CODEX_AGENT_RUNTIME_BACKEND])
28597
- };
28598
- var MODEL_CONTEXT_WINDOWS = {
28599
- ...Object.fromEntries(
28600
- MODEL_REGISTRY.flatMap((model) => model.contextWindow === void 0 ? [] : [[model.id, model.contextWindow]])
28601
- )
28602
- };
28603
- var MODEL_PROVIDERS = {
28604
- ...Object.fromEntries(MODEL_REGISTRY.map((model) => [model.id, model.provider]))
28605
- };
28606
- var MODEL_REASONING_CONFIG = Object.fromEntries(
28607
- MODEL_REGISTRY.flatMap((model) => model.reasoning ? [[model.id, model.reasoning]] : [])
28608
- );
28609
- var MODEL_DEFINITIONS_BY_ID = Object.fromEntries(MODEL_REGISTRY.map((model) => [model.id, model]));
28610
- function splitModelIdentifier(value) {
28611
- let hasSeparator = false;
28612
- for (const separator of [":", "/"]) {
28613
- const index = value.indexOf(separator);
28614
- if (index <= 0 || index >= value.length - 1) continue;
28615
- hasSeparator = true;
28616
- const providerID = value.slice(0, index);
28617
- const modelID = value.slice(index + 1);
28618
- if (MODEL_PROVIDERS_SET.has(providerID) && modelID.length > 0) {
28619
- return { providerID, modelID };
28620
- }
28621
- }
28622
- if (hasSeparator) return void 0;
28623
- return value.length > 0 ? { modelID: value } : void 0;
28624
- }
28625
- function getRawModelValue(raw) {
28626
- if (typeof raw === "string") return asNonEmptyString(raw);
28627
- if (!isRecord(raw)) return void 0;
28628
- return asNonEmptyString(raw.modelID) ?? asNonEmptyString(raw.modelId) ?? asNonEmptyString(raw.id);
28629
- }
28630
- function getExplicitProvider(raw) {
28631
- if (isRecord(raw)) {
28632
- const providerID = asNonEmptyString(raw.providerID) ?? asNonEmptyString(raw.providerId);
28633
- if (providerID && MODEL_PROVIDERS_SET.has(providerID)) {
28634
- return providerID;
28635
- }
28636
- }
28637
- const modelValue = getRawModelValue(raw);
28638
- if (!modelValue) return void 0;
28639
- return splitModelIdentifier(modelValue)?.providerID;
28640
- }
28641
- function extractModelId(raw) {
28642
- const value = getRawModelValue(raw);
28643
- if (!value) return void 0;
28644
- return splitModelIdentifier(value)?.modelID;
28645
- }
28646
- function getProviderForModel(modelId) {
28647
- return MODEL_PROVIDERS[modelId] ?? "openai";
28648
- }
28649
- function toModelSelection(raw) {
28650
- const modelID = extractModelId(raw);
28651
- if (!modelID) return void 0;
28652
- return {
28653
- providerID: getExplicitProvider(raw) ?? getProviderForModel(modelID),
28654
- modelID
28655
- };
28656
- }
28657
- function getModelDefinition(raw) {
28658
- const modelID = extractModelId(raw);
28659
- if (!modelID) return void 0;
28660
- return MODEL_DEFINITIONS_BY_ID[modelID];
28661
- }
28662
- function formatModelLabel(raw, options = {}) {
28663
- const includeProvider = options.includeProvider ?? false;
28664
- const definition = getModelDefinition(raw);
28665
- if (definition) {
28666
- return includeProvider ? `${MODEL_PROVIDER_NAMES[definition.provider]} / ${definition.name}` : definition.name;
28667
- }
28668
- const selection = toModelSelection(raw);
28669
- if (!selection) return void 0;
28670
- const providerName = MODEL_PROVIDER_NAMES[selection.providerID];
28671
- return includeProvider ? `${providerName} / ${selection.modelID}` : selection.modelID;
28672
- }
28673
- function buildModelProviderGroups(models2) {
28674
- const groups = /* @__PURE__ */ new Map();
28675
- for (const model of models2) {
28676
- let group = groups.get(model.provider);
28677
- if (!group) {
28678
- group = { id: model.provider, name: MODEL_PROVIDER_NAMES[model.provider], models: [] };
28679
- groups.set(model.provider, group);
28680
- }
28681
- group.models.push({
28682
- id: model.id,
28683
- name: model.name,
28684
- label: formatModelLabel(model, { includeProvider: true }) ?? model.name,
28685
- backends: model.backends,
28686
- reasoning: model.reasoning
28687
- });
28688
- }
28689
- const defaultModelIds = new Set(
28690
- models2.filter((model) => model.sessionStart?.isDefault === true).map((model) => model.id)
28691
- );
28692
- for (const group of groups.values()) {
28693
- group.models.sort((a, b) => Number(defaultModelIds.has(b.id)) - Number(defaultModelIds.has(a.id)));
28694
- }
28695
- return [...groups.values()];
28696
- }
28697
- var SESSION_START_MODEL_ID_SET_ANY_BACKEND = new Set(
28698
- AGENT_RUNTIME_BACKENDS.flatMap((backend) => [...SESSION_START_MODEL_IDS_BY_BACKEND[backend]])
28699
- );
28700
- var PUBLIC_SESSION_START_MODEL_PROVIDER_GROUPS = buildModelProviderGroups(
28701
- MODEL_REGISTRY.filter(
28702
- (model) => SESSION_START_MODEL_ID_SET_ANY_BACKEND.has(model.id) && model.visibility !== "internal_probe"
28703
- )
28704
- );
28705
- var CODEX_SUBSCRIPTION_SESSION_START_MODEL_PROVIDER_GROUPS = buildModelProviderGroups(
28706
- MODEL_REGISTRY.filter(
28707
- (model) => SESSION_START_MODEL_ID_SET_ANY_BACKEND.has(model.id) && (model.visibility !== "internal_probe" || model.requiresCodexSubscriptionAuth === true)
28708
- )
28709
- );
28710
-
28711
- // src/commands/models.ts
28712
- async function modelsListCommand(options = {}, command) {
28713
- const { config: config2 } = resolveBusinessContext(command, options);
28714
- const groups = await apiFetch(config2, "/api/models");
28715
- const models2 = groups.flatMap(
28716
- (group) => group.models.map((model) => {
28717
- const backends = model.backends ?? [group.id];
28718
- return {
28719
- ...model,
28720
- backend: group.id,
28721
- provider: group.id,
28722
- backends,
28723
- default: backends.some(
28724
- (backend) => DEFAULT_SESSION_START_MODEL_ID_BY_BACKEND[backend] === model.id
28725
- )
28726
- };
28727
- })
28728
- );
28729
- emit(command, options, { models: models2 }, (payload) => {
28730
- for (const model of payload.models) {
28731
- console.log(`${String(model.id)} ${String(model.backend)}${model.default ? " default" : ""}`);
28732
- }
28733
- });
28734
- }
28735
-
28736
- // src/commands/repos.ts
28737
- async function reposListCommand(options = {}, command) {
28738
- const { config: config2 } = resolveBusinessContext(command, options);
28739
- const payload = await apiFetch(config2, "/api/repos");
28740
- emit(command, options, { repos: payload.repos, ssoOrgs: payload.ssoOrgs }, (data) => {
28741
- for (const repo of data.repos) {
28742
- console.log(`${String(repo.fullName ?? repo.name ?? repo.repo ?? "")} ${String(repo.defaultBranch ?? "")}`);
28743
- }
28744
- if (data.ssoOrgs.length > 0) console.log(`SSO withheld orgs: ${data.ssoOrgs.length}`);
28745
- });
28746
- }
28747
- async function repoBranchesCommand(repoArg, options = {}, command) {
28748
- const { config: config2 } = resolveBusinessContext(command, options);
28749
- const repo = parseRepoArgOrCurrent(repoArg);
28750
- const payload = await apiFetch(
28751
- config2,
28752
- `/api/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.repo)}/branches`
28753
- );
28754
- emit(command, options, payload, (data) => {
28755
- for (const branch of data.branches) console.log(String(branch.name ?? branch.branch ?? ""));
28756
- });
28757
- }
28758
-
28759
- // src/commands/review.ts
28760
- var PR_REVIEW_POLL_INTERVAL_MS = 1e4;
28761
- var PR_REVIEW_WAIT_TIMEOUT_MS = 30 * 60 * 1e3;
28762
- var SANDBOX_RUNTIME_BACKENDS = {
28763
- e2b: "e2b_cloud",
28764
- modal: "modal_cloud",
28765
- sailbox: "sail_cloud"
28766
- };
28767
- function isSandboxRuntimeOption(runtime) {
28768
- return Object.hasOwn(SANDBOX_RUNTIME_BACKENDS, runtime);
28769
- }
28770
- function resolveSandboxRuntimeBackend(runtime) {
28771
- if (runtime === void 0) return null;
28772
- if (!isSandboxRuntimeOption(runtime)) {
28773
- throw new CliError("user", "Sandbox runtime must be modal, e2b, or sailbox.");
28774
- }
28775
- return SANDBOX_RUNTIME_BACKENDS[runtime];
28776
- }
28777
- async function reviewCommand(prUrl, options = {}, command) {
28778
- assertArcanistSessionMutationAllowed("review");
28779
- const sandboxRuntimeBackend = resolveSandboxRuntimeBackend(options.sandboxRuntime);
28780
- const pollIntervalMs = options.wait ? parsePollInterval(options.pollInterval, { defaultMs: PR_REVIEW_POLL_INTERVAL_MS, maxMs: null }) : null;
28781
- const { config: config2 } = resolveBusinessContext(command, options);
28782
- const trigger = await apiFetch(config2, "/api/pr-reviews", {
28783
- method: "POST",
28784
- body: JSON.stringify({
28785
- prUrl,
28786
- ...sandboxRuntimeBackend !== null ? { sandboxRuntimeBackend } : {}
28787
- })
28788
- });
28789
- if (!options.wait || trigger.cached) {
28790
- emit(command, options, trigger, (payload) => {
28791
- if (payload.cached) console.log(`Zeus review already completed: ${payload.verdict ?? "unknown"}.`);
28792
- else console.log(`Started Zeus review${payload.sessionId ? ` (${payload.sessionId})` : ""}.`);
28793
- });
28794
- return;
28795
- }
28796
- const triggeredSessionId = trigger.sessionId ?? null;
28797
- const result = await pollUntilSettled({
28798
- pollIntervalMs: pollIntervalMs ?? PR_REVIEW_POLL_INTERVAL_MS,
28799
- timeoutMs: PR_REVIEW_WAIT_TIMEOUT_MS,
28800
- timeoutMessage: "Timed out waiting for Zeus review results.",
28801
- fetchStatus: async () => {
28802
- const status = await apiFetch(
28803
- config2,
28804
- `/api/pr-reviews/status?prUrl=${encodeURIComponent(prUrl)}`
28805
- );
28806
- if (status.status === "cancelled" || status.status === "admission_rejected") {
28807
- assertSameAttempt(triggeredSessionId, status.sessionId, "PR review");
28808
- if (status.status === "cancelled") {
28809
- throw new CliError(
28810
- "conflict",
28811
- status.terminalReason ? `Zeus review was cancelled: ${status.terminalReason}` : "Zeus review was cancelled."
28812
- );
28813
- }
28814
- throw new CliError(
28815
- "user",
28816
- status.terminalReason ? `Zeus review was rejected at admission: ${status.terminalReason}` : "Zeus review was rejected at admission."
28817
- );
28053
+ if (status.status === "admission_rejected") {
28054
+ throw new CliError(
28055
+ "user",
28056
+ status.terminalReason ? `Arcanist review was rejected at admission: ${status.terminalReason}` : "Arcanist review was rejected at admission."
28057
+ );
28818
28058
  }
28819
28059
  if (status.status !== "completed") return null;
28820
- assertSameAttempt(triggeredSessionId, status.sessionId, "PR review");
28821
28060
  return {
28822
28061
  ...status,
28823
28062
  status: "completed",
28824
- sessionId: status.sessionId ?? triggeredSessionId,
28063
+ sessionId: status.sessionId ?? trigger.sessionId ?? null,
28825
28064
  verdict: status.verdict ?? "unknown"
28826
28065
  };
28827
28066
  }
28828
28067
  });
28829
28068
  emit(command, options, result, (payload) => {
28830
- console.log(`Zeus review: ${sanitizeTerminalText(payload.verdict)}. ${payload.findings.length} finding(s).`);
28069
+ console.log(`Arcanist review: ${sanitizeTerminalText(payload.verdict)}. ${payload.findings.length} finding(s).`);
28831
28070
  for (const finding of payload.findings) {
28832
28071
  console.log(
28833
28072
  `${sanitizeTerminalText(finding.severity)} ${sanitizeTerminalText(finding.file)}:${finding.line} - ${sanitizeTerminalText(finding.title)}`
@@ -28837,7 +28076,7 @@ async function reviewCommand(prUrl, options = {}, command) {
28837
28076
  }
28838
28077
 
28839
28078
  // src/commands/sandbox.ts
28840
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
28079
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
28841
28080
  import { dirname as dirname2 } from "path";
28842
28081
 
28843
28082
  // ../../shared/sandbox-layer/parser.ts
@@ -29179,7 +28418,7 @@ function splitLogicalInstructions(path, text) {
29179
28418
  return result;
29180
28419
  }
29181
28420
  function splitEnvTokens(path, body, startLine) {
29182
- const tokens2 = [];
28421
+ const tokens = [];
29183
28422
  let current = "";
29184
28423
  let quote = null;
29185
28424
  for (const char of body) {
@@ -29195,7 +28434,7 @@ function splitEnvTokens(path, body, startLine) {
29195
28434
  }
29196
28435
  if (/\s/.test(char)) {
29197
28436
  if (current) {
29198
- tokens2.push(current);
28437
+ tokens.push(current);
29199
28438
  current = "";
29200
28439
  }
29201
28440
  continue;
@@ -29203,16 +28442,16 @@ function splitEnvTokens(path, body, startLine) {
29203
28442
  current += char;
29204
28443
  }
29205
28444
  if (quote) throw new SandboxLayerValidationError(path, "ENV contains an unterminated quote", startLine);
29206
- if (current) tokens2.push(current);
29207
- return tokens2;
28445
+ if (current) tokens.push(current);
28446
+ return tokens;
29208
28447
  }
29209
28448
  function parseEnvValues(path, body, startLine) {
29210
28449
  const values = {};
29211
- const tokens2 = splitEnvTokens(path, body, startLine);
29212
- if (tokens2.length > MAX_ENV_VARS) {
28450
+ const tokens = splitEnvTokens(path, body, startLine);
28451
+ if (tokens.length > MAX_ENV_VARS) {
29213
28452
  throw new SandboxLayerValidationError(path, `ENV may set at most ${MAX_ENV_VARS} variables`, startLine);
29214
28453
  }
29215
- for (const token of tokens2) {
28454
+ for (const token of tokens) {
29216
28455
  const separator = token.indexOf("=");
29217
28456
  if (separator <= 0) {
29218
28457
  throw new SandboxLayerValidationError(path, "ENV entries must use KEY=value form", startLine);
@@ -29441,19 +28680,11 @@ var sandboxLayerBuildLogsResponseSchema = external_exports.object({
29441
28680
  logs: external_exports.array(sandboxLayerBuildLogSchema)
29442
28681
  });
29443
28682
  var sandboxLayerCliBuildRequestSchema = sandboxLayerBuildRequestSchema.partial().required({ id: true, status: true }).extend({ createdBy: sandboxLayerBuildActorSchema.nullable().optional() });
29444
- var sandboxLayerCliBuildHistoryItemSchema = sandboxLayerBuildHistoryItemSchema.extend({
29445
- startedAt: external_exports.number().nullable().optional(),
29446
- failureSummary: sandboxLayerFailureSummarySchema.nullable().optional()
29447
- });
29448
28683
  var sandboxLayerCliBuildLogSchema = sandboxLayerBuildLogSchema.partial().required({ sequence: true, message: true }).extend({ created_at: external_exports.union([external_exports.string(), external_exports.number()]).optional() });
29449
28684
  var sandboxLayerCliBuildRequestResponseSchema = external_exports.object({
29450
28685
  ok: external_exports.boolean(),
29451
28686
  buildRequest: sandboxLayerCliBuildRequestSchema
29452
28687
  });
29453
- var sandboxLayerCliBuildHistoryResponseSchema = external_exports.object({
29454
- ok: external_exports.boolean(),
29455
- builds: external_exports.array(sandboxLayerCliBuildHistoryItemSchema)
29456
- });
29457
28688
  var sandboxLayerCliBuildLogsResponseSchema = external_exports.object({
29458
28689
  ok: external_exports.boolean(),
29459
28690
  logs: external_exports.array(sandboxLayerCliBuildLogSchema)
@@ -29547,52 +28778,6 @@ function printTerminalBuildSummary(buildRequest) {
29547
28778
  if (failure?.stderrPreview) console.log(`Stderr: ${failure.stderrPreview}`);
29548
28779
  printActiveTemplateStatus(buildRequest);
29549
28780
  }
29550
- function printBuildHistory(builds) {
29551
- if (builds.length === 0) {
29552
- console.log("No sandbox builds found.");
29553
- return;
29554
- }
29555
- for (const build of builds) {
29556
- console.log(
29557
- [
29558
- `Build ${build.id} is ${build.status}.`,
29559
- `source=${build.sourceRepo}`,
29560
- `commit=${build.commitSha}`,
29561
- `profile=${build.resourceProfileKey}`,
29562
- build.templateId ? `template=${build.templateId}` : null,
29563
- `base=${build.baseTemplateRef}@${build.baseVersion}`,
29564
- `builtBy=${buildActorLabel(build.createdBy)}`
29565
- ].filter(Boolean).join(" ")
29566
- );
29567
- }
29568
- }
29569
- function selectionTierLabel(tier) {
29570
- if (tier === "repo_local") return "repo-local";
29571
- if (tier === "repo_assignment") return "assigned";
29572
- return "workspace default";
29573
- }
29574
- function printSandboxStatus(payload) {
29575
- if (payload.selection) {
29576
- console.log(`Using ${selectionTierLabel(payload.selection.tier)} sandbox.`);
29577
- console.log(`Template: ${payload.selection.templateId}`);
29578
- console.log(`Source: ${payload.selection.sourceRepo}`);
29579
- console.log(`Commit: ${payload.selection.commitSha}`);
29580
- console.log(`Profile: ${payload.selection.resourceProfileKey}`);
29581
- console.log(`Built from: ${payload.selection.baseTemplateRef} @ ${payload.selection.baseVersion}`);
29582
- console.log(`Base status: ${payload.selection.baseStatus}`);
29583
- console.log(`Built by: ${buildActorLabel(payload.selection.createdBy)}`);
29584
- return;
29585
- }
29586
- console.log("Using Arcanist default sandbox.");
29587
- if (payload.fallback?.templateId) console.log(`Template: ${payload.fallback.templateId}`);
29588
- if (payload.latestRepoBuild) {
29589
- console.log(`Latest custom build: ${payload.latestRepoBuild.status}`);
29590
- console.log(`Commit: ${payload.latestRepoBuild.commitSha}`);
29591
- console.log(`Profile: ${payload.latestRepoBuild.resourceProfileKey}`);
29592
- console.log(`Built from: ${payload.latestRepoBuild.baseTemplateRef} @ ${payload.latestRepoBuild.baseVersion}`);
29593
- console.log(`Built by: ${buildActorLabel(payload.latestRepoBuild.createdBy)}`);
29594
- }
29595
- }
29596
28781
  function printLogs(logs) {
29597
28782
  for (const log of logs) {
29598
28783
  console.log(log.message);
@@ -29692,13 +28877,6 @@ function handleValidationError(err, options, command) {
29692
28877
  }
29693
28878
  throw err;
29694
28879
  }
29695
- function sourceBody(sourceRepo, manifestPath) {
29696
- return {
29697
- sourceRepoOwner: sourceRepo.owner,
29698
- sourceRepoName: sourceRepo.repo,
29699
- manifestPath
29700
- };
29701
- }
29702
28880
  async function sandboxInitCommand(options = {}, command) {
29703
28881
  const runtime = getRuntimeOptions(command, options);
29704
28882
  if (existsSync3(DEFAULT_MANIFEST_PATH)) throw new CliError("conflict", `${DEFAULT_MANIFEST_PATH} already exists`);
@@ -29734,12 +28912,12 @@ async function sandboxInitCommand(options = {}, command) {
29734
28912
  async function sandboxValidateCommand(options = {}, command) {
29735
28913
  const manifestPath = options.manifest ?? DEFAULT_MANIFEST_PATH;
29736
28914
  assertManifestExists(manifestPath);
29737
- const manifestText = readFileSync3(manifestPath, "utf8");
28915
+ const manifestText = readFileSync2(manifestPath, "utf8");
29738
28916
  let layerPath;
29739
28917
  try {
29740
28918
  layerPath = parseSandboxLayerManifest(manifestPath, manifestText).layer.dockerfile;
29741
28919
  assertManifestExists(layerPath);
29742
- const layerText = readFileSync3(layerPath, "utf8");
28920
+ const layerText = readFileSync2(layerPath, "utf8");
29743
28921
  const parsed = await parseSandboxLayerSource({ manifestPath, manifestText, layerPath, layerText });
29744
28922
  const payload = formatValidationPayload({ manifestPath, layerPath, parsed });
29745
28923
  emit(command, options, payload, (validationPayload) => {
@@ -29815,206 +28993,518 @@ async function sandboxBuildCommand(sourceRepoArg, options = {}, command) {
29815
28993
  process.exitCode = 1;
29816
28994
  }
29817
28995
  }
29818
- async function sandboxStatusCommand(repoArg, options = {}, command) {
29819
- const { config: config2 } = resolveBusinessContext(command, options);
29820
- const businessId = await resolveBusinessId(config2, options);
29821
- const repo = parseRepoArgOrCurrent(repoArg);
29822
- const payload = await apiFetch(
29823
- config2,
29824
- `/api/businesses/${encodeURIComponent(businessId)}/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(
29825
- repo.repo
29826
- )}/sandbox-layer/resolution`
29827
- );
29828
- emit(command, options, payload, printSandboxStatus);
29829
- }
29830
- async function sandboxHistoryCommand(sourceRepoArg, options = {}, command) {
29831
- const { config: config2 } = resolveBusinessContext(command, options);
29832
- const businessId = await resolveBusinessId(config2, options);
29833
- const sourceRepo = parseRepoArgOrCurrent(sourceRepoArg);
29834
- const params = new URLSearchParams({ sourceRepo: repoPath(sourceRepo) });
29835
- if (options.targetRepo) params.set("targetRepo", repoPath(parseRepoArgOrCurrent(options.targetRepo)));
29836
- if (options.status) params.set("status", options.status);
29837
- if (options.limit) {
29838
- const limit = Number(options.limit);
29839
- if (!Number.isInteger(limit) || limit < 1) {
29840
- throw new CliError("user", "--limit must be a positive integer.");
29841
- }
29842
- params.set("limit", String(limit));
29843
- }
29844
- const payload = sandboxLayerCliBuildHistoryResponseSchema.parse(
29845
- await apiFetch(
29846
- config2,
29847
- `/api/businesses/${encodeURIComponent(businessId)}/sandbox-layer/build-requests?${params.toString()}`
29848
- )
29849
- );
29850
- emit(command, options, payload, (historyPayload) => printBuildHistory(historyPayload.builds));
29851
- }
29852
- async function sandboxRebuildStaleCommand(options = {}, command) {
29853
- const { config: config2 } = resolveBusinessContext(command, options);
29854
- if (!options.dryRun && !options.yes) {
29855
- throw new CliError("user", "Use --dry-run to preview or --yes to start a rebuild campaign.");
29856
- }
29857
- if (options.all && options.business) {
29858
- throw new CliError("user", "Use either --all or --business, not both.");
28996
+
28997
+ // ../../shared/constants/observability.ts
28998
+ var ZEUS_SCOUT_HEALTH_PUBLISH_OUTCOMES = ["published", "publish_failed", "gate_rejected"];
28999
+ var ZEUS_SCOUT_HEALTHS = ["not_applicable", "never_ran", "all_empty", "completed_empty", "yielded"];
29000
+
29001
+ // ../../shared/constants/agent-runtime-evidence.ts
29002
+ var PR_REVIEW_CAPTURE_ID_PATTERN = /^cap-[0-9a-f]{8}$/;
29003
+ var PR_REVIEW_CAPTURE_LABEL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9 ._-]{0,79}$/;
29004
+ var PR_REVIEW_CAPTURE_ALT_MAX_CHARS = 200;
29005
+ var PR_REVIEW_APP_REQUEST_ID_PATTERN = /^req-[0-9a-f]{8}$/;
29006
+ var PR_REVIEW_VIDEO_MAX_DURATION_MS = 9e4;
29007
+
29008
+ // ../../shared/types/doc-lookup.ts
29009
+ var DOC_LOOKUP_QUERY_MAX_CHARS = 300;
29010
+ var DOC_LOOKUP_URL_MAX_CHARS = 500;
29011
+ var DOC_LOOKUP_SEARCH_RESULTS = 3;
29012
+ var DOC_LOOKUP_TEXT_MAX_CHARS = 6e3;
29013
+ var DOC_LOOKUP_ID_PATTERN = /^dl-[0-9a-f]{8}$/;
29014
+ var DOC_LOOKUP_BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set([
29015
+ "localhost",
29016
+ "localhost.localdomain",
29017
+ "metadata",
29018
+ "metadata.google.internal",
29019
+ "instance-data",
29020
+ "instance-data.ec2.internal"
29021
+ ]);
29022
+ function isBlockedIpLiteral(host) {
29023
+ const ipv43 = host.split(".");
29024
+ if (ipv43.length === 4 && ipv43.every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255)) {
29025
+ const [a, b] = ipv43.map(Number);
29026
+ if (a === 0 || a === 10 || a === 127) return true;
29027
+ if (a === 169 && b === 254) return true;
29028
+ if (a === 172 && b >= 16 && b <= 31) return true;
29029
+ if (a === 192 && b === 168) return true;
29030
+ if (a === 100 && b >= 64 && b <= 127) return true;
29031
+ if (a === 198 && (b === 18 || b === 19)) return true;
29032
+ return false;
29859
29033
  }
29860
- const businessId = options.all ? null : await resolveBusinessId(config2, options);
29861
- let payload = await apiFetch(config2, "/api/admin/sandbox-layer/rebuild-campaigns", {
29862
- method: "POST",
29863
- body: JSON.stringify({
29864
- scope: options.all ? "all" : "business",
29865
- ...businessId ? { businessId } : {},
29866
- reason: "base_update",
29867
- dryRun: Boolean(options.dryRun)
29868
- })
29869
- }).catch((error61) => {
29870
- if (error61 instanceof ApiError && error61.status === 403) {
29871
- throw new CliError("auth", "Only Arcanist internal admins can rebuild stale sandbox layers.");
29034
+ if (host.includes(":")) {
29035
+ const lower = host.toLowerCase();
29036
+ if (lower === "::" || lower === "::1") return true;
29037
+ if (/^fe[89ab]/.test(lower)) return true;
29038
+ if (/^f[cd]/.test(lower)) return true;
29039
+ if (lower.startsWith("::ffff:")) {
29040
+ const tail = lower.slice("::ffff:".length);
29041
+ const hexWords = tail.match(/^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
29042
+ if (hexWords) {
29043
+ const hi = Number.parseInt(hexWords[1], 16);
29044
+ const lo = Number.parseInt(hexWords[2], 16);
29045
+ return isBlockedIpLiteral(`${hi >> 8}.${hi & 255}.${lo >> 8}.${lo & 255}`);
29046
+ }
29047
+ return isBlockedIpLiteral(tail);
29872
29048
  }
29873
- throw error61;
29874
- });
29875
- if (options.follow && !options.dryRun) {
29876
- payload = await followRebuildCampaign(config2, payload.campaign.id);
29049
+ return false;
29877
29050
  }
29878
- emit(command, options, payload, printRebuildCampaign);
29051
+ return false;
29879
29052
  }
29880
- async function followRebuildCampaign(config2, campaignId) {
29881
- for (; ; ) {
29882
- const payload = await apiFetch(
29883
- config2,
29884
- `/api/admin/sandbox-layer/rebuild-campaigns/${encodeURIComponent(campaignId)}`
29885
- );
29886
- if (isCampaignTerminal(payload.campaign.status)) return payload;
29887
- await sleep(DEFAULT_POLL_INTERVAL_MS);
29888
- }
29889
- }
29890
- function isCampaignTerminal(status) {
29891
- return status === "completed" || status === "completed_with_failures" || status === "failed";
29892
- }
29893
- function printRebuildCampaign(payload) {
29894
- console.log(`Campaign: ${payload.campaign.id}`);
29895
- console.log(`Status: ${payload.campaign.status}`);
29896
- console.log(`Active artifacts scanned: ${payload.campaign.summary.activeArtifactsScanned}`);
29897
- console.log(`Stale artifacts found: ${payload.campaign.summary.staleArtifactsFound}`);
29898
- console.log(`Current artifacts skipped: ${payload.campaign.summary.currentArtifactsSkipped}`);
29899
- console.log(`Missing installation skips: ${payload.campaign.summary.missingInstallationSkips}`);
29900
- console.log(`Source unavailable skips: ${payload.campaign.summary.sourceUnavailableSkips}`);
29901
- console.log(`Unversioned base skips: ${payload.campaign.summary.unversionedBaseSkips}`);
29902
- console.log(`Active changed skips: ${payload.campaign.summary.activeChangedSkips}`);
29903
- console.log(`Failed items: ${payload.campaign.summary.failedItems}`);
29904
- console.log(`Promoted items: ${payload.campaign.summary.promotedItems}`);
29905
- console.log(`Builds queued: ${payload.campaign.summary.buildsQueued}`);
29906
- if (payload.campaign.status === "completed_with_failures" || payload.campaign.status === "failed") {
29907
- console.log("Previous active templates remain active for failed or skipped rebuild items.");
29908
- }
29909
- }
29910
- async function sandboxLogsCommand(buildId, options = {}, command) {
29911
- const { config: config2 } = resolveBusinessContext(command, options);
29912
- const businessId = await resolveBusinessId(config2, options);
29913
- let afterSequence = options.afterSequence == null ? void 0 : Number(options.afterSequence);
29914
- if (afterSequence != null && (!Number.isInteger(afterSequence) || afterSequence < 0)) {
29915
- throw new CliError("user", "--after-sequence must be a non-negative integer.");
29916
- }
29917
- const limit = options.limit == null ? void 0 : Number(options.limit);
29918
- if (limit != null && (!Number.isInteger(limit) || limit < 1)) {
29919
- throw new CliError("user", "--limit must be a positive integer.");
29920
- }
29921
- const json2 = isJson(command, options);
29922
- if (options.follow) {
29923
- const finalBuild = await waitForBuild(config2, businessId, buildId, {
29924
- follow: true,
29925
- pollIntervalMs: parsePollInterval(options.pollInterval, {
29926
- defaultMs: DEFAULT_POLL_INTERVAL_MS,
29927
- minMs: MIN_POLL_INTERVAL_MS,
29928
- maxMs: null
29929
- }),
29930
- json: json2,
29931
- afterSequence,
29932
- limit
29933
- });
29934
- if (json2) writeJson({ type: "build", buildRequest: finalBuild });
29935
- else printTerminalBuildSummary(finalBuild);
29936
- if (finalBuild.status !== "completed") process.exitCode = 1;
29937
- return;
29053
+ function isPlainHttpUrl(value) {
29054
+ try {
29055
+ const parsed = new URL(value);
29056
+ const host = parsed.hostname.toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
29057
+ return (parsed.protocol === "http:" || parsed.protocol === "https:") && parsed.username === "" && parsed.password === "" && !DOC_LOOKUP_BLOCKED_HOSTNAMES.has(host) && !isBlockedIpLiteral(host);
29058
+ } catch {
29059
+ return false;
29938
29060
  }
29939
- const payload = sandboxLayerCliBuildLogsResponseSchema.parse(
29940
- await apiFetch(config2, buildLogsPath(businessId, buildId, { afterSequence, limit }))
29941
- );
29942
- if (json2) writeJson(payload);
29943
- else printLogs(payload.logs);
29944
29061
  }
29945
- async function sandboxAssignDefaultCommand(sourceRepoArg, options = {}, command) {
29946
- const { config: config2 } = resolveBusinessContext(command, options);
29947
- const businessId = await resolveBusinessId(config2, options);
29948
- const sourceRepo = parseRepoArgOrCurrent(sourceRepoArg);
29949
- const manifestPath = options.manifest ?? DEFAULT_MANIFEST_PATH;
29950
- const payload = await apiFetch(
29951
- config2,
29952
- `/api/businesses/${encodeURIComponent(businessId)}/sandbox-layer/default-source`,
29953
- { method: "PUT", body: JSON.stringify(sourceBody(sourceRepo, manifestPath)) }
29954
- );
29955
- emit(command, options, payload, () => {
29956
- console.log(`Set default sandbox layer source to ${repoPath(sourceRepo)}.`);
29957
- console.log(
29958
- `Build missing target coverage with: arcanist sandbox build ${repoPath(sourceRepo)} --business ${businessId}`
29959
- );
29960
- });
29961
- }
29962
- async function sandboxAssignRepoCommand(targetRepoArg, sourceRepoArg, options = {}, command) {
29963
- const { config: config2 } = resolveBusinessContext(command, options);
29964
- const businessId = await resolveBusinessId(config2, options);
29965
- const targetRepo = parseRepoArgOrCurrent(targetRepoArg);
29966
- const sourceRepo = parseRepoArgOrCurrent(sourceRepoArg);
29967
- const manifestPath = options.manifest ?? DEFAULT_MANIFEST_PATH;
29968
- const payload = await apiFetch(
29969
- config2,
29970
- `/api/businesses/${encodeURIComponent(businessId)}/repos/${encodeURIComponent(targetRepo.owner)}/${encodeURIComponent(
29971
- targetRepo.repo
29972
- )}/sandbox-layer/assignment`,
29973
- { method: "PUT", body: JSON.stringify(sourceBody(sourceRepo, manifestPath)) }
29974
- );
29975
- emit(command, options, payload, () => {
29976
- console.log(`Assigned ${repoPath(sourceRepo)} as sandbox layer source for ${repoPath(targetRepo)}.`);
29977
- console.log(
29978
- `Build target coverage with: arcanist sandbox build ${repoPath(sourceRepo)} --target-repo ${repoPath(
29979
- targetRepo
29980
- )} --business ${businessId}`
29981
- );
29062
+ var DocLookupRequestSchema = external_exports.discriminatedUnion("kind", [
29063
+ external_exports.object({
29064
+ kind: external_exports.literal("search"),
29065
+ query: external_exports.string().trim().min(3).max(DOC_LOOKUP_QUERY_MAX_CHARS)
29066
+ }).strict(),
29067
+ external_exports.object({
29068
+ kind: external_exports.literal("fetch"),
29069
+ url: external_exports.string().url().max(DOC_LOOKUP_URL_MAX_CHARS).refine(isPlainHttpUrl, { message: "url must be http(s) without credentials" })
29070
+ }).strict()
29071
+ ]);
29072
+ var DocLookupResultSchema = external_exports.object({
29073
+ url: external_exports.string().max(DOC_LOOKUP_URL_MAX_CHARS),
29074
+ title: external_exports.string().max(300).nullable(),
29075
+ publishedDate: external_exports.string().max(40).nullable(),
29076
+ text: external_exports.string().max(DOC_LOOKUP_TEXT_MAX_CHARS)
29077
+ }).strict();
29078
+ var DocLookupResponseSchema = external_exports.object({
29079
+ results: external_exports.array(DocLookupResultSchema).max(DOC_LOOKUP_SEARCH_RESULTS)
29080
+ }).strict();
29081
+
29082
+ // ../../shared/constants/durations.ts
29083
+ var SECOND = 1e3;
29084
+ var DATADOG_POST_TIMEOUT_MS = 2 * SECOND;
29085
+ var MINUTE = 60 * SECOND;
29086
+ var PROVIDER_RETRY_ATTEMPT_TIMEOUT_MS = 10 * MINUTE;
29087
+ var HOUR = 60 * MINUTE;
29088
+ var SANDBOX_TELEMETRY_CAPABILITY_TTL_MS = 2 * HOUR;
29089
+ var SANDBOX_TELEMETRY_RATE_LIMIT_WINDOW_MS = 1 * MINUTE;
29090
+ var WORKER_VERSION_HANDOFF_CLOSE_WINDOW_MS = 2 * MINUTE;
29091
+ var ZEUS_TELEMETRY_MAX_DURATION_MS = 24 * HOUR;
29092
+ var D1_READ_ATTEMPT_TIMEOUT_MS = 5 * SECOND;
29093
+ var DAY = 24 * HOUR;
29094
+ var ENVIRONMENT_IMAGE_AGE_THRESHOLD_MS = 7 * DAY;
29095
+ var INTERNAL_ZEUS_COHORT_MAX_WINDOW_MS = 31 * DAY;
29096
+ var ZEUS_REVIEW_TELEMETRY_DETAIL_RETENTION_MS = 30 * DAY;
29097
+ var PR_CHAT_TIMEOUT_MS = 45 * SECOND;
29098
+ var SLACK_API_TIMEOUT_MS = 20 * SECOND;
29099
+ var EXA_REQUEST_TIMEOUT_MS = 15 * SECOND;
29100
+ var ONBOARDING_SETUP_SCRIPT_DURATION_TARGET_MS = 5 * MINUTE;
29101
+ var AUTHORED_RUNTIME_ABI_READ_TIMEOUT_MS = 10 * SECOND;
29102
+ var GIGAFLOW_SEND_TIMEOUT_MS = 5 * SECOND;
29103
+ var GITHUB_REQUEST_SLEEP_BUDGET_MS = 20 * SECOND;
29104
+ var GITHUB_REQUEST_ATTEMPT_TIMEOUT_MS = 5 * SECOND;
29105
+ var GITHUB_REQUEST_WAIT_UNTIL_SLEEP_BUDGET_MS = 8 * SECOND;
29106
+ var PR_REVIEW_DELIVERY_LEASE_MS = 50 * SECOND;
29107
+ var PR_REVIEW_DETACHED_TRIGGER_LEASE_MS = 50 * SECOND;
29108
+ var PR_REVIEW_REQUEST_LEASE_MS = 5 * MINUTE;
29109
+ var ZEUS_FINDING_FIX_JUDGE_TIMEOUT_MS = 20 * SECOND;
29110
+ var ZEUS_FINDING_FIX_JEV_TIMEOUT_MS = 10 * SECOND;
29111
+ var ZEUS_FINDING_REPLY_JEV_TIMEOUT_MS = 5 * SECOND;
29112
+ var ZEUS_FINDING_EVIDENCE_JEV_TIMEOUT_MS = 5 * SECOND;
29113
+ var ZEUS_PRIOR_FINDING_MATCH_JEV_TIMEOUT_MS = 5 * SECOND;
29114
+ var PR_MENTION_INTENT_TIMEOUT_MS = 5 * SECOND;
29115
+ var PR_BROWSER_QA_INTENT_TIMEOUT_MS = 10 * SECOND;
29116
+ var ANUBIS_LANDMARK_FALLBACK_TIMEOUT_MS = 8 * SECOND;
29117
+ var ANUBIS_FIELD_TEXT_TIMEOUT_MS = 20 * SECOND;
29118
+ var ZEUS_FINDING_FIX_JUDGE_PR_BUDGET_MS = 25 * SECOND;
29119
+ var ZEUS_FINDING_FIX_JUDGE_TURTLE_BUDGET_MS = 5 * MINUTE;
29120
+ var ZEUS_FINDING_FIX_JUDGE_SWEEP_LOOKBACK_MS = 14 * DAY;
29121
+ var PR_REVIEW_STORED_SHAPE_SUPPORT_MS = 14 * DAY;
29122
+ var PR_REVIEW_DELIVERY_AMBIGUITY_MS = 60 * SECOND;
29123
+ var PR_REVIEW_SUMMARY_REFRESH_MS = 5 * MINUTE;
29124
+ var PR_REVIEW_SUMMARY_WRITER_LEASE_MS = 2 * MINUTE;
29125
+ var PR_REVIEW_GENERATION_RETRY_MS = 5 * MINUTE;
29126
+ var PR_REVIEW_ATTACHED_GENERATION_PROBE_MS = 20 * MINUTE;
29127
+ var PR_REVIEW_GENERATION_FAST_RETRY_MS = 10 * SECOND;
29128
+ var PR_REVIEW_GENERATION_FAST_FAIL_MAX_MS = 60 * SECOND;
29129
+ var PR_REVIEW_DELIVERY_RETRY_MS = 5 * MINUTE;
29130
+ var SESSION_INTERVENTION_TTL_MS = 7 * 24 * HOUR;
29131
+ var BROWSER_STATE_RELOGIN_INTERVENTION_TTL_MS = 30 * DAY;
29132
+ var REVIEW_SCREENSHOT_PUBLIC_URL_TTL_MS = 30 * DAY;
29133
+ var ENVIRONMENT_FINDING_COLLECTION_WINDOW_MS = 4 * HOUR;
29134
+ var ENVIRONMENT_LIMITATION_TRIAGE_JEV_TIMEOUT_MS = 5 * SECOND;
29135
+ var ENVIRONMENT_RECENT_REVIEW_WINDOW_MS = 7 * 24 * HOUR;
29136
+ var WARM_DEMAND_WEEK_MS = 7 * DAY;
29137
+ var WARM_POOL_CLAIM_LEASE_MS = 5 * MINUTE;
29138
+ var WARM_POOL_MAX_CLOCK_SKEW_MS = 60 * SECOND;
29139
+ var WARM_POOL_PREPARE_LEASE_MS = 20 * MINUTE;
29140
+ var WARM_POOL_REPLACE_AFTER_MS = 90 * MINUTE;
29141
+ var WARM_POOL_MAX_AGE_MS = 2 * HOUR;
29142
+ var WARM_POOL_PREPARE_BUDGET_MS = 8 * MINUTE;
29143
+ var WARM_POOL_MARKER_WRITE_TIMEOUT_MS = 10 * SECOND;
29144
+ var WARM_POOL_PREFLIGHT_TIMEOUT_MS = 90 * SECOND;
29145
+ var WARM_POOL_CLAIM_BUDGET_MS = 30 * SECOND;
29146
+ var WARM_POOL_BRIDGE_CONNECT_TIMEOUT_MS = 45 * SECOND;
29147
+ var ENVIRONMENT_JOB_LOCK_WAIT_MS = 10 * MINUTE;
29148
+ var ENVIRONMENT_JOB_LOCK_RENEWAL_MS = 30 * SECOND;
29149
+ var ENVIRONMENT_JOB_LOCK_TTL_MS = 90 * SECOND;
29150
+ var TURTLE_SANDBOX_TIMEOUT_MS = 20 * MINUTE;
29151
+ var TURTLE_COMMAND_TIMEOUT_MS = 30 * SECOND;
29152
+ var TURTLE_INSTALL_TIMEOUT_MS = 2 * MINUTE;
29153
+ var TURTLE_MODEL_TIMEOUT_MS = 8 * MINUTE;
29154
+ var TURTLE_PR_METADATA_TIMEOUT_MS = 90 * SECOND;
29155
+ var TURTLE_TRANSCRIPT_FLUSH_MS = 2 * SECOND;
29156
+ var TURTLE_CI_REPAIR_LEASE_MS = 3 * MINUTE;
29157
+ var TURTLE_CI_REPAIR_LOST_MESSAGE_BUDGET_MS = 30 * MINUTE;
29158
+ var TURTLE_CI_REPAIR_PUSH_BUDGET_MS = 24 * HOUR;
29159
+ var TURTLE_CI_REPAIR_AWAITING_PR_BUDGET_MS = 30 * MINUTE;
29160
+ var TURTLE_ZEUS_REPAIR_LEASE_MS = 3 * MINUTE;
29161
+ var TURTLE_ZEUS_REPAIR_LOST_MESSAGE_BUDGET_MS = 30 * MINUTE;
29162
+ var TURTLE_ZEUS_REPAIR_PUSH_BUDGET_MS = 24 * HOUR;
29163
+ var TURTLE_ZEUS_REPAIR_WAIT_CI_BUDGET_MS = TURTLE_CI_REPAIR_PUSH_BUDGET_MS + HOUR;
29164
+ var ANUBIS_READINESS_TIMEOUT_MAX_MS = 10 * MINUTE;
29165
+
29166
+ // ../../shared/constants/prompt-budgets.ts
29167
+ var PROMPT_BUDGET_NODES = {
29168
+ // The lifecycle deadline is a root because callers may override it per session.
29169
+ PROMPT_STARTUP: { ms: 24e4, parent: null },
29170
+ // Dispatch begins after startup and has a separate lifecycle override surface.
29171
+ PROMPT_DISPATCH: { ms: 18e4, parent: null },
29172
+ // Transport liveness is phase-independent and does not sit inside prompt startup.
29173
+ SANDBOX_LIVENESS: { ms: 6e4, parent: null },
29174
+ CODEX_STARTUP: { ms: 3e4, parent: "PROMPT_STARTUP" },
29175
+ PR_HEAD_CHECKOUT: { ms: 24e4, parent: "PROMPT_STARTUP" },
29176
+ WORKSPACE_SETUP_WAIT: { ms: 6e5, parent: "PROMPT_STARTUP" },
29177
+ APP_RUNTIME_READY: { ms: 93e4, parent: "PROMPT_STARTUP" },
29178
+ // Review preflight is background work; no wall budget is declared today.
29179
+ REVIEW_PREFLIGHT: { ms: null, parent: "PROMPT_STARTUP" }
29180
+ };
29181
+ var ZEUS_SCOUT_TIMEOUT_MS = 10 * 6e4;
29182
+ var ZEUS_REVIEWER_LANE_TIMEOUT_MS = 10 * 6e4;
29183
+ var ZEUS_PROMPT_STARTUP_TIMEOUT_MS = PROMPT_BUDGET_NODES.PROMPT_STARTUP.ms + ZEUS_SCOUT_TIMEOUT_MS;
29184
+ var ANUBIS_QA_PLANNING_DEADLINE_MS = 3e5;
29185
+ var ANUBIS_QA_INVESTIGATION_DEADLINE_MS = 15 * 6e4;
29186
+ var ANUBIS_QA_SETTLEMENT_WALL_MS = 5 * 6e4;
29187
+ var ANUBIS_QA_PROMPT_WALL_MS = ANUBIS_READINESS_TIMEOUT_MAX_MS + ANUBIS_QA_PLANNING_DEADLINE_MS + ANUBIS_QA_INVESTIGATION_DEADLINE_MS + ANUBIS_QA_SETTLEMENT_WALL_MS;
29188
+ var ZEUS_SCOUT_MAX_CANDIDATES_PER_SCOUT = 8;
29189
+ var ZEUS_SCOUT_EXCERPT_MAX_FILE_BYTES = 256 * 1024;
29190
+ var ZEUS_REVIEW_INVESTIGATION_DEADLINE_MS = 15.5 * 6e4;
29191
+ var ZEUS_SCOUT_TARGETED_MAX_QUESTIONS = 7;
29192
+ var ZEUS_SCOUT_DISCOVERY_MAX_SHARDS = 4;
29193
+ var ZEUS_SCOUT_TARGETED_MAX_FLEET = ZEUS_SCOUT_TARGETED_MAX_QUESTIONS + ZEUS_SCOUT_DISCOVERY_MAX_SHARDS + 1;
29194
+ var ZEUS_SCOUT_TARGETED_INPUT_MAX_BYTES = 64 * 1024;
29195
+ var ZEUS_SCOUT_TARGETED_OUTPUT_MAX_BYTES = 64 * 1024;
29196
+
29197
+ // ../../shared/types/zeus-review-investigation.ts
29198
+ var ZEUS_SCOUT_MAX_CANDIDATES = ZEUS_SCOUT_MAX_CANDIDATES_PER_SCOUT * ZEUS_SCOUT_TARGETED_MAX_FLEET;
29199
+ var prose = external_exports.string().trim().min(1).max(4e3);
29200
+ var ZeusReviewWorkItemBaseSchema = external_exports.object({
29201
+ id: external_exports.string().regex(/^[a-zA-Z0-9._:-]+$/).max(80).describe("Stable id you choose for this work item; reuse it to update the item."),
29202
+ changedPath: prose.describe("Changed file path from the admitted diff."),
29203
+ producer: prose.describe("Code that produces the changed value or behavior."),
29204
+ consumer: prose.describe("Actual code that consumes it."),
29205
+ inputState: prose.describe("Concrete input or state that reaches the consumer."),
29206
+ status: external_exports.enum(["pending", "settled", "refuted", "below_bar", "external", "environment", "budget_exhausted"]).describe("pending while open; any other status closes the item and needs reason."),
29207
+ evidence: external_exports.array(prose).max(ZEUS_SCOUT_MAX_CANDIDATES).describe("Source or probe references. Required for settled, refuted, below_bar."),
29208
+ reason: prose.describe("Why the item has this status. Optional only while pending."),
29209
+ candidateIds: external_exports.array(external_exports.string().min(1).max(80)).max(ZEUS_SCOUT_MAX_CANDIDATES).describe("Delivered scout candidate ids this item covers."),
29210
+ findingIds: external_exports.array(external_exports.string().min(1).max(80)).max(ZEUS_SCOUT_MAX_CANDIDATES).describe("Ids of findings this item supports.")
29211
+ }).strict();
29212
+ var ZeusReviewWorkItemSchema = ZeusReviewWorkItemBaseSchema.refine(
29213
+ (item) => !["settled", "refuted", "below_bar"].includes(item.status) || item.evidence.length > 0,
29214
+ {
29215
+ message: "A settled decision needs source or probe evidence."
29216
+ }
29217
+ );
29218
+ var ZEUS_REVIEW_PENDING_REASON = "Pending: not yet investigated.";
29219
+ var CANDIDATE_REFERENCE_PREFIX = "candidate:";
29220
+ var ZeusReviewWorkItemInputSchema = ZeusReviewWorkItemBaseSchema.extend({
29221
+ status: ZeusReviewWorkItemBaseSchema.shape.status.default("pending"),
29222
+ evidence: ZeusReviewWorkItemBaseSchema.shape.evidence.default([]),
29223
+ // Accepts the `candidate:<id>` evidence reference form; the prefix is stripped before the id length cap applies.
29224
+ candidateIds: external_exports.array(
29225
+ external_exports.string().min(1).max(CANDIDATE_REFERENCE_PREFIX.length + 80).transform(
29226
+ (id) => id.startsWith(CANDIDATE_REFERENCE_PREFIX) ? id.slice(CANDIDATE_REFERENCE_PREFIX.length) : id
29227
+ )
29228
+ ).max(ZEUS_SCOUT_MAX_CANDIDATES).describe(ZeusReviewWorkItemBaseSchema.shape.candidateIds.description).optional(),
29229
+ findingIds: ZeusReviewWorkItemBaseSchema.shape.findingIds.optional(),
29230
+ reason: ZeusReviewWorkItemBaseSchema.shape.reason.optional()
29231
+ }).transform((item, ctx) => {
29232
+ if (item.reason === void 0 && item.status !== "pending") {
29233
+ ctx.addIssue({ code: "custom", path: ["reason"], message: `reason is required when status is ${item.status}` });
29234
+ return external_exports.NEVER;
29235
+ }
29236
+ const parsed = ZeusReviewWorkItemSchema.safeParse({
29237
+ ...item,
29238
+ candidateIds: item.candidateIds ?? [],
29239
+ findingIds: item.findingIds ?? [],
29240
+ reason: item.reason ?? ZEUS_REVIEW_PENDING_REASON
29982
29241
  });
29983
- }
29984
- async function sandboxUnassignDefaultCommand(options = {}, command) {
29985
- if (isJson(command, options) && options.yes !== true) {
29986
- throw new CliError("user", "`sandbox unassign default --json` requires --yes.");
29242
+ if (!parsed.success) {
29243
+ for (const issue3 of parsed.error.issues) ctx.addIssue({ code: "custom", path: issue3.path, message: issue3.message });
29244
+ return external_exports.NEVER;
29987
29245
  }
29988
- const { config: config2 } = resolveBusinessContext(command, options);
29989
- const businessId = await resolveBusinessId(config2, options);
29990
- if (!options.yes) await confirmOrThrow("Clear the business default sandbox layer source?");
29991
- const payload = await apiFetch(
29992
- config2,
29993
- `/api/businesses/${encodeURIComponent(businessId)}/sandbox-layer/default-source`,
29994
- { method: "DELETE" }
29995
- );
29996
- emit(command, options, payload, () => console.log("Cleared default sandbox layer source."));
29997
- }
29998
- async function sandboxUnassignRepoCommand(targetRepoArg, options = {}, command) {
29999
- if (isJson(command, options) && options.yes !== true) {
30000
- throw new CliError("user", "`sandbox unassign repo --json` requires --yes.");
29246
+ const { candidateIds, findingIds, ...normalized } = parsed.data;
29247
+ return {
29248
+ ...normalized,
29249
+ ...item.candidateIds === void 0 ? {} : { candidateIds },
29250
+ ...item.findingIds === void 0 ? {} : { findingIds }
29251
+ };
29252
+ });
29253
+ var ZeusScoutInvestigationIdSchema = external_exports.string().regex(/^q-[1-9][0-9]*$/).max(16);
29254
+ var ZeusReviewInvestigationSchema = external_exports.object({
29255
+ baseSha: external_exports.string().nullable(),
29256
+ headSha: external_exports.string().nullable(),
29257
+ items: external_exports.array(ZeusReviewWorkItemSchema),
29258
+ unreadRanges: external_exports.array(external_exports.object({ path: external_exports.string(), start: external_exports.number().int(), end: external_exports.number().int() })),
29259
+ reconciliationUsed: external_exports.boolean(),
29260
+ budgetExhausted: external_exports.boolean(),
29261
+ pendingPaths: external_exports.array(external_exports.string()),
29262
+ omittedRangeCount: external_exports.number().int().nonnegative(),
29263
+ omittedPendingPathCount: external_exports.number().int().nonnegative()
29264
+ }).refine(
29265
+ (value) => new TextEncoder().encode(JSON.stringify(value)).byteLength <= ZEUS_SCOUT_TARGETED_INPUT_MAX_BYTES,
29266
+ {
29267
+ message: "Investigation ledger exceeds the transport budget."
30001
29268
  }
30002
- const { config: config2 } = resolveBusinessContext(command, options);
30003
- const businessId = await resolveBusinessId(config2, options);
30004
- const targetRepo = parseRepoArgOrCurrent(targetRepoArg);
30005
- if (!options.yes) await confirmOrThrow(`Clear sandbox layer assignment for ${repoPath(targetRepo)}?`);
30006
- const payload = await apiFetch(
30007
- config2,
30008
- `/api/businesses/${encodeURIComponent(businessId)}/repos/${encodeURIComponent(targetRepo.owner)}/${encodeURIComponent(
30009
- targetRepo.repo
30010
- )}/sandbox-layer/assignment`,
30011
- { method: "DELETE" }
30012
- );
30013
- emit(command, options, payload, () => console.log(`Cleared sandbox layer assignment for ${repoPath(targetRepo)}.`));
30014
- }
29269
+ );
29270
+
29271
+ // ../../shared/types/agent-runtime-evidence.ts
29272
+ var captureIdSchema = external_exports.string().regex(PR_REVIEW_CAPTURE_ID_PATTERN);
29273
+ var PrReviewCaptureKindSchema = external_exports.enum(["screenshot", "video"]);
29274
+ var PrReviewCaptureSchema = external_exports.object({
29275
+ captureId: captureIdSchema,
29276
+ label: external_exports.string().regex(PR_REVIEW_CAPTURE_LABEL_PATTERN),
29277
+ artifactId: external_exports.string().min(1).max(80),
29278
+ url: external_exports.string().url().max(2e3),
29279
+ kind: PrReviewCaptureKindSchema.default("screenshot"),
29280
+ /** Video length in milliseconds; absent on a screenshot. */
29281
+ durationMs: external_exports.number().int().min(0).max(PR_REVIEW_VIDEO_MAX_DURATION_MS).optional(),
29282
+ /** The uncropped frame behind a cropped screenshot; the published image links to it. */
29283
+ fullUrl: external_exports.string().url().max(2e3).optional(),
29284
+ /** A still the published comment shows in place of a video, linking to the clip. */
29285
+ posterUrl: external_exports.string().url().max(2e3).optional(),
29286
+ /** One sentence saying what the image shows; it is all that remains once the signed URL expires. */
29287
+ alt: external_exports.string().min(1).max(PR_REVIEW_CAPTURE_ALT_MAX_CHARS).optional()
29288
+ }).strict();
29289
+ var PrReviewSessionCaptureSchema = PrReviewCaptureSchema.extend({
29290
+ finalPath: external_exports.string().max(500).optional()
29291
+ }).strict();
29292
+ var ZeusSessionFlywheelAppRequestSchema = external_exports.object({
29293
+ requestId: external_exports.string().regex(PR_REVIEW_APP_REQUEST_ID_PATTERN),
29294
+ method: external_exports.enum(["GET", "HEAD"]),
29295
+ /** Path plus query as the model asked for it, on the app origin. */
29296
+ path: external_exports.string().min(1).max(2e3),
29297
+ status: external_exports.number().int().min(100).max(599).nullable(),
29298
+ authenticated: external_exports.boolean(),
29299
+ redirected: external_exports.object({ location: external_exports.string().max(2e3) }).strict().nullable(),
29300
+ contentType: external_exports.string().max(200).nullable(),
29301
+ bodySha256: external_exports.string().regex(/^[0-9a-f]{64}$/i).nullable(),
29302
+ bodyExcerpt: external_exports.string().max(2e3).nullable(),
29303
+ promptId: external_exports.string().min(1),
29304
+ bodyOmitted: external_exports.enum(["binary", "too_large", "unreadable"]).optional()
29305
+ }).strict();
29306
+ var DOC_LOOKUP_STATUSES = [
29307
+ "ok",
29308
+ "empty",
29309
+ "rate_limited",
29310
+ "upstream_error",
29311
+ "invalid_input",
29312
+ "budget_exhausted",
29313
+ "page_unavailable",
29314
+ "unavailable"
29315
+ ];
29316
+ var ZeusSessionFlywheelDocLookupSchema = external_exports.object({
29317
+ lookupId: external_exports.string().regex(DOC_LOOKUP_ID_PATTERN),
29318
+ kind: external_exports.enum(["search", "fetch"]),
29319
+ /** Model-authored query or URL, verbatim after redact(). This is the audit record; keep it. */
29320
+ input: external_exports.string().min(1).max(DOC_LOOKUP_URL_MAX_CHARS),
29321
+ status: external_exports.enum(DOC_LOOKUP_STATUSES),
29322
+ resultUrls: external_exports.array(external_exports.string().max(DOC_LOOKUP_URL_MAX_CHARS)).max(DOC_LOOKUP_SEARCH_RESULTS),
29323
+ durationMs: external_exports.number().int().nonnegative(),
29324
+ promptId: external_exports.string().min(1),
29325
+ thread: external_exports.enum(["primary", "scout"]),
29326
+ investigationId: ZeusScoutInvestigationIdSchema.optional()
29327
+ }).strict();
29328
+
29329
+ // ../../shared/zeus/candidate-ledger-conservation.ts
29330
+ var ZEUS_CANDIDATE_LEDGER_STAGES = ["bundle", "ledger", "disposition", "lineage"];
30015
29331
 
30016
29332
  // ../../shared/events/bridge.ts
30017
29333
  var ZEUS_TELEMETRY_EVENT_TYPE = "zeus_telemetry";
29334
+ var ZEUS_TELEMETRY_PROJECTED_FIELDS = {
29335
+ "zeus.verification_observed": {
29336
+ observedCount: "number",
29337
+ hasObservedCommand: "boolean",
29338
+ matchedNoTestsCount: "number",
29339
+ runnerMismatchCount: "number",
29340
+ runnerResolved: "boolean",
29341
+ resolvedPackageManager: { kind: "string_enum", values: ["npm", "pnpm", "yarn", "bun"] }
29342
+ },
29343
+ "zeus.publish_rejected": {
29344
+ reason: "string",
29345
+ checkCount: "number",
29346
+ findingCount: "number",
29347
+ verdict: "string",
29348
+ firstAttempt: "boolean",
29349
+ attemptOrdinal: "number",
29350
+ contractVersion: "string",
29351
+ schemaPath: "string",
29352
+ schemaIssue: "string",
29353
+ schemaValue: "string"
29354
+ },
29355
+ // Not a scout counter, but the same transit contract: no metric anywhere derives a redaction
29356
+ // count from the publication, so the event stream is the only place it can be observed at all.
29357
+ // Over-masking is a reader-facing defect, so a per-publish count is the signal that catches it.
29358
+ "zeus.publish_secret_value_redacted": { count: "number" },
29359
+ "zeus.publish_note_pipeline_state": {
29360
+ attemptOrdinal: "number",
29361
+ droppedCount: "number",
29362
+ contractVersion: "string"
29363
+ },
29364
+ "zeus.publish_notes_unverified": {
29365
+ attemptOrdinal: "number",
29366
+ findingCount: "number",
29367
+ contractVersion: "string"
29368
+ },
29369
+ "zeus.publish_empty_review_unattested": {
29370
+ attemptOrdinal: "number",
29371
+ noteCount: "number",
29372
+ contractVersion: "string"
29373
+ },
29374
+ // Record-only base-control audit, one per review with blockers. `detail` is the post-deploy
29375
+ // signal: a high `no_base_run` share means the base-checkout teaching is not landing.
29376
+ "zeus.publish_blocking_downgraded": {
29377
+ detail: { kind: "string_enum", values: ["no_base_run", "no_matched_pair"] },
29378
+ blockingCount: "number",
29379
+ attemptOrdinal: "number"
29380
+ },
29381
+ // One per publish that found stray files. The only channel that shows whether
29382
+ // stale-main branches and sandbox-written paths keep reaching reviewed diffs.
29383
+ "zeus.diff_hygiene": {
29384
+ alreadyOnBaseCount: "number",
29385
+ sandboxGeneratedCount: "number",
29386
+ truncated: "boolean"
29387
+ },
29388
+ "zeus.scout.health": {
29389
+ health: { kind: "string_enum", values: ZEUS_SCOUT_HEALTHS },
29390
+ dispatch_mode: { kind: "string_enum", values: ["targeted", "broad"] },
29391
+ unresolvedInvestigationCount: "number",
29392
+ // Declared as an enum, not a free string: the blackout monitor filters on this value, and an
29393
+ // undeclared or misspelled outcome would silently drop every session out of the alert rather
29394
+ // than into it.
29395
+ publishOutcome: { kind: "string_enum", values: ZEUS_SCOUT_HEALTH_PUBLISH_OUTCOMES },
29396
+ candidateTotal: "number",
29397
+ expectedScoutCount: "number",
29398
+ observedScoutCount: "number",
29399
+ statuses: "string_array"
29400
+ },
29401
+ "zeus.scout.agreement": {
29402
+ candidateCount: "number",
29403
+ publishedCandidateCount: "number",
29404
+ examinedRejectedCount: "number",
29405
+ notExaminedCount: "number",
29406
+ notExaminedEnvironmentCount: "number",
29407
+ notExaminedExternalCount: "number",
29408
+ notExaminedNoneCount: "number",
29409
+ unrunPublishedCount: "number",
29410
+ unrunRemovedCount: "number",
29411
+ notDeliveredCount: "number",
29412
+ notSelectedCount: "number"
29413
+ },
29414
+ // One per publish. `conserved:false` is a bridge defect in `violationStage`; the enum keeps a
29415
+ // misspelled stage out of the alarm instead of silently accepting it.
29416
+ "zeus.candidate_ledger": {
29417
+ conserved: "boolean",
29418
+ candidateCount: "number",
29419
+ droppedCount: "number",
29420
+ publishedCandidateCount: "number",
29421
+ rejectedCount: "number",
29422
+ notDeliveredCount: "number",
29423
+ notSelectedCount: "number",
29424
+ violationStage: { kind: "string_enum", values: ["none", ...ZEUS_CANDIDATE_LEDGER_STAGES] }
29425
+ },
29426
+ "zeus.judge.withheld_defects": { count: "number" },
29427
+ "zeus.judge.intent_claims": {
29428
+ count: "number",
29429
+ verified: "number",
29430
+ contradicted: "number",
29431
+ notChecked: "number",
29432
+ uncitedContradicted: "number",
29433
+ danglingContradicted: "number",
29434
+ unmatched: "number",
29435
+ overflow: "number"
29436
+ },
29437
+ "zeus.scout.completion": {
29438
+ role: "string",
29439
+ status: "string",
29440
+ durationMs: "number",
29441
+ inputTokens: "number",
29442
+ outputTokens: "number",
29443
+ candidateCount: "number"
29444
+ },
29445
+ // One per reader lane per review. The only production evidence of each reader's outcome,
29446
+ // whether its lead delegated to zeus-specialist subagents, and — via `runtime`/`model` —
29447
+ // which lane produced it; the --by-reader ledger split grades on those fields.
29448
+ "zeus.reviewer_lane.completed": {
29449
+ status: "string",
29450
+ error: "string",
29451
+ durationMs: "number",
29452
+ finishedAfterScouts: "boolean",
29453
+ readerFinishMinusScoutMs: "number",
29454
+ scoutJoinWaitMs: "number",
29455
+ candidateCount: "number",
29456
+ droppedCount: "number",
29457
+ subagentDispatched: "boolean",
29458
+ modelRequestCount: "number",
29459
+ readToolCalls: "number",
29460
+ grepToolCalls: "number",
29461
+ globToolCalls: "number",
29462
+ otherToolCalls: "number",
29463
+ readerModel: "string",
29464
+ runtime: "string",
29465
+ model: "string"
29466
+ },
29467
+ "zeus.scout.bundle_omitted": { omittedCandidateCount: "number", droppedExcerptCount: "number" },
29468
+ "zeus.scout.patch_truncated": {
29469
+ role: "string",
29470
+ sampleIndex: "number",
29471
+ investigationId: "string",
29472
+ ownedFileCount: "number",
29473
+ truncatedFileCount: "number",
29474
+ omittedFileCount: "number",
29475
+ withheldChars: "number"
29476
+ },
29477
+ "zeus.scout.patch_unavailable": {
29478
+ role: "string",
29479
+ sampleIndex: "number",
29480
+ reason: "string",
29481
+ ownedFileCount: "number",
29482
+ patchedFileCount: "number"
29483
+ },
29484
+ // One per brokered doc lookup; the only channel tying a lookup's outcome and
29485
+ // broker result count back to its scout investigation.
29486
+ "zeus.doc_lookup": {
29487
+ kind: { kind: "string_enum", values: ["search", "fetch"] },
29488
+ status: { kind: "string_enum", values: DOC_LOOKUP_STATUSES },
29489
+ thread: { kind: "string_enum", values: ["primary", "scout"] },
29490
+ investigationId: "string",
29491
+ durationMs: "number",
29492
+ resultCount: "number"
29493
+ },
29494
+ "zeus.review.prompt_budget_truncated": {
29495
+ excessChars: "number",
29496
+ reviewContextPacketTruncated: "boolean",
29497
+ droppedSectionNames: "string_array",
29498
+ truncatedSectionNames: "string_array",
29499
+ beforeChars: "number",
29500
+ afterChars: "number"
29501
+ },
29502
+ "zeus.review.prompt_budget_externalized": {
29503
+ beforeChars: "number",
29504
+ afterChars: "number",
29505
+ externalizedSectionNames: "string_array"
29506
+ }
29507
+ };
30018
29508
 
30019
29509
  // ../../shared/onboarding/events.ts
30020
29510
  var ONBOARDING_STAGES = [
@@ -31130,6 +30620,9 @@ var ERROR_CODES = [
31130
30620
  // Review preflight could not read a changed file at the PR head. Deterministic for a
31131
30621
  // given head, so the control plane must not spend its retry budget on it.
31132
30622
  "review_preflight_input_rejected",
30623
+ // The PR checkout (token, fetch, import, or reset) failed before review dispatch. Retryable:
30624
+ // the durable review request owns the replacement generation.
30625
+ "review_checkout_failed",
31133
30626
  "unknown"
31134
30627
  ];
31135
30628
  var ERROR_CODE_SET = new Set(ERROR_CODES);
@@ -31178,6 +30671,7 @@ var ERROR_CODE_LABELS = {
31178
30671
  question_delivery_failed: "Answer could not be delivered to the agent",
31179
30672
  malformed_search_command: "A search command with invalid syntax was blocked",
31180
30673
  review_preflight_input_rejected: "The review could not read a changed file at the PR head",
30674
+ review_checkout_failed: "The PR checkout could not be prepared",
31181
30675
  unknown: "Unknown failure"
31182
30676
  };
31183
30677
  var LEGACY_ERROR_CODE_LABELS = {
@@ -31842,11 +31336,11 @@ async function getSessionCommand(sessionId, options, command) {
31842
31336
  if (resultLine) console.log(resultLine);
31843
31337
  }
31844
31338
  async function sessionEventsCommand(sessionId, options, command) {
31845
- const afterSequence = resolveSequenceOption(options.afterSequence, options.after, "--after-sequence", "--after");
31846
- const beforeSequence = resolveSequenceOption(options.beforeSequence, options.before, "--before-sequence", "--before");
31339
+ const afterSequence = options.afterSequence;
31340
+ const beforeSequence = options.beforeSequence;
31847
31341
  if (options.follow) {
31848
31342
  if (beforeSequence) {
31849
- throw new CliError("user", "--before-sequence/--before cannot be used with --follow.");
31343
+ throw new CliError("user", "--before-sequence cannot be used with --follow.");
31850
31344
  }
31851
31345
  if (options.promptId) {
31852
31346
  throw new CliError(
@@ -31901,519 +31395,6 @@ async function sessionEventsCommand(sessionId, options, command) {
31901
31395
  }
31902
31396
  if (textOpen) process.stdout.write("\n");
31903
31397
  }
31904
- function resolveSequenceOption(canonicalValue, aliasValue, canonicalFlag, aliasFlag) {
31905
- if (canonicalValue !== void 0 && aliasValue !== void 0) {
31906
- throw new CliError("user", `${aliasFlag} cannot be combined with ${canonicalFlag}.`);
31907
- }
31908
- return canonicalValue ?? aliasValue;
31909
- }
31910
- async function usageCommand(sessionId, options, command) {
31911
- const runtime = getRuntimeOptions(command, options);
31912
- const config2 = requireConfig(runtime);
31913
- const payload = await apiFetch(config2, `/api/sessions/${sessionId}/usage`);
31914
- if (isJson(command, options)) {
31915
- writeJson(payload);
31916
- return;
31917
- }
31918
- const usage = payload.usage && typeof payload.usage === "object" && !Array.isArray(payload.usage) ? payload.usage : payload;
31919
- console.log(`Input tokens: ${String(usage.inputTokens ?? 0)}`);
31920
- console.log(`Output tokens: ${String(usage.outputTokens ?? 0)}`);
31921
- console.log(`Total tokens: ${String(usage.totalTokens ?? 0)}`);
31922
- if (usage.totalCostUsd !== void 0) console.log(`Cost: ${String(usage.totalCostUsd)}`);
31923
- }
31924
-
31925
- // ../../shared/utils/shell.ts
31926
- var SHELL_ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
31927
-
31928
- // ../../shared/types/sandbox.ts
31929
- var APP_RUNTIME_ENTRY_TYPE = {
31930
- Compose: "compose",
31931
- /**
31932
- * Contract-only: the control plane synthesizes a command entry from a profile's `bootScript`.
31933
- * Profiles no longer store command entries (see the App Runtime schema in repo-runtime.ts).
31934
- */
31935
- Command: "command"
31936
- };
31937
-
31938
- // ../../shared/types/repo-runtime.ts
31939
- var ENVIRONMENT_REPOS_MAX = 8;
31940
- var RENDERED_PATH_PREFIXES_MAX = 64;
31941
- var SANDBOX_PATH_PATTERN = /^\/workspace\/(?!\.+$)(?!.*\.\.)[A-Za-z0-9._-]+$/;
31942
- var EnvironmentRepoSchema = external_exports.object({
31943
- repoOwner: external_exports.string().trim().min(1, "repoOwner is required"),
31944
- repoName: external_exports.string().trim().min(1, "repoName is required"),
31945
- sandboxPath: external_exports.string().regex(SANDBOX_PATH_PATTERN, "sandboxPath must be a single path segment under /workspace").optional()
31946
- }).strict();
31947
- function environmentRepoSandboxPath(repo) {
31948
- return repo.sandboxPath ?? `/workspace/${repo.repoName}`;
31949
- }
31950
- var EnvironmentReposSchema = external_exports.array(EnvironmentRepoSchema).min(1, "at least one repo is required").max(ENVIRONMENT_REPOS_MAX, `at most ${ENVIRONMENT_REPOS_MAX} repos are allowed`).superRefine((repos2, ctx) => {
31951
- if (repos2.length === 0) return;
31952
- const primaryOwner = repos2[0].repoOwner.toLowerCase();
31953
- if (repos2[0].sandboxPath !== void 0) {
31954
- ctx.addIssue({
31955
- code: "custom",
31956
- path: [0, "sandboxPath"],
31957
- message: `the primary repo is always cloned to /workspace/${repos2[0].repoName}`
31958
- });
31959
- }
31960
- const seenRepos = /* @__PURE__ */ new Set();
31961
- const seenPaths = /* @__PURE__ */ new Set();
31962
- repos2.forEach((repo, index) => {
31963
- if (repo.repoOwner.toLowerCase() !== primaryOwner) {
31964
- ctx.addIssue({
31965
- code: "custom",
31966
- path: [index, "repoOwner"],
31967
- message: "all repos must share the primary repo's owner"
31968
- });
31969
- }
31970
- const repoKey = `${repo.repoOwner}/${repo.repoName}`.toLowerCase();
31971
- if (seenRepos.has(repoKey)) {
31972
- ctx.addIssue({ code: "custom", path: [index], message: `duplicate repo ${repo.repoOwner}/${repo.repoName}` });
31973
- }
31974
- seenRepos.add(repoKey);
31975
- const path = environmentRepoSandboxPath(repo);
31976
- if (!SANDBOX_PATH_PATTERN.test(path)) {
31977
- ctx.addIssue({
31978
- code: "custom",
31979
- path: [index],
31980
- message: `repo name ${repo.repoName} needs an explicit sandboxPath`
31981
- });
31982
- return;
31983
- }
31984
- if (seenPaths.has(path)) {
31985
- ctx.addIssue({ code: "custom", path: [index], message: `sandbox path ${path} is already taken` });
31986
- }
31987
- seenPaths.add(path);
31988
- });
31989
- });
31990
- var CredentialDeclarationSchema = external_exports.object({
31991
- name: external_exports.string(),
31992
- envVar: external_exports.string(),
31993
- source: external_exports.enum(["business_openai_key", "business_anthropic_key"]).optional()
31994
- });
31995
- var StringRecordSchema = external_exports.record(
31996
- external_exports.string().regex(SHELL_ENV_KEY_PATTERN, "must be a POSIX-style environment variable name"),
31997
- external_exports.string()
31998
- );
31999
- var ComposeAppRuntimeEntrySchema = external_exports.object({
32000
- type: external_exports.enum([APP_RUNTIME_ENTRY_TYPE.Compose]),
32001
- files: external_exports.array(external_exports.string()).optional(),
32002
- service: external_exports.string().optional(),
32003
- profiles: external_exports.array(external_exports.string()).optional()
32004
- }).strict();
32005
- var AppRuntimeProfileConfigSchema = external_exports.object({
32006
- cwd: external_exports.string().optional(),
32007
- bootScript: external_exports.string().max(65536).describe(
32008
- "Optional bash bringup script. When set it overrides the entry: arcanist-app start runs it on the sandbox host from cwd with runtimeVariables plus HOST and PORT, and waits on ready.path. The app must listen on url.hostPort. Leave unset to boot from the entry."
32009
- ).optional(),
32010
- kind: external_exports.string().optional(),
32011
- runner: external_exports.string().optional(),
32012
- entry: ComposeAppRuntimeEntrySchema.optional(),
32013
- url: external_exports.object({ hostPort: external_exports.number().int().optional(), path: external_exports.string().optional() }).strict().optional(),
32014
- portMapping: external_exports.object({ containerPort: external_exports.number().int().optional() }).strict().optional(),
32015
- additionalPorts: external_exports.array(
32016
- external_exports.object({
32017
- service: external_exports.string().optional(),
32018
- hostPort: external_exports.number().int().optional(),
32019
- containerPort: external_exports.number().int().optional()
32020
- }).strict()
32021
- ).optional(),
32022
- runtimeVariables: StringRecordSchema.optional(),
32023
- composeEnv: StringRecordSchema.optional(),
32024
- generatedComposeEnv: external_exports.record(
32025
- external_exports.string(),
32026
- external_exports.union([
32027
- external_exports.object({ type: external_exports.literal("alphanumeric"), length: external_exports.number().int() }).strict(),
32028
- external_exports.object({ type: external_exports.literal("hex"), bytes: external_exports.number().int() }).strict()
32029
- ])
32030
- ).optional(),
32031
- env: StringRecordSchema.optional(),
32032
- ready: external_exports.object({
32033
- path: external_exports.string().optional(),
32034
- nonEmptyPath: external_exports.string().optional(),
32035
- timeoutSeconds: external_exports.number().int().optional()
32036
- }).strict().optional(),
32037
- open: external_exports.object({ path: external_exports.string().optional() }).strict().optional(),
32038
- renderedPathPrefixes: external_exports.array(external_exports.string().max(512)).max(RENDERED_PATH_PREFIXES_MAX).describe(
32039
- "Repo-relative directories the booted app is built from. When set, a review boots the app only when the diff touches a non-test file under one of them; when unset, the file-extension heuristic decides."
32040
- ).optional(),
32041
- auth: external_exports.object({
32042
- command: external_exports.string().optional(),
32043
- validatePath: external_exports.string().optional(),
32044
- browserStateEnvVar: external_exports.string().optional(),
32045
- credentials: external_exports.array(CredentialDeclarationSchema).optional()
32046
- }).strict().optional(),
32047
- e2e: external_exports.object({
32048
- testCommand: external_exports.string(),
32049
- seedCommand: external_exports.string().optional(),
32050
- resetCommand: external_exports.string().optional(),
32051
- credentials: external_exports.array(CredentialDeclarationSchema).optional()
32052
- }).strict().optional()
32053
- }).strict();
32054
- var LEGACY_DOCKERFILE_ENTRY_TYPE = "dockerfile";
32055
- var LegacyAppRuntimeEntrySchema = external_exports.union([
32056
- external_exports.object({ type: external_exports.literal(APP_RUNTIME_ENTRY_TYPE.Command), command: external_exports.string().optional() }).passthrough(),
32057
- external_exports.object({
32058
- type: external_exports.literal(LEGACY_DOCKERFILE_ENTRY_TYPE),
32059
- context: external_exports.string().optional(),
32060
- dockerfile: external_exports.string().optional(),
32061
- service: external_exports.string().optional()
32062
- }).passthrough()
32063
- ]);
32064
- var AppRuntimeProfileConfigReadSchema = AppRuntimeProfileConfigSchema.extend({
32065
- entry: external_exports.union([ComposeAppRuntimeEntrySchema, LegacyAppRuntimeEntrySchema]).optional()
32066
- });
32067
- var EnvironmentProvisioningConfigSchema = external_exports.object({
32068
- sandbox: external_exports.object({ appRuntime: AppRuntimeProfileConfigSchema.optional() }).strict().optional()
32069
- }).strict();
32070
- var EnvironmentProvisioningConfigReadSchema = external_exports.object({
32071
- sandbox: external_exports.object({ appRuntime: AppRuntimeProfileConfigReadSchema.optional() }).strict().optional()
32072
- }).strict();
32073
-
32074
- // src/commands/setup.ts
32075
- var INSTALL_POLL_INTERVAL_MS = 5e3;
32076
- var INSTALL_WAIT_TIMEOUT_MS = 20 * 60 * 1e3;
32077
- var ONBOARDING_REPOS_PATH = "/api/onboarding/repos";
32078
- async function setupCommand(repoArgs = [], options = {}, command) {
32079
- if (repoArgs.length > MAX_ENVIRONMENT_SETUP_REPOSITORIES) {
32080
- throw new CliError("user", `At most ${MAX_ENVIRONMENT_SETUP_REPOSITORIES} repositories can be prepared at once.`);
32081
- }
32082
- if (options.workspace && repoArgs.length === 1) {
32083
- throw new CliError("user", "A multi-repository workspace requires at least two repositories.");
32084
- }
32085
- if (options.workspace && repoArgs.length > ENVIRONMENT_REPOS_MAX) {
32086
- throw new CliError("user", `A workspace supports at most ${ENVIRONMENT_REPOS_MAX} repositories.`);
32087
- }
32088
- const runtime = getRuntimeOptions(command, options);
32089
- let config2 = loadConfig(runtime);
32090
- if (!config2) {
32091
- const envOrFlag = resolveEnvOrFlagConfig(runtime, false);
32092
- if ((process.stdin.isTTY !== true || process.stdout.isTTY !== true) && !envOrFlag?.token) {
32093
- throw new CliError("auth", "Not logged in.", { hint: "Run `arcanist auth login` or set `ARCANIST_TOKEN`." });
32094
- }
32095
- config2 = await loginCommand({ ...runtime, emitResult: false }, command);
32096
- }
32097
- const requested = repoArgs.map((value) => {
32098
- const repo = parseRepoArg(value);
32099
- return `${repo.owner}/${repo.repo}`;
32100
- });
32101
- const inventory = await ensureGitHubRepositories({ config: config2, requested, runtime });
32102
- if (!inventory) return;
32103
- const eligible = eligibleRepositoryNames(inventory.repos);
32104
- const selected = selectRepositories({ eligible, requested, runtime });
32105
- if (!selected) return;
32106
- const repositories = await prepareEnvironments(config2, {
32107
- repositories: selected.map((value) => parseRepoArg(value)),
32108
- workspace: options.workspace
32109
- });
32110
- const rejected = repositories.filter((result) => result.status === "forbidden" || result.status === "failed");
32111
- const accepted = repositories.filter((result) => result.status !== "forbidden" && result.status !== "failed");
32112
- if (accepted.length === 0) {
32113
- throw new CliError(
32114
- rejected.some((result) => result.status === "forbidden") ? "auth" : "server",
32115
- `Environment setup was rejected for: ${rejected.map((result) => result.fullName).join(", ")}.`
32116
- );
32117
- }
32118
- const queued = accepted.filter((result) => result.status !== "already_ready");
32119
- const tracking = trackingCommands(queued.map((result) => result.fullName));
32120
- const payload = {
32121
- status: rejected.length > 0 ? "partial" : queued.length > 0 ? "queued" : "ready",
32122
- repositories,
32123
- workspace: options.workspace === true,
32124
- tracking
32125
- };
32126
- emit(void 0, runtime, payload, () => {
32127
- console.log(
32128
- queued.length > 0 ? `Environment preparation started for ${queued.length} ${queued.length === 1 ? "repository" : "repositories"}.` : "The selected environments are ready."
32129
- );
32130
- if (rejected.length > 0) {
32131
- console.log(`Could not start: ${rejected.map((result) => sanitizeTerminalText(result.fullName)).join(", ")}.`);
32132
- }
32133
- for (const line of tracking) console.log(line);
32134
- });
32135
- }
32136
- function eligibleRepositoryNames(repos2) {
32137
- return repos2.filter((repo) => {
32138
- return typeof repo.fullName === "string" && repo.canReview === true;
32139
- }).map((repo) => repo.fullName).sort((a, b) => a.localeCompare(b));
32140
- }
32141
- function selectRepositories(options) {
32142
- const { eligible, requested, runtime } = options;
32143
- if (requested.length === 0) {
32144
- emit(
32145
- void 0,
32146
- runtime,
32147
- {
32148
- status: "selection_required",
32149
- repositories: eligible,
32150
- next: "Rerun with one or more owner/repo arguments."
32151
- },
32152
- () => {
32153
- console.log("Repositories available to prepare:");
32154
- for (const repository of eligible) console.log(` ${sanitizeTerminalText(repository)}`);
32155
- console.log("Rerun with one or more owner/repo arguments.");
32156
- }
32157
- );
32158
- return null;
32159
- }
32160
- const eligibleNames = new Map(eligible.map((repository) => [repository.toLowerCase(), repository]));
32161
- const selected = [
32162
- ...new Map(
32163
- requested.map((repository) => [
32164
- repository.toLowerCase(),
32165
- eligibleNames.get(repository.toLowerCase()) ?? repository
32166
- ])
32167
- ).values()
32168
- ];
32169
- const unavailable = selected.filter((repository) => !eligibleNames.has(repository.toLowerCase()));
32170
- if (unavailable.length > 0) {
32171
- throw new CliError("auth", `Repositories are unavailable or lack write access: ${unavailable.join(", ")}.`);
32172
- }
32173
- return selected;
32174
- }
32175
- async function ensureGitHubRepositories(options) {
32176
- const { config: config2, requested, runtime } = options;
32177
- let inventory = await apiFetch(config2, `${ONBOARDING_REPOS_PATH}?refresh=1`);
32178
- const readOnly = new Set(
32179
- inventory.repos.filter((repo) => repo.canReview !== true && typeof repo.fullName === "string").map((repo) => String(repo.fullName).toLowerCase())
32180
- );
32181
- const inaccessible = requested.filter((repository) => readOnly.has(repository.toLowerCase()));
32182
- if (inaccessible.length > 0) {
32183
- throw new CliError("auth", `Repositories lack write access: ${inaccessible.join(", ")}.`);
32184
- }
32185
- if (inventorySatisfiesRequest(inventory, requested)) return inventory;
32186
- if (inventory.ssoOrgs.length > 0) return emitSsoRequirement({ apiUrl: config2.apiUrl, inventory, runtime });
32187
- const install = await apiFetch(config2, "/api/github/install-url");
32188
- emitSetupAction(runtime, { kind: "install_github", url: install.url });
32189
- if (!runtime.json && !runtime.quiet) console.error("Waiting for GitHub repository access...");
32190
- const deadline = Date.now() + INSTALL_WAIT_TIMEOUT_MS;
32191
- while (Date.now() < deadline) {
32192
- await sleep(INSTALL_POLL_INTERVAL_MS);
32193
- inventory = await apiFetch(config2, ONBOARDING_REPOS_PATH);
32194
- if (inventorySatisfiesRequest(inventory, requested)) return inventory;
32195
- if (inventory.ssoOrgs.length > 0) return emitSsoRequirement({ apiUrl: config2.apiUrl, inventory, runtime });
32196
- }
32197
- throw new CliError("server", "Timed out waiting for GitHub repository access.", {
32198
- hint: `Finish repository selection at ${install.url}, then rerun \`arcanist setup\`.`
32199
- });
32200
- }
32201
- function inventorySatisfiesRequest(inventory, requested) {
32202
- const eligible = new Set(eligibleRepositoryNames(inventory.repos).map((repository) => repository.toLowerCase()));
32203
- if (requested.length > 0) return requested.every((repository) => eligible.has(repository.toLowerCase()));
32204
- return inventory.complete === true && eligible.size > 0;
32205
- }
32206
- function emitSsoRequirement(options) {
32207
- const { apiUrl, inventory, runtime } = options;
32208
- for (const org of inventory.ssoOrgs) {
32209
- if (typeof org.authorizeUrl !== "string") continue;
32210
- const url2 = org.authorizeUrl.startsWith("/") ? `${apiUrl}${org.authorizeUrl}` : org.authorizeUrl;
32211
- emitSetupAction(runtime, {
32212
- kind: "authorize_github_sso",
32213
- url: url2,
32214
- organization: org.login ?? org.orgId
32215
- });
32216
- }
32217
- emit(
32218
- void 0,
32219
- runtime,
32220
- {
32221
- status: "action_required",
32222
- action: "authorize_github_sso",
32223
- repositories: eligibleRepositoryNames(inventory.repos),
32224
- ssoOrgs: inventory.ssoOrgs,
32225
- next: "Authorize GitHub SSO, then rerun arcanist setup."
32226
- },
32227
- () => console.log("Authorize GitHub SSO, then rerun arcanist setup.")
32228
- );
32229
- return null;
32230
- }
32231
- function emitSetupAction(runtime, action) {
32232
- if (runtime.json) process.stderr.write(`${JSON.stringify({ type: "action_required", action })}
32233
- `);
32234
- else process.stderr.write(`Action required: ${action.url}
32235
- `);
32236
- }
32237
- function trackingCommands(repositories) {
32238
- return repositories.map((repository) => `Track later: ${commandForNextAction("wait", repository)}`);
32239
- }
32240
-
32241
- // src/commands/stop.ts
32242
- async function stopCommand(sessionId, options = {}, command) {
32243
- const { config: config2 } = resolveBusinessContext(command, options);
32244
- let status;
32245
- try {
32246
- const response = await apiFetch(config2, `/api/sessions/${sessionId}/stop`, {
32247
- method: "POST"
32248
- });
32249
- status = response.status ?? "stopping";
32250
- } catch (err) {
32251
- const parsed = parseStopBlocked(err);
32252
- if (!parsed) throw err;
32253
- status = parsed.reason;
32254
- }
32255
- emit(command, options, { sessionId, status }, (payload) => {
32256
- if (payload.status === "already_stopped") {
32257
- console.log(`Session ${payload.sessionId} is already stopped.`);
32258
- } else if (payload.status === "stopping" || payload.status === "stopped") {
32259
- console.log(`Stop requested for session ${payload.sessionId}.`);
32260
- } else {
32261
- console.log(`Session ${payload.sessionId} cannot be stopped from phase=${payload.status}.`);
32262
- }
32263
- });
32264
- }
32265
- function parseStopBlocked(err) {
32266
- const body = parseConflictBody(err);
32267
- if (!body || body.error !== "session_not_stoppable") return null;
32268
- return { reason: typeof body.reason === "string" ? body.reason : "not_stoppable" };
32269
- }
32270
-
32271
- // src/commands/test-creds.ts
32272
- import { readFileSync as readFileSync4 } from "fs";
32273
- function basePath(businessId, repo) {
32274
- return `/api/businesses/${encodeURIComponent(businessId)}/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.repo)}/test-credentials`;
32275
- }
32276
- function readValue(options) {
32277
- const sources = [options.value, options.valueFile, options.valueStdin].filter((v) => v !== void 0 && v !== false);
32278
- if (sources.length !== 1) {
32279
- throw new CliError("user", "Provide exactly one of --value, --value-file, or --value-stdin.");
32280
- }
32281
- if (options.value !== void 0) return options.value;
32282
- const raw = options.valueFile ? readFileSync4(options.valueFile, "utf8") : readFileSync4(0, "utf8");
32283
- return raw.replace(/\r?\n$/, "");
32284
- }
32285
- async function listTestCredentialsCommand(repo, options, command) {
32286
- const { config: config2 } = resolveBusinessContext(command, options);
32287
- const repoArg = parseRepoArg(repo);
32288
- const payload = await apiFetch(config2, basePath(options.business, repoArg));
32289
- emit(command, options, payload, (listPayload) => {
32290
- if (listPayload.credentials.length === 0) {
32291
- console.log(`No test credentials configured for ${repoArg.owner}/${repoArg.repo}.`);
32292
- return;
32293
- }
32294
- for (const cred of listPayload.credentials) {
32295
- const ts = new Date(cred.updatedAt).toISOString();
32296
- console.log(`${cred.name} ${ts} ${cred.rotatedByUserId ?? "-"}`);
32297
- }
32298
- });
32299
- }
32300
- async function setTestCredentialCommand(repo, name, options, command) {
32301
- const { config: config2 } = resolveBusinessContext(command, options);
32302
- const repoArg = parseRepoArg(repo);
32303
- const value = readValue(options);
32304
- const payload = await apiFetch(
32305
- config2,
32306
- `${basePath(options.business, repoArg)}/${encodeURIComponent(name)}`,
32307
- { method: "PUT", body: JSON.stringify({ value }) }
32308
- );
32309
- emit(
32310
- command,
32311
- options,
32312
- payload,
32313
- () => console.log(`Set test credential '${name}' for ${repoArg.owner}/${repoArg.repo}.`)
32314
- );
32315
- }
32316
- async function deleteTestCredentialCommand(repo, name, options, command) {
32317
- if (isJson(command, options) && options.yes !== true) {
32318
- throw new CliError("user", "`test-creds delete --json` requires --yes.");
32319
- }
32320
- if (options.yes !== true) {
32321
- const repoArg2 = parseRepoArg(repo);
32322
- await confirmOrThrow(`Delete test credential '${name}' for ${repoArg2.owner}/${repoArg2.repo}?`);
32323
- }
32324
- const { config: config2 } = resolveBusinessContext(command, options);
32325
- const repoArg = parseRepoArg(repo);
32326
- const payload = await apiFetch(
32327
- config2,
32328
- `${basePath(options.business, repoArg)}/${encodeURIComponent(name)}`,
32329
- { method: "DELETE" }
32330
- );
32331
- emit(
32332
- command,
32333
- options,
32334
- payload,
32335
- () => console.log(`Deleted test credential '${name}' for ${repoArg.owner}/${repoArg.repo}.`)
32336
- );
32337
- }
32338
-
32339
- // src/commands/tokens.ts
32340
- async function listTokensCommand(options, command) {
32341
- const { config: config2 } = resolveBusinessContext(command, options);
32342
- const { items: data, nextCursor } = await fetchPages(
32343
- "tokens list --all",
32344
- options.all === true,
32345
- options.cursor,
32346
- async (cursor) => {
32347
- const query = new URLSearchParams();
32348
- if (options.limit) query.set("limit", options.limit);
32349
- if (cursor) query.set("cursor", cursor);
32350
- const payload2 = await apiFetch(
32351
- config2,
32352
- `/api/cli-tokens${query.size ? `?${query.toString()}` : ""}`
32353
- );
32354
- return { items: payload2.data, nextCursor: payload2.nextCursor };
32355
- }
32356
- );
32357
- const payload = { data, nextCursor };
32358
- emit(command, options, payload, (listPayload) => {
32359
- if (listPayload.data.length === 0) {
32360
- console.log("No CLI tokens found.");
32361
- return;
32362
- }
32363
- for (const token of listPayload.data) {
32364
- console.log(
32365
- `${String(token.id)} ${String(token.scope)} ${String(token.tokenPrefix)} ${String(token.revokedAt ? "revoked" : "active")}`
32366
- );
32367
- }
32368
- if (listPayload.nextCursor) console.log(`Next cursor: ${listPayload.nextCursor}`);
32369
- });
32370
- }
32371
- async function createTokenCommand(options, command) {
32372
- const { config: config2 } = resolveBusinessContext(command, options);
32373
- const expiresInDays = options.expiresInDays === void 0 ? void 0 : Number(options.expiresInDays);
32374
- if (expiresInDays !== void 0 && (!Number.isInteger(expiresInDays) || expiresInDays <= 0)) {
32375
- throw new CliError("user", "expiresInDays must be a positive integer.");
32376
- }
32377
- let payload;
32378
- try {
32379
- payload = await apiFetch(config2, "/api/cli-tokens", {
32380
- method: "POST",
32381
- headers: { "Idempotency-Key": options.idempotencyKey ?? randomIdempotencyKey() },
32382
- body: JSON.stringify({
32383
- scope: options.scope ?? "read",
32384
- ...expiresInDays !== void 0 ? { expiresInDays } : {}
32385
- })
32386
- });
32387
- } catch (err) {
32388
- if (err instanceof ApiError && err.status === 409 && err.data?.serverCode === "duplicate_request") {
32389
- throw new CliError(err.code, err.message, {
32390
- exitCode: err.exitCode,
32391
- requestId: err.requestId,
32392
- data: err.data,
32393
- hint: "Use a new --idempotency-key for a new token, or retry the original command only if the first result was lost."
32394
- });
32395
- }
32396
- throw err;
32397
- }
32398
- emit(command, options, payload, (tokenPayload) => {
32399
- console.log(`Token: ${String(tokenPayload.token)}`);
32400
- console.log(`ID: ${String(tokenPayload.id)}`);
32401
- console.log(`Scope: ${String(tokenPayload.scope)}`);
32402
- });
32403
- }
32404
- async function revokeTokenCommand(tokenId, options, command) {
32405
- if (isJson(command, options) && options.yes !== true) {
32406
- throw new CliError("user", "`tokens revoke --json` requires --yes.");
32407
- }
32408
- if (options.yes !== true) {
32409
- await confirmOrThrow(`Revoke CLI token ${tokenId}?`);
32410
- }
32411
- const { config: config2 } = resolveBusinessContext(command, options);
32412
- const payload = await apiFetch(config2, `/api/cli-tokens/${tokenId}/revoke`, {
32413
- method: "POST"
32414
- });
32415
- emit(command, options, payload, () => console.log(`Revoked token ${tokenId}.`));
32416
- }
32417
31398
 
32418
31399
  // src/commands/transcript.ts
32419
31400
  async function transcriptCommand(sessionId, options, command) {
@@ -32488,11 +31469,10 @@ program.hook("preAction", (_thisCommand, actionCommand) => {
32488
31469
  applyColorEnvironment(getRuntimeOptions(actionCommand));
32489
31470
  });
32490
31471
  var auth = program.command("auth").description("Authentication commands");
32491
- auth.command("login").description("Authenticate through your browser").option("--token-stdin", "Read token from stdin instead of interactive prompt").addHelpText(
31472
+ auth.command("login").description("Store a CLI token minted in Settings > CLI").option("--token-stdin", "Read token from stdin").addHelpText(
32492
31473
  "after",
32493
31474
  `
32494
31475
  Examples:
32495
- arcanist auth login
32496
31476
  printf "arc_..." | arcanist auth login --token-stdin
32497
31477
  ARCANIST_TOKEN=arc_... arcanist auth whoami --json
32498
31478
  `
@@ -32507,21 +31487,6 @@ Examples:
32507
31487
  ARCANIST_TOKEN=arc_... arcanist auth whoami --json
32508
31488
  `
32509
31489
  ).action((options, command) => whoamiCommand(options, command));
32510
- program.command("setup").description("Connect GitHub repositories and prepare their Zeus environments").argument("[repos...]", "Repositories in owner/name form").option("--workspace", "Prepare the repositories as one multi-repository workspace").addHelpText(
32511
- "after",
32512
- `
32513
- Examples:
32514
- arcanist setup
32515
- arcanist setup acme/web
32516
- arcanist setup acme/web acme/api --json
32517
- arcanist setup acme/web acme/api --workspace
32518
-
32519
- With no repositories, the command returns the eligible repositories. Rerun with the
32520
- repositories to prepare. By default each repository gets its own environment; use
32521
- --workspace to prepare them together. Builds continue after the command exits; use
32522
- the emitted environment status commands to track them.
32523
- `
32524
- ).action((repos2, options, command) => setupCommand(repos2, options, command));
32525
31490
  var codex = program.command("codex").description("Codex subscription (bring-your-own-subscription) commands");
32526
31491
  codex.command("login").description("Authenticate a Codex/ChatGPT subscription and store it for your Codex sessions").option("--codex-path <path>", "Path to the codex executable (default: codex on PATH, or ARCANIST_CODEX_BIN)").addHelpText(
32527
31492
  "after",
@@ -32537,32 +31502,19 @@ Examples:
32537
31502
  ).action((options, command) => codexLoginCommand(options, command));
32538
31503
  codex.command("use <state>").description("Turn using your saved Codex subscription auth for OpenAI sessions on or off (state: on|off)").action((state, options, command) => codexUseCommand(state, options, command));
32539
31504
  codex.command("status").description("Show Codex subscription availability and whether auth is saved").action((options, command) => codexStatusCommand(options, command));
32540
- codex.command("logout").description("Deactivate the selector and remove the stored Codex subscription auth for your user").action((options, command) => codexLogoutCommand(options, command));
32541
31505
  var sessions = program.command("sessions").description("Session commands");
32542
- program.command("review <pr-url>").description("Run a Zeus review for a GitHub pull request").option("--sandbox-runtime <runtime>", "Force the sandbox runtime (Arcanist members only): modal, e2b, or sailbox").option("--wait", "Wait until the review is published, cancelled, or admission-rejected").option("--poll-interval <ms>", "Polling interval in milliseconds while waiting (default 10000)").addHelpText(
31506
+ program.command("review <pr-url>").description("Run an Arcanist review for a GitHub pull request").option("--sandbox-runtime <runtime>", "Force the sandbox runtime (Arcanist members only): modal or e2b").option("--wait", "Wait until the review is published, cancelled, or admission-rejected").option("--poll-interval <ms>", "Polling interval in milliseconds while waiting (default 10000)").addHelpText(
32543
31507
  "after",
32544
31508
  `
32545
31509
  Examples:
32546
31510
  arcanist review https://github.com/org/repo/pull/123 --wait --json
32547
- arcanist review https://github.com/org/repo/pull/123 --sandbox-runtime sailbox --wait
31511
+ arcanist review https://github.com/org/repo/pull/123 --sandbox-runtime modal --wait
32548
31512
 
32549
31513
  --wait returns when the review is published, cancelled, or admission-rejected. The 30-minute timeout applies only while status is in_flight.
32550
- Zeus always uses the pinned Codex reviewer.
32551
- Zeus review is currently available to Arcanist members only.
31514
+ Arcanist always uses the pinned Codex reviewer.
31515
+ Arcanist review is currently available to Arcanist members only.
32552
31516
  `
32553
31517
  ).action((prUrl, options, command) => reviewCommand(prUrl, options, command));
32554
- sessions.command("stop").description("Stop the active run for a session").argument("<session-id>", "Session ID").addHelpText(
32555
- "after",
32556
- `
32557
- Examples:
32558
- arcanist sessions stop <session-id>
32559
- arcanist sessions stop <session-id> --json
32560
-
32561
- JSON:
32562
- JSON mode returns {sessionId, status}
32563
- status is the server stop status or 409 stop-block reason. Known values include stopping, stopped, already_stopped, not_stoppable, and lifecycle phase names.
32564
- `
32565
- ).action((sessionId, options, command) => stopCommand(sessionId, options, command));
32566
31518
  sessions.command("get").description("Get session details").argument("<session-id>", "Session ID").addHelpText(
32567
31519
  "after",
32568
31520
  `
@@ -32587,13 +31539,12 @@ JSON:
32587
31539
  JSON mode returns {sessions, nextCursor}. --all follows cursors until nextCursor is null.
32588
31540
  `
32589
31541
  ).action((options, command) => listSessionsCommand(options, command));
32590
- sessions.command("events").description("Read or follow session replay events").argument("<session-id>", "Session ID").option("--after-sequence <n>", "Return events after this sequence").option("--after <n>", "Alias for --after-sequence").option("--before-sequence <n>", "Return events before this sequence").option("--before <n>", "Alias for --before-sequence").option("--prompt-id <id>", "Filter events by prompt ID").option("--limit <n>", "Maximum events to return").option("--follow", "Follow events until a terminal phase").option("--poll-interval <ms>", "Polling interval in milliseconds", String(DEFAULT_WATCH_POLL_INTERVAL_MS)).option("--timeout <ms>", "Stop following after this many milliseconds; 0 or omitted means unbounded").addHelpText(
31542
+ sessions.command("events").description("Read or follow session replay events").argument("<session-id>", "Session ID").option("--after-sequence <n>", "Return events after this sequence").option("--before-sequence <n>", "Return events before this sequence").option("--prompt-id <id>", "Filter events by prompt ID").option("--limit <n>", "Maximum events to return").option("--follow", "Follow events until a terminal phase").option("--poll-interval <ms>", "Polling interval in milliseconds", String(DEFAULT_WATCH_POLL_INTERVAL_MS)).option("--timeout <ms>", "Stop following after this many milliseconds; 0 or omitted means unbounded").addHelpText(
32591
31543
  "after",
32592
31544
  `
32593
31545
  Examples:
32594
31546
  arcanist sessions events <session-id> --json
32595
31547
  arcanist sessions events <session-id> --after-sequence 540 --limit 250 --json
32596
- arcanist sessions events <session-id> --after 540 --limit 250 --json
32597
31548
  arcanist sessions events <session-id> --follow --json
32598
31549
  arcanist sessions events <session-id> --follow --timeout 60000 --json
32599
31550
 
@@ -32613,14 +31564,6 @@ Examples:
32613
31564
  arcanist sessions transcript <session-id> --last 20
32614
31565
  `
32615
31566
  ).action((sessionId, options, command) => transcriptCommand(sessionId, options, command));
32616
- sessions.command("usage").description("Get token usage for a session").argument("<session-id>", "Session ID").addHelpText(
32617
- "after",
32618
- `
32619
- Examples:
32620
- arcanist sessions usage <session-id>
32621
- arcanist sessions usage <session-id> --json
32622
- `
32623
- ).action((sessionId, options, command) => usageCommand(sessionId, options, command));
32624
31567
  var artifacts = sessions.command("artifacts").description("List or download session artifacts (screenshots, videos)").argument("<session-id>", "Session ID").option("-o, --output <dir>", "Directory to write downloads into; defaults to the current directory").option("--all", "Download every artifact for the session instead of listing them").addHelpText(
32625
31568
  "after",
32626
31569
  `
@@ -32652,15 +31595,8 @@ JSON:
32652
31595
  JSON mode returns {repos, ssoOrgs}
32653
31596
  `
32654
31597
  ).action((options, command) => reposListCommand(options, command));
32655
- repos.command("branches").description("List repository branches").argument("[repo]", "Repository owner/name; defaults to current git remote").addHelpText(
32656
- "after",
32657
- `
32658
- JSON:
32659
- JSON mode returns {branches}
32660
- `
32661
- ).action((repo, options, command) => repoBranchesCommand(repo, options, command));
32662
- var environments = program.command("environments").alias("env").description("Zeus environment setup commands");
32663
- environments.command("prepare").description("Prepare a repository for Zeus reviews").argument("[repo]", "Repository owner/name; defaults to current git remote").option("--wait", "Wait until setup is ready or needs attention").option("--poll-interval <ms>", "Polling interval while waiting (default 5000)").addHelpText(
31598
+ var environments = program.command("environments").alias("env").description("Arcanist environment setup commands");
31599
+ environments.command("prepare").description("Prepare a repository for Arcanist reviews").argument("[repo]", "Repository owner/name; defaults to current git remote").option("--wait", "Wait until setup is ready or needs attention").option("--poll-interval <ms>", "Polling interval while waiting (default 5000)").addHelpText(
32664
31600
  "after",
32665
31601
  `
32666
31602
  Examples:
@@ -32680,7 +31616,7 @@ The stable status values are not_started, pending, running, escalated, succeeded
32680
31616
  `
32681
31617
  ).action((repo, options, command) => environmentStatusCommand(repo, options, command));
32682
31618
  environments.command("retry").description("Retry a failed repository environment setup").argument("[repo]", "Repository owner/name; defaults to current git remote").option("--wait", "Wait until setup is ready or needs attention").option("--poll-interval <ms>", "Polling interval while waiting (default 5000)").action((repo, options, command) => environmentRetryCommand(repo, options, command));
32683
- environments.command("login").description("Log into your app once in a local browser and save the login for Zeus sessions").argument("[environment]", "Environment id or repository owner/name; defaults to current git remote").option("--url <url>", "Page to open first instead of the app runtime URL").option("--validate-path <path>", "Signed-in-only page Arcanist opens to verify the login, e.g. /dashboard").option("--yes", "Add captured provider hosts to trusted domains without asking").option("--no-network-policy", "Do not touch the environment's trusted domains").option("--clear", "Remove the saved browser login").addHelpText(
31619
+ environments.command("login").description("Log into your app once in a local browser and save the login for Arcanist sessions").argument("[environment]", "Environment id or repository owner/name; defaults to current git remote").option("--url <url>", "Page to open first instead of the app runtime URL").option("--validate-path <path>", "Signed-in-only page Arcanist opens to verify the login, e.g. /dashboard").option("--yes", "Add captured provider hosts to trusted domains without asking").option("--no-network-policy", "Do not touch the environment's trusted domains").option("--clear", "Remove the saved browser login").addHelpText(
32684
31620
  "after",
32685
31621
  `
32686
31622
  Examples:
@@ -32693,14 +31629,6 @@ JSON mode returns {environmentId, version, counts: {cookieDomainCount, storageOr
32693
31629
  With --clear it returns {environmentId, deleted}. Instructions and prompts go to stderr; the capture path needs an interactive terminal (pass --yes --validate-path to skip the other prompts).
32694
31630
  `
32695
31631
  ).action((environment, options, command) => environmentLoginCommand(environment, options, command));
32696
- var models = program.command("models").description("Model discovery commands");
32697
- models.command("list").description("List session-start models").addHelpText(
32698
- "after",
32699
- `
32700
- JSON:
32701
- JSON mode returns {models}
32702
- `
32703
- ).action((options, command) => modelsListCommand(options, command));
32704
31632
  var sandbox = program.command("sandbox").description("Sandbox layer commands");
32705
31633
  sandbox.command("init").description("Create sandbox layer source files").action((options, command) => sandboxInitCommand(options, command));
32706
31634
  sandbox.command("validate").description("Validate local sandbox layer source files").option("--manifest <path>", "Manifest path", ".arcanist/sandbox.yaml").action((options, command) => sandboxValidateCommand(options, command));
@@ -32715,61 +31643,6 @@ JSON:
32715
31643
  {type:"build", buildRequest:{...}}
32716
31644
  `
32717
31645
  ).action((sourceRepo, options, command) => sandboxBuildCommand(sourceRepo, options, command));
32718
- sandbox.command("status").description("Show sandbox layer status").argument("[repo]", "Repository owner/name; defaults to current git remote").option("--business <id>", "Business ID; defaults to authenticated user's business").action((repo, options, command) => sandboxStatusCommand(repo, options, command));
32719
- sandbox.command("history").description("Show sandbox layer build history").argument("[source-repo]", "Source repository owner/name; defaults to current git remote").option("--business <id>", "Business ID; defaults to authenticated user's business").option("--target-repo <repo>", "Target repository owner/name for resource-profile coverage").option("--status <status>", "Filter by build status").option("--limit <n>", "Maximum builds to return").action((sourceRepo, options, command) => sandboxHistoryCommand(sourceRepo, options, command));
32720
- sandbox.command("logs").description("Print sandbox layer build logs").argument("<build-id>", "Build ID").option("--business <id>", "Business ID; defaults to authenticated user's business").option("--follow", "Follow logs").option("--after-sequence <n>", "Return logs after this sequence").option("--limit <n>", "Maximum log chunks to return").option("--poll-interval <ms>", "Polling interval in milliseconds").addHelpText(
32721
- "after",
32722
- `
32723
- JSON:
32724
- Without --follow, --json prints one object: {ok, logs}
32725
- With --follow, --json emits {type:"logs"} lines, then a terminal {type:"build"} line, and exits.
32726
- `
32727
- ).action((buildId, options, command) => sandboxLogsCommand(buildId, options, command));
32728
- sandbox.command("rebuild-stale").description("Create stale sandbox template rebuild campaigns").option("--business <id>", "Business ID to scan; defaults to authenticated user's business").option("--all", "Scan all businesses").option("--dry-run", "Preview stale rebuilds without queueing provider builds").option("--yes", "Start a rebuild campaign").option("--follow", "Follow campaign progress").action((options, command) => sandboxRebuildStaleCommand(options, command));
32729
- var sandboxAssign = sandbox.command("assign").description("Assign sandbox layer sources");
32730
- sandboxAssign.command("default").description("Set the business default sandbox layer source").argument("<source-repo>", "Source repository owner/name").option("--manifest <path>", "Manifest path", ".arcanist/sandbox.yaml").option("--business <id>", "Business ID; defaults to authenticated user's business").action((sourceRepo, options, command) => sandboxAssignDefaultCommand(sourceRepo, options, command));
32731
- sandboxAssign.command("repo").description("Assign a sandbox layer source to one target repo").argument("<target-repo>", "Target repository owner/name").argument("<source-repo>", "Source repository owner/name").option("--manifest <path>", "Manifest path", ".arcanist/sandbox.yaml").option("--business <id>", "Business ID; defaults to authenticated user's business").action(
32732
- (targetRepo, sourceRepo, options, command) => sandboxAssignRepoCommand(targetRepo, sourceRepo, options, command)
32733
- );
32734
- var sandboxUnassign = sandbox.command("unassign").description("Remove sandbox layer source assignments");
32735
- sandboxUnassign.command("default").description("Clear the business default sandbox layer source").option("--business <id>", "Business ID; defaults to authenticated user's business").option("--yes", "Skip confirmation").action((options, command) => sandboxUnassignDefaultCommand(options, command));
32736
- sandboxUnassign.command("repo").description("Clear a target repo sandbox layer assignment").argument("<target-repo>", "Target repository owner/name").option("--business <id>", "Business ID; defaults to authenticated user's business").option("--yes", "Skip confirmation").action((targetRepo, options, command) => sandboxUnassignRepoCommand(targetRepo, options, command));
32737
- var egress = program.command("egress").description("Workspace egress allowlist commands");
32738
- var egressSource = egress.command("source").description("Manage the workspace egress allowlist source repo");
32739
- egressSource.command("set").description("Set the source repo for .arcanist/egress-allowlist.txt").argument("<source-repo>", "Source repository owner/name").option("--business <id>", "Business ID; defaults to authenticated user's business").action((sourceRepo, options, command) => egressSourceSetCommand(sourceRepo, options, command));
32740
- egressSource.command("get").description("Show the configured egress allowlist source").option("--business <id>", "Business ID; defaults to authenticated user's business").action((options, command) => egressSourceGetCommand(options, command));
32741
- egress.command("add").description("Open or update a PR adding domains to the egress allowlist source file").argument("<domains...>", "Exact domain names to add").option("--business <id>", "Business ID; defaults to authenticated user's business").option("--reason <text>", "Reason to include in the PR body").action((domains, options, command) => egressAddCommand(domains, options, command));
32742
- egress.command("sync").description("Apply the merged egress allowlist source file to the runtime business policy").option("--business <id>", "Business ID; defaults to authenticated user's business").action((options, command) => egressSyncCommand(options, command));
32743
- egress.command("validate").description("Validate a local egress allowlist source file").argument("[path]", "Path to validate", ".arcanist/egress-allowlist.txt").action((path, options, command) => egressValidateCommand(path, options, command));
32744
- var tokens = program.command("tokens").description("CLI token commands");
32745
- tokens.command("list").description("List CLI tokens").option("--limit <n>", "Maximum tokens to return").option("--cursor <cursor>", "Pagination cursor").option("--all", "Fetch all pages").action((options, command) => listTokensCommand(options, command));
32746
- tokens.command("create").description("Create a CLI token and print it once").option("--scope <scope>", "Token scope: read or write", "read").option("--expires-in-days <days>", "Token expiry in days").option("--idempotency-key <uuid>", "Request idempotency key for safe manual retries").addHelpText(
32747
- "after",
32748
- `
32749
- Examples:
32750
- arcanist tokens create --scope read
32751
- arcanist tokens create --scope read --json
32752
- `
32753
- ).action((options, command) => createTokenCommand(options, command));
32754
- tokens.command("revoke").description("Revoke a CLI token").argument("<id>", "Token ID").option("--yes", "Confirm revocation without prompting").addHelpText(
32755
- "after",
32756
- `
32757
- Examples:
32758
- arcanist tokens revoke 42
32759
- arcanist tokens revoke 42 --yes --json
32760
- `
32761
- ).action((id, options, command) => revokeTokenCommand(id, options, command));
32762
- var testCreds = program.command("test-creds").description("Manage repo-scoped test credentials referenced by the Environment App Runtime Profile");
32763
- testCreds.command("list").description("List test credentials configured for a repo").argument("<repo>", "Repository in 'owner/name' form").requiredOption("--business <id>", "Business ID that owns the repo").action((repo, options, command) => listTestCredentialsCommand(repo, options, command));
32764
- testCreds.command("set").description("Set or rotate a test credential value").argument("<repo>", "Repository in 'owner/name' form").argument("<name>", "Credential name (must match the Environment profile's credentials[].name)").requiredOption("--business <id>", "Business ID that owns the repo").option("--value <value>", "Credential value (insecure on shared shells; prefer --value-stdin)").option("--value-file <path>", "Read value from a file").option("--value-stdin", "Read value from stdin").addHelpText(
32765
- "after",
32766
- `
32767
- Examples:
32768
- echo "$E2E_PASS" | arcanist test-creds set tryarcanist/arcanist test_user_password --business biz-1 --value-stdin
32769
- arcanist test-creds set tryarcanist/arcanist test_user_email --business biz-1 --value-file ./email.txt
32770
- `
32771
- ).action((repo, name, options, command) => setTestCredentialCommand(repo, name, options, command));
32772
- testCreds.command("delete").description("Delete a test credential").argument("<repo>", "Repository in 'owner/name' form").argument("<name>", "Credential name").requiredOption("--business <id>", "Business ID that owns the repo").option("--yes", "Confirm deletion without prompting").action((repo, name, options, command) => deleteTestCredentialCommand(repo, name, options, command));
32773
31646
  program.command("help").description("display help for command").argument("[path...]", "Command path to explain").action((path) => {
32774
31647
  commandForHelpPath(program, path).help({ error: false });
32775
31648
  });