@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 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
- const provider = resolveProvider(options, config);
624
- const providerName = getProviderDisplayName(provider);
634
+ let currentProvider = resolveProvider(options, config);
625
635
  const [ghAuth, aiOk] = await Promise.all([
626
636
  checkGhAuth(),
627
- checkAIProvider(provider)
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
- `${providerName} CLI not found. Please install ${provider} CLI first.`
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
- options.provider
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
- const provider = options.provider ?? config.ai.provider;
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(provider)
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
- `${providerName} CLI not found. Please install ${provider} CLI first.`
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
- try {
1696
- logger.info(
1697
- `Generating PR description with ${colors.provider(providerName)}...`
1698
- );
1699
- logger.newline();
1700
- const result = await invokeAI(
1701
- { prompt, streamOutput: true },
1702
- config,
1703
- options.provider
1704
- );
1705
- prBody = result.output;
1706
- logger.newline();
1707
- } catch (error) {
1708
- logger.warning(`${providerName} invocation failed: ${error}`);
1709
- prBody = generateFallbackBody(issue, commits);
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 ${providerName} by [gent](https://github.com/Rotorsoft/gent)*`;
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 inquirer5 from "inquirer";
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 inquirer5.prompt([
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.0",
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
- if (state.isOnMain) {
2704
- actions.push({ id: "create", label: "new", shortcut: "n" });
2705
- actions.push({ id: "list", label: "list", shortcut: "l" });
2706
- actions.push({ id: "refresh", label: "refresh", shortcut: "f" });
2707
- actions.push({ id: "switch-provider", label: "ai", shortcut: "a" });
2708
- actions.push({ id: "quit", label: "quit", shortcut: "q" });
2709
- return actions;
2710
- }
2711
- if (state.hasUncommittedChanges) {
2712
- actions.push({ id: "commit", label: "commit", shortcut: "c" });
2713
- }
2714
- if (state.hasUnpushedCommits && state.commits.length > 0) {
2715
- actions.push({ id: "push", label: "push", shortcut: "p" });
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
- section("Ticket");
2912
- if (state.issue) {
2913
- out(
2914
- row(
2915
- chalk3.cyan(`#${state.issue.number}`) + " " + chalk3.bold(truncate(state.issue.title, descMax - 6)),
2916
- w
2917
- )
2918
- );
2919
- const desc = extractDescription(state.issue.body, descMax);
2920
- if (desc) out(row(chalk3.dim(desc), w));
2921
- const tags = [];
2922
- if (state.workflowStatus !== "none")
2923
- tags.push(workflowBadge(state.workflowStatus));
2924
- for (const prefix of ["type:", "priority:", "risk:", "area:"]) {
2925
- const l = state.issue.labels.find((x) => x.startsWith(prefix));
2926
- if (l) tags.push(chalk3.dim(l));
2927
- }
2928
- if (tags.length) out(row(tags.join(" "), w));
2929
- } else {
2930
- out(row(chalk3.dim("No linked issue"), w));
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
- out(row(chalk3.magenta(state.branch), w));
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
- section("Pull Request");
2944
- if (state.pr) {
2945
- const titleText = state.pr.title ? " " + truncate(state.pr.title, descMax - 12) : "";
2946
- out(row(chalk3.cyan(`#${state.pr.number}`) + titleText, w));
2947
- out(
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
- chalk3.cyan("UI changes detected") + chalk3.dim(" \xB7 video capture available"),
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
- section("Commits");
2975
- if (state.commits.length > 0) {
2976
- const max = 6;
2977
- for (const c of state.commits.slice(0, max)) {
2978
- out(row(c.substring(0, w - 5), w));
2979
- }
2980
- if (state.commits.length > max) {
2981
- out(row(chalk3.dim(`\u2026 and ${state.commits.length - max} more`), w));
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");