@rotorsoft/gent 1.13.1 → 1.13.3

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
@@ -30,12 +30,14 @@ import {
30
30
  loadConfig,
31
31
  logger,
32
32
  replyToReviewComment,
33
+ resolveProvider,
33
34
  sanitizeSlug,
35
+ setRuntimeProvider,
34
36
  setupLabelsCommand,
35
37
  sortByPriority,
36
38
  updateIssueLabels,
37
39
  withSpinner
38
- } from "./chunk-KLHUMY5L.js";
40
+ } from "./chunk-SKWCS6Z2.js";
39
41
 
40
42
  // src/index.ts
41
43
  import { Command } from "commander";
@@ -182,11 +184,14 @@ async function initCommand(options) {
182
184
  initializeProgress(config, cwd);
183
185
  logger.success(`Created ${colors.file(config.progress.file)}`);
184
186
  logger.newline();
185
- logger.box("Setup Complete", `Next steps:
187
+ logger.box(
188
+ "Setup Complete",
189
+ `Next steps:
186
190
  1. Edit ${colors.file("AGENT.md")} with your project-specific instructions
187
191
  2. Edit ${colors.file(".gent.yml")} to customize settings
188
192
  3. Run ${colors.command("gent setup-labels")} to create GitHub labels
189
- 4. Run ${colors.command("gent create <description>")} to create your first ticket`);
193
+ 4. Run ${colors.command("gent create <description>")} to create your first ticket`
194
+ );
190
195
  const { setupLabels } = await inquirer.prompt([
191
196
  {
192
197
  type: "confirm",
@@ -196,7 +201,7 @@ async function initCommand(options) {
196
201
  }
197
202
  ]);
198
203
  if (setupLabels) {
199
- const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-3ANC76NF.js");
204
+ const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-C6YHJ637.js");
200
205
  await setupLabelsCommand2();
201
206
  }
202
207
  }
@@ -227,7 +232,9 @@ async function invokeAI(options, config, providerOverride) {
227
232
  if (isRateLimitError(error, provider)) {
228
233
  if (config.ai.auto_fallback && config.ai.fallback_provider && !providerOverride) {
229
234
  const fallback = config.ai.fallback_provider;
230
- logger.warning(`Rate limit reached on ${getProviderDisplayName(provider)}, switching to ${getProviderDisplayName(fallback)}...`);
235
+ logger.warning(
236
+ `Rate limit reached on ${getProviderDisplayName(provider)}, switching to ${getProviderDisplayName(fallback)}...`
237
+ );
231
238
  const output = await invokeInternal(fallback, options);
232
239
  return { output, provider: fallback };
233
240
  }
@@ -654,7 +661,7 @@ async function createCommand(description, options) {
654
661
  logger.bold("Creating AI-enhanced ticket...");
655
662
  logger.newline();
656
663
  const config = loadConfig();
657
- const provider = options.provider ?? config.ai.provider;
664
+ const provider = resolveProvider(options, config);
658
665
  const providerName = getProviderDisplayName(provider);
659
666
  const [ghAuth, aiOk] = await Promise.all([
660
667
  checkGhAuth(),
@@ -665,21 +672,39 @@ async function createCommand(description, options) {
665
672
  return;
666
673
  }
667
674
  if (!aiOk) {
668
- logger.error(`${providerName} CLI not found. Please install ${provider} CLI first.`);
675
+ logger.error(
676
+ `${providerName} CLI not found. Please install ${provider} CLI first.`
677
+ );
669
678
  return;
670
679
  }
671
680
  const agentInstructions = loadAgentInstructions();
672
681
  let aiOutput;
673
682
  let additionalHints = null;
674
683
  while (true) {
675
- const prompt = buildTicketPrompt(description, agentInstructions, additionalHints);
684
+ const prompt = buildTicketPrompt(
685
+ description,
686
+ agentInstructions,
687
+ additionalHints
688
+ );
676
689
  try {
677
- console.log(chalk.dim(`\u250C\u2500 Generating ticket with ${providerName}... \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510`));
690
+ console.log(
691
+ chalk.dim(
692
+ `\u250C\u2500 Generating ticket with ${providerName}... \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510`
693
+ )
694
+ );
678
695
  logger.newline();
679
- const result = await invokeAI({ prompt, streamOutput: true }, config, options.provider);
696
+ const result = await invokeAI(
697
+ { prompt, streamOutput: true },
698
+ config,
699
+ options.provider
700
+ );
680
701
  aiOutput = result.output;
681
702
  logger.newline();
682
- console.log(chalk.dim("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
703
+ console.log(
704
+ chalk.dim(
705
+ "\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"
706
+ )
707
+ );
683
708
  logger.newline();
684
709
  } catch (error) {
685
710
  logger.error(`${providerName} invocation failed: ${error}`);
@@ -687,7 +712,9 @@ async function createCommand(description, options) {
687
712
  }
688
713
  const meta = parseTicketMeta(aiOutput);
689
714
  if (!meta) {
690
- logger.warning("Could not parse metadata from AI output. Using defaults.");
715
+ logger.warning(
716
+ "Could not parse metadata from AI output. Using defaults."
717
+ );
691
718
  }
692
719
  const finalMeta = meta || {
693
720
  type: "feature",
@@ -860,11 +887,7 @@ async function hasUncommittedChanges() {
860
887
  }
861
888
  async function getUnpushedCommits() {
862
889
  try {
863
- const { stdout } = await execa2("git", [
864
- "log",
865
- "@{u}..HEAD",
866
- "--oneline"
867
- ]);
890
+ const { stdout } = await execa2("git", ["log", "@{u}..HEAD", "--oneline"]);
868
891
  return stdout.trim().length > 0;
869
892
  } catch {
870
893
  return true;
@@ -907,11 +930,7 @@ async function getCommitsSinceBase(base = "main") {
907
930
  }
908
931
  async function getDiffSummary(base = "main") {
909
932
  try {
910
- const { stdout } = await execa2("git", [
911
- "diff",
912
- `${base}...HEAD`,
913
- "--stat"
914
- ]);
933
+ const { stdout } = await execa2("git", ["diff", `${base}...HEAD`, "--stat"]);
915
934
  return stdout.trim();
916
935
  } catch {
917
936
  return "";
@@ -930,7 +949,10 @@ async function getLastCommitTimestamp() {
930
949
  return stdout.trim();
931
950
  }
932
951
  async function listLocalBranches() {
933
- const { stdout } = await execa2("git", ["branch", "--format=%(refname:short)"]);
952
+ const { stdout } = await execa2("git", [
953
+ "branch",
954
+ "--format=%(refname:short)"
955
+ ]);
934
956
  return stdout.trim().split("\n").filter(Boolean);
935
957
  }
936
958
  async function remoteBranchExists(name) {
@@ -1145,12 +1167,7 @@ async function listCommand(options) {
1145
1167
  logger.info("No issues found matching the criteria.");
1146
1168
  return;
1147
1169
  }
1148
- await presentSelector(
1149
- choices2,
1150
- currentBranch,
1151
- defaultBranch,
1152
- config
1153
- );
1170
+ await presentSelector(choices2, currentBranch, defaultBranch, config);
1154
1171
  return;
1155
1172
  }
1156
1173
  const labelFilter = options.label ? [options.label] : [];
@@ -1260,15 +1277,10 @@ async function presentSelector(choices, currentBranch, defaultBranch, config) {
1260
1277
  });
1261
1278
  logger.success(`Switched to ${colors.branch(targetBranch)}`);
1262
1279
  } else if (await remoteBranchExists(targetBranch)) {
1263
- await withSpinner(
1264
- `Fetching ${targetBranch} from remote...`,
1265
- async () => {
1266
- await fetchAndCheckout(targetBranch);
1267
- }
1268
- );
1269
- logger.success(
1270
- `Fetched and switched to ${colors.branch(targetBranch)}`
1271
- );
1280
+ await withSpinner(`Fetching ${targetBranch} from remote...`, async () => {
1281
+ await fetchAndCheckout(targetBranch);
1282
+ });
1283
+ logger.success(`Fetched and switched to ${colors.branch(targetBranch)}`);
1272
1284
  } else {
1273
1285
  logger.warning(
1274
1286
  `Branch ${colors.branch(targetBranch)} not found locally or on remote.`
@@ -1334,7 +1346,9 @@ async function runCommand(issueNumberArg, options) {
1334
1346
  return;
1335
1347
  }
1336
1348
  if (!aiOk) {
1337
- logger.error(`${providerName} CLI not found. Please install ${provider} CLI first.`);
1349
+ logger.error(
1350
+ `${providerName} CLI not found. Please install ${provider} CLI first.`
1351
+ );
1338
1352
  return;
1339
1353
  }
1340
1354
  const hasChanges = await hasUncommittedChanges();
@@ -1362,7 +1376,9 @@ async function runCommand(issueNumberArg, options) {
1362
1376
  }
1363
1377
  issueNumber = parseInt(issueNumberArg, 10);
1364
1378
  } else {
1365
- logger.error("Please provide an issue number. Use 'gent switch' to browse tickets.");
1379
+ logger.error(
1380
+ "Please provide an issue number. Use 'gent switch' to browse tickets."
1381
+ );
1366
1382
  return;
1367
1383
  }
1368
1384
  let issue;
@@ -1375,7 +1391,9 @@ async function runCommand(issueNumberArg, options) {
1375
1391
  return;
1376
1392
  }
1377
1393
  if (!issue.labels.includes(workflowLabels.ready)) {
1378
- logger.warning(`Issue #${issueNumber} does not have the '${workflowLabels.ready}' label.`);
1394
+ logger.warning(
1395
+ `Issue #${issueNumber} does not have the '${workflowLabels.ready}' label.`
1396
+ );
1379
1397
  const { proceed } = await inquirer4.prompt([
1380
1398
  {
1381
1399
  type: "confirm",
@@ -1389,11 +1407,19 @@ async function runCommand(issueNumberArg, options) {
1389
1407
  }
1390
1408
  }
1391
1409
  logger.newline();
1392
- logger.box("Issue Details", `#${issue.number}: ${issue.title}
1393
- Labels: ${issue.labels.join(", ")}`);
1410
+ logger.box(
1411
+ "Issue Details",
1412
+ `#${issue.number}: ${issue.title}
1413
+ Labels: ${issue.labels.join(", ")}`
1414
+ );
1394
1415
  logger.newline();
1395
1416
  const type = extractTypeFromLabels(issue.labels);
1396
- const branchName = await generateBranchName(config, issueNumber, issue.title, type);
1417
+ const branchName = await generateBranchName(
1418
+ config,
1419
+ issueNumber,
1420
+ issue.title,
1421
+ type
1422
+ );
1397
1423
  const currentBranch = await getCurrentBranch();
1398
1424
  const onMain = await isOnMainBranch();
1399
1425
  if (await branchExists(branchName)) {
@@ -1419,7 +1445,9 @@ Labels: ${issue.labels.join(", ")}`);
1419
1445
  }
1420
1446
  } else {
1421
1447
  if (!onMain) {
1422
- logger.warning(`Not on main branch (currently on ${colors.branch(currentBranch)}).`);
1448
+ logger.warning(
1449
+ `Not on main branch (currently on ${colors.branch(currentBranch)}).`
1450
+ );
1423
1451
  const { fromMain } = await inquirer4.prompt([
1424
1452
  {
1425
1453
  type: "confirm",
@@ -1443,15 +1471,24 @@ Labels: ${issue.labels.join(", ")}`);
1443
1471
  add: [workflowLabels.inProgress],
1444
1472
  remove: [workflowLabels.ready]
1445
1473
  });
1446
- logger.success(`Updated issue labels: ${colors.label(workflowLabels.ready)} \u2192 ${colors.label(workflowLabels.inProgress)}`);
1474
+ logger.success(
1475
+ `Updated issue labels: ${colors.label(workflowLabels.ready)} \u2192 ${colors.label(workflowLabels.inProgress)}`
1476
+ );
1447
1477
  } catch (error) {
1448
1478
  logger.warning(`Failed to update labels: ${error}`);
1449
1479
  }
1450
1480
  const agentInstructions = loadAgentInstructions();
1451
1481
  const progressContent = readProgress(config);
1452
- const prompt = buildImplementationPrompt(issue, agentInstructions, progressContent, config);
1482
+ const prompt = buildImplementationPrompt(
1483
+ issue,
1484
+ agentInstructions,
1485
+ progressContent,
1486
+ config
1487
+ );
1453
1488
  logger.newline();
1454
- logger.info(`Starting ${colors.provider(providerName)} implementation session...`);
1489
+ logger.info(
1490
+ `Starting ${colors.provider(providerName)} implementation session...`
1491
+ );
1455
1492
  logger.dim(`${providerName} will implement the feature and create a commit.`);
1456
1493
  logger.dim("Review the changes before pushing.");
1457
1494
  logger.newline();
@@ -1465,14 +1502,20 @@ Labels: ${issue.labels.join(", ")}`);
1465
1502
  let aiExitCode;
1466
1503
  let usedProvider = provider;
1467
1504
  try {
1468
- const { result, provider: actualProvider } = await invokeAIInteractive(prompt, config, options.provider);
1505
+ const { result, provider: actualProvider } = await invokeAIInteractive(
1506
+ prompt,
1507
+ config,
1508
+ options.provider
1509
+ );
1469
1510
  usedProvider = actualProvider;
1470
1511
  aiExitCode = result.exitCode ?? void 0;
1471
1512
  } catch (error) {
1472
1513
  if (error && typeof error === "object" && "exitCode" in error) {
1473
1514
  aiExitCode = error.exitCode;
1474
1515
  }
1475
- logger.error(`${getProviderDisplayName(usedProvider)} session failed: ${error}`);
1516
+ logger.error(
1517
+ `${getProviderDisplayName(usedProvider)} session failed: ${error}`
1518
+ );
1476
1519
  } finally {
1477
1520
  process.off("SIGINT", handleSignal);
1478
1521
  process.off("SIGTERM", handleSignal);
@@ -1491,7 +1534,9 @@ Labels: ${issue.labels.join(", ")}`);
1491
1534
  add: [workflowLabels.completed],
1492
1535
  remove: [workflowLabels.inProgress]
1493
1536
  });
1494
- logger.success(`Updated labels: ${colors.label(workflowLabels.inProgress)} \u2192 ${colors.label(workflowLabels.completed)}`);
1537
+ logger.success(
1538
+ `Updated labels: ${colors.label(workflowLabels.inProgress)} \u2192 ${colors.label(workflowLabels.completed)}`
1539
+ );
1495
1540
  } catch (error) {
1496
1541
  logger.warning(`Failed to update labels: ${error}`);
1497
1542
  }
@@ -1509,13 +1554,17 @@ Please review the changes and create a PR when ready.`
1509
1554
  } else {
1510
1555
  const isRateLimited = aiExitCode === 2;
1511
1556
  if (isRateLimited) {
1512
- logger.warning(`${usedProviderName} session ended due to rate limits. No commits were created.`);
1557
+ logger.warning(
1558
+ `${usedProviderName} session ended due to rate limits. No commits were created.`
1559
+ );
1513
1560
  try {
1514
1561
  await updateIssueLabels(issueNumber, {
1515
1562
  add: [workflowLabels.blocked],
1516
1563
  remove: [workflowLabels.inProgress]
1517
1564
  });
1518
- logger.info(`Updated labels: ${colors.label(workflowLabels.inProgress)} \u2192 ${colors.label(workflowLabels.blocked)}`);
1565
+ logger.info(
1566
+ `Updated labels: ${colors.label(workflowLabels.inProgress)} \u2192 ${colors.label(workflowLabels.blocked)}`
1567
+ );
1519
1568
  } catch (error) {
1520
1569
  logger.warning(`Failed to update labels: ${error}`);
1521
1570
  }
@@ -1531,15 +1580,20 @@ No commits were created. Please retry later.`
1531
1580
  logger.warning(`Failed to post comment: ${error}`);
1532
1581
  }
1533
1582
  } else {
1534
- logger.warning(`${usedProviderName} session completed but no commits were created. Labels unchanged.`);
1583
+ logger.warning(
1584
+ `${usedProviderName} session completed but no commits were created. Labels unchanged.`
1585
+ );
1535
1586
  }
1536
1587
  return;
1537
1588
  }
1538
1589
  logger.newline();
1539
- logger.box("Next Steps", `1. Review changes: ${colors.command("git diff HEAD~1")}
1590
+ logger.box(
1591
+ "Next Steps",
1592
+ `1. Review changes: ${colors.command("git diff HEAD~1")}
1540
1593
  2. Run tests: ${colors.command("npm test")}
1541
1594
  3. Push branch: ${colors.command("git push -u origin " + branchName)}
1542
- 4. Create PR: ${colors.command("gent pr")}`);
1595
+ 4. Create PR: ${colors.command("gent pr")}`
1596
+ );
1543
1597
  }
1544
1598
 
1545
1599
  // src/commands/pr.ts
@@ -1605,7 +1659,9 @@ async function prCommand(options) {
1605
1659
  process.exit(1);
1606
1660
  }
1607
1661
  if (!aiOk) {
1608
- logger.error(`${providerName} CLI not found. Please install ${provider} CLI first.`);
1662
+ logger.error(
1663
+ `${providerName} CLI not found. Please install ${provider} CLI first.`
1664
+ );
1609
1665
  process.exit(1);
1610
1666
  }
1611
1667
  if (await isOnMainBranch()) {
@@ -1614,7 +1670,9 @@ async function prCommand(options) {
1614
1670
  }
1615
1671
  const existingPr = await getPrForBranch();
1616
1672
  if (existingPr) {
1617
- logger.warning(`A PR already exists for this branch: ${colors.url(existingPr.url)}`);
1673
+ logger.warning(
1674
+ `A PR already exists for this branch: ${colors.url(existingPr.url)}`
1675
+ );
1618
1676
  return;
1619
1677
  }
1620
1678
  const currentBranch = await getCurrentBranch();
@@ -1644,7 +1702,9 @@ async function prCommand(options) {
1644
1702
  if (issueNumber) {
1645
1703
  try {
1646
1704
  issue = await getIssue(issueNumber);
1647
- logger.info(`Linked issue: ${colors.issue(`#${issueNumber}`)} - ${issue.title}`);
1705
+ logger.info(
1706
+ `Linked issue: ${colors.issue(`#${issueNumber}`)} - ${issue.title}`
1707
+ );
1648
1708
  } catch {
1649
1709
  logger.warning(`Could not fetch issue #${issueNumber}`);
1650
1710
  }
@@ -1671,7 +1731,9 @@ async function prCommand(options) {
1671
1731
  logger.warning("Playwright not available. Skipping video capture.");
1672
1732
  logger.dim("Install Playwright with: npm install -D playwright");
1673
1733
  } else {
1674
- logger.info("Playwright available - AI will capture demo video via MCP");
1734
+ logger.info(
1735
+ "Playwright available - AI will capture demo video via MCP"
1736
+ );
1675
1737
  captureVideoInstructions = `
1676
1738
 
1677
1739
  IMPORTANT: This PR contains UI changes. Use the Playwright MCP plugin to:
@@ -1686,9 +1748,15 @@ IMPORTANT: This PR contains UI changes. Use the Playwright MCP plugin to:
1686
1748
  const prompt = buildPrPrompt(issue, commits, diffSummary) + captureVideoInstructions;
1687
1749
  let prBody;
1688
1750
  try {
1689
- logger.info(`Generating PR description with ${colors.provider(providerName)}...`);
1751
+ logger.info(
1752
+ `Generating PR description with ${colors.provider(providerName)}...`
1753
+ );
1690
1754
  logger.newline();
1691
- const result = await invokeAI({ prompt, streamOutput: true }, config, options.provider);
1755
+ const result = await invokeAI(
1756
+ { prompt, streamOutput: true },
1757
+ config,
1758
+ options.provider
1759
+ );
1692
1760
  prBody = result.output;
1693
1761
  logger.newline();
1694
1762
  } catch (error) {
@@ -1727,7 +1795,9 @@ IMPORTANT: This PR contains UI changes. Use the Playwright MCP plugin to:
1727
1795
  add: [workflowLabels.completed],
1728
1796
  remove: [workflowLabels.inProgress]
1729
1797
  });
1730
- logger.success(`Updated labels: ${colors.label(workflowLabels.inProgress)} \u2192 ${colors.label(workflowLabels.completed)}`);
1798
+ logger.success(
1799
+ `Updated labels: ${colors.label(workflowLabels.inProgress)} \u2192 ${colors.label(workflowLabels.completed)}`
1800
+ );
1731
1801
  } catch {
1732
1802
  }
1733
1803
  }
@@ -1887,7 +1957,9 @@ function isActionableThread(thread) {
1887
1957
  if (thread.isResolved === false || thread.isResolved === void 0 || thread.isResolved === null) {
1888
1958
  return true;
1889
1959
  }
1890
- return (thread.comments ?? []).some((comment) => isActionableText(comment.body));
1960
+ return (thread.comments ?? []).some(
1961
+ (comment) => isActionableText(comment.body)
1962
+ );
1891
1963
  }
1892
1964
  function isActionableText(text) {
1893
1965
  const normalized = text.toLowerCase();
@@ -1942,11 +2014,15 @@ async function fixCommand(options) {
1942
2014
  process.exit(1);
1943
2015
  }
1944
2016
  if (!aiOk) {
1945
- logger.error(`${providerName} CLI not found. Please install ${provider} CLI first.`);
2017
+ logger.error(
2018
+ `${providerName} CLI not found. Please install ${provider} CLI first.`
2019
+ );
1946
2020
  process.exit(1);
1947
2021
  }
1948
2022
  if (await isOnMainBranch()) {
1949
- logger.error("Cannot apply fixes from main/master branch. Switch to the PR branch first.");
2023
+ logger.error(
2024
+ "Cannot apply fixes from main/master branch. Switch to the PR branch first."
2025
+ );
1950
2026
  process.exit(1);
1951
2027
  }
1952
2028
  const hasChanges = await hasUncommittedChanges();
@@ -1969,21 +2045,30 @@ async function fixCommand(options) {
1969
2045
  return getPrForBranch();
1970
2046
  });
1971
2047
  if (!pr) {
1972
- logger.error("No pull request found for the current branch. Create one with 'gent pr' first.");
2048
+ logger.error(
2049
+ "No pull request found for the current branch. Create one with 'gent pr' first."
2050
+ );
1973
2051
  process.exit(1);
1974
2052
  }
1975
2053
  const lastCommitTimestamp = await getLastCommitTimestamp();
1976
- const reviewData = await withSpinner("Fetching review feedback...", async () => {
1977
- return getPrReviewData(pr.number);
1978
- });
2054
+ const reviewData = await withSpinner(
2055
+ "Fetching review feedback...",
2056
+ async () => {
2057
+ return getPrReviewData(pr.number);
2058
+ }
2059
+ );
1979
2060
  const totalComments = countReviewComments(reviewData);
1980
2061
  if (totalComments === 0) {
1981
2062
  logger.error(`No review comments found for PR #${pr.number}.`);
1982
2063
  process.exit(1);
1983
2064
  }
1984
- const { items, summary } = summarizeReviewFeedback(reviewData, { afterTimestamp: lastCommitTimestamp });
2065
+ const { items, summary } = summarizeReviewFeedback(reviewData, {
2066
+ afterTimestamp: lastCommitTimestamp
2067
+ });
1985
2068
  if (items.length === 0 || !summary) {
1986
- logger.error("No new actionable review feedback found since your last commit.");
2069
+ logger.error(
2070
+ "No new actionable review feedback found since your last commit."
2071
+ );
1987
2072
  process.exit(1);
1988
2073
  }
1989
2074
  logger.newline();
@@ -2000,8 +2085,14 @@ async function fixCommand(options) {
2000
2085
  });
2001
2086
  const agentInstructions = loadAgentInstructions();
2002
2087
  const progressContent = readProgress(config);
2003
- const prompt = buildImplementationPrompt(issue, agentInstructions, progressContent, config, `## Review Feedback
2004
- ${summary}`);
2088
+ const prompt = buildImplementationPrompt(
2089
+ issue,
2090
+ agentInstructions,
2091
+ progressContent,
2092
+ config,
2093
+ `## Review Feedback
2094
+ ${summary}`
2095
+ );
2005
2096
  logger.newline();
2006
2097
  logger.info(`Starting ${colors.provider(providerName)} fix session...`);
2007
2098
  logger.dim("Review feedback will be appended to the implementation prompt.");
@@ -2015,7 +2106,11 @@ ${summary}`);
2015
2106
  process.on("SIGTERM", handleSignal);
2016
2107
  let aiExitCode;
2017
2108
  try {
2018
- const { result } = await invokeAIInteractive(prompt, config, options.provider);
2109
+ const { result } = await invokeAIInteractive(
2110
+ prompt,
2111
+ config,
2112
+ options.provider
2113
+ );
2019
2114
  aiExitCode = result.exitCode ?? void 0;
2020
2115
  } catch (error) {
2021
2116
  if (error && typeof error === "object" && "exitCode" in error) {
@@ -2039,18 +2134,28 @@ ${summary}`);
2039
2134
  }
2040
2135
  const isRateLimited = aiExitCode === 2;
2041
2136
  if (isRateLimited) {
2042
- logger.warning(`${providerName} session ended due to rate limits. No commits were created.`);
2137
+ logger.warning(
2138
+ `${providerName} session ended due to rate limits. No commits were created.`
2139
+ );
2043
2140
  return;
2044
2141
  }
2045
- logger.warning(`${providerName} session completed but no commits were created.`);
2142
+ logger.warning(
2143
+ `${providerName} session completed but no commits were created.`
2144
+ );
2046
2145
  }
2047
2146
  function countReviewComments(data) {
2048
- const reviewBodies = data.reviews.filter((review) => review.body?.trim()).length;
2147
+ const reviewBodies = data.reviews.filter(
2148
+ (review) => review.body?.trim()
2149
+ ).length;
2049
2150
  const threadBodies = data.reviewThreads.reduce((count, thread) => {
2050
- const threadCount = (thread.comments ?? []).filter((comment) => comment.body?.trim()).length;
2151
+ const threadCount = (thread.comments ?? []).filter(
2152
+ (comment) => comment.body?.trim()
2153
+ ).length;
2051
2154
  return count + threadCount;
2052
2155
  }, 0);
2053
- const prComments = (data.comments ?? []).filter((comment) => comment.body?.trim()).length;
2156
+ const prComments = (data.comments ?? []).filter(
2157
+ (comment) => comment.body?.trim()
2158
+ ).length;
2054
2159
  return reviewBodies + threadBodies + prComments;
2055
2160
  }
2056
2161
  async function replyToFeedbackItems(prNumber, items) {
@@ -2069,7 +2174,9 @@ async function replyToFeedbackItems(prNumber, items) {
2069
2174
  }
2070
2175
  }
2071
2176
  if (repliedCount > 0) {
2072
- logger.dim(`Replied to ${repliedCount} feedback item${repliedCount > 1 ? "s" : ""}.`);
2177
+ logger.dim(
2178
+ `Replied to ${repliedCount} feedback item${repliedCount > 1 ? "s" : ""}.`
2179
+ );
2073
2180
  }
2074
2181
  }
2075
2182
 
@@ -2081,7 +2188,7 @@ import { homedir } from "os";
2081
2188
  // package.json
2082
2189
  var package_default = {
2083
2190
  name: "@rotorsoft/gent",
2084
- version: "1.13.1",
2191
+ version: "1.13.3",
2085
2192
  description: "AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs",
2086
2193
  keywords: [
2087
2194
  "cli",
@@ -2333,7 +2440,9 @@ async function statusCommand() {
2333
2440
  logger.info(` Active: ${colors.provider(providerName)}`);
2334
2441
  if (config.ai.fallback_provider) {
2335
2442
  const fallbackName = getProviderDisplayName(config.ai.fallback_provider);
2336
- logger.info(` Fallback: ${fallbackName} (auto: ${config.ai.auto_fallback ? "enabled" : "disabled"})`);
2443
+ logger.info(
2444
+ ` Fallback: ${fallbackName} (auto: ${config.ai.auto_fallback ? "enabled" : "disabled"})`
2445
+ );
2337
2446
  }
2338
2447
  logger.newline();
2339
2448
  logger.bold("Prerequisites:");
@@ -2424,13 +2533,19 @@ async function statusCommand() {
2424
2533
  try {
2425
2534
  const lastCommitTimestamp = await getLastCommitTimestamp();
2426
2535
  const reviewData = await getPrReviewData(prStatus.number);
2427
- const { items } = summarizeReviewFeedback(reviewData, { afterTimestamp: lastCommitTimestamp });
2536
+ const { items } = summarizeReviewFeedback(reviewData, {
2537
+ afterTimestamp: lastCommitTimestamp
2538
+ });
2428
2539
  hasActionableFeedback = items.length > 0;
2429
2540
  if (prStatus.reviewDecision) {
2430
- logger.info(` Review: ${formatReviewDecision(prStatus.reviewDecision)}`);
2541
+ logger.info(
2542
+ ` Review: ${formatReviewDecision(prStatus.reviewDecision)}`
2543
+ );
2431
2544
  }
2432
2545
  if (items.length > 0) {
2433
- logger.warning(` ${items.length} actionable comment${items.length > 1 ? "s" : ""} to fix with ${colors.command("gent fix")}:`);
2546
+ logger.warning(
2547
+ ` ${items.length} actionable comment${items.length > 1 ? "s" : ""} to fix with ${colors.command("gent fix")}:`
2548
+ );
2434
2549
  for (const item of items) {
2435
2550
  const location = formatFeedbackLocation(item);
2436
2551
  const body = truncateFeedbackBody(item.body, 60);
@@ -2445,10 +2560,14 @@ async function statusCommand() {
2445
2560
  }
2446
2561
  } else if (prStatus.state === "merged") {
2447
2562
  logger.success(" This PR has been merged!");
2448
- logger.dim(` Run ${colors.command("git checkout main && git pull")} to sync`);
2563
+ logger.dim(
2564
+ ` Run ${colors.command("git checkout main && git pull")} to sync`
2565
+ );
2449
2566
  } else if (prStatus.state === "closed") {
2450
2567
  logger.warning(" This PR was closed without merging");
2451
- logger.dim(` Consider reopening or creating a new PR if changes are still needed`);
2568
+ logger.dim(
2569
+ ` Consider reopening or creating a new PR if changes are still needed`
2570
+ );
2452
2571
  }
2453
2572
  } else {
2454
2573
  logger.info(" No PR created yet");
@@ -2580,7 +2699,9 @@ async function aggregateState() {
2580
2699
  try {
2581
2700
  const lastCommitTimestamp = await getLastCommitTimestamp();
2582
2701
  const reviewData = await getPrReviewData(pr.number);
2583
- const { items } = summarizeReviewFeedback(reviewData, { afterTimestamp: lastCommitTimestamp });
2702
+ const { items } = summarizeReviewFeedback(reviewData, {
2703
+ afterTimestamp: lastCommitTimestamp
2704
+ });
2584
2705
  reviewFeedback = items;
2585
2706
  hasActionableFeedback = items.length > 0;
2586
2707
  } catch {
@@ -2621,7 +2742,7 @@ function getAvailableActions(state) {
2621
2742
  if (state.isOnMain) {
2622
2743
  actions.push({ id: "create", label: "new", shortcut: "n" });
2623
2744
  actions.push({ id: "list", label: "list", shortcut: "l" });
2624
- actions.push({ id: "switch-provider", label: "switch", shortcut: "s" });
2745
+ actions.push({ id: "switch-provider", label: "ai", shortcut: "a" });
2625
2746
  actions.push({ id: "quit", label: "quit", shortcut: "q" });
2626
2747
  return actions;
2627
2748
  }
@@ -2629,10 +2750,10 @@ function getAvailableActions(state) {
2629
2750
  actions.push({ id: "commit", label: "commit", shortcut: "c" });
2630
2751
  }
2631
2752
  if (state.hasUnpushedCommits && state.commits.length > 0) {
2632
- actions.push({ id: "push", label: "Push", shortcut: "P" });
2753
+ actions.push({ id: "push", label: "push", shortcut: "p" });
2633
2754
  }
2634
2755
  if (!state.pr && state.commits.length > 0) {
2635
- actions.push({ id: "pr", label: "Create pr", shortcut: "C" });
2756
+ actions.push({ id: "pr", label: "pr", shortcut: "r" });
2636
2757
  }
2637
2758
  if (state.issue && state.pr?.state !== "merged") {
2638
2759
  actions.push({ id: "implement", label: "implement", shortcut: "i" });
@@ -2646,7 +2767,7 @@ function getAvailableActions(state) {
2646
2767
  actions.push({ id: "checkout-main", label: "main", shortcut: "m" });
2647
2768
  }
2648
2769
  actions.push({ id: "list", label: "list", shortcut: "l" });
2649
- actions.push({ id: "switch-provider", label: "switch", shortcut: "s" });
2770
+ actions.push({ id: "switch-provider", label: "ai", shortcut: "a" });
2650
2771
  actions.push({ id: "quit", label: "quit", shortcut: "q" });
2651
2772
  return actions;
2652
2773
  }
@@ -2788,11 +2909,14 @@ function renderDashboard(state, actions, hint, refreshing) {
2788
2909
  const w = termWidth();
2789
2910
  const descMax = w - 8;
2790
2911
  const version2 = getVersion();
2791
- const titleLabel = refreshing ? `gent v${version2} ${chalk3.yellow("Refreshing\u2026")}` : `gent v${version2}`;
2912
+ const titleLabel = `gent v${version2}`;
2792
2913
  console.log(topRow(titleLabel, w));
2914
+ renderSettings(state, w);
2793
2915
  if (!state.isGitRepo) {
2794
2916
  console.log(row(chalk3.red("Not a git repository"), w));
2795
- console.log(row(chalk3.dim("Run gent init in a git repo to get started"), w));
2917
+ console.log(
2918
+ row(chalk3.dim("Run gent init in a git repo to get started"), w)
2919
+ );
2796
2920
  console.log(botRow(w));
2797
2921
  return;
2798
2922
  }
@@ -2803,19 +2927,26 @@ function renderDashboard(state, actions, hint, refreshing) {
2803
2927
  return;
2804
2928
  }
2805
2929
  if (state.isOnMain) {
2806
- console.log(row(chalk3.magenta(state.branch) + chalk3.dim(" \xB7 ready to start new work"), w));
2930
+ console.log(
2931
+ row(
2932
+ chalk3.magenta(state.branch) + chalk3.dim(" \xB7 ready to start new work"),
2933
+ w
2934
+ )
2935
+ );
2807
2936
  if (state.hasUncommittedChanges) {
2808
2937
  console.log(row(chalk3.yellow("\u25CF uncommitted changes"), w));
2809
2938
  }
2810
- console.log(midRow("Settings", w));
2811
- renderSettings(state, w);
2812
2939
  if (hint) {
2813
2940
  console.log(midRow("Hint", w));
2814
2941
  console.log(row(chalk3.yellow(hint), w));
2815
2942
  }
2816
2943
  console.log(divRow(w));
2817
- for (const line of formatCommandBar(actions, w)) {
2818
- console.log(row(line, w));
2944
+ if (refreshing) {
2945
+ console.log(row(chalk3.yellow("Refreshing\u2026"), w));
2946
+ } else {
2947
+ for (const line of formatCommandBar(actions, w)) {
2948
+ console.log(row(line, w));
2949
+ }
2819
2950
  }
2820
2951
  console.log(botRow(w));
2821
2952
  console.log();
@@ -2826,14 +2957,17 @@ function renderDashboard(state, actions, hint, refreshing) {
2826
2957
  };
2827
2958
  section("Ticket");
2828
2959
  if (state.issue) {
2829
- console.log(row(
2830
- chalk3.cyan(`#${state.issue.number}`) + " " + chalk3.bold(truncate(state.issue.title, descMax - 6)),
2831
- w
2832
- ));
2960
+ console.log(
2961
+ row(
2962
+ chalk3.cyan(`#${state.issue.number}`) + " " + chalk3.bold(truncate(state.issue.title, descMax - 6)),
2963
+ w
2964
+ )
2965
+ );
2833
2966
  const desc = extractDescription(state.issue.body, descMax);
2834
2967
  if (desc) console.log(row(chalk3.dim(desc), w));
2835
2968
  const tags = [];
2836
- if (state.workflowStatus !== "none") tags.push(workflowBadge(state.workflowStatus));
2969
+ if (state.workflowStatus !== "none")
2970
+ tags.push(workflowBadge(state.workflowStatus));
2837
2971
  for (const prefix of ["type:", "priority:", "risk:", "area:"]) {
2838
2972
  const l = state.issue.labels.find((x) => x.startsWith(prefix));
2839
2973
  if (l) tags.push(chalk3.dim(l));
@@ -2845,7 +2979,8 @@ function renderDashboard(state, actions, hint, refreshing) {
2845
2979
  section("Branch");
2846
2980
  console.log(row(chalk3.magenta(state.branch), w));
2847
2981
  const bits = [];
2848
- if (state.commits.length > 0) bits.push(chalk3.dim(`${state.commits.length} ahead`));
2982
+ if (state.commits.length > 0)
2983
+ bits.push(chalk3.dim(`${state.commits.length} ahead`));
2849
2984
  if (state.hasUncommittedChanges) bits.push(chalk3.yellow("\u25CF uncommitted"));
2850
2985
  if (state.hasUnpushedCommits) bits.push(chalk3.yellow("\u25CF unpushed"));
2851
2986
  if (!state.hasUncommittedChanges && !state.hasUnpushedCommits && state.commits.length > 0) {
@@ -2855,20 +2990,29 @@ function renderDashboard(state, actions, hint, refreshing) {
2855
2990
  section("Pull Request");
2856
2991
  if (state.pr) {
2857
2992
  const titleText = state.pr.title ? " " + truncate(state.pr.title, descMax - 12) : "";
2858
- console.log(row(
2859
- chalk3.cyan(`#${state.pr.number}`) + titleText,
2860
- w
2861
- ));
2862
- console.log(row(
2863
- prBadge(state.pr.state, state.pr.isDraft) + reviewBadge(state.pr.reviewDecision),
2864
- w
2865
- ));
2993
+ console.log(row(chalk3.cyan(`#${state.pr.number}`) + titleText, w));
2994
+ console.log(
2995
+ row(
2996
+ prBadge(state.pr.state, state.pr.isDraft) + reviewBadge(state.pr.reviewDecision),
2997
+ w
2998
+ )
2999
+ );
2866
3000
  if (state.hasActionableFeedback) {
2867
3001
  const n = state.reviewFeedback.length;
2868
- console.log(row(chalk3.yellow(`${n} actionable comment${n !== 1 ? "s" : ""} pending`), w));
3002
+ console.log(
3003
+ row(
3004
+ chalk3.yellow(`${n} actionable comment${n !== 1 ? "s" : ""} pending`),
3005
+ w
3006
+ )
3007
+ );
2869
3008
  }
2870
3009
  if (state.hasUIChanges && state.isPlaywrightAvailable && state.config.video.enabled && state.pr.state === "open") {
2871
- console.log(row(chalk3.cyan("UI changes detected") + chalk3.dim(" \xB7 video capture available"), w));
3010
+ console.log(
3011
+ row(
3012
+ chalk3.cyan("UI changes detected") + chalk3.dim(" \xB7 video capture available"),
3013
+ w
3014
+ )
3015
+ );
2872
3016
  }
2873
3017
  console.log(row(chalk3.dim(state.pr.url), w));
2874
3018
  } else {
@@ -2878,23 +3022,27 @@ function renderDashboard(state, actions, hint, refreshing) {
2878
3022
  if (state.commits.length > 0) {
2879
3023
  const max = 6;
2880
3024
  for (const c of state.commits.slice(0, max)) {
2881
- console.log(row(c, w));
3025
+ console.log(row(c.substring(0, w - 5), w));
2882
3026
  }
2883
3027
  if (state.commits.length > max) {
2884
- console.log(row(chalk3.dim(`\u2026 and ${state.commits.length - max} more`), w));
3028
+ console.log(
3029
+ row(chalk3.dim(`\u2026 and ${state.commits.length - max} more`), w)
3030
+ );
2885
3031
  }
2886
3032
  } else {
2887
3033
  console.log(row(chalk3.dim("No commits"), w));
2888
3034
  }
2889
- section("Settings");
2890
- renderSettings(state, w);
2891
3035
  if (hint) {
2892
3036
  section("Hint");
2893
3037
  console.log(row(chalk3.yellow(hint), w));
2894
3038
  }
2895
3039
  console.log(divRow(w));
2896
- for (const line of formatCommandBar(actions, w)) {
2897
- console.log(row(line, w));
3040
+ if (refreshing) {
3041
+ console.log(row(chalk3.yellow("Refreshing\u2026"), w));
3042
+ } else {
3043
+ for (const line of formatCommandBar(actions, w)) {
3044
+ console.log(row(line, w));
3045
+ }
2898
3046
  }
2899
3047
  console.log(botRow(w));
2900
3048
  }
@@ -2940,7 +3088,7 @@ async function waitForKey(validKeys) {
2940
3088
  stdin.on("data", onData);
2941
3089
  });
2942
3090
  }
2943
- async function executeAction(actionId, state, providerSetter) {
3091
+ async function executeAction(actionId, state) {
2944
3092
  switch (actionId) {
2945
3093
  case "quit":
2946
3094
  return false;
@@ -3017,7 +3165,7 @@ async function executeAction(actionId, state, providerSetter) {
3017
3165
  }
3018
3166
  case "switch-provider":
3019
3167
  clearScreen();
3020
- await handleSwitchProvider(state, providerSetter);
3168
+ await handleSwitchProvider(state);
3021
3169
  return true;
3022
3170
  case "checkout-main": {
3023
3171
  clearScreen();
@@ -3040,15 +3188,47 @@ async function handleCommit(state) {
3040
3188
  console.log(status);
3041
3189
  console.log();
3042
3190
  await execa4("git", ["add", "-A"]);
3043
- const { stdout: diffStat } = await execa4("git", ["diff", "--cached", "--stat"]);
3191
+ const { stdout: diffStat } = await execa4("git", [
3192
+ "diff",
3193
+ "--cached",
3194
+ "--stat"
3195
+ ]);
3044
3196
  const { stdout: diffPatch } = await execa4("git", ["diff", "--cached"]);
3045
3197
  const diffContent = (diffStat + "\n\n" + diffPatch).slice(0, 4e3);
3046
3198
  const issueNumber = state.issue?.number ?? null;
3047
3199
  const issueTitle = state.issue?.title ?? null;
3048
3200
  const provider = state.config.ai.provider;
3049
3201
  const providerName = getProviderDisplayName(provider);
3050
- logger.info(`Generating commit message with ${providerName}...`);
3051
- const message = await generateCommitMessage(diffContent, issueNumber, issueTitle, state);
3202
+ const { mode } = await inquirer7.prompt([
3203
+ {
3204
+ type: "list",
3205
+ name: "mode",
3206
+ message: "How would you like to provide the commit message?",
3207
+ choices: [
3208
+ { name: `Generate with ${providerName}`, value: "ai" },
3209
+ { name: "Enter manually", value: "manual" }
3210
+ ]
3211
+ }
3212
+ ]);
3213
+ let message;
3214
+ if (mode === "manual") {
3215
+ const { manualInput } = await inquirer7.prompt([
3216
+ {
3217
+ type: "input",
3218
+ name: "manualInput",
3219
+ message: "Commit message (empty to cancel):"
3220
+ }
3221
+ ]);
3222
+ message = manualInput.trim() || CANCEL;
3223
+ } else {
3224
+ logger.info(`Generating commit message with ${providerName}...`);
3225
+ message = await generateCommitMessage(
3226
+ diffContent,
3227
+ issueNumber,
3228
+ issueTitle,
3229
+ state
3230
+ );
3231
+ }
3052
3232
  if (message === CANCEL) {
3053
3233
  await execa4("git", ["reset", "HEAD"]);
3054
3234
  logger.info("Cancelled");
@@ -3076,7 +3256,11 @@ Co-Authored-By: ${providerName} <${providerEmail}>`;
3076
3256
  }
3077
3257
  async function generateCommitMessage(diffContent, issueNumber, issueTitle, state) {
3078
3258
  try {
3079
- const prompt = buildCommitMessagePrompt(diffContent, issueNumber, issueTitle);
3259
+ const prompt = buildCommitMessagePrompt(
3260
+ diffContent,
3261
+ issueNumber,
3262
+ issueTitle
3263
+ );
3080
3264
  const result = await invokeAI({ prompt, streamOutput: true }, state.config);
3081
3265
  let message = result.output.trim().split("\n")[0].trim();
3082
3266
  for (const q of ['"', "'", "`"]) {
@@ -3157,7 +3341,7 @@ async function handlePush() {
3157
3341
  }
3158
3342
  }
3159
3343
  var PROVIDERS = ["claude", "gemini", "codex"];
3160
- async function handleSwitchProvider(state, setProvider) {
3344
+ async function handleSwitchProvider(state) {
3161
3345
  const current = state.config.ai.provider;
3162
3346
  const { provider } = await inquirer7.prompt([
3163
3347
  {
@@ -3172,7 +3356,7 @@ async function handleSwitchProvider(state, setProvider) {
3172
3356
  }
3173
3357
  ]);
3174
3358
  if (provider === current) return;
3175
- setProvider(provider);
3359
+ setRuntimeProvider(provider);
3176
3360
  logger.success(`Provider switched to ${provider} (session only)`);
3177
3361
  }
3178
3362
  async function handleCheckoutMain() {
@@ -3224,11 +3408,7 @@ async function promptContinue() {
3224
3408
  }
3225
3409
  async function tuiCommand() {
3226
3410
  let running = true;
3227
- let providerOverride = null;
3228
3411
  let lastActions = [];
3229
- const setProvider = (p) => {
3230
- providerOverride = p;
3231
- };
3232
3412
  const config = loadConfig();
3233
3413
  let lastState = {
3234
3414
  isGitRepo: true,
@@ -3256,9 +3436,6 @@ async function tuiCommand() {
3256
3436
  clearScreen();
3257
3437
  renderDashboard(lastState, lastActions, void 0, true);
3258
3438
  const state = await aggregateState();
3259
- if (providerOverride) {
3260
- state.config.ai.provider = providerOverride;
3261
- }
3262
3439
  const actions = getAvailableActions(state);
3263
3440
  lastState = state;
3264
3441
  lastActions = actions;
@@ -3276,7 +3453,7 @@ async function tuiCommand() {
3276
3453
  const key = await waitForKey(validKeys);
3277
3454
  const action = actions.find((a) => a.shortcut === key);
3278
3455
  if (action) {
3279
- running = await executeAction(action.id, state, setProvider);
3456
+ running = await executeAction(action.id, state);
3280
3457
  }
3281
3458
  }
3282
3459
  }
@@ -3288,13 +3465,17 @@ function startVersionCheck() {
3288
3465
  checkForUpdates().then((result) => {
3289
3466
  if (result.updateAvailable && result.latestVersion) {
3290
3467
  logger.newline();
3291
- logger.warning(formatUpgradeNotification(result.currentVersion, result.latestVersion));
3468
+ logger.warning(
3469
+ formatUpgradeNotification(result.currentVersion, result.latestVersion)
3470
+ );
3292
3471
  }
3293
3472
  }).catch(() => {
3294
3473
  });
3295
3474
  }
3296
3475
  var program = new Command();
3297
- program.name("gent").description("AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs").version(version).option("--skip-update-check", "Skip checking for CLI updates").hook("preAction", (thisCommand) => {
3476
+ program.name("gent").description(
3477
+ "AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs"
3478
+ ).version(version).option("--skip-update-check", "Skip checking for CLI updates").hook("preAction", (thisCommand) => {
3298
3479
  if (!thisCommand.opts().skipUpdateCheck) {
3299
3480
  startVersionCheck();
3300
3481
  }
@@ -3305,27 +3486,46 @@ program.command("init").description("Initialize gent workflow in current reposit
3305
3486
  program.command("setup-labels").description("Setup GitHub labels for AI workflow").action(async () => {
3306
3487
  await setupLabelsCommand();
3307
3488
  });
3308
- program.command("create <description>").description("Create an AI-enhanced GitHub issue").option("-y, --yes", "Skip confirmation and create issue immediately").option("-p, --provider <provider>", "AI provider to use (claude, gemini, or codex)").option("-t, --title <title>", "Override the generated issue title").action(async (description, options) => {
3309
- await createCommand(description, { yes: options.yes, provider: options.provider, title: options.title });
3489
+ program.command("create <description>").description("Create an AI-enhanced GitHub issue").option("-y, --yes", "Skip confirmation and create issue immediately").option(
3490
+ "-p, --provider <provider>",
3491
+ "AI provider to use (claude, gemini, or codex)"
3492
+ ).option("-t, --title <title>", "Override the generated issue title").action(async (description, options) => {
3493
+ await createCommand(description, {
3494
+ yes: options.yes,
3495
+ provider: options.provider,
3496
+ title: options.title
3497
+ });
3310
3498
  });
3311
- program.command("list").description("List and switch to GitHub issues").option("-l, --label <label>", "Filter by label").option("-s, --status <status>", "Filter by workflow status (ready, in-progress, completed, blocked, all)").option("-n, --limit <number>", "Maximum number of issues to show", "20").action(async (options) => {
3499
+ program.command("list").description("List and switch to GitHub issues").option("-l, --label <label>", "Filter by label").option(
3500
+ "-s, --status <status>",
3501
+ "Filter by workflow status (ready, in-progress, completed, blocked, all)"
3502
+ ).option("-n, --limit <number>", "Maximum number of issues to show", "20").action(async (options) => {
3312
3503
  await listCommand({
3313
3504
  label: options.label,
3314
3505
  status: options.status,
3315
3506
  limit: parseInt(options.limit, 10)
3316
3507
  });
3317
3508
  });
3318
- program.command("run [issue-number]").description("Run AI to implement a GitHub issue").option("-p, --provider <provider>", "AI provider to use (claude, gemini, or codex)").action(async (issueNumber, options) => {
3509
+ program.command("run [issue-number]").description("Run AI to implement a GitHub issue").option(
3510
+ "-p, --provider <provider>",
3511
+ "AI provider to use (claude, gemini, or codex)"
3512
+ ).action(async (issueNumber, options) => {
3319
3513
  await runCommand(issueNumber, { provider: options.provider });
3320
3514
  });
3321
- program.command("pr").description("Create an AI-enhanced pull request").option("-d, --draft", "Create as draft PR").option("-p, --provider <provider>", "AI provider to use (claude, gemini, or codex)").option("--no-video", "Disable video capture for UI changes").action(async (options) => {
3515
+ program.command("pr").description("Create an AI-enhanced pull request").option("-d, --draft", "Create as draft PR").option(
3516
+ "-p, --provider <provider>",
3517
+ "AI provider to use (claude, gemini, or codex)"
3518
+ ).option("--no-video", "Disable video capture for UI changes").action(async (options) => {
3322
3519
  await prCommand({
3323
3520
  draft: options.draft,
3324
3521
  provider: options.provider,
3325
3522
  video: options.video
3326
3523
  });
3327
3524
  });
3328
- program.command("fix").description("Apply PR review feedback using AI").option("-p, --provider <provider>", "AI provider to use (claude, gemini, or codex)").action(async (options) => {
3525
+ program.command("fix").description("Apply PR review feedback using AI").option(
3526
+ "-p, --provider <provider>",
3527
+ "AI provider to use (claude, gemini, or codex)"
3528
+ ).action(async (options) => {
3329
3529
  await fixCommand({ provider: options.provider });
3330
3530
  });
3331
3531
  program.command("status").description("Show current workflow status").action(async () => {