@tryarcanist/cli 0.1.175 → 0.1.177

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 +48 -2
  2. package/dist/index.js +233 -52
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -266,9 +266,12 @@ arcanist sessions list
266
266
  arcanist sessions list --status idle --limit 20 --json
267
267
  arcanist sessions list --search "architect agent" --repo your-org/your-repo
268
268
  arcanist sessions list --scope business --cursor <cursor>
269
+ arcanist sessions list --all --json
269
270
  ```
270
271
 
271
- JSON mode returns `{sessions, nextCursor}`. Search is metadata-only: generated titles and repo metadata.
272
+ JSON mode returns `{sessions, nextCursor}`.
273
+ `--all` follows cursors until completion and returns `nextCursor: null`.
274
+ Search is metadata-only: generated titles and repo metadata.
272
275
 
273
276
  ### `arcanist sessions search <query>`
274
277
 
@@ -276,9 +279,10 @@ JSON mode returns `{sessions, nextCursor}`. Search is metadata-only: generated t
276
279
  arcanist sessions search "architect agent"
277
280
  arcanist sessions search "mcp debugging" --repo your-org/your-repo --json
278
281
  arcanist sessions search "repo access" --status idle --scope business --limit 20 --cursor <cursor>
282
+ arcanist sessions search "repo access" --all --json
279
283
  ```
280
284
 
281
- Uses the same metadata-only index and filters as `sessions list`: `--status`, `--scope`, `--repo`, `--limit`, and `--cursor`.
285
+ Uses the same metadata-only index and filters as `sessions list`: `--status`, `--scope`, `--repo`, `--limit`, `--cursor`, and `--all`.
282
286
 
283
287
  ### `arcanist sessions events <session-id>`
284
288
 
@@ -309,8 +313,11 @@ Renders a session transcript from the stored session export.
309
313
  ```bash
310
314
  arcanist sessions transcript abc123
311
315
  arcanist sessions transcript abc123 --json
316
+ arcanist sessions transcript abc123 --last 20
312
317
  ```
313
318
 
319
+ `--last <n>` renders only the last `n` stored transcript events after fetching the session export.
320
+
314
321
  ### `arcanist sessions watch <session-id>`
315
322
 
316
323
  Watches session activity until the session becomes idle. For machine-readable streaming, prefer `arcanist sessions events --follow --json`.
@@ -327,6 +334,42 @@ arcanist sessions usage abc123
327
334
  arcanist sessions usage abc123 --json
328
335
  ```
329
336
 
337
+ ### `arcanist repos list`
338
+
339
+ ```bash
340
+ arcanist repos list
341
+ arcanist repos list --json
342
+ ```
343
+
344
+ JSON mode returns `{repos, ssoOrgs}`.
345
+
346
+ ### `arcanist repos branches [repo]`
347
+
348
+ ```bash
349
+ arcanist repos branches tryarcanist/arcanist
350
+ arcanist repos branches https://github.com/tryarcanist/arcanist --json
351
+ ```
352
+
353
+ JSON mode returns `{branches}`.
354
+
355
+ ### `arcanist repos skills [repo]`
356
+
357
+ ```bash
358
+ arcanist repos skills tryarcanist/arcanist
359
+ arcanist repos skills https://github.com/tryarcanist/arcanist --json
360
+ ```
361
+
362
+ JSON mode returns `{skills}`.
363
+
364
+ ### `arcanist models list`
365
+
366
+ ```bash
367
+ arcanist models list
368
+ arcanist models list --json
369
+ ```
370
+
371
+ JSON mode returns `{models}`.
372
+
330
373
  ### `arcanist automations create <repo-url> [prompt]`
331
374
 
332
375
  Creates a scheduled automation for a GitHub repo. Create/delete require a write-scoped CLI token; read-scoped tokens can list automations. The server validates repo access, normalizes cron expressions, dedupes retries by repo+cron+prompt, and caps each business at 20 enabled rules.
@@ -368,8 +411,11 @@ arcanist automations delete rule_123 --yes --json
368
411
  arcanist tokens list
369
412
  arcanist tokens list --limit 10 --json
370
413
  arcanist tokens list --cursor <cursor> --json
414
+ arcanist tokens list --all --json
371
415
  ```
372
416
 
