@tryarcanist/cli 0.1.298 → 0.1.299

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 +5 -17
  2. package/dist/index.js +103 -153
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Arcanist CLI
2
2
 
3
- Command-line interface for [Arcanist](https://www.tryarcanist.com): run Anubis QA verifications, follow session output, and manage access tokens from your terminal or automation.
3
+ Command-line interface for [Arcanist](https://www.tryarcanist.com): follow session output and manage access tokens from your terminal or automation.
4
4
 
5
5
  ## Install
6
6
 
@@ -14,12 +14,12 @@ Requires Node.js 22 or newer.
14
14
 
15
15
  ```bash
16
16
  arcanist auth login # paste a token from Settings > CLI
17
- arcanist anubis https://github.com/your-org/your-repo/pull/123 --wait --json
17
+ arcanist sessions list --json
18
18
  ```
19
19
 
20
20
  ```bash
21
- export ARCANIST_TOKEN=arc_... # write-scoped for anubis/stop
22
- SESSION_ID=$(arcanist anubis your-org/your-repo/pull/123 --json | jq -r .sessionId)
21
+ export ARCANIST_TOKEN=arc_... # write-scoped for session mutations
22
+ SESSION_ID=$(arcanist sessions list --json | jq -r '.sessions[0].id')
23
23
  arcanist sessions events "$SESSION_ID" --follow --json
24
24
  ```
25
25
 
@@ -351,24 +351,12 @@ arcanist tokens revoke 42 --yes --json
351
351
 
352
352
  Under `--json`, `--yes` is required.
353
353
 
354
- ### `arcanist anubis <pr-url>`
355
-
356
- Runs an Anubis QA verification on a pull request: Anubis boots your app and a browser in a sandbox, tests the PR's changed behavior through the running product, and posts the verdict as a PR comment marked `arcanist-anubis:v1`. Needs a write-scoped token and write access to the repository.
357
-
358
- ```bash
359
- arcanist anubis https://github.com/your-org/your-repo/pull/123 --wait --json
360
- ```
361
-
362
- Without `--wait`, JSON mode returns `{sessionId, claimed? | duplicate?}`; with `--wait`, `{sessionId, sessionStatus, verdict, summary?}`. Known verdicts are `works`, `broken`, and `could-not-verify`.
363
- `--poll-interval <ms>` overrides the 15s status polling cadence while waiting.
364
- Active runs are deduplicated server-side, so the idempotency-key option does not apply.
365
-
366
354
  ## Automation recipes
367
355
 
368
356
  Chain commands with `--json` and `jq`:
369
357
 
370
358
  ```bash
371
- SESSION_ID=$(arcanist anubis your-org/your-repo/pull/123 --json | jq -r .sessionId)
359
+ SESSION_ID=$(arcanist sessions list --json | jq -r '.sessions[0].id')
372
360
  arcanist sessions events "$SESSION_ID" --follow --json | jq -r 'select(.type == "assistant_message")'
373
361
  ```
374
362
 
package/dist/index.js CHANGED
@@ -7364,6 +7364,10 @@ var require_dist = __commonJS({
7364
7364
  import { createRequire as createRequire2 } from "module";
7365
7365
  import { Command } from "commander";
7366
7366
 
7367
+ // src/commands/artifacts.ts
7368
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
7369
+ import { basename, join as join2, resolve as resolve2 } from "path";
7370
+
7367
7371
  // src/api.ts
7368
7372
  import { createRequire } from "module";
7369
7373
 
@@ -7629,10 +7633,6 @@ async function resolveBusinessId(config, options) {
7629
7633
  throw new CliError("user", "--business is required when the authenticated token has no business context.");
7630
7634
  }
7631
7635
 
7632
- // src/runtime.ts
7633
- import { randomUUID } from "crypto";
7634
- import { createInterface } from "readline/promises";
7635
-
7636
7636
  // src/config.ts
7637
7637
  import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
7638
7638
  import { homedir } from "os";
@@ -7824,6 +7824,8 @@ function isLoopbackHost(parsed) {
7824
7824
  }
7825
7825
 
7826
7826
  // src/runtime.ts
7827
+ import { randomUUID } from "crypto";
7828
+ import { createInterface } from "readline/promises";
7827
7829
  var ASCII_FIRST_PRINTABLE = 32;
7828
7830
  var ASCII_DELETE = 127;
7829
7831
  var ANSI_CONTROL_SEQUENCE = /\u001b\[[0-9:;<=>?]*[ -/]*[@-~]/g;
@@ -7999,137 +8001,12 @@ function isControlCharacter(char) {
7999
8001
  return code < ASCII_FIRST_PRINTABLE || code === ASCII_DELETE;
8000
8002
  }
8001
8003
 
8002
- // ../../shared/utils/timing.ts
8003
- function sleep(ms) {
8004
- return new Promise((resolve3) => setTimeout(resolve3, ms));
8005
- }
8006
-
8007
- // src/status-poll.ts
8008
- async function pollUntilSettled(opts) {
8009
- const deadline = Date.now() + opts.timeoutMs;
8010
- while (Date.now() < deadline) {
8011
- const settled = await opts.fetchStatus();
8012
- if (settled !== null) return settled;
8013
- await sleep(opts.pollIntervalMs);
8014
- }
8015
- throw new CliError("server", opts.timeoutMessage);
8016
- }
8017
- function assertSameAttempt(triggeredSessionId, settledSessionId, label) {
8018
- if (triggeredSessionId && settledSessionId !== triggeredSessionId) {
8019
- throw new CliError("conflict", `${label} completed for a different attempt; retry the command.`);
8020
- }
8021
- }
8022
-
8023
- // ../../shared/session/phase.ts
8024
- var PHASES = [
8025
- "idle",
8026
- "running",
8027
- "review_starting",
8028
- "waiting_for_input",
8029
- "finalizing",
8030
- "review_listening",
8031
- "completed",
8032
- "superseded",
8033
- "needs_you",
8034
- "blocked",
8035
- "failed",
8036
- "stopped",
8037
- "archived"
8038
- ];
8039
- var TERMINAL_PHASES_ARRAY = [
8040
- "completed",
8041
- "superseded",
8042
- "needs_you",
8043
- "blocked",
8044
- "failed",
8045
- "stopped",
8046
- "archived"
8047
- ];
8048
- var TERMINAL_PHASES = new Set(TERMINAL_PHASES_ARRAY);
8049
- var TERMINAL_FOR_FALLBACK_POLLING_PHASES = new Set(
8050
- TERMINAL_PHASES_ARRAY.filter((phase) => phase !== "completed")
8051
- );
8052
- var CHILD_SLOT_RELEASE_PHASES = new Set(
8053
- TERMINAL_PHASES_ARRAY.filter((phase) => phase !== "stopped")
8054
- );
8055
- var ARCHIVABLE_STALE_TERMINAL_PHASES_ARRAY = TERMINAL_PHASES_ARRAY.filter(
8056
- (phase) => phase !== "archived" && phase !== "needs_you" && phase !== "blocked" && phase !== "stopped"
8057
- );
8058
- function isTerminalPhase(phase, _sessionKind) {
8059
- return TERMINAL_PHASES.has(phase);
8060
- }
8061
-
8062
- // src/constants/watch.ts
8063
- var MIN_WATCH_POLL_INTERVAL_MS = 250;
8064
- var DEFAULT_WATCH_POLL_INTERVAL_MS = 1e3;
8065
- var MAX_WATCH_POLL_INTERVAL_MS = 6e4;
8066
- var WATCH_REPLAY_PAGE_SIZE = 200;
8067
- function isWatchTerminal(phase) {
8068
- return isTerminalPhase(phase);
8069
- }
8070
-
8071
- // src/utils/poll-interval.ts
8072
- function parsePollInterval(raw, opts = {}) {
8073
- const defaultMs = opts.defaultMs ?? DEFAULT_WATCH_POLL_INTERVAL_MS;
8074
- const minMs = opts.minMs ?? MIN_WATCH_POLL_INTERVAL_MS;
8075
- const maxMs = opts.maxMs === void 0 ? MAX_WATCH_POLL_INTERVAL_MS : opts.maxMs;
8076
- if (!raw) return defaultMs;
8077
- if (!/^\d+$/.test(raw)) {
8078
- throw new CliError("user", "Polling interval must be a non-negative integer.");
8079
- }
8080
- const value = Number(raw);
8081
- if (value < minMs || maxMs !== null && value > maxMs) {
8082
- const range = maxMs === null ? `at least ${minMs}` : `between ${minMs} and ${maxMs}`;
8083
- throw new CliError("user", `Polling interval must be ${range} milliseconds.`);
8084
- }
8085
- return value;
8086
- }
8087
-
8088
8004
  // src/utils/terminal-text.ts
8089
8005
  function sanitizeTerminalText(value) {
8090
8006
  return value.replace(/\u001b\][^\u0007]*(?:\u0007|\u001b\\)/g, "").replace(/\u001b\[[0-?]*[ -\/]*[@-~]/g, "").replace(/[\u0000-\u001f\u007f]/g, "");
8091
8007
  }
8092
8008
 
8093
- // src/commands/anubis.ts
8094
- var ANUBIS_POLL_INTERVAL_MS = 15e3;
8095
- var ANUBIS_WAIT_TIMEOUT_MS = 45 * 60 * 1e3;
8096
- async function anubisCommand(prUrl, options = {}, command) {
8097
- assertArcanistSessionMutationAllowed("anubis");
8098
- const pollIntervalMs = options.wait ? parsePollInterval(options.pollInterval, { defaultMs: ANUBIS_POLL_INTERVAL_MS, maxMs: null }) : null;
8099
- const { config } = resolveBusinessContext(command, options);
8100
- const trigger = await apiFetch(config, "/api/anubis-runs", {
8101
- method: "POST",
8102
- body: JSON.stringify({ prUrl })
8103
- });
8104
- if (!options.wait) {
8105
- emit(command, options, trigger, (payload) => {
8106
- console.log(`${payload.duplicate ? "Attached to existing" : "Started"} Anubis run (${payload.sessionId}).`);
8107
- });
8108
- return;
8109
- }
8110
- const result = await pollUntilSettled({
8111
- pollIntervalMs: pollIntervalMs ?? ANUBIS_POLL_INTERVAL_MS,
8112
- timeoutMs: ANUBIS_WAIT_TIMEOUT_MS,
8113
- timeoutMessage: "Timed out waiting for Anubis results.",
8114
- fetchStatus: async () => {
8115
- const status = await apiFetch(
8116
- config,
8117
- `/api/anubis-runs/status?prUrl=${encodeURIComponent(prUrl)}`
8118
- );
8119
- if (status.sessionStatus === "in_flight") return null;
8120
- assertSameAttempt(trigger.sessionId, status.sessionId, "Anubis");
8121
- return status;
8122
- }
8123
- });
8124
- emit(command, options, result, (payload) => {
8125
- console.log(`Anubis: ${sanitizeTerminalText(payload.verdict ?? "no verdict")}.`);
8126
- if (payload.summary) console.log(sanitizeTerminalText(payload.summary));
8127
- });
8128
- }
8129
-
8130
8009
  // src/commands/artifacts.ts
8131
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
8132
- import { basename, join as join2, resolve as resolve2 } from "path";
8133
8010
  async function fetchArtifacts(config, sessionId) {
8134
8011
  const payload = await apiFetch(
8135
8012
  config,
@@ -8569,6 +8446,92 @@ async function egressValidateCommand(path = EGRESS_ALLOWLIST_SOURCE_PATH, option
8569
8446
  });
8570
8447
  }
8571
8448
 
8449
+ // ../../shared/utils/timing.ts
8450
+ function sleep(ms) {
8451
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
8452
+ }
8453
+
8454
+ // src/status-poll.ts
8455
+ async function pollUntilSettled(opts) {
8456
+ const deadline = Date.now() + opts.timeoutMs;
8457
+ while (Date.now() < deadline) {
8458
+ const settled = await opts.fetchStatus();
8459
+ if (settled !== null) return settled;
8460
+ await sleep(opts.pollIntervalMs);
8461
+ }
8462
+ throw new CliError("server", opts.timeoutMessage);
8463
+ }
8464
+ function assertSameAttempt(triggeredSessionId, settledSessionId, label) {
8465
+ if (triggeredSessionId && settledSessionId !== triggeredSessionId) {
8466
+ throw new CliError("conflict", `${label} completed for a different attempt; retry the command.`);
8467
+ }
8468
+ }
8469
+
8470
+ // ../../shared/session/phase.ts
8471
+ var PHASES = [
8472
+ "idle",
8473
+ "running",
8474
+ "review_starting",
8475
+ "waiting_for_input",
8476
+ "finalizing",
8477
+ "review_listening",
8478
+ "completed",
8479
+ "superseded",
8480
+ "needs_you",
8481
+ "blocked",
8482
+ "failed",
8483
+ "stopped",
8484
+ "archived"
8485
+ ];
8486
+ var TERMINAL_PHASES_ARRAY = [
8487
+ "completed",
8488
+ "superseded",
8489
+ "needs_you",
8490
+ "blocked",
8491
+ "failed",
8492
+ "stopped",
8493
+ "archived"
8494
+ ];
8495
+ var TERMINAL_PHASES = new Set(TERMINAL_PHASES_ARRAY);
8496
+ var TERMINAL_FOR_FALLBACK_POLLING_PHASES = new Set(
8497
+ TERMINAL_PHASES_ARRAY.filter((phase) => phase !== "completed")
8498
+ );
8499
+ var CHILD_SLOT_RELEASE_PHASES = new Set(
8500
+ TERMINAL_PHASES_ARRAY.filter((phase) => phase !== "stopped")
8501
+ );
8502
+ var ARCHIVABLE_STALE_TERMINAL_PHASES_ARRAY = TERMINAL_PHASES_ARRAY.filter(
8503
+ (phase) => phase !== "archived" && phase !== "needs_you" && phase !== "blocked" && phase !== "stopped"
8504
+ );
8505
+ function isTerminalPhase(phase, _sessionKind) {
8506
+ return TERMINAL_PHASES.has(phase);
8507
+ }
8508
+
8509
+ // src/constants/watch.ts
8510
+ var MIN_WATCH_POLL_INTERVAL_MS = 250;
8511
+ var DEFAULT_WATCH_POLL_INTERVAL_MS = 1e3;
8512
+ var MAX_WATCH_POLL_INTERVAL_MS = 6e4;
8513
+ var WATCH_REPLAY_PAGE_SIZE = 200;
8514
+ function isWatchTerminal(phase) {
8515
+ return isTerminalPhase(phase);
8516
+ }
8517
+
8518
+ // src/utils/poll-interval.ts
8519
+ function parsePollInterval(raw, opts = {}) {
8520
+ const defaultMs = opts.defaultMs ?? DEFAULT_WATCH_POLL_INTERVAL_MS;
8521
+ const minMs = opts.minMs ?? MIN_WATCH_POLL_INTERVAL_MS;
8522
+ const maxMs = opts.maxMs === void 0 ? MAX_WATCH_POLL_INTERVAL_MS : opts.maxMs;
8523
+ if (!raw) return defaultMs;
8524
+ if (!/^\d+$/.test(raw)) {
8525
+ throw new CliError("user", "Polling interval must be a non-negative integer.");
8526
+ }
8527
+ const value = Number(raw);
8528
+ if (value < minMs || maxMs !== null && value > maxMs) {
8529
+ const range = maxMs === null ? `at least ${minMs}` : `between ${minMs} and ${maxMs}`;
8530
+ throw new CliError("user", `Polling interval must be ${range} milliseconds.`);
8531
+ }
8532
+ return value;
8533
+ }
8534
+
8572
8535
  // src/commands/environments.ts
8573
8536
  var SETUP_POLL_INTERVAL_MS = 5e3;
8574
8537
  var SETUP_WAIT_TIMEOUT_MS = 2 * 60 * 60 * 1e3;
@@ -9332,6 +9295,11 @@ import { dirname as dirname2 } from "path";
9332
9295
  // ../../shared/sandbox-layer/parser.ts
9333
9296
  var import_yaml = __toESM(require_dist(), 1);
9334
9297
 
9298
+ // ../../shared/utils/bytes.ts
9299
+ function utf8ByteLength(value) {
9300
+ return new TextEncoder().encode(value).byteLength;
9301
+ }
9302
+
9335
9303
  // ../../shared/utils/hex.ts
9336
9304
  function bytesToHex(buf) {
9337
9305
  const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
@@ -9395,9 +9363,6 @@ function issue(path, message, options = {}) {
9395
9363
  message
9396
9364
  };
9397
9365
  }
9398
- function byteLength(value) {
9399
- return new TextEncoder().encode(value).byteLength;
9400
- }
9401
9366
  function rejectSecrets(path, text) {
9402
9367
  const match = text.match(SECRET_PATTERN);
9403
9368
  if (match) {
@@ -9411,7 +9376,7 @@ function assertSize(path, text, maxBytes) {
9411
9376
  if (text.includes("\0")) {
9412
9377
  throw new SandboxLayerValidationError(path, "file must not contain NUL bytes");
9413
9378
  }
9414
- if (byteLength(text) > maxBytes) {
9379
+ if (utf8ByteLength(text) > maxBytes) {
9415
9380
  throw new SandboxLayerValidationError(path, `file exceeds ${maxBytes} byte limit`);
9416
9381
  }
9417
9382
  }
@@ -9606,7 +9571,7 @@ function parseSandboxLayerManifest(path, text) {
9606
9571
  const parsedCommand = [];
9607
9572
  command.forEach((arg, argIndex) => {
9608
9573
  const argField = `${field}.${argIndex}`;
9609
- if (typeof arg !== "string" || arg.length === 0 || arg.includes("\0") || /[\r\n]/.test(arg) || byteLength(arg) > MAX_SMOKE_ARG_BYTES) {
9574
+ if (typeof arg !== "string" || arg.length === 0 || arg.includes("\0") || /[\r\n]/.test(arg) || utf8ByteLength(arg) > MAX_SMOKE_ARG_BYTES) {
9610
9575
  issues.push(
9611
9576
  issue(
9612
9577
  path,
@@ -9658,7 +9623,7 @@ function splitLogicalInstructions(path, text) {
9658
9623
  if (!current) startLine = lineNumber;
9659
9624
  const continued = /\\\s*$/.test(rawLine);
9660
9625
  current += (current ? " " : "") + rawLine.replace(/\\\s*$/, "").trim();
9661
- if (byteLength(current) > MAX_LAYER_BYTES) {
9626
+ if (utf8ByteLength(current) > MAX_LAYER_BYTES) {
9662
9627
  throw new SandboxLayerValidationError(path, "continued instruction exceeds layer size limit", startLine);
9663
9628
  }
9664
9629
  if (!continued) {
@@ -9722,7 +9687,7 @@ function parseEnvValues(path, body, startLine) {
9722
9687
  if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
9723
9688
  value = value.slice(1, -1);
9724
9689
  }
9725
- if (!value || value.includes("\0") || /[\r\n]/.test(value) || byteLength(value) > MAX_ENV_VALUE_BYTES) {
9690
+ if (!value || value.includes("\0") || /[\r\n]/.test(value) || utf8ByteLength(value) > MAX_ENV_VALUE_BYTES) {
9726
9691
  throw new SandboxLayerValidationError(
9727
9692
  path,
9728
9693
  `ENV value for '${key}' must be 1-${MAX_ENV_VALUE_BYTES} bytes with no NUL or newline`,
@@ -9744,7 +9709,7 @@ function parseEnvValues(path, body, startLine) {
9744
9709
  return values;
9745
9710
  }
9746
9711
  function assertRunSafety(path, command, startLine) {
9747
- if (byteLength(command) > MAX_RUN_COMMAND_BYTES) {
9712
+ if (utf8ByteLength(command) > MAX_RUN_COMMAND_BYTES) {
9748
9713
  throw new SandboxLayerValidationError(path, `RUN command exceeds ${MAX_RUN_COMMAND_BYTES} byte limit`, startLine);
9749
9714
  }
9750
9715
  if (/(^|\s)--mount=/.test(command)) {
@@ -12439,7 +12404,7 @@ var program = new Command().name("arcanist").description("Arcanist CLI").version
12439
12404
  `
12440
12405
  Examples:
12441
12406
  arcanist auth login --token-stdin
12442
- arcanist anubis https://github.com/org/repo/pull/123 --json | jq -r .sessionId
12407
+ arcanist sessions list --json
12443
12408
  arcanist sessions events <session-id> --follow --json
12444
12409
 
12445
12410
  Exit codes:
@@ -12500,21 +12465,6 @@ instead (members only).
12500
12465
  Zeus review is currently available to Arcanist members only.
12501
12466
  `
12502
12467
  ).action((prUrl, options, command) => reviewCommand(prUrl, options, command));
12503
- program.command("anubis <pr-url>").description("Run an Anubis QA verification for a GitHub pull request").option("--wait", "Wait for the verdict").option("--poll-interval <ms>", "Polling interval in milliseconds while waiting (default 15000)").addHelpText(
12504
- "after",
12505
- `
12506
- Examples:
12507
- arcanist anubis https://github.com/org/repo/pull/123 --wait --json
12508
- arcanist anubis https://github.com/org/repo/pull/123
12509
-
12510
- --json without --wait returns { sessionId, claimed? | duplicate? }.
12511
- --wait --json returns { sessionId, sessionStatus, verdict, summary? }.
12512
- Known verdicts are works, broken, and could-not-verify.
12513
- Anubis deduplicates active runs server-side; it does not use --idempotency-key.
12514
- The result is posted as a PR comment marked arcanist-anubis:v1.
12515
- Anubis requires a write-scoped CLI token and repository write access.
12516
- `
12517
- ).action((prUrl, options, command) => anubisCommand(prUrl, options, command));
12518
12468
  program.command("hades <repo>").description("Start an internal Hades run measuring Zeus's review capability on a Try Arcanist repository").addHelpText(
12519
12469
  "after",
12520
12470
  `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryarcanist/cli",
3
- "version": "0.1.298",
3
+ "version": "0.1.299",
4
4
  "description": "CLI for Arcanist - create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {