@tryarcanist/cli 0.1.129 → 0.1.131

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 +32 -0
  2. package/dist/index.js +182 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -235,6 +235,38 @@ arcanist sessions usage abc123
235
235
  arcanist sessions usage abc123 --json
236
236
  ```
237
237
 
238
+ ### `arcanist automations create <repo-url> [prompt]`
239
+
240
+ 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.
241
+
242
+ ```bash
243
+ arcanist automations create your-org/your-repo "summarize new regressions" --cron "*/15 * * * *"
244
+ printf "summarize new regressions" | arcanist automations create your-org/your-repo --prompt-stdin --cron "*/15 * * * *" --json
245
+ arcanist automations create https://github.com/your-org/your-repo - --cron "0 14 * * 1-5" --name "Weekday summary"
246
+ ```
247
+
248
+ JSON mode prints the created rule. Human-readable mode prints the rule ID, repo, normalized cron, and next fire time.
249
+
250
+ ### `arcanist automations list`
251
+
252
+ ```bash
253
+ arcanist automations list
254
+ arcanist automations list --limit 20 --json
255
+ arcanist automations list --cursor <cursor>
256
+ arcanist automations list --all --json
257
+ ```
258
+
259
+ Default output reads one page. `--all` follows pagination until completion. JSON mode returns `{items, nextCursor}`.
260
+
261
+ ### `arcanist automations delete <id>`
262
+
263
+ Deletes a scheduled automation. Interactive mode prompts for confirmation; pass `--yes` for non-interactive use. `--json` requires `--yes`.
264
+
265
+ ```bash
266
+ arcanist automations delete rule_123
267
+ arcanist automations delete rule_123 --yes --json
268
+ ```
269
+
238
270
  ### `arcanist tokens list`
239
271
 
240
272
  ```bash
package/dist/index.js CHANGED
@@ -378,6 +378,161 @@ async function whoamiCommand(options, command) {
378
378
  if (payload.tokenScope) console.log(`Token scope: ${String(payload.tokenScope)}`);
379
379
  }
380
380
 
381
+ // ../../shared/github/repo-url.ts
382
+ function parseGithubRepoFullName(value) {
383
+ const shorthand = value.match(/^([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)$/);
384
+ if (shorthand) return { owner: shorthand[1], repo: shorthand[2] };
385
+ const ssh = value.match(/git@[^:]+:([^/]+)\/([^/]+?)(?:\.git)?$/);
386
+ if (ssh) return { owner: ssh[1], repo: ssh[2] };
387
+ const https = value.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
388
+ if (https) return { owner: https[1], repo: https[2] };
389
+ return null;
390
+ }
391
+
392
+ // src/commands/automations.ts
393
+ var AUTOMATION_ERROR_HINTS = {
394
+ invalid_cron: "Use a standard five-field cron expression, for example: */15 * * * *",
395
+ repo_not_available: "Check that the token owner has GitHub access and Arcanist is installed for that repo.",
396
+ duplicate_rule: "An enabled automation already exists for this repo, cron, and prompt.",
397
+ rule_cap_reached: "Delete or disable an existing automation before creating another one. The cap is 20 enabled rules.",
398
+ installation_unresolved: "Reconnect or reinstall the GitHub integration for that repository, then retry."
399
+ };
400
+ async function createAutomationCommand(repoUrl, promptArg, options, command) {
401
+ const repo = parseAutomationRepo(repoUrl);
402
+ const prompt = await resolvePromptInput(promptArg, options);
403
+ const runtime = getRuntimeOptions(command, options);
404
+ const config = requireConfig(runtime);
405
+ const body = {
406
+ repoOwner: repo.owner,
407
+ repoName: repo.repo,
408
+ cron: options.cron,
409
+ prompt
410
+ };
411
+ if (options.name !== void 0) body.name = options.name;
412
+ const payload = await automationApiFetch(config, "/api/automation/schedules", {
413
+ method: "POST",
414
+ body: JSON.stringify(body)
415
+ });
416
+ if (isJson(command, options)) {
417
+ writeJson(payload.data);
418
+ return;
419
+ }
420
+ printAutomationRule(payload.data);
421
+ }
422
+ async function listAutomationsCommand(options, command) {
423
+ const runtime = getRuntimeOptions(command, options);
424
+ const config = requireConfig(runtime);
425
+ const items = [];
426
+ let cursor = options.cursor;
427
+ let nextCursor = null;
428
+ do {
429
+ const query = new URLSearchParams();
430
+ if (options.limit) query.set("limit", options.limit);
431
+ if (cursor) query.set("cursor", cursor);
432
+ const payload = await automationApiFetch(
433
+ config,
434
+ `/api/automation/schedules${query.size ? `?${query.toString()}` : ""}`
435
+ );
436
+ items.push(...payload.data.items);
437
+ nextCursor = payload.data.nextCursor;
438
+ cursor = nextCursor ?? void 0;
439
+ } while (options.all === true && nextCursor);
440
+ const output = { items, nextCursor: options.all === true ? null : nextCursor };
441
+ if (isJson(command, options)) {
442
+ writeJson(output);
443
+ return;
444
+ }
445
+ if (items.length === 0) {
446
+ console.log("No automations found.");
447
+ return;
448
+ }
449
+ for (const rule of items) {
450
+ console.log(
451
+ `${rule.id} ${rule.repoOwner}/${rule.repoName} ${rule.normalizedCron} ${String(rule.enabled)} ${formatTime(rule.nextFireAt)}`
452
+ );
453
+ }
454
+ if (output.nextCursor) console.log(`Next cursor: ${output.nextCursor}`);
455
+ }
456
+ async function deleteAutomationCommand(id, options, command) {
457
+ if (isJson(command, options) && options.yes !== true) {
458
+ throw new CliError("user", "`automations delete --json` requires --yes.");
459
+ }
460
+ if (options.yes !== true) {
461
+ await confirmOrThrow(`Delete automation ${id}?`);
462
+ }
463
+ const runtime = getRuntimeOptions(command, options);
464
+ const config = requireConfig(runtime);
465
+ await automationApiFetchText(config, `/api/automation/schedules/${encodeURIComponent(id)}`, { method: "DELETE" });
466
+ if (isJson(command, options)) {
467
+ writeJson({ ok: true, id });
468
+ return;
469
+ }
470
+ console.log(`Deleted automation ${id}.`);
471
+ }
472
+ function parseAutomationRepo(value) {
473
+ if (/^git@/i.test(value) && !/^git@github\.com:/i.test(value)) {
474
+ throw new CliError("user", "repo-url must be owner/name or a GitHub URL.");
475
+ }
476
+ const parsed = parseGithubRepoFullName(value);
477
+ if (!parsed) throw new CliError("user", "repo-url must be owner/name or a GitHub URL.");
478
+ return { owner: parsed.owner, repo: parsed.repo };
479
+ }
480
+ async function automationApiFetch(config, path, init) {
481
+ try {
482
+ return await apiFetch(config, path, init);
483
+ } catch (err) {
484
+ throw mapAutomationApiError(err);
485
+ }
486
+ }
487
+ async function automationApiFetchText(config, path, init) {
488
+ try {
489
+ return await apiFetchText(config, path, init);
490
+ } catch (err) {
491
+ throw mapAutomationApiError(err);
492
+ }
493
+ }
494
+ function mapAutomationApiError(err) {
495
+ if (!(err instanceof ApiError)) {
496
+ return err instanceof CliError ? err : new CliError("server", err instanceof Error ? err.message : String(err));
497
+ }
498
+ const parsed = parseApiErrorBody2(err.body);
499
+ const serverCode = parsed?.error;
500
+ return new CliError(codeForStatus(err.status), parsed?.message || serverCode || err.message, {
501
+ exitCode: err.exitCode,
502
+ hint: serverCode ? AUTOMATION_ERROR_HINTS[serverCode] : void 0,
503
+ requestId: err.requestId
504
+ });
505
+ }
506
+ function parseApiErrorBody2(body) {
507
+ try {
508
+ const parsed = JSON.parse(body);
509
+ if (!parsed || typeof parsed !== "object") return null;
510
+ const record = parsed;
511
+ return {
512
+ error: typeof record.error === "string" ? record.error : void 0,
513
+ message: typeof record.message === "string" ? record.message : void 0
514
+ };
515
+ } catch {
516
+ return null;
517
+ }
518
+ }
519
+ function codeForStatus(status) {
520
+ if (status === 401 || status === 403) return "auth";
521
+ if (status === 404) return "not_found";
522
+ if (status === 409) return "conflict";
523
+ if (status >= 500) return "server";
524
+ return "user";
525
+ }
526
+ function printAutomationRule(rule) {
527
+ console.log(`ID: ${rule.id}`);
528
+ console.log(`Repo: ${rule.repoOwner}/${rule.repoName}`);
529
+ console.log(`Cron: ${rule.normalizedCron}`);
530
+ console.log(`Next fire: ${formatTime(rule.nextFireAt)}`);
531
+ }
532
+ function formatTime(value) {
533
+ return typeof value === "number" ? new Date(value).toISOString() : "none";
534
+ }
535
+
381
536
  // ../../shared/agent/agent-runtime-backend.ts
382
537
  var CODEX_AGENT_RUNTIME_BACKEND = "codex";
383
538
  var CLAUDE_CODE_AGENT_RUNTIME_BACKEND = "claude_code";
@@ -2551,17 +2706,6 @@ import { execFileSync } from "child_process";
2551
2706
  import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
2552
2707
  import { dirname } from "path";
2553
2708
 
2554
- // ../../shared/github/repo-url.ts
2555
- function parseGithubRepoFullName(value) {
2556
- const shorthand = value.match(/^([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)$/);
2557
- if (shorthand) return { owner: shorthand[1], repo: shorthand[2] };
2558
- const ssh = value.match(/git@[^:]+:([^/]+)\/([^/]+?)(?:\.git)?$/);
2559
- if (ssh) return { owner: ssh[1], repo: ssh[2] };
2560
- const https = value.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
2561
- if (https) return { owner: https[1], repo: https[2] };
2562
- return null;
2563
- }
2564
-
2565
2709
  // ../../shared/sandbox-layer/parser.ts
2566
2710
  import { isMap, isScalar, isSeq, parseDocument } from "yaml";
2567
2711
 
@@ -3693,7 +3837,7 @@ async function getSessionCommand(sessionId, options, command) {
3693
3837
  }
3694
3838
  const session = payload.session && typeof payload.session === "object" ? payload.session : payload;
3695
3839
  console.log(`Session: ${String(session.sessionId ?? session.id ?? sessionId)}`);
3696
- console.log(`Status: ${String(session.status ?? "unknown")}`);
3840
+ console.log(`Status: ${String(session.phase ?? session.status ?? "unknown")}`);
3697
3841
  if (session.repoUrl) console.log(`Repo: ${String(session.repoUrl)}`);
3698
3842
  if (session.title) console.log(`Title: ${String(session.title)}`);
3699
3843
  }
@@ -4129,6 +4273,32 @@ Examples:
4129
4273
  arcanist sessions usage <session-id> --json
4130
4274
  `
4131
4275
  ).action((sessionId, options, command) => usageCommand(sessionId, options, command));
4276
+ var automations = program.command("automations").description("Automation commands");
4277
+ 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("--prompt-stdin", "Read prompt from stdin").addHelpText(
4278
+ "after",
4279
+ `
4280
+ Examples:
4281
+ arcanist automations create tryarcanist/arcanist "summarize regressions" --cron "*/15 * * * *"
4282
+ printf "summarize regressions" | arcanist automations create tryarcanist/arcanist --prompt-stdin --cron "*/15 * * * *" --json
4283
+ `
4284
+ ).action((repoUrl, prompt, options, command) => createAutomationCommand(repoUrl, prompt, options, command));
4285
+ automations.command("list").description("List scheduled automations").option("--limit <n>", "Maximum automations to return").option("--cursor <cursor>", "Pagination cursor").option("--all", "Fetch all pages").addHelpText(
4286
+ "after",
4287
+ `
4288
+ Examples:
4289
+ arcanist automations list
4290
+ arcanist automations list --limit 20 --json
4291
+ arcanist automations list --all --json
4292
+ `
4293
+ ).action((options, command) => listAutomationsCommand(options, command));
4294
+ automations.command("delete").description("Delete a scheduled automation").argument("<id>", "Automation ID").option("--yes", "Confirm deletion without prompting").addHelpText(
4295
+ "after",
4296
+ `
4297
+ Examples:
4298
+ arcanist automations delete auto_123
4299
+ arcanist automations delete auto_123 --yes --json
4300
+ `
4301
+ ).action((id, options, command) => deleteAutomationCommand(id, options, command));
4132
4302
  var sandbox = program.command("sandbox").description("Sandbox layer commands");
4133
4303
  sandbox.command("init").description("Create sandbox layer source files").action((options, command) => sandboxInitCommand(options, command));
4134
4304
  sandbox.command("validate").description("Validate local sandbox layer source files").option("--manifest <path>", "Manifest path", ".arcanist/sandbox.yaml").action((options, command) => sandboxValidateCommand(options, command));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryarcanist/cli",
3
- "version": "0.1.129",
3
+ "version": "0.1.131",
4
4
  "description": "CLI for Arcanist — create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {