@tryarcanist/cli 0.1.141 → 0.1.142

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 -11
  2. package/dist/index.js +69 -6
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -132,7 +132,7 @@ arcanist sessions create your-org/your-repo "retry-safe create" --idempotency-ke
132
132
 
133
133
  `--backend` picks the agent runtime backend: `codex` (default) or `claude_code`. `--model` must be valid for the chosen backend — codex runs OpenAI models (`gpt-5.4` default, `gpt-5.5`), `claude_code` runs Anthropic models (`claude-opus-4-8` default, `claude-sonnet-4-6`) — and the CLI rejects a mismatch before any network call. `claude_code` sessions require an Anthropic API key configured in Settings.
134
134
 
135
- `--wait` blocks until the created prompt finishes, prints the resulting PR/branch line in human-readable mode when it is already available, and exits non-zero if the prompt finishes with `status: failed`, making it suitable for cron or other schedulers that alert on command failure. `--poll-interval <ms>` tunes the completion check frequency.
135
+ `--wait` blocks until the created prompt finishes, prints the resulting PR/branch line in human-readable mode when it is already available, and exits non-zero if the prompt finishes with `status: failed`, making it suitable for cron or other schedulers that alert on command failure. JSON mode waits quietly and prints the create payload after the prompt completes successfully. `--poll-interval <ms>` tunes the completion check frequency.
136
136
 
137
137
  Use `--base-branch <branch>` to target a non-default base branch. The CLI only validates that the branch value is non-empty; branch existence is checked later by the session runtime.
138
138
 
@@ -146,7 +146,7 @@ Repeatable `--uploaded-file <path>` flags attach local UTF-8 text files to the p
146
146
 
147
147
  `--onboarding` creates an onboarding session: the agent authors the repo's `.arcanist.json`, `.arcanist/` runtime files, `ARCANIST.md`, and conditionally `.arcanist/sandbox.yaml` + `.arcanist/sandbox.layer.Dockerfile` (when the base sandbox lacks a needed toolchain), proves what it can inside its sandbox, and opens the setup PR ready for review. The onboarding behavior is driven by the bridge's canonical onboarding playbook, not the prompt, so `--onboarding` needs no prompt: `arcanist sessions create <repo> --onboarding --wait`. Any prompt supplied alongside `--onboarding` is ignored. Team use; not part of the external API surface.
148
148
 
149
- JSON mode returns `{sessionId, sessionUrl?, repoUrl, model?, agentRuntimeBackend?, reasoningEffort?, baseBranch?, promptId?}`. `sessionUrl` is emitted only when the server returns it; `agentRuntimeBackend` only when `--backend` is passed.
149
+ JSON mode returns `{sessionId, sessionUrl?, repoUrl, model?, agentRuntimeBackend?, reasoningEffort?, baseBranch?, startBranch?, onboarding?, promptId?}`. `sessionUrl` is emitted only when the server returns it; `agentRuntimeBackend` only when `--backend` is passed. With `--wait`, JSON mode also includes best-effort result fields when available: `prUrl?`, `publishedBranch?`, and `lastBranch?`.
150
150
 
151
151
  ### `arcanist sessions send <session-id> [prompt]`
152
152
 
@@ -170,7 +170,7 @@ arcanist sessions stop abc123
170
170
  arcanist sessions stop abc123 --json
171
171
  ```
172
172
 
173
- JSON mode returns `{sessionId, status}` where status is `stopping` or `already_stopped`.
173
+ JSON mode returns `{sessionId, status}`. `status` is the server stop status or, on a 409 stop-block, the block reason; it is an open set. Known values include `stopping`, `stopped`, `already_stopped`, `not_stoppable`, and lifecycle phase names.
174
174
 
175
175
  ### `arcanist sessions get <session-id>`
176
176
 
@@ -216,7 +216,13 @@ arcanist sessions events abc123 --follow --json
216
216
 
217
217
  `--after` and `--before` are aliases for `--after-sequence` and `--before-sequence`.
218
218
 
219
- Follow mode emits NDJSON, one replay event per line, until the session becomes idle.
219
+ JSON mode without `--follow` returns one JSON object from `/events/history`: `{events: [...], ...}`. Canonical events carry `phase`.
220
+
221
+ Follow mode emits NDJSON, one `{sequence, type, data}` object per line, until the session becomes idle. Actionable follow-mode signals:
222
+
223
+ - PR opened or updated: `pr_created` / `pr_updated`; URL at `data.prUrl`.
224
+ - Waiting for human input: `question`; question text at `data.question`.
225
+ - Terminal: `prompt_completed`, `prompt_failed`, and `session_idle`. Follow JSON mode suppresses lifecycle `status` frames, so key on these event names rather than `waiting_for_input`.
220
226
 
221
227
  ### `arcanist sessions transcript <session-id>`
222
228
 
@@ -329,15 +335,12 @@ arcanist sessions create your-org/your-repo "verify the new rate limiter is acti
329
335
 
330
336
  For cron, prefer `ARCANIST_TOKEN` or a logged-in `~/.arcanist/config.json` over passing `--token` on the command line.
331
337
 
332
- To capture the PR URL after a successful run, use `create --wait` for the exit code, then read the session state:
338
+ To capture the PR URL after a successful run, read the best-effort result fields from `create --wait --json`:
333
339
 
334
340
  ```bash
335
- SESSION_ID=$(arcanist sessions create your-org/your-repo "fix the flaky test" --wait --json | jq -r .sessionId)
336
- for _ in {1..20}; do
337
- PR_URL=$(arcanist sessions get "$SESSION_ID" --json | jq -r '.session.prUrl // empty')
338
- [ -n "$PR_URL" ] && break
339
- sleep 5
340
- done
341
+ RESULT=$(arcanist sessions create your-org/your-repo "fix the flaky test" --wait --json)
342
+ PR_URL=$(jq -r '.prUrl // empty' <<<"$RESULT")
343
+ SESSION_ID=$(jq -r '.sessionId' <<<"$RESULT")
341
344
  [ -n "$PR_URL" ] || { echo "PR URL not ready for $SESSION_ID" >&2; exit 1; }
342
345
  ```
343
346
 
package/dist/index.js CHANGED
@@ -83,7 +83,7 @@ function parseApiErrorBody(body) {
83
83
  }
84
84
  }
85
85
  function hintForHttpStatus(status) {
86
- if (status === 401 || status === 403) return "Run `arcanist login` or set `ARCANIST_TOKEN`.";
86
+ if (status === 401 || status === 403) return "Run `arcanist auth login` or set `ARCANIST_TOKEN`.";
87
87
  if (status === 404) return "List sessions with `arcanist sessions list`.";
88
88
  if (status === 409) return "Check the current resource state and retry the command when it is ready.";
89
89
  if (status >= 500) return "Retry later or check the control-plane logs.";
@@ -172,7 +172,7 @@ function saveConfig(config) {
172
172
  function requireConfig(overrides) {
173
173
  const config = loadConfig(overrides);
174
174
  if (!config) {
175
- throw new CliError("auth", "Not logged in.", { hint: "Run `arcanist login` or set `ARCANIST_TOKEN`." });
175
+ throw new CliError("auth", "Not logged in.", { hint: "Run `arcanist auth login` or set `ARCANIST_TOKEN`." });
176
176
  }
177
177
  return config;
178
178
  }
@@ -2051,9 +2051,7 @@ function stringField(value) {
2051
2051
  return typeof value === "string" && value.trim() ? value : null;
2052
2052
  }
2053
2053
  function formatSessionResult(session) {
2054
- const prUrl = stringField(session.prUrl);
2055
- const publishedBranch = stringField(session.publishedBranch);
2056
- const lastBranch = stringField(session.lastBranch);
2054
+ const { prUrl, publishedBranch, lastBranch } = extractSessionResultFields(session);
2057
2055
  const baseBranch = stringField(session.baseBranch);
2058
2056
  const branch = publishedBranch ?? (lastBranch && lastBranch !== baseBranch ? lastBranch : null);
2059
2057
  if (prUrl) {
@@ -2064,6 +2062,16 @@ function formatSessionResult(session) {
2064
2062
  }
2065
2063
  return null;
2066
2064
  }
2065
+ function extractSessionResultFields(session) {
2066
+ const result = {};
2067
+ const prUrl = stringField(session.prUrl);
2068
+ const publishedBranch = stringField(session.publishedBranch);
2069
+ const lastBranch = stringField(session.lastBranch);
2070
+ if (prUrl) result.prUrl = prUrl;
2071
+ if (publishedBranch) result.publishedBranch = publishedBranch;
2072
+ if (lastBranch) result.lastBranch = lastBranch;
2073
+ return result;
2074
+ }
2067
2075
  function latestCompletedPromptResult(prompts) {
2068
2076
  for (let i = prompts.length - 1; i >= 0; i--) {
2069
2077
  if (prompts[i].status === "completed") return prompts[i].result;
@@ -2501,6 +2509,7 @@ function validateRepoUrl(url) {
2501
2509
  return `Invalid repo URL: "${url}". Expected a GitHub URL (https://github.com/owner/repo) or owner/repo shorthand.`;
2502
2510
  }
2503
2511
  var PROMPT_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed"]);
2512
+ var RESULT_FETCH_ATTEMPTS = 5;
2504
2513
  var ONBOARDING_PROMPT = "Onboard this repository onto Arcanist.";
2505
2514
  function selectCreatedPrompt(prompts, promptId) {
2506
2515
  if (prompts.length === 0) return null;
@@ -2563,6 +2572,25 @@ async function waitForCreatedPrompt(sessionId, promptId, sessionUrl, pollInterva
2563
2572
  );
2564
2573
  }
2565
2574
  }
2575
+ function unwrapSessionState(payload) {
2576
+ return payload.session && typeof payload.session === "object" ? payload.session : payload;
2577
+ }
2578
+ async function fetchSessionResultFields(config, sessionId) {
2579
+ const sessionPayload = await apiFetch(config, `/api/sessions/${sessionId}`);
2580
+ return extractSessionResultFields(unwrapSessionState(sessionPayload));
2581
+ }
2582
+ async function fetchSettledSessionResultFields(config, sessionId, pollIntervalMs) {
2583
+ const effectivePollIntervalMs = Math.max(pollIntervalMs, MIN_WATCH_POLL_INTERVAL_MS);
2584
+ let latest = {};
2585
+ for (let attempt = 0; attempt < RESULT_FETCH_ATTEMPTS; attempt++) {
2586
+ latest = await fetchSessionResultFields(config, sessionId);
2587
+ if (latest.prUrl || latest.publishedBranch) return latest;
2588
+ if (attempt < RESULT_FETCH_ATTEMPTS - 1) {
2589
+ await sleep(effectivePollIntervalMs);
2590
+ }
2591
+ }
2592
+ return latest;
2593
+ }
2566
2594
  async function createCommand(repoUrl, promptArg, options, command) {
2567
2595
  const runtime = getRuntimeOptions(command, options);
2568
2596
  const config = requireConfig(runtime);
@@ -2616,6 +2644,7 @@ async function createCommand(repoUrl, promptArg, options, command) {
2616
2644
  });
2617
2645
  const sessionId = sessionData.sessionId;
2618
2646
  let promptId;
2647
+ let waitResultFields = {};
2619
2648
  try {
2620
2649
  const promptData = await apiFetch(
2621
2650
  config,
@@ -2644,10 +2673,16 @@ async function createCommand(repoUrl, promptArg, options, command) {
2644
2673
  if (sessionData.sessionUrl) console.log(`URL: ${sessionData.sessionUrl}`);
2645
2674
  }
2646
2675
  await waitForCreatedPrompt(sessionId, promptId, sessionData.sessionUrl, waitPollIntervalMs, runtime, command);
2676
+ if (isJson(command, options)) {
2677
+ try {
2678
+ waitResultFields = await fetchSettledSessionResultFields(config, sessionId, waitPollIntervalMs);
2679
+ } catch {
2680
+ }
2681
+ }
2647
2682
  if (!isJson(command, options)) {
2648
2683
  try {
2649
2684
  const sessionPayload = await apiFetch(config, `/api/sessions/${sessionId}`);
2650
- const sessionState = sessionPayload.session && typeof sessionPayload.session === "object" ? sessionPayload.session : sessionPayload;
2685
+ const sessionState = unwrapSessionState(sessionPayload);
2651
2686
  const resultLine = formatSessionResult(sessionState);
2652
2687
  if (resultLine) console.log(resultLine);
2653
2688
  } catch {
@@ -2665,12 +2700,14 @@ async function createCommand(repoUrl, promptArg, options, command) {
2665
2700
  if (startBranch) output.startBranch = startBranch;
2666
2701
  if (options.onboarding) output.onboarding = true;
2667
2702
  if (promptId) output.promptId = promptId;
2703
+ Object.assign(output, waitResultFields);
2668
2704
  writeJson(output);
2669
2705
  return;
2670
2706
  }
2671
2707
  if (options.wait) return;
2672
2708
  console.log(`Session: ${sessionId}`);
2673
2709
  if (sessionData.sessionUrl) console.log(`URL: ${sessionData.sessionUrl}`);
2710
+ console.log(`Follow with: arcanist sessions events ${sessionId} --follow --json`);
2674
2711
  }
2675
2712
 
2676
2713
  // src/commands/egress.ts
@@ -4356,6 +4393,11 @@ Examples:
4356
4393
  printf "fix bug" | arcanist sessions create https://github.com/org/repo --prompt-stdin --json
4357
4394
  printf "fix bug" | arcanist sessions create https://github.com/org/repo --prompt-stdin --wait
4358
4395
  arcanist sessions create https://github.com/org/repo - --json | jq -r .sessionId | xargs -I{} arcanist sessions events {} --follow --json
4396
+
4397
+ JSON:
4398
+ JSON mode returns {sessionId, sessionUrl?, repoUrl, model?, agentRuntimeBackend?, reasoningEffort?, baseBranch?, startBranch?, onboarding?, promptId?}
4399
+ --wait --json also includes best-effort result fields when available: prUrl?, publishedBranch?, lastBranch?
4400
+ Follow async progress with: arcanist sessions events <session-id> --follow --json
4359
4401
  `
4360
4402
  );
4361
4403
  }
@@ -4371,6 +4413,9 @@ Examples:
4371
4413
  arcanist sessions send <session-id> "also update tests"
4372
4414
  arcanist sessions send <session-id> "use this log" --uploaded-file failing.log
4373
4415
  printf "also update tests" | arcanist sessions send <session-id> --prompt-stdin --json
4416
+
4417
+ JSON:
4418
+ JSON mode returns {sessionId, promptId?}
4374
4419
  `
4375
4420
  );
4376
4421
  }
@@ -4405,6 +4450,10 @@ sessions.command("stop").description("Stop the active run for a session").argume
4405
4450
  Examples:
4406
4451
  arcanist sessions stop <session-id>
4407
4452
  arcanist sessions stop <session-id> --json
4453
+
4454
+ JSON:
4455
+ JSON mode returns {sessionId, status}
4456
+ status is the server stop status or 409 stop-block reason. Known values include stopping, stopped, already_stopped, not_stoppable, and lifecycle phase names.
4408
4457
  `
4409
4458
  ).action((sessionId, options, command) => stopCommand(sessionId, options, command));
4410
4459
  sessions.command("get").description("Get session details").argument("<session-id>", "Session ID").addHelpText(
@@ -4413,6 +4462,9 @@ sessions.command("get").description("Get session details").argument("<session-id
4413
4462
  Examples:
4414
4463
  arcanist sessions get <session-id>
4415
4464
  arcanist sessions get <session-id> --json
4465
+
4466
+ JSON:
4467
+ JSON mode returns the raw session payload. Result fields are at .session.prUrl, .session.publishedBranch, and .session.lastBranch.
4416
4468
  `
4417
4469
  ).action((sessionId, options, command) => getSessionCommand(sessionId, options, command));
4418
4470
  sessions.command("list").description("List sessions").option("--status <status>", "Filter by session status").option("--scope <scope>", "Session scope: mine or business").option("--search <query>", "Search session titles and repo metadata").option("--repo <repo>", "Filter by repo metadata").option("--limit <n>", "Maximum sessions to return").option("--cursor <cursor>", "Pagination cursor").addHelpText(
@@ -4422,6 +4474,9 @@ Examples:
4422
4474
  arcanist sessions list
4423
4475
  arcanist sessions list --status idle --json
4424
4476
  arcanist sessions list --search "architect agent" --repo owner/repo
4477
+
4478
+ JSON:
4479
+ JSON mode returns {sessions, nextCursor}
4425
4480
  `
4426
4481
  ).action((options, command) => listSessionsCommand(options, command));
4427
4482
  sessions.command("search").description("Search sessions by title and repo metadata").argument("<query>", "Search query").option("--status <status>", "Filter by session status").option("--scope <scope>", "Session scope: mine or business").option("--repo <repo>", "Filter by repo metadata").option("--limit <n>", "Maximum sessions to return").option("--cursor <cursor>", "Pagination cursor").addHelpText(
@@ -4430,6 +4485,9 @@ sessions.command("search").description("Search sessions by title and repo metada
4430
4485
  Examples:
4431
4486
  arcanist sessions search "architect agent"
4432
4487
  arcanist sessions search "mcp debugging" --repo owner/repo --json
4488
+
4489
+ JSON:
4490
+ JSON mode returns {sessions, nextCursor}
4433
4491
  `
4434
4492
  ).action((query, options, command) => searchSessionsCommand(query, options, command));
4435
4493
  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 the session is idle").option("--poll-interval <ms>", "Polling interval in milliseconds", String(DEFAULT_WATCH_POLL_INTERVAL_MS)).addHelpText(
@@ -4440,6 +4498,11 @@ Examples:
4440
4498
  arcanist sessions events <session-id> --after-sequence 540 --limit 250 --json
4441
4499
  arcanist sessions events <session-id> --after 540 --limit 250 --json
4442
4500
  arcanist sessions events <session-id> --follow --json
4501
+
4502
+ JSON:
4503
+ JSON mode without --follow returns one JSON object from /events/history: {events: [...], ...}; canonical events carry phase.
4504
+ --follow --json emits NDJSON, one {sequence, type, data} object per line.
4505
+ Actionable follow types: pr_created/pr_updated use data.prUrl; question uses data.question; terminal types are prompt_completed, prompt_failed, and session_idle.
4443
4506
  `
4444
4507
  ).action((sessionId, options, command) => sessionEventsCommand(sessionId, options, command));
4445
4508
  sessions.command("transcript").description("Render a session transcript").argument("<session-id>", "Session ID").addHelpText(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryarcanist/cli",
3
- "version": "0.1.141",
3
+ "version": "0.1.142",
4
4
  "description": "CLI for Arcanist — create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {