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.
- package/README.md +14 -0
- package/dist/index.js +122 -26
- 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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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/
|
|
1934
|
+
// src/commands/execute.ts
|
|
1935
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
1885
1936
|
import { Command as Command4 } from "commander";
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
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(
|
|
2119
|
+
program.addCommand(executeCommand);
|
|
2024
2120
|
program.addCommand(executePromptCommand);
|
|
2025
2121
|
program.showHelpAfterError();
|
|
2026
2122
|
program.parse();
|