@tryarcanist/cli 0.1.175 → 0.1.176

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 +36 -0
  2. package/dist/index.js +139 -27
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -327,6 +327,42 @@ arcanist sessions usage abc123
327
327
  arcanist sessions usage abc123 --json
328
328
  ```
329
329
 
330
+ ### `arcanist repos list`
331
+
332
+ ```bash
333
+ arcanist repos list
334
+ arcanist repos list --json
335
+ ```
336
+
337
+ JSON mode returns `{repos, ssoOrgs}`.
338
+
339
+ ### `arcanist repos branches [repo]`
340
+
341
+ ```bash
342
+ arcanist repos branches tryarcanist/arcanist
343
+ arcanist repos branches https://github.com/tryarcanist/arcanist --json
344
+ ```
345
+
346
+ JSON mode returns `{branches}`.
347
+
348
+ ### `arcanist repos skills [repo]`
349
+
350
+ ```bash
351
+ arcanist repos skills tryarcanist/arcanist
352
+ arcanist repos skills https://github.com/tryarcanist/arcanist --json
353
+ ```
354
+
355
+ JSON mode returns `{skills}`.
356
+
357
+ ### `arcanist models list`
358
+
359
+ ```bash
360
+ arcanist models list
361
+ arcanist models list --json
362
+ ```
363
+
364
+ JSON mode returns `{models}`.
365
+
330
366
  ### `arcanist automations create <repo-url> [prompt]`
331
367
 
332
368
  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.
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,
@@ -4796,7 +4878,7 @@ function parseStopBlocked(err) {
4796
4878
 
4797
4879
  // src/commands/test-creds.ts
4798
4880
  import { readFileSync as readFileSync4 } from "fs";
4799
- function parseRepoArg3(repo) {
4881
+ function parseRepoArg4(repo) {
4800
4882
  const trimmed = repo.trim();
4801
4883
  const slashIndex = trimmed.indexOf("/");
4802
4884
  if (slashIndex <= 0 || slashIndex === trimmed.length - 1) {
@@ -4818,7 +4900,7 @@ function readValue(options) {
4818
4900
  }
4819
4901
  async function listTestCredentialsCommand(repo, options, command) {
4820
4902
  const { config } = resolveBusinessContext(command, options);
4821
- const repoArg = parseRepoArg3(repo);
4903
+ const repoArg = parseRepoArg4(repo);
4822
4904
  const payload = await apiFetch(config, basePath(options.business, repoArg));
4823
4905
  emit(command, options, payload, (listPayload) => {
4824
4906
  if (listPayload.credentials.length === 0) {
@@ -4833,7 +4915,7 @@ async function listTestCredentialsCommand(repo, options, command) {
4833
4915
  }
4834
4916
  async function setTestCredentialCommand(repo, name, options, command) {
4835
4917
  const { config } = resolveBusinessContext(command, options);
4836
- const repoArg = parseRepoArg3(repo);
4918
+ const repoArg = parseRepoArg4(repo);
4837
4919
  const value = readValue(options);
4838
4920
  const payload = await apiFetch(
4839
4921
  config,
@@ -4852,11 +4934,11 @@ async function deleteTestCredentialCommand(repo, name, options, command) {
4852
4934
  throw new CliError("user", "`test-creds delete --json` requires --yes.");
4853
4935
  }
4854
4936
  if (options.yes !== true) {
4855
- const repoArg2 = parseRepoArg3(repo);
4937
+ const repoArg2 = parseRepoArg4(repo);
4856
4938
  await confirmOrThrow(`Delete test credential '${name}' for ${repoArg2.owner}/${repoArg2.name}?`);
4857
4939
  }
4858
4940
  const { config } = resolveBusinessContext(command, options);
4859
- const repoArg = parseRepoArg3(repo);
4941
+ const repoArg = parseRepoArg4(repo);
4860
4942
  const payload = await apiFetch(
4861
4943
  config,
4862
4944
  `${basePath(options.business, repoArg)}/${encodeURIComponent(name)}`,
@@ -5165,6 +5247,36 @@ Examples:
5165
5247
  arcanist sessions usage <session-id> --json
5166
5248
  `
5167
5249
  ).action((sessionId, options, command) => usageCommand(sessionId, options, command));
5250
+ var repos = program.command("repos").description("Repository discovery commands");
5251
+ repos.command("list").description("List accessible repositories").addHelpText(
5252
+ "after",
5253
+ `
5254
+ JSON:
5255
+ JSON mode returns {repos, ssoOrgs}
5256
+ `
5257
+ ).action((options, command) => reposListCommand(options, command));
5258
+ repos.command("branches").description("List repository branches").argument("[repo]", "Repository owner/name; defaults to current git remote").addHelpText(
5259
+ "after",
5260
+ `
5261
+ JSON:
5262
+ JSON mode returns {branches}
5263
+ `
5264
+ ).action((repo, options, command) => repoBranchesCommand(repo, options, command));
5265
+ repos.command("skills").description("List repository skills").argument("[repo]", "Repository owner/name; defaults to current git remote").addHelpText(
5266
+ "after",
5267
+ `
5268
+ JSON:
5269
+ JSON mode returns {skills}
5270
+ `
5271
+ ).action((repo, options, command) => repoSkillsCommand(repo, options, command));
5272
+ var models = program.command("models").description("Model discovery commands");
5273
+ models.command("list").description("List session-start models").addHelpText(
5274
+ "after",
5275
+ `
5276
+ JSON:
5277
+ JSON mode returns {models}
5278
+ `
5279
+ ).action((options, command) => modelsListCommand(options, command));
5168
5280
  var automations = program.command("automations").description("Automation commands");
5169
5281
  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
5282
  "--model <model>",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryarcanist/cli",
3
- "version": "0.1.175",
3
+ "version": "0.1.176",
4
4
  "description": "CLI for Arcanist — create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {