@rotorsoft/gent 1.13.1 → 1.13.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
@@ -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.2",
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,7 +2927,12 @@ 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
  }
@@ -2814,8 +2943,12 @@ function renderDashboard(state, actions, hint, refreshing) {
2814
2943
  console.log(row(chalk3.yellow(hint), w));
2815
2944
  }
2816
2945
  console.log(divRow(w));
2817
- for (const line of formatCommandBar(actions, w)) {
2818
- console.log(row(line, w));
2946
+ if (refreshing) {
2947
+ console.log(row(chalk3.yellow("Refreshing\u2026"), w));
2948
+ } else {
2949
+ for (const line of formatCommandBar(actions, w)) {
2950
+ console.log(row(line, w));
2951
+ }
2819
2952
  }
2820
2953
  console.log(botRow(w));
2821
2954
  console.log();
@@ -2826,14 +2959,17 @@ function renderDashboard(state, actions, hint, refreshing) {
2826
2959
  };
2827
2960
  section("Ticket");
2828
2961
  if (state.issue) {
2829
- console.log(row(
2830
- chalk3.cyan(`#${state.issue.number}`) + " " + chalk3.bold(truncate(state.issue.title, descMax - 6)),
2831
- w
2832
- ));
2962
+ console.log(
2963
+ row(
2964
+ chalk3.cyan(`#${state.issue.number}`) + " " + chalk3.bold(truncate(state.issue.title, descMax - 6)),
2965
+ w
2966
+ )
2967
+ );
2833
2968
  const desc = extractDescription(state.issue.body, descMax);
2834
2969
  if (desc) console.log(row(chalk3.dim(desc), w));
2835
2970
  const tags = [];
2836
- if (state.workflowStatus !== "none") tags.push(workflowBadge(state.workflowStatus));
2971
+ if (state.workflowStatus !== "none")
2972
+ tags.push(workflowBadge(state.workflowStatus));
2837
2973
  for (const prefix of ["type:", "priority:", "risk:", "area:"]) {
2838
2974
  const l = state.issue.labels.find((x) => x.startsWith(prefix));
2839
2975
  if (l) tags.push(chalk3.dim(l));
@@ -2845,7 +2981,8 @@ function renderDashboard(state, actions, hint, refreshing) {
2845
2981
  section("Branch");
2846
2982
  console.log(row(chalk3.magenta(state.branch), w));
2847
2983
  const bits = [];
2848
- if (state.commits.length > 0) bits.push(chalk3.dim(`${state.commits.length} ahead`));
2984
+ if (state.commits.length > 0)
2985
+ bits.push(chalk3.dim(`${state.commits.length} ahead`));
2849
2986
  if (state.hasUncommittedChanges) bits.push(chalk3.yellow("\u25CF uncommitted"));
2850
2987
  if (state.hasUnpushedCommits) bits.push(chalk3.yellow("\u25CF unpushed"));
2851
2988
  if (!state.hasUncommittedChanges && !state.hasUnpushedCommits && state.commits.length > 0) {
@@ -2855,20 +2992,29 @@ function renderDashboard(state, actions, hint, refreshing) {
2855
2992
  section("Pull Request");
2856
2993
  if (state.pr) {
2857
2994
  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
- ));
2995
+ console.log(row(chalk3.cyan(`#${state.pr.number}`) + titleText, w));
2996
+ console.log(
2997
+ row(
2998
+ prBadge(state.pr.state, state.pr.isDraft) + reviewBadge(state.pr.reviewDecision),
2999
+ w
3000
+ )
3001
+ );
2866
3002
  if (state.hasActionableFeedback) {
2867
3003
  const n = state.reviewFeedback.length;
2868
- console.log(row(chalk3.yellow(`${n} actionable comment${n !== 1 ? "s" : ""} pending`), w));
3004
+ console.log(
3005
+ row(
3006
+ chalk3.yellow(`${n} actionable comment${n !== 1 ? "s" : ""} pending`),
3007
+ w
3008
+ )
3009
+ );
2869
3010
  }
2870
3011
  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));
3012
+ console.log(
3013
+ row(
3014
+ chalk3.cyan("UI changes detected") + chalk3.dim(" \xB7 video capture available"),
3015
+ w
3016
+ )
3017
+ );
2872
3018
  }
2873
3019
  console.log(row(chalk3.dim(state.pr.url), w));
2874
3020
  } else {
@@ -2878,23 +3024,27 @@ function renderDashboard(state, actions, hint, refreshing) {
2878
3024
  if (state.commits.length > 0) {
2879
3025
  const max = 6;
2880
3026
  for (const c of state.commits.slice(0, max)) {
2881
- console.log(row(c, w));
3027
+ console.log(row(c.substring(0, w - 5), w));
2882
3028
  }
2883
3029
  if (state.commits.length > max) {
2884
- console.log(row(chalk3.dim(`\u2026 and ${state.commits.length - max} more`), w));
3030
+ console.log(
3031
+ row(chalk3.dim(`\u2026 and ${state.commits.length - max} more`), w)
3032
+ );
2885
3033
  }
2886
3034
  } else {
2887
3035
  console.log(row(chalk3.dim("No commits"), w));
2888
3036
  }
2889
- section("Settings");
2890
- renderSettings(state, w);
2891
3037
  if (hint) {
2892
3038
  section("Hint");
2893
3039
  console.log(row(chalk3.yellow(hint), w));
2894
3040
  }
2895
3041
  console.log(divRow(w));
2896
- for (const line of formatCommandBar(actions, w)) {
2897
- console.log(row(line, w));
3042
+ if (refreshing) {
3043
+ console.log(row(chalk3.yellow("Refreshing\u2026"), w));
3044
+ } else {
3045
+ for (const line of formatCommandBar(actions, w)) {
3046
+ console.log(row(line, w));
3047
+ }
2898
3048
  }
2899
3049
  console.log(botRow(w));
2900
3050
  }
@@ -2940,7 +3090,7 @@ async function waitForKey(validKeys) {
2940
3090
  stdin.on("data", onData);
2941
3091
  });
2942
3092
  }
2943
- async function executeAction(actionId, state, providerSetter) {
3093
+ async function executeAction(actionId, state) {
2944
3094
  switch (actionId) {
2945
3095
  case "quit":
2946
3096
  return false;
@@ -3017,7 +3167,7 @@ async function executeAction(actionId, state, providerSetter) {
3017
3167
  }
3018
3168
  case "switch-provider":
3019
3169
  clearScreen();
3020
- await handleSwitchProvider(state, providerSetter);
3170
+ await handleSwitchProvider(state);
3021
3171
  return true;
3022
3172
  case "checkout-main": {
3023
3173
  clearScreen();
@@ -3040,15 +3190,47 @@ async function handleCommit(state) {
3040
3190
  console.log(status);
3041
3191
  console.log();
3042
3192
  await execa4("git", ["add", "-A"]);
3043
- const { stdout: diffStat } = await execa4("git", ["diff", "--cached", "--stat"]);
3193
+ const { stdout: diffStat } = await execa4("git", [
3194
+ "diff",
3195
+ "--cached",
3196
+ "--stat"
3197
+ ]);
3044
3198
  const { stdout: diffPatch } = await execa4("git", ["diff", "--cached"]);
3045
3199
  const diffContent = (diffStat + "\n\n" + diffPatch).slice(0, 4e3);
3046
3200
  const issueNumber = state.issue?.number ?? null;
3047
3201
  const issueTitle = state.issue?.title ?? null;
3048
3202
  const provider = state.config.ai.provider;
3049
3203
  const providerName = getProviderDisplayName(provider);
3050
- logger.info(`Generating commit message with ${providerName}...`);
3051
- const message = await generateCommitMessage(diffContent, issueNumber, issueTitle, state);
3204
+ const { mode } = await inquirer7.prompt([
3205
+ {
3206
+ type: "list",
3207
+ name: "mode",
3208
+ message: "How would you like to provide the commit message?",
3209
+ choices: [
3210
+ { name: `Generate with ${providerName}`, value: "ai" },
3211
+ { name: "Enter manually", value: "manual" }
3212
+ ]
3213
+ }
3214
+ ]);
3215
+ let message;
3216
+ if (mode === "manual") {
3217
+ const { manualInput } = await inquirer7.prompt([
3218
+ {
3219
+ type: "input",
3220
+ name: "manualInput",
3221
+ message: "Commit message (empty to cancel):"
3222
+ }
3223
+ ]);
3224
+ message = manualInput.trim() || CANCEL;
3225
+ } else {
3226
+ logger.info(`Generating commit message with ${providerName}...`);
3227
+ message = await generateCommitMessage(
3228
+ diffContent,
3229
+ issueNumber,
3230
+ issueTitle,
3231
+ state
3232
+ );
3233
+ }
3052
3234
  if (message === CANCEL) {
3053
3235
  await execa4("git", ["reset", "HEAD"]);
3054
3236
  logger.info("Cancelled");
@@ -3076,7 +3258,11 @@ Co-Authored-By: ${providerName} <${providerEmail}>`;
3076
3258
  }
3077
3259
  async function generateCommitMessage(diffContent, issueNumber, issueTitle, state) {
3078
3260
  try {
3079
- const prompt = buildCommitMessagePrompt(diffContent, issueNumber, issueTitle);
3261
+ const prompt = buildCommitMessagePrompt(
3262
+ diffContent,
3263
+ issueNumber,
3264
+ issueTitle
3265
+ );
3080
3266
  const result = await invokeAI({ prompt, streamOutput: true }, state.config);
3081
3267
  let message = result.output.trim().split("\n")[0].trim();
3082
3268
  for (const q of ['"', "'", "`"]) {
@@ -3157,7 +3343,7 @@ async function handlePush() {
3157
3343
  }
3158
3344
  }
3159
3345
  var PROVIDERS = ["claude", "gemini", "codex"];
3160
- async function handleSwitchProvider(state, setProvider) {
3346
+ async function handleSwitchProvider(state) {
3161
3347
  const current = state.config.ai.provider;
3162
3348
  const { provider } = await inquirer7.prompt([
3163
3349
  {
@@ -3172,7 +3358,7 @@ async function handleSwitchProvider(state, setProvider) {
3172
3358
  }
3173
3359
  ]);
3174
3360
  if (provider === current) return;
3175
- setProvider(provider);
3361
+ setRuntimeProvider(provider);
3176
3362
  logger.success(`Provider switched to ${provider} (session only)`);
3177
3363
  }
3178
3364
  async function handleCheckoutMain() {
@@ -3224,11 +3410,7 @@ async function promptContinue() {
3224
3410
  }
3225
3411
  async function tuiCommand() {
3226
3412
  let running = true;
3227
- let providerOverride = null;
3228
3413
  let lastActions = [];
3229
- const setProvider = (p) => {
3230
- providerOverride = p;
3231
- };
3232
3414
  const config = loadConfig();
3233
3415
  let lastState = {
3234
3416
  isGitRepo: true,
@@ -3256,9 +3438,6 @@ async function tuiCommand() {
3256
3438
  clearScreen();
3257
3439
  renderDashboard(lastState, lastActions, void 0, true);
3258
3440
  const state = await aggregateState();
3259
- if (providerOverride) {
3260
- state.config.ai.provider = providerOverride;
3261
- }
3262
3441
  const actions = getAvailableActions(state);
3263
3442
  lastState = state;
3264
3443
  lastActions = actions;
@@ -3276,7 +3455,7 @@ async function tuiCommand() {
3276
3455
  const key = await waitForKey(validKeys);
3277
3456
  const action = actions.find((a) => a.shortcut === key);
3278
3457
  if (action) {
3279
- running = await executeAction(action.id, state, setProvider);
3458
+ running = await executeAction(action.id, state);
3280
3459
  }
3281
3460
  }
3282
3461
  }
@@ -3288,13 +3467,17 @@ function startVersionCheck() {
3288
3467
  checkForUpdates().then((result) => {
3289
3468
  if (result.updateAvailable && result.latestVersion) {
3290
3469
  logger.newline();
3291
- logger.warning(formatUpgradeNotification(result.currentVersion, result.latestVersion));
3470
+ logger.warning(
3471
+ formatUpgradeNotification(result.currentVersion, result.latestVersion)
3472
+ );
3292
3473
  }
3293
3474
  }).catch(() => {
3294
3475
  });
3295
3476
  }
3296
3477
  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) => {
3478
+ program.name("gent").description(
3479
+ "AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs"
3480
+ ).version(version).option("--skip-update-check", "Skip checking for CLI updates").hook("preAction", (thisCommand) => {
3298
3481
  if (!thisCommand.opts().skipUpdateCheck) {
3299
3482
  startVersionCheck();
3300
3483
  }
@@ -3305,27 +3488,46 @@ program.command("init").description("Initialize gent workflow in current reposit
3305
3488
  program.command("setup-labels").description("Setup GitHub labels for AI workflow").action(async () => {
3306
3489
  await setupLabelsCommand();
3307
3490
  });
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 });
3491
+ program.command("create <description>").description("Create an AI-enhanced GitHub issue").option("-y, --yes", "Skip confirmation and create issue immediately").option(
3492
+ "-p, --provider <provider>",
3493
+ "AI provider to use (claude, gemini, or codex)"
3494
+ ).option("-t, --title <title>", "Override the generated issue title").action(async (description, options) => {
3495
+ await createCommand(description, {
3496
+ yes: options.yes,
3497
+ provider: options.provider,
3498
+ title: options.title
3499
+ });
3310
3500
  });
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) => {
3501
+ program.command("list").description("List and switch to GitHub issues").option("-l, --label <label>", "Filter by label").option(
3502
+ "-s, --status <status>",
3503
+ "Filter by workflow status (ready, in-progress, completed, blocked, all)"
3504
+ ).option("-n, --limit <number>", "Maximum number of issues to show", "20").action(async (options) => {
3312
3505
  await listCommand({
3313
3506
  label: options.label,
3314
3507
  status: options.status,
3315
3508
  limit: parseInt(options.limit, 10)
3316
3509
  });
3317
3510
  });
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) => {
3511
+ program.command("run [issue-number]").description("Run AI to implement a GitHub issue").option(
3512
+ "-p, --provider <provider>",
3513
+ "AI provider to use (claude, gemini, or codex)"
3514
+ ).action(async (issueNumber, options) => {
3319
3515
  await runCommand(issueNumber, { provider: options.provider });
3320
3516
  });
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) => {
3517
+ program.command("pr").description("Create an AI-enhanced pull request").option("-d, --draft", "Create as draft PR").option(
3518
+ "-p, --provider <provider>",
3519
+ "AI provider to use (claude, gemini, or codex)"
3520
+ ).option("--no-video", "Disable video capture for UI changes").action(async (options) => {
3322
3521
  await prCommand({
3323
3522
  draft: options.draft,
3324
3523
  provider: options.provider,
3325
3524
  video: options.video
3326
3525
  });
3327
3526
  });
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) => {
3527
+ program.command("fix").description("Apply PR review feedback using AI").option(
3528
+ "-p, --provider <provider>",
3529
+ "AI provider to use (claude, gemini, or codex)"
3530
+ ).action(async (options) => {
3329
3531
  await fixCommand({ provider: options.provider });
3330
3532
  });
3331
3533
  program.command("status").description("Show current workflow status").action(async () => {