@rotorsoft/gent 1.15.0 → 1.15.2
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/dist/index.js +178 -128
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -212,6 +212,17 @@ import chalk from "chalk";
|
|
|
212
212
|
// src/lib/ai-provider.ts
|
|
213
213
|
import { spawn } from "child_process";
|
|
214
214
|
import { execa } from "execa";
|
|
215
|
+
async function getOtherAvailableProviders(currentProvider) {
|
|
216
|
+
const allProviders = ["claude", "gemini", "codex"];
|
|
217
|
+
const others = allProviders.filter((p) => p !== currentProvider);
|
|
218
|
+
const available = [];
|
|
219
|
+
for (const p of others) {
|
|
220
|
+
if (await checkAIProvider(p)) {
|
|
221
|
+
available.push(p);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return available;
|
|
225
|
+
}
|
|
215
226
|
async function invokeInternal(provider, options) {
|
|
216
227
|
switch (provider) {
|
|
217
228
|
case "claude":
|
|
@@ -620,11 +631,10 @@ async function createCommand(description, options) {
|
|
|
620
631
|
logger.bold("Creating AI-enhanced ticket...");
|
|
621
632
|
logger.newline();
|
|
622
633
|
const config = loadConfig();
|
|
623
|
-
|
|
624
|
-
const providerName = getProviderDisplayName(provider);
|
|
634
|
+
let currentProvider = resolveProvider(options, config);
|
|
625
635
|
const [ghAuth, aiOk] = await Promise.all([
|
|
626
636
|
checkGhAuth(),
|
|
627
|
-
checkAIProvider(
|
|
637
|
+
checkAIProvider(currentProvider)
|
|
628
638
|
]);
|
|
629
639
|
if (!ghAuth) {
|
|
630
640
|
logger.error("Not authenticated with GitHub. Run 'gh auth login' first.");
|
|
@@ -632,7 +642,7 @@ async function createCommand(description, options) {
|
|
|
632
642
|
}
|
|
633
643
|
if (!aiOk) {
|
|
634
644
|
logger.error(
|
|
635
|
-
`${
|
|
645
|
+
`${getProviderDisplayName(currentProvider)} CLI not found. Please install ${currentProvider} CLI first.`
|
|
636
646
|
);
|
|
637
647
|
return;
|
|
638
648
|
}
|
|
@@ -640,6 +650,7 @@ async function createCommand(description, options) {
|
|
|
640
650
|
let aiOutput;
|
|
641
651
|
let additionalHints = null;
|
|
642
652
|
while (true) {
|
|
653
|
+
const providerName = getProviderDisplayName(currentProvider);
|
|
643
654
|
const prompt = buildTicketPrompt(
|
|
644
655
|
description,
|
|
645
656
|
agentInstructions,
|
|
@@ -655,7 +666,7 @@ async function createCommand(description, options) {
|
|
|
655
666
|
const result = await invokeAI(
|
|
656
667
|
{ prompt, streamOutput: true },
|
|
657
668
|
config,
|
|
658
|
-
|
|
669
|
+
currentProvider
|
|
659
670
|
);
|
|
660
671
|
aiOutput = result.output;
|
|
661
672
|
logger.newline();
|
|
@@ -666,6 +677,33 @@ async function createCommand(description, options) {
|
|
|
666
677
|
);
|
|
667
678
|
logger.newline();
|
|
668
679
|
} catch (error) {
|
|
680
|
+
if (error && typeof error === "object" && "rateLimited" in error) {
|
|
681
|
+
logger.warning(`${providerName} is rate limited.`);
|
|
682
|
+
const others = await getOtherAvailableProviders(currentProvider);
|
|
683
|
+
if (others.length > 0) {
|
|
684
|
+
const { nextProvider } = await inquirer2.prompt([
|
|
685
|
+
{
|
|
686
|
+
type: "list",
|
|
687
|
+
name: "nextProvider",
|
|
688
|
+
message: "Would you like to try another provider?",
|
|
689
|
+
choices: [
|
|
690
|
+
...others.map((p) => ({
|
|
691
|
+
name: `Switch to ${getProviderDisplayName(p)}`,
|
|
692
|
+
value: p
|
|
693
|
+
})),
|
|
694
|
+
{ name: "Cancel", value: "cancel" }
|
|
695
|
+
]
|
|
696
|
+
}
|
|
697
|
+
]);
|
|
698
|
+
if (nextProvider !== "cancel") {
|
|
699
|
+
currentProvider = nextProvider;
|
|
700
|
+
logger.info(
|
|
701
|
+
`Switching to ${getProviderDisplayName(currentProvider)}...`
|
|
702
|
+
);
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
669
707
|
logger.error(`${providerName} invocation failed: ${error}`);
|
|
670
708
|
return;
|
|
671
709
|
}
|
|
@@ -1600,15 +1638,15 @@ async function getChangedFiles(baseBranch = "main") {
|
|
|
1600
1638
|
}
|
|
1601
1639
|
|
|
1602
1640
|
// src/commands/pr.ts
|
|
1641
|
+
import inquirer5 from "inquirer";
|
|
1603
1642
|
async function prCommand(options) {
|
|
1604
1643
|
logger.bold("Creating AI-enhanced pull request...");
|
|
1605
1644
|
logger.newline();
|
|
1606
1645
|
const config = loadConfig();
|
|
1607
|
-
|
|
1608
|
-
const providerName = getProviderDisplayName(provider);
|
|
1646
|
+
let currentProvider = options.provider ?? config.ai.provider;
|
|
1609
1647
|
const [ghAuth, aiOk] = await Promise.all([
|
|
1610
1648
|
checkGhAuth(),
|
|
1611
|
-
checkAIProvider(
|
|
1649
|
+
checkAIProvider(currentProvider)
|
|
1612
1650
|
]);
|
|
1613
1651
|
if (!ghAuth) {
|
|
1614
1652
|
logger.error("Not authenticated with GitHub. Run 'gh auth login' first.");
|
|
@@ -1616,7 +1654,7 @@ async function prCommand(options) {
|
|
|
1616
1654
|
}
|
|
1617
1655
|
if (!aiOk) {
|
|
1618
1656
|
logger.error(
|
|
1619
|
-
`${
|
|
1657
|
+
`${getProviderDisplayName(currentProvider)} CLI not found. Please install ${currentProvider} CLI first.`
|
|
1620
1658
|
);
|
|
1621
1659
|
return;
|
|
1622
1660
|
}
|
|
@@ -1692,26 +1730,59 @@ IMPORTANT: This PR contains UI changes. Use the Playwright MCP plugin to:
|
|
|
1692
1730
|
}
|
|
1693
1731
|
const prompt = buildPrPrompt(issue, commits, diffSummary) + captureVideoInstructions;
|
|
1694
1732
|
let prBody;
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1733
|
+
let usedProvider = currentProvider;
|
|
1734
|
+
while (true) {
|
|
1735
|
+
const providerName = getProviderDisplayName(usedProvider);
|
|
1736
|
+
try {
|
|
1737
|
+
logger.info(
|
|
1738
|
+
`Generating PR description with ${colors.provider(providerName)}...`
|
|
1739
|
+
);
|
|
1740
|
+
logger.newline();
|
|
1741
|
+
const result = await invokeAI(
|
|
1742
|
+
{ prompt, streamOutput: true },
|
|
1743
|
+
config,
|
|
1744
|
+
usedProvider
|
|
1745
|
+
);
|
|
1746
|
+
prBody = result.output;
|
|
1747
|
+
logger.newline();
|
|
1748
|
+
break;
|
|
1749
|
+
} catch (error) {
|
|
1750
|
+
if (error && typeof error === "object" && "rateLimited" in error) {
|
|
1751
|
+
logger.warning(`${providerName} is rate limited.`);
|
|
1752
|
+
const others = await getOtherAvailableProviders(usedProvider);
|
|
1753
|
+
if (others.length > 0) {
|
|
1754
|
+
const { nextProvider } = await inquirer5.prompt([
|
|
1755
|
+
{
|
|
1756
|
+
type: "list",
|
|
1757
|
+
name: "nextProvider",
|
|
1758
|
+
message: "Would you like to try another provider?",
|
|
1759
|
+
choices: [
|
|
1760
|
+
...others.map((p) => ({
|
|
1761
|
+
name: `Switch to ${getProviderDisplayName(p)}`,
|
|
1762
|
+
value: p
|
|
1763
|
+
})),
|
|
1764
|
+
{ name: "Fall back to basic description", value: "fallback" }
|
|
1765
|
+
]
|
|
1766
|
+
}
|
|
1767
|
+
]);
|
|
1768
|
+
if (nextProvider !== "fallback") {
|
|
1769
|
+
usedProvider = nextProvider;
|
|
1770
|
+
logger.info(
|
|
1771
|
+
`Switching to ${getProviderDisplayName(usedProvider)}...`
|
|
1772
|
+
);
|
|
1773
|
+
continue;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
logger.warning(`${providerName} invocation failed: ${error}`);
|
|
1778
|
+
prBody = generateFallbackBody(issue, commits);
|
|
1779
|
+
break;
|
|
1780
|
+
}
|
|
1710
1781
|
}
|
|
1711
1782
|
prBody += `
|
|
1712
1783
|
|
|
1713
1784
|
---
|
|
1714
|
-
*Created with ${
|
|
1785
|
+
*Created with ${getProviderDisplayName(usedProvider)} by [gent](https://github.com/Rotorsoft/gent)*`;
|
|
1715
1786
|
const prTitle = issue?.title || commits[0] || currentBranch;
|
|
1716
1787
|
let prUrl;
|
|
1717
1788
|
try {
|
|
@@ -1793,7 +1864,7 @@ function generateFallbackBody(issue, commits) {
|
|
|
1793
1864
|
}
|
|
1794
1865
|
|
|
1795
1866
|
// src/commands/fix.ts
|
|
1796
|
-
import
|
|
1867
|
+
import inquirer6 from "inquirer";
|
|
1797
1868
|
|
|
1798
1869
|
// src/lib/review-feedback.ts
|
|
1799
1870
|
var ACTIONABLE_KEYWORDS = [
|
|
@@ -1990,7 +2061,7 @@ async function fixCommand(options) {
|
|
|
1990
2061
|
const hasChanges = await hasUncommittedChanges();
|
|
1991
2062
|
if (hasChanges) {
|
|
1992
2063
|
logger.warning("You have uncommitted changes.");
|
|
1993
|
-
const { proceed } = await
|
|
2064
|
+
const { proceed } = await inquirer6.prompt([
|
|
1994
2065
|
{
|
|
1995
2066
|
type: "confirm",
|
|
1996
2067
|
name: "proceed",
|
|
@@ -2150,7 +2221,7 @@ import { homedir } from "os";
|
|
|
2150
2221
|
// package.json
|
|
2151
2222
|
var package_default = {
|
|
2152
2223
|
name: "@rotorsoft/gent",
|
|
2153
|
-
version: "1.15.
|
|
2224
|
+
version: "1.15.2",
|
|
2154
2225
|
description: "AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs",
|
|
2155
2226
|
keywords: [
|
|
2156
2227
|
"cli",
|
|
@@ -2700,25 +2771,20 @@ function getAvailableActions(state) {
|
|
|
2700
2771
|
actions.push({ id: "quit", label: "quit", shortcut: "q" });
|
|
2701
2772
|
return actions;
|
|
2702
2773
|
}
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
if (!state.pr && state.commits.length > 0) {
|
|
2718
|
-
actions.push({ id: "pr", label: "pr", shortcut: "r" });
|
|
2719
|
-
}
|
|
2720
|
-
if (state.issue && state.pr?.state !== "merged") {
|
|
2721
|
-
actions.push({ id: "run", label: "run", shortcut: "u" });
|
|
2774
|
+
actions.push({ id: "create", label: "new", shortcut: "n" });
|
|
2775
|
+
if (!state.isOnMain) {
|
|
2776
|
+
if (state.hasUncommittedChanges) {
|
|
2777
|
+
actions.push({ id: "commit", label: "commit", shortcut: "c" });
|
|
2778
|
+
}
|
|
2779
|
+
if (state.hasUnpushedCommits && state.commits.length > 0) {
|
|
2780
|
+
actions.push({ id: "push", label: "push", shortcut: "p" });
|
|
2781
|
+
}
|
|
2782
|
+
if (!state.pr && state.commits.length > 0) {
|
|
2783
|
+
actions.push({ id: "pr", label: "pr", shortcut: "r" });
|
|
2784
|
+
}
|
|
2785
|
+
if (state.issue && state.pr?.state !== "merged") {
|
|
2786
|
+
actions.push({ id: "run", label: "run", shortcut: "u" });
|
|
2787
|
+
}
|
|
2722
2788
|
}
|
|
2723
2789
|
actions.push({ id: "list", label: "list", shortcut: "l" });
|
|
2724
2790
|
actions.push({ id: "refresh", label: "refresh", shortcut: "f" });
|
|
@@ -2879,58 +2945,38 @@ function buildDashboardLines(state, actions, hint, refreshing) {
|
|
|
2879
2945
|
out(botRow(w));
|
|
2880
2946
|
return lines;
|
|
2881
2947
|
}
|
|
2882
|
-
if (state.isOnMain) {
|
|
2883
|
-
out(
|
|
2884
|
-
row(
|
|
2885
|
-
chalk3.magenta(state.branch) + chalk3.dim(" \xB7 ready to start new work"),
|
|
2886
|
-
w
|
|
2887
|
-
)
|
|
2888
|
-
);
|
|
2889
|
-
if (state.hasUncommittedChanges) {
|
|
2890
|
-
out(row(chalk3.yellow("\u25CF uncommitted changes"), w));
|
|
2891
|
-
}
|
|
2892
|
-
if (hint) {
|
|
2893
|
-
out(midRow("Hint", w));
|
|
2894
|
-
out(row(chalk3.yellow(hint), w));
|
|
2895
|
-
}
|
|
2896
|
-
out(divRow(w));
|
|
2897
|
-
if (refreshing) {
|
|
2898
|
-
out(row(chalk3.yellow("Refreshing\u2026"), w));
|
|
2899
|
-
} else {
|
|
2900
|
-
for (const line of formatCommandBar(actions, w)) {
|
|
2901
|
-
out(row(line, w));
|
|
2902
|
-
}
|
|
2903
|
-
}
|
|
2904
|
-
out(botRow(w));
|
|
2905
|
-
out("");
|
|
2906
|
-
return lines;
|
|
2907
|
-
}
|
|
2908
2948
|
const section = (title) => {
|
|
2909
2949
|
out(midRow(title, w));
|
|
2910
2950
|
};
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
const
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2951
|
+
if (state.issue || !state.isOnMain) {
|
|
2952
|
+
section("Ticket");
|
|
2953
|
+
if (state.issue) {
|
|
2954
|
+
out(
|
|
2955
|
+
row(
|
|
2956
|
+
chalk3.cyan(`#${state.issue.number}`) + " " + chalk3.bold(truncate(state.issue.title, descMax - 6)),
|
|
2957
|
+
w
|
|
2958
|
+
)
|
|
2959
|
+
);
|
|
2960
|
+
const desc = extractDescription(state.issue.body, descMax);
|
|
2961
|
+
if (desc) out(row(chalk3.dim(desc), w));
|
|
2962
|
+
const tags = [];
|
|
2963
|
+
if (state.workflowStatus !== "none")
|
|
2964
|
+
tags.push(workflowBadge(state.workflowStatus));
|
|
2965
|
+
for (const prefix of ["type:", "priority:", "risk:", "area:"]) {
|
|
2966
|
+
const l = state.issue.labels.find((x) => x.startsWith(prefix));
|
|
2967
|
+
if (l) tags.push(chalk3.dim(l));
|
|
2968
|
+
}
|
|
2969
|
+
if (tags.length) out(row(tags.join(" "), w));
|
|
2970
|
+
} else {
|
|
2971
|
+
out(row(chalk3.dim("No linked issue"), w));
|
|
2972
|
+
}
|
|
2931
2973
|
}
|
|
2932
2974
|
section("Branch");
|
|
2933
|
-
|
|
2975
|
+
let branchLine = chalk3.magenta(state.branch);
|
|
2976
|
+
if (state.isOnMain && !state.hasUncommittedChanges) {
|
|
2977
|
+
branchLine += chalk3.dim(" \xB7 ready to start new work");
|
|
2978
|
+
}
|
|
2979
|
+
out(row(branchLine, w));
|
|
2934
2980
|
const bits = [];
|
|
2935
2981
|
if (state.commits.length > 0)
|
|
2936
2982
|
bits.push(chalk3.dim(`${state.commits.length} ahead`));
|
|
@@ -2940,48 +2986,52 @@ function buildDashboardLines(state, actions, hint, refreshing) {
|
|
|
2940
2986
|
bits.push(chalk3.green("\u25CF synced"));
|
|
2941
2987
|
}
|
|
2942
2988
|
if (bits.length) out(row(bits.join(chalk3.dim(" \xB7 ")), w));
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
row(
|
|
2949
|
-
prBadge(state.pr.state, state.pr.isDraft) + reviewBadge(state.pr.reviewDecision),
|
|
2950
|
-
w
|
|
2951
|
-
)
|
|
2952
|
-
);
|
|
2953
|
-
if (state.hasActionableFeedback) {
|
|
2954
|
-
const n = state.reviewFeedback.length;
|
|
2955
|
-
out(
|
|
2956
|
-
row(
|
|
2957
|
-
chalk3.yellow(`${n} actionable comment${n !== 1 ? "s" : ""} pending`),
|
|
2958
|
-
w
|
|
2959
|
-
)
|
|
2960
|
-
);
|
|
2961
|
-
}
|
|
2962
|
-
if (state.hasUIChanges && state.isPlaywrightAvailable && state.config.video.enabled && state.pr.state === "open") {
|
|
2989
|
+
if (state.pr || !state.isOnMain) {
|
|
2990
|
+
section("Pull Request");
|
|
2991
|
+
if (state.pr) {
|
|
2992
|
+
const titleText = state.pr.title ? " " + truncate(state.pr.title, descMax - 12) : "";
|
|
2993
|
+
out(row(chalk3.cyan(`#${state.pr.number}`) + titleText, w));
|
|
2963
2994
|
out(
|
|
2964
2995
|
row(
|
|
2965
|
-
|
|
2996
|
+
prBadge(state.pr.state, state.pr.isDraft) + reviewBadge(state.pr.reviewDecision),
|
|
2966
2997
|
w
|
|
2967
2998
|
)
|
|
2968
2999
|
);
|
|
3000
|
+
if (state.hasActionableFeedback) {
|
|
3001
|
+
const n = state.reviewFeedback.length;
|
|
3002
|
+
out(
|
|
3003
|
+
row(
|
|
3004
|
+
chalk3.yellow(`${n} actionable comment${n !== 1 ? "s" : ""} pending`),
|
|
3005
|
+
w
|
|
3006
|
+
)
|
|
3007
|
+
);
|
|
3008
|
+
}
|
|
3009
|
+
if (state.hasUIChanges && state.isPlaywrightAvailable && state.config.video.enabled && state.pr.state === "open") {
|
|
3010
|
+
out(
|
|
3011
|
+
row(
|
|
3012
|
+
chalk3.cyan("UI changes detected") + chalk3.dim(" \xB7 video capture available"),
|
|
3013
|
+
w
|
|
3014
|
+
)
|
|
3015
|
+
);
|
|
3016
|
+
}
|
|
3017
|
+
out(row(chalk3.dim(state.pr.url), w));
|
|
3018
|
+
} else {
|
|
3019
|
+
out(row(chalk3.dim("No PR created"), w));
|
|
2969
3020
|
}
|
|
2970
|
-
out(row(chalk3.dim(state.pr.url), w));
|
|
2971
|
-
} else {
|
|
2972
|
-
out(row(chalk3.dim("No PR created"), w));
|
|
2973
3021
|
}
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
3022
|
+
if (state.commits.length > 0 || !state.isOnMain) {
|
|
3023
|
+
section("Commits");
|
|
3024
|
+
if (state.commits.length > 0) {
|
|
3025
|
+
const max = 6;
|
|
3026
|
+
for (const c of state.commits.slice(0, max)) {
|
|
3027
|
+
out(row(c.substring(0, w - 5), w));
|
|
3028
|
+
}
|
|
3029
|
+
if (state.commits.length > max) {
|
|
3030
|
+
out(row(chalk3.dim(`\u2026 and ${state.commits.length - max} more`), w));
|
|
3031
|
+
}
|
|
3032
|
+
} else {
|
|
3033
|
+
out(row(chalk3.dim("No commits"), w));
|
|
2982
3034
|
}
|
|
2983
|
-
} else {
|
|
2984
|
-
out(row(chalk3.dim("No commits"), w));
|
|
2985
3035
|
}
|
|
2986
3036
|
if (hint) {
|
|
2987
3037
|
section("Hint");
|