automata-cli 0.4.0-develop.124 → 0.4.0-develop.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 +14 -0
  2. package/dist/index.js +122 -26
  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
+ };
696
+ }
697
+ if (typeof response.data.paging?.total === "number") {
698
+ return { total: response.data.paging.total };
679
699
  }
680
- return response.data.paging?.total ?? null;
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");
@@ -1881,16 +1931,62 @@ ${issue.body}`;
1881
1931
  }
1882
1932
  });
1883
1933
 
1884
- // src/commands/test.ts
1934
+ // src/commands/execute.ts
1935
+ import { readFileSync as readFileSync3 } from "fs";
1885
1936
  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 });
1937
+ function readStdin() {
1938
+ return new Promise((resolve3, reject) => {
1939
+ const chunks = [];
1940
+ process.stdin.on("data", (chunk) => chunks.push(chunk));
1941
+ process.stdin.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
1942
+ process.stdin.on("error", reject);
1943
+ });
1944
+ }
1945
+ async function resolvePrompt(options) {
1946
+ const hasCli = options.prompt !== void 0;
1947
+ const hasFile = options.filePrompt !== void 0;
1948
+ if (hasCli && hasFile) {
1949
+ process.stderr.write("Error: --prompt is mutually exclusive with --file-prompt.\n");
1950
+ process.exit(1);
1951
+ }
1952
+ if (hasCli) {
1953
+ return options.prompt;
1954
+ }
1955
+ if (hasFile) {
1956
+ const path = options.filePrompt;
1957
+ try {
1958
+ return readFileSync3(path, "utf8");
1959
+ } catch {
1960
+ process.stderr.write(`Error: Cannot read file: ${path}
1961
+ `);
1962
+ process.exit(1);
1963
+ }
1964
+ }
1965
+ if (!process.stdin.isTTY) {
1966
+ const content = await readStdin();
1967
+ if (content.trim().length === 0) {
1968
+ process.stderr.write("Error: No prompt provided. Use --prompt, --file-prompt, or pipe via stdin.\n");
1969
+ process.exit(1);
1970
+ }
1971
+ return content;
1972
+ }
1973
+ process.stderr.write("Error: No prompt provided. Use --prompt, --file-prompt, or pipe via stdin.\n");
1974
+ process.exit(1);
1975
+ }
1976
+ 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) => {
1977
+ const executor = options.with.toLowerCase();
1978
+ if (executor !== "claude" && executor !== "codex") {
1979
+ process.stderr.write(`Error: --with must be 'claude' or 'codex', got '${options.with}'.
1980
+ `);
1981
+ process.exit(1);
1982
+ }
1983
+ const prompt = await resolvePrompt(options);
1984
+ if (executor === "codex") {
1985
+ invokeCodexCode(prompt, { yolo: true, model: options.model });
1986
+ } else {
1987
+ await invokeClaudeCode(prompt, { yolo: true, verbose: !options.silent, model: options.model });
1988
+ }
1892
1989
  });
1893
- var testCommand = new Command4("test").description("Test commands for verifying automata integrations").addCommand(testClaudeCmd).addCommand(testCodexCmd);
1894
1990
 
1895
1991
  // src/commands/executePrompt.ts
1896
1992
  import { Command as Command5 } from "commander";
@@ -2020,7 +2116,7 @@ program.name("automata").description("Automata CLI tool").version(version, "-v,
2020
2116
  program.addCommand(configCommand);
2021
2117
  program.addCommand(gitCommand);
2022
2118
  program.addCommand(implementNextCommand);
2023
- program.addCommand(testCommand);
2119
+ program.addCommand(executeCommand);
2024
2120
  program.addCommand(executePromptCommand);
2025
2121
  program.showHelpAfterError();
2026
2122
  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.131",
4
4
  "description": "Automata CLI tool",
5
5
  "type": "module",
6
6
  "bin": {