417
+ `--all` follows cursors until completion and returns `nextCursor: null` in JSON mode.
418
+
373
419
  ### `arcanist tokens create`
374
420
 
375
421
  Creates a CLI token and prints the plaintext token exactly once.
package/dist/index.js CHANGED
@@ -952,9 +952,9 @@ function formatModelLabel(raw, options = {}) {
952
952
  const providerName = MODEL_PROVIDER_NAMES[selection.providerID];
953
953
  return includeProvider ? `${providerName} / ${selection.modelID}` : selection.modelID;
954
954
  }
955
- function buildModelProviderGroups(models) {
955
+ function buildModelProviderGroups(models2) {
956
956
  const groups = /* @__PURE__ */ new Map();
957
- for (const model of models) {
957
+ for (const model of models2) {
958
958
  let group = groups.get(model.provider);
959
959
  if (!group) {
960
960
  group = { id: model.provider, name: MODEL_PROVIDER_NAMES[model.provider], models: [] };
@@ -3380,6 +3380,31 @@ async function messageCommand(sessionId, promptArg, options = {}, command) {
3380
3380
  console.log(`Message sent to session ${sessionId}.`);
3381
3381
  }
3382
3382
 
3383
+ // src/commands/models.ts
3384
+ async function modelsListCommand(options = {}, command) {
3385
+ const { config } = resolveBusinessContext(command, options);
3386
+ const groups = await apiFetch(config, "/api/models");
3387
+ const models2 = groups.flatMap(
3388
+ (group) => group.models.map((model) => {
3389
+ const backends = model.backends ?? [group.id];
3390
+ return {
3391
+ ...model,
3392
+ backend: group.id,
3393
+ provider: group.id,
3394
+ backends,
3395
+ default: backends.some(
3396
+ (backend) => DEFAULT_SESSION_START_MODEL_ID_BY_BACKEND[backend] === model.id
3397
+ )
3398
+ };
3399
+ })
3400
+ );
3401
+ emit(command, options, { models: models2 }, (payload) => {
3402
+ for (const model of payload.models) {
3403
+ console.log(`${String(model.id)} ${String(model.backend)}${model.default ? " default" : ""}`);
3404
+ }
3405
+ });
3406
+ }
3407
+
3383
3408
  // ../../shared/agent/verify-directive.ts
3384
3409
  var MAX_TARGET_PR_URL_LENGTH = 500;
3385
3410
  function normalizeGithubPullRequestUrl(rawUrl) {
@@ -3511,6 +3536,63 @@ async function qaCommand(prUrl, options, command) {
3511
3536
  console.log(`Follow with: arcanist sessions events ${sessionId} --follow --json`);
3512
3537
  }
3513
3538
 
3539
+ // src/commands/repos.ts
3540
+ import { execFileSync } from "child_process";
3541
+ function git(args) {
3542
+ try {
3543
+ return execFileSync("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
3544
+ } catch (err) {
3545
+ throw new CliError("user", `git ${args.join(" ")} failed: ${stringifyError(err)}`);
3546
+ }
3547
+ }
3548
+ function currentRepo() {
3549
+ const parsed = parseGithubRepoFullName(git(["remote", "get-url", "origin"]));
3550
+ if (!parsed) throw new CliError("user", "origin remote must point at a GitHub repository");
3551
+ return { owner: parsed.owner, repo: parsed.repo };
3552
+ }
3553
+ function parseRepoArg2(value) {
3554
+ if (!value) return currentRepo();
3555
+ const parsed = parseGithubRepoFullName(value);
3556
+ if (!parsed) throw new CliError("user", "repo must be owner/name or a GitHub URL");
3557
+ return { owner: parsed.owner, repo: parsed.repo };
3558
+ }
3559
+ async function reposListCommand(options = {}, command) {
3560
+ const { config } = resolveBusinessContext(command, options);
3561
+ const payload = await apiFetch(
3562
+ config,
3563
+ "/api/repos"
3564
+ );
3565
+ emit(command, options, { repos: payload.repos, ssoOrgs: payload.ssoOrgs }, (data) => {
3566
+ for (const repo of data.repos) {
3567
+ console.log(`${String(repo.fullName ?? repo.name ?? repo.repo ?? "")} ${String(repo.defaultBranch ?? "")}`);
3568
+ }
3569
+ if (data.ssoOrgs.length > 0) console.log(`SSO withheld orgs: ${data.ssoOrgs.length}`);
3570
+ });
3571
+ }
3572
+ async function repoBranchesCommand(repoArg, options = {}, command) {
3573
+ const { config } = resolveBusinessContext(command, options);
3574
+ const repo = parseRepoArg2(repoArg);
3575
+ await apiFetch(config, "/api/repos");
3576
+ const payload = await apiFetch(
3577
+ config,
3578
+ `/api/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.repo)}/branches`
3579
+ );
3580
+ emit(command, options, payload, (data) => {
3581
+ for (const branch of data.branches) console.log(String(branch.name ?? branch.branch ?? ""));
3582
+ });
3583
+ }
3584
+ async function repoSkillsCommand(repoArg, options = {}, command) {
3585
+ const { config } = resolveBusinessContext(command, options);
3586
+ const repo = parseRepoArg2(repoArg);
3587
+ const payload = await apiFetch(
3588
+ config,
3589
+ `/api/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.repo)}/skills`
3590
+ );
3591
+ emit(command, options, payload, (data) => {
3592
+ for (const skill of data.skills) console.log(`${String(skill.name ?? "")} ${String(skill.description ?? "")}`);
3593
+ });
3594
+ }
3595
+
3514
3596
  // src/commands/respond.ts
3515
3597
  async function resolveAnswerInput(answerArg, options) {
3516
3598
  const shouldReadStdin = options.answerStdin === true || answerArg === "-";
@@ -3563,7 +3645,7 @@ function parseRespondConflict(err) {
3563
3645
  }
3564
3646
 
3565
3647
  // src/commands/sandbox.ts
3566
- import { execFileSync } from "child_process";
3648
+ import { execFileSync as execFileSync2 } from "child_process";
3567
3649
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
3568
3650
  import { dirname as dirname2 } from "path";
3569
3651
 
@@ -4099,34 +4181,34 @@ var DEFAULT_MANIFEST_PATH = SANDBOX_LAYER_MANIFEST_PATH;
4099
4181
  var DEFAULT_POLL_INTERVAL_MS = 2500;
4100
4182
  var MIN_POLL_INTERVAL_MS = 250;
4101
4183
  var TERMINAL_BUILD_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "canceled"]);
4102
- function git(args) {
4184
+ function git2(args) {
4103
4185
  try {
4104
- return execFileSync("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
4186
+ return execFileSync2("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
4105
4187
  } catch (err) {
4106
4188
  throw new CliError("user", `git ${args.join(" ")} failed: ${stringifyError(err)}`);
4107
4189
  }
4108
4190
  }
4109
- function currentRepo() {
4110
- const remote = git(["remote", "get-url", "origin"]);
4191
+ function currentRepo2() {
4192
+ const remote = git2(["remote", "get-url", "origin"]);
4111
4193
  const parsed = parseGithubRepoFullName(remote);
4112
4194
  if (!parsed) throw new CliError("user", "origin remote must point at a GitHub repository");
4113
4195
  return { owner: parsed.owner, repo: parsed.repo };
4114
4196
  }
4115
4197
  function currentRef() {
4116
- return git(["rev-parse", "--abbrev-ref", "HEAD"]);
4198
+ return git2(["rev-parse", "--abbrev-ref", "HEAD"]);
4117
4199
  }
4118
4200
  function assertCleanAndPushed() {
4119
- const dirty = git(["status", "--porcelain"]);
4201
+ const dirty = git2(["status", "--porcelain"]);
4120
4202
  if (dirty) throw new CliError("user", "Sandbox layer source must be committed before build.");
4121
- const upstream = git(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]);
4122
- const ahead = git(["rev-list", "--count", `${upstream}..HEAD`]);
4203
+ const upstream = git2(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]);
4204
+ const ahead = git2(["rev-list", "--count", `${upstream}..HEAD`]);
4123
4205
  if (ahead !== "0")
4124
4206
  throw new CliError("user", "Current commit is not pushed; push before building the sandbox layer.");
4125
4207
  }
4126
4208
  function assertManifestExists(path) {
4127
4209
  if (!existsSync2(path)) throw new CliError("user", `Missing sandbox manifest: ${path}`);
4128
4210
  }
4129
- function parseRepoArg2(value, fallback) {
4211
+ function parseRepoArg3(value, fallback) {
4130
4212
  if (!value) return fallback();
4131
4213
  const parsed = parseGithubRepoFullName(value);
4132
4214
  if (!parsed) throw new CliError("user", "repo must be owner/name or a GitHub URL");
@@ -4303,7 +4385,7 @@ function formatValidationPayload(input) {
4303
4385
  }
4304
4386
  function nextBuildCommand(manifestPath) {
4305
4387
  try {
4306
- const repo = currentRepo();
4388
+ const repo = currentRepo2();
4307
4389
  const manifestArg = manifestPath === DEFAULT_MANIFEST_PATH ? "" : ` --manifest ${manifestPath}`;
4308
4390
  return `arcanist sandbox build ${repoPath(repo)} --wait --follow${manifestArg}`;
4309
4391
  } catch {
@@ -4393,8 +4475,8 @@ async function sandboxBuildCommand(sourceRepoArg, options = {}, command) {
4393
4475
  const manifestPath = options.manifest ?? DEFAULT_MANIFEST_PATH;
4394
4476
  assertManifestExists(manifestPath);
4395
4477
  if (!options.ref) assertCleanAndPushed();
4396
- const sourceRepo = parseRepoArg2(sourceRepoArg, currentRepo);
4397
- const targetRepo = options.targetRepo ? parseRepoArg2(options.targetRepo, currentRepo) : null;
4478
+ const sourceRepo = parseRepoArg3(sourceRepoArg, currentRepo2);
4479
+ const targetRepo = options.targetRepo ? parseRepoArg3(options.targetRepo, currentRepo2) : null;
4398
4480
  const businessId = await resolveBusinessId(config, options);
4399
4481
  const ref = options.ref ?? currentRef();
4400
4482
  let payload;
@@ -4445,7 +4527,7 @@ async function sandboxBuildCommand(sourceRepoArg, options = {}, command) {
4445
4527
  async function sandboxStatusCommand(repoArg, options = {}, command) {
4446
4528
  const { config } = resolveBusinessContext(command, options);
4447
4529
  const businessId = await resolveBusinessId(config, options);
4448
- const repo = parseRepoArg2(repoArg, currentRepo);
4530
+ const repo = parseRepoArg3(repoArg, currentRepo2);
4449
4531
  const payload = await apiFetch(
4450
4532
  config,
4451
4533
  `/api/businesses/${encodeURIComponent(businessId)}/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(
@@ -4457,9 +4539,9 @@ async function sandboxStatusCommand(repoArg, options = {}, command) {
4457
4539
  async function sandboxHistoryCommand(sourceRepoArg, options = {}, command) {
4458
4540
  const { config } = resolveBusinessContext(command, options);
4459
4541
  const businessId = await resolveBusinessId(config, options);
4460
- const sourceRepo = parseRepoArg2(sourceRepoArg, currentRepo);
4542
+ const sourceRepo = parseRepoArg3(sourceRepoArg, currentRepo2);
4461
4543
  const params = new URLSearchParams({ sourceRepo: repoPath(sourceRepo) });
4462
- if (options.targetRepo) params.set("targetRepo", repoPath(parseRepoArg2(options.targetRepo, currentRepo)));
4544
+ if (options.targetRepo) params.set("targetRepo", repoPath(parseRepoArg3(options.targetRepo, currentRepo2)));
4463
4545
  if (options.status) params.set("status", options.status);
4464
4546
  if (options.limit) {
4465
4547
  const limit = Number(options.limit);
@@ -4560,7 +4642,7 @@ async function sandboxLogsCommand(buildId, options = {}, command) {
4560
4642
  async function sandboxAssignDefaultCommand(sourceRepoArg, options = {}, command) {
4561
4643
  const { config } = resolveBusinessContext(command, options);
4562
4644
  const businessId = await resolveBusinessId(config, options);
4563
- const sourceRepo = parseRepoArg2(sourceRepoArg, currentRepo);
4645
+ const sourceRepo = parseRepoArg3(sourceRepoArg, currentRepo2);
4564
4646
  const manifestPath = options.manifest ?? DEFAULT_MANIFEST_PATH;
4565
4647
  const payload = await apiFetch(
4566
4648
  config,
@@ -4577,8 +4659,8 @@ async function sandboxAssignDefaultCommand(sourceRepoArg, options = {}, command)
4577
4659
  async function sandboxAssignRepoCommand(targetRepoArg, sourceRepoArg, options = {}, command) {
4578
4660
  const { config } = resolveBusinessContext(command, options);
4579
4661
  const businessId = await resolveBusinessId(config, options);
4580
- const targetRepo = parseRepoArg2(targetRepoArg, currentRepo);
4581
- const sourceRepo = parseRepoArg2(sourceRepoArg, currentRepo);
4662
+ const targetRepo = parseRepoArg3(targetRepoArg, currentRepo2);
4663
+ const sourceRepo = parseRepoArg3(sourceRepoArg, currentRepo2);
4582
4664
  const manifestPath = options.manifest ?? DEFAULT_MANIFEST_PATH;
4583
4665
  const payload = await apiFetch(
4584
4666
  config,
@@ -4616,7 +4698,7 @@ async function sandboxUnassignRepoCommand(targetRepoArg, options = {}, command)
4616
4698
  }
4617
4699
  const { config } = resolveBusinessContext(command, options);
4618
4700
  const businessId = await resolveBusinessId(config, options);
4619
- const targetRepo = parseRepoArg2(targetRepoArg, currentRepo);
4701
+ const targetRepo = parseRepoArg3(targetRepoArg, currentRepo2);
4620
4702
  if (!options.yes) await confirmOrThrow(`Clear sandbox layer assignment for ${repoPath(targetRepo)}?`);
4621
4703
  const payload = await apiFetch(
4622
4704
  config,
@@ -4629,20 +4711,35 @@ async function sandboxUnassignRepoCommand(targetRepoArg, options = {}, command)
4629
4711
  }
4630
4712
 
4631
4713
  // src/commands/sessions.ts
4714
+ var MAX_ALL_PAGES = 1e3;
4632
4715
  async function listSessionsCommand(options, command) {
4633
4716
  const runtime = getRuntimeOptions(command, options);
4634
4717
  const config = requireConfig(runtime);
4635
- const query = new URLSearchParams();
4636
- if (options.status) query.set("status", options.status);
4637
- if (options.scope) query.set("scope", options.scope);
4638
- if (options.search) query.set("q", options.search);
4639
- if (options.repo) query.set("repo", options.repo);
4640
- if (options.limit) query.set("limit", options.limit);
4641
- if (options.cursor) query.set("cursor", options.cursor);
4642
- const payload = await apiFetch(
4643
- config,
4644
- `/api/sessions${query.size ? `?${query.toString()}` : ""}`
4645
- );
4718
+ const sessions2 = [];
4719
+ let cursor = options.cursor;
4720
+ let nextCursor = null;
4721
+ let pageCount = 0;
4722
+ do {
4723
+ pageCount += 1;
4724
+ if (options.all === true && pageCount > MAX_ALL_PAGES) {
4725
+ throw new CliError("user", `sessions list --all exceeded ${MAX_ALL_PAGES} pages without reaching the end.`);
4726
+ }
4727
+ const query = new URLSearchParams();
4728
+ if (options.status) query.set("status", options.status);
4729
+ if (options.scope) query.set("scope", options.scope);
4730
+ if (options.search) query.set("q", options.search);
4731
+ if (options.repo) query.set("repo", options.repo);
4732
+ if (options.limit) query.set("limit", options.limit);
4733
+ if (cursor) query.set("cursor", cursor);
4734
+ const payload2 = await apiFetch(
4735
+ config,
4736
+ `/api/sessions${query.size ? `?${query.toString()}` : ""}`
4737
+ );
4738
+ sessions2.push(...payload2.sessions);
4739
+ nextCursor = payload2.nextCursor;
4740
+ cursor = nextCursor ?? void 0;
4741
+ } while (options.all === true && nextCursor);
4742
+ const payload = { sessions: sessions2, nextCursor: options.all === true ? null : nextCursor };
4646
4743
  if (isJson(command, options)) {
4647
4744
  writeJson(payload);
4648
4745
  return;
@@ -4796,7 +4893,7 @@ function parseStopBlocked(err) {
4796
4893
 
4797
4894
  // src/commands/test-creds.ts
4798
4895
  import { readFileSync as readFileSync4 } from "fs";
4799
- function parseRepoArg3(repo) {
4896
+ function parseRepoArg4(repo) {
4800
4897
  const trimmed = repo.trim();
4801
4898
  const slashIndex = trimmed.indexOf("/");
4802
4899
  if (slashIndex <= 0 || slashIndex === trimmed.length - 1) {
@@ -4818,7 +4915,7 @@ function readValue(options) {
4818
4915
  }
4819
4916
  async function listTestCredentialsCommand(repo, options, command) {
4820
4917
  const { config } = resolveBusinessContext(command, options);
4821
- const repoArg = parseRepoArg3(repo);
4918
+ const repoArg = parseRepoArg4(repo);
4822
4919
  const payload = await apiFetch(config, basePath(options.business, repoArg));
4823
4920
  emit(command, options, payload, (listPayload) => {
4824
4921
  if (listPayload.credentials.length === 0) {
@@ -4833,7 +4930,7 @@ async function listTestCredentialsCommand(repo, options, command) {
4833
4930
  }
4834
4931
  async function setTestCredentialCommand(repo, name, options, command) {
4835
4932
  const { config } = resolveBusinessContext(command, options);
4836
- const repoArg = parseRepoArg3(repo);
4933
+ const repoArg = parseRepoArg4(repo);
4837
4934
  const value = readValue(options);
4838
4935
  const payload = await apiFetch(
4839
4936
  config,
@@ -4852,11 +4949,11 @@ async function deleteTestCredentialCommand(repo, name, options, command) {
4852
4949
  throw new CliError("user", "`test-creds delete --json` requires --yes.");
4853
4950
  }
4854
4951
  if (options.yes !== true) {
4855
- const repoArg2 = parseRepoArg3(repo);
4952
+ const repoArg2 = parseRepoArg4(repo);
4856
4953
  await confirmOrThrow(`Delete test credential '${name}' for ${repoArg2.owner}/${repoArg2.name}?`);
4857
4954
  }
4858
4955
  const { config } = resolveBusinessContext(command, options);
4859
- const repoArg = parseRepoArg3(repo);
4956
+ const repoArg = parseRepoArg4(repo);
4860
4957
  const payload = await apiFetch(
4861
4958
  config,
4862
4959
  `${basePath(options.business, repoArg)}/${encodeURIComponent(name)}`,
@@ -4871,15 +4968,30 @@ async function deleteTestCredentialCommand(repo, name, options, command) {
4871
4968
  }
4872
4969
 
4873
4970
  // src/commands/tokens.ts
4971
+ var MAX_ALL_PAGES2 = 1e3;
4874
4972
  async function listTokensCommand(options, command) {
4875
4973
  const { config } = resolveBusinessContext(command, options);
4876
- const query = new URLSearchParams();
4877
- if (options.limit) query.set("limit", options.limit);
4878
- if (options.cursor) query.set("cursor", options.cursor);
4879
- const payload = await apiFetch(
4880
- config,
4881
- `/api/cli-tokens${query.size ? `?${query.toString()}` : ""}`
4882
- );
4974
+ const data = [];
4975
+ let cursor = options.cursor;
4976
+ let nextCursor = null;
4977
+ let pageCount = 0;
4978
+ do {
4979
+ pageCount += 1;
4980
+ if (options.all === true && pageCount > MAX_ALL_PAGES2) {
4981
+ throw new CliError("user", `tokens list --all exceeded ${MAX_ALL_PAGES2} pages without reaching the end.`);
4982
+ }
4983
+ const query = new URLSearchParams();
4984
+ if (options.limit) query.set("limit", options.limit);
4985
+ if (cursor) query.set("cursor", cursor);
4986
+ const payload2 = await apiFetch(
4987
+ config,
4988
+ `/api/cli-tokens${query.size ? `?${query.toString()}` : ""}`
4989
+ );
4990
+ data.push(...payload2.data);
4991
+ nextCursor = payload2.nextCursor;
4992
+ cursor = nextCursor ?? void 0;
4993
+ } while (options.all === true && nextCursor);
4994
+ const payload = { data, nextCursor: options.all === true ? null : nextCursor };
4883
4995
  emit(command, options, payload, (listPayload) => {
4884
4996
  if (listPayload.data.length === 0) {
4885
4997
  console.log("No CLI tokens found.");
@@ -4943,8 +5055,44 @@ async function revokeTokenCommand(tokenId, options, command) {
4943
5055
  // src/commands/transcript.ts
4944
5056
  async function transcriptCommand(sessionId, options, command) {
4945
5057
  const { config } = resolveBusinessContext(command, options);
5058
+ const last = parseLast(options.last);
4946
5059
  const exportData = await apiFetch(config, `/api/sessions/${sessionId}/export`);
4947
- emit(command, options, exportData, (payload) => console.log(renderSessionTranscript(payload)));
5060
+ const payload = last == null ? exportData : sliceTranscript(exportData, last, { includeBoundaryEvent: !isJson(command, options) });
5061
+ emit(command, options, payload, (data) => console.log(renderSessionTranscript(data)));
5062
+ }
5063
+ function parseLast(raw) {
5064
+ if (raw === void 0) return null;
5065
+ const parsed = Number(raw);
5066
+ if (!Number.isInteger(parsed) || parsed <= 0) throw new CliError("user", "--last must be a positive integer.");
5067
+ return parsed;
5068
+ }
5069
+ function sliceTranscript(exportData, last, options) {
5070
+ const start = Math.max(0, exportData.events.length - last);
5071
+ const boundary = findPromptBoundary(exportData.events, start);
5072
+ const tail = exportData.events.slice(start);
5073
+ const events = options.includeBoundaryEvent && boundary !== null && boundary < start ? [exportData.events[boundary], ...tail] : tail;
5074
+ const promptIds = promptIdsForEvents(events);
5075
+ if (boundary !== null) {
5076
+ const boundaryPromptId = getRawSessionEventPromptId(exportData.events[boundary]);
5077
+ if (boundaryPromptId) promptIds.add(boundaryPromptId);
5078
+ }
5079
+ const prompts = exportData.prompts.filter((prompt) => promptIds.has(prompt.id));
5080
+ return { ...exportData, prompts, events };
5081
+ }
5082
+ function findPromptBoundary(events, start) {
5083
+ for (let index = start; index >= 0; index--) {
5084
+ const event = events[index];
5085
+ if (event && getRawSessionEventKind(event) === "prompt_processing") return index;
5086
+ }
5087
+ return null;
5088
+ }
5089
+ function promptIdsForEvents(events) {
5090
+ const promptIds = /* @__PURE__ */ new Set();
5091
+ for (const event of events) {
5092
+ const promptId = getRawSessionEventPromptId(event);
5093
+ if (promptId) promptIds.add(promptId);
5094
+ }
5095
+ return promptIds;
4948
5096
  }
4949
5097
 
4950
5098
  // src/index.ts
@@ -5103,27 +5251,29 @@ JSON:
5103
5251
  JSON mode returns the raw session payload. Result fields are at .session.prUrl, .session.publishedBranch, and .session.lastBranch.
5104
5252
  `
5105
5253
  ).action((sessionId, options, command) => getSessionCommand(sessionId, options, command));
5106
- 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(
5254
+ 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").option("--all", "Fetch all pages").addHelpText(
5107
5255
  "after",
5108
5256
  `
5109
5257
  Examples:
5110
5258
  arcanist sessions list
5111
5259
  arcanist sessions list --status idle --json
5112
5260
  arcanist sessions list --search "architect agent" --repo owner/repo
5261
+ arcanist sessions list --all --json
5113
5262
 
5114
5263
  JSON:
5115
- JSON mode returns {sessions, nextCursor}
5264
+ JSON mode returns {sessions, nextCursor}. --all follows cursors until nextCursor is null.
5116
5265
  `
5117
5266
  ).action((options, command) => listSessionsCommand(options, command));
5118
- 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(
5267
+ 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").option("--all", "Fetch all pages").addHelpText(
5119
5268
  "after",
5120
5269
  `
5121
5270
  Examples:
5122
5271
  arcanist sessions search "architect agent"
5123
5272
  arcanist sessions search "mcp debugging" --repo owner/repo --json
5273
+ arcanist sessions search "repo access" --all --json
5124
5274
 
5125
5275
  JSON:
5126
- JSON mode returns {sessions, nextCursor}
5276
+ JSON mode returns {sessions, nextCursor}. --all follows cursors until nextCursor is null.
5127
5277
  `
5128
5278
  ).action((query, options, command) => searchSessionsCommand(query, options, command));
5129
5279
  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(
@@ -5141,12 +5291,13 @@ JSON mode without --follow returns one JSON object from /events/history: {events
5141
5291
  Actionable follow types: pr_created/pr_updated use data.prUrl; question uses data.question; terminal types are prompt_completed, prompt_failed, and session_idle.
5142
5292
  `
5143
5293
  ).action((sessionId, options, command) => sessionEventsCommand(sessionId, options, command));
5144
- sessions.command("transcript").description("Render a session transcript").argument("<session-id>", "Session ID").addHelpText(
5294
+ sessions.command("transcript").description("Render a session transcript").argument("<session-id>", "Session ID").option("--last <n>", "Render only the last n transcript entries").addHelpText(
5145
5295
  "after",
5146
5296
  `
5147
5297
  Examples:
5148
5298
  arcanist sessions transcript <session-id>
5149
5299
  arcanist sessions transcript <session-id> --json
5300
+ arcanist sessions transcript <session-id> --last 20
5150
5301
  `
5151
5302
  ).action((sessionId, options, command) => transcriptCommand(sessionId, options, command));
5152
5303
  sessions.command("watch").description("Watch session activity until it becomes idle").argument("<session-id>", "Session ID").option("--poll-interval <ms>", "Polling interval in milliseconds", String(DEFAULT_WATCH_POLL_INTERVAL_MS)).addHelpText(
@@ -5165,6 +5316,36 @@ Examples:
5165
5316
  arcanist sessions usage <session-id> --json
5166
5317
  `
5167
5318
  ).action((sessionId, options, command) => usageCommand(sessionId, options, command));
5319
+ var repos = program.command("repos").description("Repository discovery commands");
5320
+ repos.command("list").description("List accessible repositories").addHelpText(
5321
+ "after",
5322
+ `
5323
+ JSON:
5324
+ JSON mode returns {repos, ssoOrgs}
5325
+ `
5326
+ ).action((options, command) => reposListCommand(options, command));
5327
+ repos.command("branches").description("List repository branches").argument("[repo]", "Repository owner/name; defaults to current git remote").addHelpText(
5328
+ "after",
5329
+ `
5330
+ JSON:
5331
+ JSON mode returns {branches}
5332
+ `
5333
+ ).action((repo, options, command) => repoBranchesCommand(repo, options, command));
5334
+ repos.command("skills").description("List repository skills").argument("[repo]", "Repository owner/name; defaults to current git remote").addHelpText(
5335
+ "after",
5336
+ `
5337
+ JSON:
5338
+ JSON mode returns {skills}
5339
+ `
5340
+ ).action((repo, options, command) => repoSkillsCommand(repo, options, command));
5341
+ var models = program.command("models").description("Model discovery commands");
5342
+ models.command("list").description("List session-start models").addHelpText(
5343
+ "after",
5344
+ `
5345
+ JSON:
5346
+ JSON mode returns {models}
5347
+ `
5348
+ ).action((options, command) => modelsListCommand(options, command));
5168
5349
  var automations = program.command("automations").description("Automation commands");
5169
5350
  automations.command("create").description("Create a scheduled automation").argument("<repo-url>", "Repository URL").argument("[prompt]", "Prompt to run, or '-' to read stdin").requiredOption("--cron <expr>", "Cron expression").option("--name <name>", "Automation display name").option(
5170
5351
  "--model <model>",
@@ -5236,7 +5417,7 @@ egress.command("add").description("Open or update a PR adding domains to the egr
5236
5417
  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));
5237
5418
  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));
5238
5419
  var tokens = program.command("tokens").description("CLI token commands");
5239
- tokens.command("list").description("List CLI tokens").option("--limit <n>", "Maximum tokens to return").option("--cursor <cursor>", "Pagination cursor").action((options, command) => listTokensCommand(options, command));
5420
+ 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));
5240
5421
  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(
5241
5422
  "after",
5242
5423
  `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryarcanist/cli",
3
- "version": "0.1.175",
3
+ "version": "0.1.177",
4
4
  "description": "CLI for Arcanist — create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {