automata-cli 0.4.0-develop.124 → 0.4.0-develop.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 -0
  2. package/dist/index.js +139 -31
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -99,6 +99,20 @@ See [docs/implement-next.md](docs/implement-next.md) for full details.
99
99
 
100
100
  ---
101
101
 
102
+ ## `automata execute`
103
+
104
+ Delegate work to an AI executor (Claude or Codex) by providing a prompt inline, from a file, or via stdin.
105
+
106
+ ```bash
107
+ automata execute --with claude --prompt "refactor the auth module"
108
+ automata execute --with codex --file-prompt prompts/fix.md
109
+ echo "fix lint errors" | automata execute --with claude
110
+ ```
111
+
112
+ See [docs/execute.md](docs/execute.md) for full details.
113
+
114
+ ---
115
+
102
116
  ## Development
103
117
 
104
118
  ### Prerequisites
package/dist/index.js CHANGED
@@ -557,6 +557,14 @@ function extractSonarProjectKey(url) {
557
557
  return null;
558
558
  }
559
559
  }
560
+ function buildSonarPullRequestUrl(projectKey, prNumber) {
561
+ return `https://sonarcloud.io/summary/new_code?id=${encodeURIComponent(projectKey)}&pullRequest=${String(prNumber)}`;
562
+ }
563
+ function resolveSonarPullRequestUrl(url, prNumber) {
564
+ const projectKey = extractSonarProjectKey(url);
565
+ if (!projectKey) return url;
566
+ return buildSonarPullRequestUrl(projectKey, prNumber);
567
+ }
560
568
  function isSonarUrl(url) {
561
569
  try {
562
570
  const hostname = new URL(url).hostname;
@@ -671,13 +679,28 @@ async function fetchSonarJson(apiUrl) {
671
679
  clearTimeout(timeoutId);
672
680
  }
673
681
  }
674
- async function fetchSonarNewIssues(projectKey, prNumber) {
682
+ async function fetchSonarNewIssues(projectKey, prNumber, sonarcloudUrl) {
675
683
  const apiUrl = `https://sonarcloud.io/api/issues/search?componentKeys=${encodeURIComponent(projectKey)}&pullRequest=${String(prNumber)}&resolved=false&ps=1`;
676
684
  const response = await fetchSonarJson(apiUrl);
677
685
  if (!response.ok) {
678
- return null;
686
+ if (response.status === 401) {
687
+ return {
688
+ total: null,
689
+ note: `SonarCloud project is private. Open the Sonar URL in an authenticated browser: ${sonarcloudUrl}`
690
+ };
691
+ }
692
+ return {
693
+ total: null,
694
+ note: "SonarCloud new-issue count is unavailable right now."
695
+ };
679
696
  }
680
- return response.data.paging?.total ?? null;
697
+ if (typeof response.data.paging?.total === "number") {
698
+ return { total: response.data.paging.total };
699
+ }
700
+ return {
701
+ total: null,
702
+ note: "SonarCloud did not return a new-issue count for this pull request."
703
+ };
681
704
  }
682
705
  async function fetchSonarGateViolations(projectKey, prNumber) {
683
706
  const apiUrl = `https://sonarcloud.io/api/qualitygates/project_status?projectKey=${encodeURIComponent(projectKey)}&pullRequest=${String(prNumber)}`;
@@ -793,19 +816,22 @@ async function fetchSonarFailureSummary(projectKey, prNumber, sonarcloudUrl) {
793
816
  if (!gateResult.ok && gateResult.status === 401) {
794
817
  return {
795
818
  summary: sonarPrivateFailureSummary(sonarcloudUrl),
796
- issueTotal: null
819
+ issueTotal: null,
820
+ issueNote: `SonarCloud project is private. Open the Sonar URL in an authenticated browser: ${sonarcloudUrl}`
797
821
  };
798
822
  }
799
823
  if (!issuesResult.ok && issuesResult.status === 401) {
800
824
  return {
801
825
  summary: sonarPrivateFailureSummary(sonarcloudUrl),
802
- issueTotal: null
826
+ issueTotal: null,
827
+ issueNote: `SonarCloud project is private. Open the Sonar URL in an authenticated browser: ${sonarcloudUrl}`
803
828
  };
804
829
  }
805
830
  if (!hotspotsResult.ok && hotspotsResult.status === 401) {
806
831
  return {
807
832
  summary: sonarPrivateFailureSummary(sonarcloudUrl),
808
- issueTotal: null
833
+ issueTotal: null,
834
+ issueNote: `SonarCloud project is private. Open the Sonar URL in an authenticated browser: ${sonarcloudUrl}`
809
835
  };
810
836
  }
811
837
  const gateViolations = gateResult.ok ? gateResult.data.gateViolations : [];
@@ -813,6 +839,7 @@ async function fetchSonarFailureSummary(projectKey, prNumber, sonarcloudUrl) {
813
839
  const securityHotspots = hotspotsResult.ok ? hotspotsResult.data : [];
814
840
  const qualityGateStatus = gateResult.ok ? gateResult.data.qualityGateStatus : void 0;
815
841
  const issueTotal = issuesResult.ok ? issuesResult.data.total : null;
842
+ const issueNote = issuesResult.ok ? void 0 : "SonarCloud new-issue count is unavailable right now.";
816
843
  if (gateViolations.length === 0 && issues.length === 0 && securityHotspots.length === 0 && !qualityGateStatus) {
817
844
  return {
818
845
  summary: {
@@ -822,7 +849,8 @@ async function fetchSonarFailureSummary(projectKey, prNumber, sonarcloudUrl) {
822
849
  securityHotspots: [],
823
850
  unavailableMessage: "SonarCloud failure details are unavailable right now."
824
851
  },
825
- issueTotal
852
+ issueTotal,
853
+ issueNote
826
854
  };
827
855
  }
828
856
  return {
@@ -833,7 +861,8 @@ async function fetchSonarFailureSummary(projectKey, prNumber, sonarcloudUrl) {
833
861
  issues,
834
862
  securityHotspots
835
863
  },
836
- issueTotal
864
+ issueTotal,
865
+ issueNote
837
866
  };
838
867
  }
839
868
  async function getPrInfoGh(branch) {
@@ -851,12 +880,16 @@ async function getPrInfoGh(branch) {
851
880
  throw new Error(stderr.trim() || "Failed to query GitHub. Is `gh` installed and authenticated?");
852
881
  }
853
882
  const raw = JSON.parse(stdout);
854
- const failedChecks = (raw.statusCheckRollup ?? []).filter(
883
+ const statusChecks = raw.statusCheckRollup ?? [];
884
+ const failedChecks = statusChecks.filter(
855
885
  (c) => c.conclusion !== null && ["FAILURE", "TIMED_OUT", "ACTION_REQUIRED", "CANCELLED"].includes(c.conclusion)
856
886
  );
857
- const ownerRepo = failedChecks.length > 0 ? parseOwnerRepo() : null;
887
+ const sonarNeedsCheckRunOutput = statusChecks.some(
888
+ (c) => (isSonarUrl(c.detailsUrl) || c.name.toLowerCase().includes("sonar")) && !extractSonarProjectKey(c.detailsUrl)
889
+ );
890
+ const ownerRepo = failedChecks.length > 0 || sonarNeedsCheckRunOutput ? parseOwnerRepo() : null;
858
891
  const checkOutputs = ownerRepo ? fetchCheckRunOutputs(ownerRepo, raw.headRefOid) : /* @__PURE__ */ new Map();
859
- const checks = (raw.statusCheckRollup ?? []).map((c) => {
892
+ const checks = statusChecks.map((c) => {
860
893
  const enriched = checkOutputs.get(c.name);
861
894
  return {
862
895
  name: c.name,
@@ -869,20 +902,25 @@ async function getPrInfoGh(branch) {
869
902
  const sonarCheck = checks.find((c) => isSonarUrl(c.detailsUrl));
870
903
  let sonarcloudUrl;
871
904
  let sonarNewIssues;
905
+ let sonarNewIssuesNote;
872
906
  let sonarFailures;
873
907
  if (sonarCheck) {
874
- sonarcloudUrl = sonarCheck.detailsUrl;
875
- const projectKey = extractSonarProjectKey(sonarCheck.detailsUrl);
908
+ sonarcloudUrl = resolveSonarPullRequestUrl(sonarCheck.detailsUrl, raw.number);
909
+ const projectKey = extractSonarProjectKey(sonarcloudUrl);
876
910
  if (projectKey) {
877
911
  if (sonarCheck.conclusion !== null && SONAR_FAIL_CONCLUSIONS.has(sonarCheck.conclusion)) {
878
912
  const sonarSummary = await fetchSonarFailureSummary(projectKey, raw.number, sonarcloudUrl);
879
913
  sonarFailures = sonarSummary.summary;
880
914
  sonarNewIssues = sonarSummary.issueTotal;
915
+ sonarNewIssuesNote = sonarSummary.issueNote;
881
916
  } else {
882
- sonarNewIssues = await fetchSonarNewIssues(projectKey, raw.number);
917
+ const sonarNewIssuesResult = await fetchSonarNewIssues(projectKey, raw.number, sonarcloudUrl);
918
+ sonarNewIssues = sonarNewIssuesResult.total;
919
+ sonarNewIssuesNote = sonarNewIssuesResult.note;
883
920
  }
884
921
  } else {
885
922
  sonarNewIssues = null;
923
+ sonarNewIssuesNote = "Could not determine the SonarCloud project key from the check URL.";
886
924
  }
887
925
  }
888
926
  return {
@@ -891,7 +929,7 @@ async function getPrInfoGh(branch) {
891
929
  state: raw.state,
892
930
  url: raw.url,
893
931
  checks,
894
- ...sonarcloudUrl === void 0 ? {} : { sonarcloudUrl, sonarNewIssues },
932
+ ...sonarcloudUrl === void 0 ? {} : { sonarcloudUrl, sonarNewIssues, sonarNewIssuesNote },
895
933
  ...sonarFailures === void 0 ? {} : { sonarFailures }
896
934
  };
897
935
  }
@@ -1221,6 +1259,12 @@ function formatSonarFailures(sonarFailures, sonarcloudUrl) {
1221
1259
  }
1222
1260
  return lines.join("\n") + "\n";
1223
1261
  }
1262
+ function sonarFailureNote(sonarFailures) {
1263
+ if (!sonarFailures) return void 0;
1264
+ if (sonarFailures.status === "private") return sonarFailures.privateMessage;
1265
+ if (sonarFailures.status === "unavailable") return sonarFailures.unavailableMessage;
1266
+ return void 0;
1267
+ }
1224
1268
  var POLL_INTERVAL_MS = 1e4;
1225
1269
  var getPrInfoCmd = new Command2("get-pr-info").description("Show pull request info for the current branch").option("--json", "Output as JSON").option("--wait-finish-checks", "Poll until all checks complete, then print the normal get-pr-info output").addHelpText(
1226
1270
  "after",
@@ -1290,6 +1334,11 @@ URL: ${pr.url}
1290
1334
  const issueStr = pr.sonarNewIssues === null || pr.sonarNewIssues === void 0 ? "unavailable" : String(pr.sonarNewIssues);
1291
1335
  process.stdout.write(`Sonar New Issues: ${issueStr}
1292
1336
  `);
1337
+ const failureNote = sonarFailureNote(pr.sonarFailures);
1338
+ if (pr.sonarNewIssuesNote && pr.sonarNewIssuesNote !== failureNote) {
1339
+ process.stdout.write(`Sonar Note: ${sanitizeText(pr.sonarNewIssuesNote)}
1340
+ `);
1341
+ }
1293
1342
  }
1294
1343
  process.stdout.write(formatCheckSummary(pr.checks));
1295
1344
  process.stdout.write(formatChecks(pr.checks));
@@ -1737,12 +1786,13 @@ function invokeCodexCode(prompt, options = {}) {
1737
1786
  if (options.verbose) {
1738
1787
  process.stderr.write("Warning: --verbose is not supported for Codex and will be ignored.\n");
1739
1788
  }
1740
- invokeCodexCodeSync(prompt, options.yolo ?? false);
1789
+ invokeCodexCodeSync(prompt, options.yolo ?? false, options.model);
1741
1790
  }
1742
- function invokeCodexCodeSync(prompt, yolo) {
1791
+ function invokeCodexCodeSync(prompt, yolo, model) {
1743
1792
  const codexBin = resolveCommand("codex");
1744
1793
  const args = ["exec"];
1745
1794
  if (yolo) args.push("--dangerously-bypass-approvals-and-sandbox");
1795
+ if (model) args.push("--model", model);
1746
1796
  args.push(prompt);
1747
1797
  const result = spawnSync5(codexBin, args, { encoding: "utf8", stdio: "inherit" });
1748
1798
  handleSpawnError(result.error, "codex");
@@ -1829,7 +1879,7 @@ Title: ${issue.title}
1829
1879
  }
1830
1880
  return issue;
1831
1881
  }
1832
- var implementNextCommand = new Command3("implement-next").description("Find the next open GitHub issue matching the configured filter, claim it, and invoke the AI code assistant (Claude or Codex)").option("--json", "Output issue details as JSON").option("--no-claude", "Skip all AI invocation (Claude or Codex) after claiming the issue").option("--codex", "Use Codex CLI instead of Claude Code").option("--query-only", "Print issue content and exit without claiming or invoking any AI tools").option("--yolo", "Launch with --dangerously-skip-permissions (Claude) or --dangerously-bypass-approvals-and-sandbox (Codex)").option("--verbose", "Show step-by-step progress summary and final result").option("--opus", "Use claude-opus-4-6").option("--sonnet", "Use claude-sonnet-4-6").option("--haiku", "Use claude-haiku-4-5-20251001").option("--take-first", "When multiple issues match, pick the first without prompting").option("--limit <n>", "Max issues to fetch and display (default: 10)", "10").action(async (options) => {
1882
+ var implementNextCommand = new Command3("implement-next").description("Find the next open GitHub issue matching the configured filter, claim it, and invoke the AI code assistant (Claude or Codex)").option("--json", "Output issue details as JSON").option("--no-claude", "Skip all AI invocation (Claude or Codex) after claiming the issue").option("--with <executor>", "Executor to use: claude or codex", "claude").option("--query-only", "Print issue content and exit without claiming or invoking any AI tools").option("--yolo", "Launch with --dangerously-skip-permissions (Claude) or --dangerously-bypass-approvals-and-sandbox (Codex)").option("--silent", "Suppress step-by-step Claude output; show only the final summary").option("--model <string>", "Model identifier to pass to the executor").option("--take-first", "When multiple issues match, pick the first without prompting").option("--limit <n>", "Max issues to fetch and display (default: 10)", "10").action(async (options) => {
1833
1883
  const config = readConfig();
1834
1884
  validateConfig(config);
1835
1885
  const limit = Number.parseInt(options.limit, 10);
@@ -1858,6 +1908,16 @@ ${issue.body}
1858
1908
  if (options.queryOnly) {
1859
1909
  process.exit(0);
1860
1910
  }
1911
+ let executor;
1912
+ if (options.claude !== false) {
1913
+ const requestedExecutor = options.with.toLowerCase();
1914
+ if (requestedExecutor !== "claude" && requestedExecutor !== "codex") {
1915
+ process.stderr.write(`Error: --with must be 'claude' or 'codex', got '${options.with}'.
1916
+ `);
1917
+ process.exit(1);
1918
+ }
1919
+ executor = requestedExecutor;
1920
+ }
1861
1921
  try {
1862
1922
  postComment(issue.number, "working");
1863
1923
  } catch (err) {
@@ -1872,25 +1932,73 @@ ${issue.body}
1872
1932
  ${systemPrompt}
1873
1933
 
1874
1934
  ${issue.body}`;
1875
- if (options.codex) {
1876
- await invokeCodexCode(prompt, { yolo: options.yolo, verbose: options.verbose });
1935
+ if (executor === "codex") {
1936
+ if (options.silent) {
1937
+ process.stderr.write("Warning: --silent is only supported with Claude and has no effect when used with Codex.\n");
1938
+ }
1939
+ invokeCodexCode(prompt, { yolo: options.yolo, model: options.model });
1877
1940
  } else {
1878
- const model = resolveModelOption(options);
1879
- await invokeClaudeCode(prompt, { yolo: options.yolo, verbose: options.verbose, model });
1941
+ await invokeClaudeCode(prompt, { yolo: options.yolo, verbose: !options.silent, model: options.model });
1880
1942
  }
1881
1943
  }
1882
1944
  });
1883
1945
 
1884
- // src/commands/test.ts
1946
+ // src/commands/execute.ts
1947
+ import { readFileSync as readFileSync3 } from "fs";
1885
1948
  import { Command as Command4 } from "commander";
1886
- var testClaudeCmd = new Command4("claude").description("Test Claude Code invocation with a user-supplied prompt").requiredOption("--prompt <string>", "Prompt to send to Claude Code").option("--yolo", "Launch Claude Code with --dangerously-skip-permissions").option("--verbose", "Show step-by-step progress summary and final result").option("--opus", "Use claude-opus-4-6").option("--sonnet", "Use claude-sonnet-4-6").option("--haiku", "Use claude-haiku-4-5-20251001").action(async (options) => {
1887
- const model = resolveModelOption(options);
1888
- await invokeClaudeCode(options.prompt, { yolo: options.yolo, verbose: options.verbose, model });
1889
- });
1890
- var testCodexCmd = new Command4("codex").description("Test Codex CLI invocation with a user-supplied prompt").requiredOption("--prompt <string>", "Prompt to send to Codex CLI").option("--yolo", "Launch Codex with --dangerously-bypass-approvals-and-sandbox").option("--verbose", "Not supported for Codex; prints a warning and is otherwise ignored").action((options) => {
1891
- invokeCodexCode(options.prompt, { yolo: options.yolo, verbose: options.verbose });
1949
+ function readStdin() {
1950
+ return new Promise((resolve3, reject) => {
1951
+ const chunks = [];
1952
+ process.stdin.on("data", (chunk) => chunks.push(chunk));
1953
+ process.stdin.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
1954
+ process.stdin.on("error", reject);
1955
+ });
1956
+ }
1957
+ async function resolvePrompt(options) {
1958
+ const hasCli = options.prompt !== void 0;
1959
+ const hasFile = options.filePrompt !== void 0;
1960
+ if (hasCli && hasFile) {
1961
+ process.stderr.write("Error: --prompt is mutually exclusive with --file-prompt.\n");
1962
+ process.exit(1);
1963
+ }
1964
+ if (hasCli) {
1965
+ return options.prompt;
1966
+ }
1967
+ if (hasFile) {
1968
+ const path = options.filePrompt;
1969
+ try {
1970
+ return readFileSync3(path, "utf8");
1971
+ } catch {
1972
+ process.stderr.write(`Error: Cannot read file: ${path}
1973
+ `);
1974
+ process.exit(1);
1975
+ }
1976
+ }
1977
+ if (!process.stdin.isTTY) {
1978
+ const content = await readStdin();
1979
+ if (content.trim().length === 0) {
1980
+ process.stderr.write("Error: No prompt provided. Use --prompt, --file-prompt, or pipe via stdin.\n");
1981
+ process.exit(1);
1982
+ }
1983
+ return content;
1984
+ }
1985
+ process.stderr.write("Error: No prompt provided. Use --prompt, --file-prompt, or pipe via stdin.\n");
1986
+ process.exit(1);
1987
+ }
1988
+ var executeCommand = new Command4("execute").description("Delegate work to an AI executor").requiredOption("--with <executor>", "Executor to use: claude or codex").option("--prompt <string>", "Prompt to send to the executor").option("--file-prompt <path>", "Path to a file whose content is used as the prompt").option("--silent", "Suppress step-by-step Claude output; show only the final summary").option("--model <string>", "Model identifier to pass to the executor").action(async (options) => {
1989
+ const executor = options.with.toLowerCase();
1990
+ if (executor !== "claude" && executor !== "codex") {
1991
+ process.stderr.write(`Error: --with must be 'claude' or 'codex', got '${options.with}'.
1992
+ `);
1993
+ process.exit(1);
1994
+ }
1995
+ const prompt = await resolvePrompt(options);
1996
+ if (executor === "codex") {
1997
+ invokeCodexCode(prompt, { yolo: true, model: options.model });
1998
+ } else {
1999
+ await invokeClaudeCode(prompt, { yolo: true, verbose: !options.silent, model: options.model });
2000
+ }
1892
2001
  });
1893
- var testCommand = new Command4("test").description("Test commands for verifying automata integrations").addCommand(testClaudeCmd).addCommand(testCodexCmd);
1894
2002
 
1895
2003
  // src/commands/executePrompt.ts
1896
2004
  import { Command as Command5 } from "commander";
@@ -2020,7 +2128,7 @@ program.name("automata").description("Automata CLI tool").version(version, "-v,
2020
2128
  program.addCommand(configCommand);
2021
2129
  program.addCommand(gitCommand);
2022
2130
  program.addCommand(implementNextCommand);
2023
- program.addCommand(testCommand);
2131
+ program.addCommand(executeCommand);
2024
2132
  program.addCommand(executePromptCommand);
2025
2133
  program.showHelpAfterError();
2026
2134
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "automata-cli",
3
- "version": "0.4.0-develop.124",
3
+ "version": "0.4.0-develop.142",
4
4
  "description": "Automata CLI tool",
5
5
  "type": "module",
6
6
  "bin": {