@rotorsoft/gent 1.25.1 → 1.25.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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  githubRemoteCommand
4
- } from "./chunk-PHZJIEY6.js";
4
+ } from "./chunk-FML3SVK5.js";
5
5
  import {
6
6
  aiSpinnerText,
7
7
  buildIssueLabels,
@@ -11,7 +11,7 @@ import {
11
11
  setupLabelsCommand,
12
12
  sortByPriority,
13
13
  withSpinner
14
- } from "./chunk-MRF5FZO6.js";
14
+ } from "./chunk-SBG4BMMM.js";
15
15
  import {
16
16
  addIssueComment,
17
17
  addPrComment,
@@ -19,6 +19,7 @@ import {
19
19
  branchExists,
20
20
  checkAIProvider,
21
21
  checkClaudeCli,
22
+ checkCodexCLI,
22
23
  checkGeminiCli,
23
24
  checkGhAuth,
24
25
  checkGitRepo,
@@ -63,7 +64,7 @@ import {
63
64
  sanitizeSlug,
64
65
  setRuntimeProvider,
65
66
  updateIssueLabels
66
- } from "./chunk-ENNNKNLI.js";
67
+ } from "./chunk-XBUSPWBB.js";
67
68
 
68
69
  // src/index.ts
69
70
  import { Command } from "commander";
@@ -268,14 +269,12 @@ async function initCommand(options) {
268
269
  const repoInfo = await getRepoInfo();
269
270
  if (!repoInfo) {
270
271
  logger.newline();
271
- logger.box(
272
- "Setup Complete",
273
- `Next steps:
274
- 1. Edit ${colors.file("AGENT.md")} with your project-specific instructions
275
- 2. Edit ${colors.file(".gent.yml")} to customize settings
276
- 3. Create a GitHub remote: ${colors.command("gent github-remote")}
277
- 4. Run ${colors.command("gent setup-labels")} to create GitHub labels`
278
- );
272
+ logger.table("Setup Complete", [
273
+ { key: "Edit", value: `${colors.file("AGENT.md")} with your project-specific instructions` },
274
+ { key: "Edit", value: `${colors.file(".gent.yml")} to customize settings` },
275
+ { key: "Create remote", value: `Run ${colors.command("gent github-remote")}` },
276
+ { key: "Setup labels", value: `Run ${colors.command("gent setup-labels")}` }
277
+ ]);
279
278
  const { createRemote } = await inquirer.prompt([
280
279
  {
281
280
  type: "confirm",
@@ -285,7 +284,7 @@ async function initCommand(options) {
285
284
  }
286
285
  ]);
287
286
  if (createRemote) {
288
- const { githubRemoteCommand: githubRemoteCommand2 } = await import("./github-remote-YD46C4M7.js");
287
+ const { githubRemoteCommand: githubRemoteCommand2 } = await import("./github-remote-GJIXMSQK.js");
289
288
  const success = await githubRemoteCommand2();
290
289
  if (success) {
291
290
  const { setupLabels } = await inquirer.prompt([
@@ -297,21 +296,19 @@ async function initCommand(options) {
297
296
  }
298
297
  ]);
299
298
  if (setupLabels) {
300
- const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-W4Z3EJ43.js");
299
+ const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-3IV5GAOW.js");
301
300
  await setupLabelsCommand2();
302
301
  }
303
302
  }
304
303
  }
305
304
  } else {
306
305
  logger.newline();
307
- logger.box(
308
- "Setup Complete",
309
- `Next steps:
310
- 1. Edit ${colors.file("AGENT.md")} with your project-specific instructions
311
- 2. Edit ${colors.file(".gent.yml")} to customize settings
312
- 3. Run ${colors.command("gent setup-labels")} to create GitHub labels
313
- 4. Run ${colors.command("gent create <description>")} to create your first ticket`
314
- );
306
+ logger.table("Setup Complete", [
307
+ { key: "Edit", value: `${colors.file("AGENT.md")} with your project-specific instructions` },
308
+ { key: "Edit", value: `${colors.file(".gent.yml")} to customize settings` },
309
+ { key: "Setup labels", value: `Run ${colors.command("gent setup-labels")}` },
310
+ { key: "Create ticket", value: `Run ${colors.command("gent create <description>")}` }
311
+ ]);
315
312
  const { setupLabels } = await inquirer.prompt([
316
313
  {
317
314
  type: "confirm",
@@ -321,7 +318,7 @@ async function initCommand(options) {
321
318
  }
322
319
  ]);
323
320
  if (setupLabels) {
324
- const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-W4Z3EJ43.js");
321
+ const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-3IV5GAOW.js");
325
322
  await setupLabelsCommand2();
326
323
  }
327
324
  }
@@ -390,7 +387,7 @@ async function invokeAIInteractive(prompt, config, providerOverride) {
390
387
  };
391
388
  }
392
389
  case "gemini": {
393
- const args = prompt.trim() ? ["chat", prompt] : ["chat"];
390
+ const args = prompt.trim() ? [prompt] : [];
394
391
  return {
395
392
  result: execa2("gemini", args, {
396
393
  stdio: "inherit",
@@ -408,6 +405,45 @@ async function invokeAIInteractive(prompt, config, providerOverride) {
408
405
  }
409
406
  }
410
407
  }
408
+ async function runInteractiveSession(prompt, config, providerOverride) {
409
+ const provider = providerOverride ?? config.ai.provider;
410
+ const savedSigint = process.rawListeners("SIGINT").slice();
411
+ const savedSigterm = process.rawListeners("SIGTERM").slice();
412
+ process.removeAllListeners("SIGINT");
413
+ process.removeAllListeners("SIGTERM");
414
+ let signalCancelled = false;
415
+ const handler = () => {
416
+ signalCancelled = true;
417
+ };
418
+ process.on("SIGINT", handler);
419
+ process.on("SIGTERM", handler);
420
+ let exitCode;
421
+ try {
422
+ const { result } = await invokeAIInteractive(
423
+ prompt,
424
+ config,
425
+ providerOverride
426
+ );
427
+ try {
428
+ const r = await result;
429
+ exitCode = r.exitCode ?? void 0;
430
+ } catch (error) {
431
+ if (error && typeof error === "object" && "exitCode" in error) {
432
+ exitCode = error.exitCode;
433
+ }
434
+ }
435
+ } finally {
436
+ process.removeAllListeners("SIGINT");
437
+ process.removeAllListeners("SIGTERM");
438
+ for (const fn of savedSigint) {
439
+ process.on("SIGINT", fn);
440
+ }
441
+ for (const fn of savedSigterm) {
442
+ process.on("SIGTERM", fn);
443
+ }
444
+ }
445
+ return { exitCode, signalCancelled, provider };
446
+ }
411
447
  function getProviderDisplayName(provider) {
412
448
  switch (provider) {
413
449
  case "claude":
@@ -505,15 +541,16 @@ async function invokeGeminiInternal(options) {
505
541
  args.push(options.prompt);
506
542
  if (options.printOutput) {
507
543
  const subprocess = execa2("gemini", args, {
508
- stdio: "inherit"
544
+ stdio: ["ignore", "inherit", "inherit"]
509
545
  });
510
546
  await subprocess;
511
547
  return "";
512
548
  } else if (options.streamOutput) {
513
549
  return new Promise((resolve, reject) => {
514
550
  const child = spawn("gemini", args, {
515
- stdio: ["inherit", "pipe", "pipe"]
551
+ stdio: ["pipe", "pipe", "pipe"]
516
552
  });
553
+ child.stdin.end();
517
554
  let output = "";
518
555
  let firstData = true;
519
556
  child.stdout.on("data", (chunk) => {
@@ -967,18 +1004,17 @@ async function createAndDisplayIssue(title, body, labels, meta) {
967
1004
  logger.newline();
968
1005
  logger.success(`Created issue ${colors.issue(`#${issueNumber}`)}`);
969
1006
  logger.newline();
970
- logger.box(
971
- "Issue Created",
972
- `Issue: ${colors.issue(`#${issueNumber}`)}
973
- Type: ${colors.label(`type:${meta.type}`)}
974
- Priority: ${colors.label(`priority:${meta.priority}`)}
975
- Risk: ${colors.label(`risk:${meta.risk}`)}
976
- Area: ${colors.label(`area:${meta.area}`)}
977
-
978
- Next steps:
979
- 1. Review the issue on GitHub
980
- 2. Run ${colors.command(`gent run ${issueNumber}`)} to implement`
981
- );
1007
+ logger.table("Issue Created", [
1008
+ { key: "Issue", value: colors.issue(`#${issueNumber}`) },
1009
+ { key: "Type", value: colors.label(`type:${meta.type}`) },
1010
+ { key: "Priority", value: colors.label(`priority:${meta.priority}`) },
1011
+ { key: "Risk", value: colors.label(`risk:${meta.risk}`) },
1012
+ { key: "Area", value: colors.label(`area:${meta.area}`) }
1013
+ ]);
1014
+ logger.table("Next Steps", [
1015
+ { key: "Review", value: "Review the issue on GitHub" },
1016
+ { key: "Implement", value: `Run ${colors.command(`gent run ${issueNumber}`)}` }
1017
+ ]);
982
1018
  }
983
1019
 
984
1020
  // src/commands/list.ts
@@ -1503,33 +1539,23 @@ async function runCommand(issueNumberArg, options) {
1503
1539
  const spinner = createSpinner(aiSpinnerText(providerName, "implement ticket"));
1504
1540
  spinner.start();
1505
1541
  const beforeSha = await getCurrentCommitSha();
1506
- let wasCancelled = false;
1507
- const handleSignal = () => {
1508
- wasCancelled = true;
1509
- };
1510
- process.on("SIGINT", handleSignal);
1511
- process.on("SIGTERM", handleSignal);
1512
1542
  spinner.stop();
1513
1543
  let aiExitCode;
1544
+ let wasCancelled = false;
1514
1545
  let usedProvider = provider;
1515
1546
  try {
1516
- const { result, provider: actualProvider } = await invokeAIInteractive(
1547
+ const session = await runInteractiveSession(
1517
1548
  prompt,
1518
1549
  config,
1519
1550
  options.provider
1520
1551
  );
1521
- usedProvider = actualProvider;
1522
- aiExitCode = result.exitCode ?? void 0;
1552
+ aiExitCode = session.exitCode;
1553
+ wasCancelled = session.signalCancelled;
1554
+ usedProvider = session.provider;
1523
1555
  } catch (error) {
1524
- if (error && typeof error === "object" && "exitCode" in error) {
1525
- aiExitCode = error.exitCode;
1526
- }
1527
1556
  logger.error(
1528
1557
  `${getProviderDisplayName(usedProvider)} session failed: ${error}`
1529
1558
  );
1530
- } finally {
1531
- process.off("SIGINT", handleSignal);
1532
- process.off("SIGTERM", handleSignal);
1533
1559
  }
1534
1560
  logger.newline();
1535
1561
  const commitsCreated = await hasNewCommits(beforeSha);
@@ -1598,13 +1624,12 @@ No commits were created. Please retry later.`
1598
1624
  return;
1599
1625
  }
1600
1626
  logger.newline();
1601
- logger.box(
1602
- "Next Steps",
1603
- `1. Review changes: ${colors.command("git diff HEAD~1")}
1604
- 2. Run tests: ${colors.command("npm test")}
1605
- 3. Push branch: ${colors.command("git push -u origin " + branchName)}
1606
- 4. Create PR: ${colors.command("gent pr")}`
1607
- );
1627
+ logger.table("Next Steps", [
1628
+ { key: "Review changes", value: colors.command("git diff HEAD~1") },
1629
+ { key: "Run tests", value: colors.command("npm test") },
1630
+ { key: "Push branch", value: colors.command("git push -u origin " + branchName) },
1631
+ { key: "Create PR", value: colors.command("gent pr") }
1632
+ ]);
1608
1633
  }
1609
1634
 
1610
1635
  // src/lib/playwright.ts
@@ -1845,14 +1870,10 @@ IMPORTANT: This PR contains UI changes. Use the Playwright MCP plugin to:
1845
1870
  if (uiChangesForHint) {
1846
1871
  const playwrightOk = await isPlaywrightAvailable();
1847
1872
  if (playwrightOk) {
1848
- logger.bold("Next Steps");
1849
- logger.info(
1850
- "Run `claude` with Playwright MCP to record a demo video of the changes."
1851
- );
1852
- logger.info(
1853
- "Upload the result to GitHub Assets to keep the repo light."
1854
- );
1855
- logger.newline();
1873
+ logger.table("Next Steps", [
1874
+ { key: "Record video", value: "Run `claude` with Playwright MCP to record a demo video of the changes" },
1875
+ { key: "Upload", value: "Upload the result to GitHub Assets to keep the repo light" }
1876
+ ]);
1856
1877
  }
1857
1878
  }
1858
1879
  }
@@ -2142,29 +2163,19 @@ ${summary}`
2142
2163
  const spinner = createSpinner(aiSpinnerText(providerName, "apply fixes"));
2143
2164
  spinner.start();
2144
2165
  const beforeSha = await getCurrentCommitSha();
2145
- let wasCancelled = false;
2146
- const handleSignal = () => {
2147
- wasCancelled = true;
2148
- };
2149
- process.on("SIGINT", handleSignal);
2150
- process.on("SIGTERM", handleSignal);
2151
2166
  spinner.stop();
2152
2167
  let aiExitCode;
2168
+ let wasCancelled = false;
2153
2169
  try {
2154
- const { result } = await invokeAIInteractive(
2170
+ const session = await runInteractiveSession(
2155
2171
  prompt,
2156
2172
  config,
2157
2173
  options.provider
2158
2174
  );
2159
- aiExitCode = result.exitCode ?? void 0;
2175
+ aiExitCode = session.exitCode;
2176
+ wasCancelled = session.signalCancelled;
2160
2177
  } catch (error) {
2161
- if (error && typeof error === "object" && "exitCode" in error) {
2162
- aiExitCode = error.exitCode;
2163
- }
2164
2178
  logger.error(`${providerName} session failed: ${error}`);
2165
- } finally {
2166
- process.off("SIGINT", handleSignal);
2167
- process.off("SIGTERM", handleSignal);
2168
2179
  }
2169
2180
  logger.newline();
2170
2181
  if (wasCancelled) {
@@ -2225,6 +2236,9 @@ async function replyToFeedbackItems(prNumber, items) {
2225
2236
  }
2226
2237
  }
2227
2238
 
2239
+ // src/commands/status.ts
2240
+ import chalk3 from "chalk";
2241
+
2228
2242
  // src/lib/version.ts
2229
2243
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
2230
2244
  import { join as join3 } from "path";
@@ -2233,7 +2247,7 @@ import { homedir } from "os";
2233
2247
  // package.json
2234
2248
  var package_default = {
2235
2249
  name: "@rotorsoft/gent",
2236
- version: "1.25.1",
2250
+ version: "1.25.3",
2237
2251
  description: "AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs",
2238
2252
  keywords: [
2239
2253
  "cli",
@@ -2416,21 +2430,21 @@ Run: npm install -g @rotorsoft/gent`;
2416
2430
  // src/commands/status.ts
2417
2431
  function formatPrState(state, isDraft) {
2418
2432
  if (state === "merged") {
2419
- return "Merged";
2433
+ return chalk3.magenta("Merged");
2420
2434
  }
2421
2435
  if (state === "closed") {
2422
- return "Closed";
2436
+ return chalk3.red("Closed");
2423
2437
  }
2424
- return isDraft ? "Open (Draft)" : "Open";
2438
+ return isDraft ? chalk3.yellow("Open (Draft)") : chalk3.green("Open");
2425
2439
  }
2426
2440
  function formatReviewDecision(decision) {
2427
2441
  switch (decision) {
2428
2442
  case "APPROVED":
2429
- return "Approved";
2443
+ return chalk3.green("Approved");
2430
2444
  case "CHANGES_REQUESTED":
2431
- return "Changes Requested";
2445
+ return chalk3.red("Changes Requested");
2432
2446
  case "REVIEW_REQUIRED":
2433
- return "Review Required";
2447
+ return chalk3.yellow("Review Required");
2434
2448
  default:
2435
2449
  return decision.replace(/_/g, " ").toLowerCase();
2436
2450
  }
@@ -2466,114 +2480,111 @@ async function statusCommand() {
2466
2480
  }
2467
2481
  const config = loadConfig();
2468
2482
  const workflowLabels = getWorkflowLabels(config);
2469
- logger.bold("Configuration:");
2470
- if (configExists()) {
2471
- logger.success(" .gent.yml found");
2472
- } else {
2473
- logger.warning(" .gent.yml not found - using defaults");
2474
- }
2483
+ const configStatus = configExists() ? chalk3.green("Found") : chalk3.yellow("Not found - using defaults");
2484
+ let progressStatus;
2475
2485
  if (progressExists(config)) {
2476
2486
  const progress = readProgress(config);
2477
2487
  const lines = progress.split("\n").length;
2478
- logger.success(` ${config.progress.file} found (${lines} lines)`);
2488
+ progressStatus = chalk3.green(`Found (${lines} lines)`);
2479
2489
  } else {
2480
- logger.warning(` ${config.progress.file} not found`);
2490
+ progressStatus = chalk3.yellow("Not found");
2481
2491
  }
2482
- logger.newline();
2483
- logger.bold("AI Provider:");
2492
+ logger.table("Configuration", [
2493
+ { key: ".gent.yml", value: configStatus },
2494
+ { key: config.progress.file, value: progressStatus }
2495
+ ]);
2484
2496
  const providerName = getProviderDisplayName(config.ai.provider);
2485
- logger.info(` Active: ${colors.provider(providerName)}`);
2486
- if (config.ai.fallback_provider) {
2487
- const fallbackName = getProviderDisplayName(config.ai.fallback_provider);
2488
- logger.info(
2489
- ` Fallback: ${fallbackName} (auto: ${config.ai.auto_fallback ? "enabled" : "disabled"})`
2490
- );
2491
- }
2492
- logger.newline();
2493
- logger.bold("Prerequisites:");
2494
- const ghAuth = await checkGhAuth();
2495
- if (ghAuth) {
2496
- logger.success(" GitHub CLI authenticated");
2497
- } else {
2498
- logger.error(" GitHub CLI not authenticated");
2499
- }
2500
- const claudeOk = await checkClaudeCli();
2501
- const geminiOk = await checkGeminiCli();
2497
+ const fallbackValue = config.ai.fallback_provider ? `${getProviderDisplayName(config.ai.fallback_provider)} (auto: ${config.ai.auto_fallback ? "enabled" : "disabled"})` : "";
2498
+ logger.table("AI Provider", [
2499
+ { key: "Active", value: colors.provider(providerName) },
2500
+ { key: "Fallback", value: fallbackValue }
2501
+ ]);
2502
+ const [ghAuth, claudeOk, geminiOk, codexOk] = await Promise.all([
2503
+ checkGhAuth(),
2504
+ checkClaudeCli(),
2505
+ checkGeminiCli(),
2506
+ checkCodexCLI()
2507
+ ]);
2502
2508
  const getProviderStatus = (provider) => {
2503
2509
  const isActive = config.ai.provider === provider;
2504
2510
  const isFallback = config.ai.fallback_provider === provider;
2505
- const suffix = isActive ? " (active)" : isFallback ? " (fallback)" : "";
2506
- return suffix;
2511
+ return isActive ? " (active)" : isFallback ? " (fallback)" : "";
2507
2512
  };
2508
- if (claudeOk) {
2509
- logger.success(` Claude CLI available${getProviderStatus("claude")}`);
2510
- } else {
2511
- logger.error(` Claude CLI not found${getProviderStatus("claude")}`);
2512
- }
2513
- if (geminiOk) {
2514
- logger.success(` Gemini CLI available${getProviderStatus("gemini")}`);
2515
- } else {
2516
- logger.error(` Gemini CLI not found${getProviderStatus("gemini")}`);
2517
- }
2518
- logger.newline();
2519
- logger.bold("Git Status:");
2513
+ const cliStatus = (ok, provider) => {
2514
+ const suffix = getProviderStatus(provider);
2515
+ return ok ? chalk3.green(`Available${suffix}`) : chalk3.red(`Not found${suffix}`);
2516
+ };
2517
+ logger.table("Prerequisites", [
2518
+ {
2519
+ key: "GitHub CLI",
2520
+ value: ghAuth ? chalk3.green("Authenticated") : chalk3.red("Not authenticated")
2521
+ },
2522
+ { key: "Claude CLI", value: cliStatus(claudeOk, "claude") },
2523
+ { key: "Gemini CLI", value: cliStatus(geminiOk, "gemini") },
2524
+ { key: "Codex CLI", value: cliStatus(codexOk, "codex") }
2525
+ ]);
2520
2526
  const currentBranch = await getCurrentBranch();
2521
2527
  const onMain = await isOnMainBranch();
2522
2528
  const uncommitted = await hasUncommittedChanges();
2523
2529
  const baseBranch = await getDefaultBranch();
2524
- logger.info(` Branch: ${colors.branch(currentBranch)}`);
2530
+ const gitEntries = [
2531
+ { key: "Branch", value: colors.branch(currentBranch) }
2532
+ ];
2525
2533
  if (onMain) {
2526
- logger.info(" On main branch - ready to start new work");
2534
+ gitEntries.push({ key: "Status", value: "On main branch - ready to start new work" });
2527
2535
  } else {
2528
2536
  const branchInfo = parseBranchName(currentBranch);
2529
2537
  if (branchInfo) {
2530
- logger.info(` Issue: ${colors.issue(`#${branchInfo.issueNumber}`)}`);
2531
- logger.info(` Type: ${branchInfo.type}`);
2538
+ gitEntries.push({ key: "Issue", value: colors.issue(`#${branchInfo.issueNumber}`) });
2539
+ gitEntries.push({ key: "Type", value: branchInfo.type });
2532
2540
  }
2533
2541
  const commits = await getCommitsSinceBase(baseBranch);
2534
- logger.info(` Commits ahead of ${baseBranch}: ${commits.length}`);
2542
+ gitEntries.push({ key: `Commits ahead of ${baseBranch}`, value: String(commits.length) });
2535
2543
  const unpushed = await getUnpushedCommits();
2536
- if (unpushed) {
2537
- logger.warning(" Has unpushed commits");
2538
- } else {
2539
- logger.success(" Up to date with remote");
2540
- }
2544
+ gitEntries.push({
2545
+ key: "Push status",
2546
+ value: unpushed ? chalk3.yellow("Has unpushed commits") : chalk3.green("Up to date with remote")
2547
+ });
2541
2548
  }
2542
2549
  if (uncommitted) {
2543
- logger.warning(" Has uncommitted changes");
2550
+ gitEntries.push({ key: "Working tree", value: chalk3.yellow("Has uncommitted changes") });
2544
2551
  }
2545
- logger.newline();
2552
+ logger.table("Git Status", gitEntries);
2546
2553
  let prStatus = null;
2547
2554
  let hasActionableFeedback = false;
2548
2555
  if (!onMain) {
2549
2556
  const issueNumber = extractIssueNumber(currentBranch);
2550
2557
  if (issueNumber) {
2551
- logger.bold("Linked Issue:");
2552
2558
  try {
2553
2559
  const issue = await getIssue(issueNumber);
2554
- logger.info(` #${issue.number}: ${issue.title}`);
2555
- logger.info(` State: ${issue.state}`);
2556
- logger.info(` Labels: ${issue.labels.join(", ")}`);
2560
+ let workflowValue = "";
2557
2561
  if (issue.labels.includes(workflowLabels.ready)) {
2558
- logger.info(` Workflow: ${colors.label("ai-ready")}`);
2562
+ workflowValue = colors.label("ai-ready");
2559
2563
  } else if (issue.labels.includes(workflowLabels.inProgress)) {
2560
- logger.info(` Workflow: ${colors.label("ai-in-progress")}`);
2564
+ workflowValue = colors.label("ai-in-progress");
2561
2565
  } else if (issue.labels.includes(workflowLabels.completed)) {
2562
- logger.info(` Workflow: ${colors.label("ai-completed")}`);
2566
+ workflowValue = colors.label("ai-completed");
2563
2567
  } else if (issue.labels.includes(workflowLabels.blocked)) {
2564
- logger.info(` Workflow: ${colors.label("ai-blocked")}`);
2568
+ workflowValue = colors.label("ai-blocked");
2565
2569
  }
2570
+ logger.table("Linked Issue", [
2571
+ { key: "Issue", value: `${colors.issue(`#${issue.number}`)} ${issue.title}` },
2572
+ { key: "State", value: issue.state },
2573
+ { key: "Labels", value: issue.labels.map((l) => colors.label(l)).join(", ") },
2574
+ { key: "Workflow", value: workflowValue }
2575
+ ]);
2566
2576
  } catch {
2567
- logger.warning(` Could not fetch issue #${issueNumber}`);
2577
+ logger.table("Linked Issue", [
2578
+ { key: "Issue", value: chalk3.yellow(`Could not fetch #${issueNumber}`) }
2579
+ ]);
2568
2580
  }
2569
- logger.newline();
2570
2581
  }
2571
- logger.bold("Pull Request:");
2572
2582
  prStatus = await getPrStatus();
2573
2583
  if (prStatus) {
2574
- const stateDisplay = formatPrState(prStatus.state, prStatus.isDraft);
2575
- logger.info(` PR #${prStatus.number}: ${stateDisplay}`);
2576
- logger.info(` ${colors.url(prStatus.url)}`);
2584
+ const prEntries = [
2585
+ { key: "PR", value: `#${prStatus.number} ${formatPrState(prStatus.state, prStatus.isDraft)}` },
2586
+ { key: "URL", value: colors.url(prStatus.url) }
2587
+ ];
2577
2588
  if (prStatus.state === "open") {
2578
2589
  try {
2579
2590
  const lastCommitTimestamp = await getLastCommitTimestamp();
@@ -2583,75 +2594,73 @@ async function statusCommand() {
2583
2594
  });
2584
2595
  hasActionableFeedback = items.length > 0;
2585
2596
  if (prStatus.reviewDecision) {
2586
- logger.info(
2587
- ` Review: ${formatReviewDecision(prStatus.reviewDecision)}`
2588
- );
2597
+ prEntries.push({ key: "Review", value: formatReviewDecision(prStatus.reviewDecision) });
2589
2598
  }
2590
2599
  if (items.length > 0) {
2591
- logger.warning(
2592
- ` ${items.length} actionable comment${items.length > 1 ? "s" : ""} to fix with ${colors.command("gent fix")}:`
2593
- );
2600
+ prEntries.push({
2601
+ key: "Feedback",
2602
+ value: chalk3.yellow(`${items.length} actionable comment${items.length > 1 ? "s" : ""} \u2014 fix with ${colors.command("gent fix")}`)
2603
+ });
2594
2604
  for (const item of items) {
2595
2605
  const location = formatFeedbackLocation(item);
2596
2606
  const body = truncateFeedbackBody(item.body, 60);
2597
- logger.dim(` ${location}: ${body}`);
2607
+ prEntries.push({ key: "", value: chalk3.dim(`${location}: ${body}`) });
2598
2608
  }
2599
2609
  } else if (prStatus.reviewDecision === "APPROVED") {
2600
- logger.success(" Ready to merge!");
2610
+ prEntries.push({ key: "Status", value: chalk3.green("Ready to merge!") });
2601
2611
  } else {
2602
- logger.info(" No actionable review comments");
2612
+ prEntries.push({ key: "Feedback", value: "No actionable review comments" });
2603
2613
  }
2604
2614
  } catch {
2605
2615
  }
2606
2616
  } else if (prStatus.state === "merged") {
2607
- logger.success(" This PR has been merged!");
2608
- logger.dim(
2609
- ` Run ${colors.command("git checkout main && git pull")} to sync`
2610
- );
2617
+ prEntries.push({ key: "Status", value: chalk3.green("This PR has been merged!") });
2618
+ prEntries.push({ key: "Next", value: chalk3.dim(`Run ${colors.command("git checkout main && git pull")} to sync`) });
2611
2619
  } else if (prStatus.state === "closed") {
2612
- logger.warning(" This PR was closed without merging");
2613
- logger.dim(
2614
- ` Consider reopening or creating a new PR if changes are still needed`
2615
- );
2620
+ prEntries.push({ key: "Status", value: chalk3.yellow("Closed without merging") });
2621
+ prEntries.push({ key: "Next", value: chalk3.dim("Consider reopening or creating a new PR") });
2616
2622
  }
2623
+ logger.table("Pull Request", prEntries);
2617
2624
  } else {
2618
- logger.info(" No PR created yet");
2619
- logger.dim(` Run ${colors.command("gent pr")} to create one`);
2625
+ logger.table("Pull Request", [
2626
+ { key: "Status", value: "No PR created yet" },
2627
+ { key: "Next", value: chalk3.dim(`Run ${colors.command("gent pr")} to create one`) }
2628
+ ]);
2620
2629
  }
2621
- logger.newline();
2622
2630
  }
2623
- logger.bold("Suggested Actions:");
2631
+ let suggestions = [];
2624
2632
  if (onMain) {
2625
- logger.list([
2626
- `${colors.command("gent list")} - View ai-ready issues`,
2627
- `${colors.command("gent run --auto")} - Start working on highest priority issue`,
2628
- `${colors.command("gent create <description>")} - Create a new ticket`
2629
- ]);
2633
+ suggestions = [
2634
+ { key: "gent list", value: "View ai-ready issues" },
2635
+ { key: "gent run --auto", value: "Start working on highest priority issue" },
2636
+ { key: "gent create", value: "Create a new ticket" }
2637
+ ];
2630
2638
  } else if (!prStatus) {
2631
- logger.list([
2632
- `${colors.command("gent pr")} - Create a pull request`,
2633
- `${colors.command("git push")} - Push your changes`
2634
- ]);
2639
+ suggestions = [
2640
+ { key: "gent pr", value: "Create a pull request" },
2641
+ { key: "git push", value: "Push your changes" }
2642
+ ];
2635
2643
  } else if (prStatus.state === "merged") {
2636
- logger.list([
2637
- `${colors.command("git checkout main && git pull")} - Sync with merged changes`
2638
- ]);
2644
+ suggestions = [
2645
+ { key: "git checkout main && git pull", value: "Sync with merged changes" }
2646
+ ];
2639
2647
  } else if (prStatus.state === "closed") {
2640
- logger.list([
2641
- `Reopen the PR if changes are still needed`,
2642
- `${colors.command("git checkout main")} - Return to main branch`
2643
- ]);
2648
+ suggestions = [
2649
+ { key: "Reopen PR", value: "If changes are still needed" },
2650
+ { key: "git checkout main", value: "Return to main branch" }
2651
+ ];
2644
2652
  } else if (hasActionableFeedback) {
2645
- logger.list([
2646
- `${colors.command("gent fix")} - Address review comments with AI`,
2647
- `${colors.command("git push")} - Push any local changes`
2648
- ]);
2653
+ suggestions = [
2654
+ { key: "gent fix", value: "Address review comments with AI" },
2655
+ { key: "git push", value: "Push any local changes" }
2656
+ ];
2649
2657
  } else {
2650
- logger.list([
2651
- `Review and merge your PR`,
2652
- `${colors.command("git checkout main")} - Return to main branch`
2653
- ]);
2658
+ suggestions = [
2659
+ { key: "Merge PR", value: "Review and merge your pull request" },
2660
+ { key: "git checkout main", value: "Return to main branch" }
2661
+ ];
2654
2662
  }
2663
+ logger.table("Suggested Actions", suggestions);
2655
2664
  }
2656
2665
 
2657
2666
  // src/commands/tui.ts
@@ -2844,7 +2853,7 @@ function getAvailableActions(state) {
2844
2853
  }
2845
2854
 
2846
2855
  // src/tui/display.ts
2847
- import chalk3 from "chalk";
2856
+ import chalk4 from "chalk";
2848
2857
  var stripAnsi = (str) => str.replace(/\x1b\[[0-9;]*m/g, "");
2849
2858
  var visibleLen = (str) => stripAnsi(str).length;
2850
2859
  function truncateAnsi(text, max) {
@@ -2893,74 +2902,74 @@ function extractDescriptionLines(body, maxLen, maxLines = 3) {
2893
2902
  function topRow(title, w) {
2894
2903
  const label = ` ${title} `;
2895
2904
  const fill = w - 2 - label.length;
2896
- return chalk3.dim("\u250C") + chalk3.bold.cyan(label) + chalk3.dim("\u2500".repeat(Math.max(0, fill)) + "\u2510");
2905
+ return chalk4.dim("\u250C") + chalk4.bold.cyan(label) + chalk4.dim("\u2500".repeat(Math.max(0, fill)) + "\u2510");
2897
2906
  }
2898
2907
  function midRow(title, w) {
2899
2908
  const label = ` ${title} `;
2900
2909
  const fill = w - 2 - label.length;
2901
- return chalk3.dim("\u251C") + chalk3.bold.cyan(label) + chalk3.dim("\u2500".repeat(Math.max(0, fill)) + "\u2524");
2910
+ return chalk4.dim("\u251C") + chalk4.bold.cyan(label) + chalk4.dim("\u2500".repeat(Math.max(0, fill)) + "\u2524");
2902
2911
  }
2903
2912
  function divRow(w) {
2904
- return chalk3.dim("\u251C" + "\u2500".repeat(w - 2) + "\u2524");
2913
+ return chalk4.dim("\u251C" + "\u2500".repeat(w - 2) + "\u2524");
2905
2914
  }
2906
2915
  function botRow(w) {
2907
- return chalk3.dim("\u2514" + "\u2500".repeat(w - 2) + "\u2518");
2916
+ return chalk4.dim("\u2514" + "\u2500".repeat(w - 2) + "\u2518");
2908
2917
  }
2909
2918
  function row(text, w) {
2910
2919
  const inner = w - 4;
2911
2920
  const fitted = truncateAnsi(text, inner);
2912
2921
  const pad = Math.max(0, inner - visibleLen(fitted));
2913
- return chalk3.dim("\u2502") + " " + fitted + " ".repeat(pad) + " " + chalk3.dim("\u2502");
2922
+ return chalk4.dim("\u2502") + " " + fitted + " ".repeat(pad) + " " + chalk4.dim("\u2502");
2914
2923
  }
2915
2924
  function workflowBadge(status) {
2916
2925
  switch (status) {
2917
2926
  case "ready":
2918
- return chalk3.bgGreen.black(" READY ");
2927
+ return chalk4.bgGreen.black(" READY ");
2919
2928
  case "in-progress":
2920
- return chalk3.bgYellow.black(" IN PROGRESS ");
2929
+ return chalk4.bgYellow.black(" IN PROGRESS ");
2921
2930
  case "completed":
2922
- return chalk3.bgBlue.white(" COMPLETED ");
2931
+ return chalk4.bgBlue.white(" COMPLETED ");
2923
2932
  case "blocked":
2924
- return chalk3.bgRed.white(" BLOCKED ");
2933
+ return chalk4.bgRed.white(" BLOCKED ");
2925
2934
  default:
2926
2935
  return "";
2927
2936
  }
2928
2937
  }
2929
2938
  function prBadge(state, draft) {
2930
- if (state === "merged") return chalk3.bgMagenta.white(" MERGED ");
2931
- if (state === "closed") return chalk3.bgRed.white(" CLOSED ");
2932
- return draft ? chalk3.bgYellow.black(" DRAFT ") : chalk3.bgGreen.black(" OPEN ");
2939
+ if (state === "merged") return chalk4.bgMagenta.white(" MERGED ");
2940
+ if (state === "closed") return chalk4.bgRed.white(" CLOSED ");
2941
+ return draft ? chalk4.bgYellow.black(" DRAFT ") : chalk4.bgGreen.black(" OPEN ");
2933
2942
  }
2934
2943
  function reviewBadge(decision) {
2935
2944
  if (!decision) return "";
2936
2945
  switch (decision) {
2937
2946
  case "APPROVED":
2938
- return " " + chalk3.green("Approved");
2947
+ return " " + chalk4.green("Approved");
2939
2948
  case "CHANGES_REQUESTED":
2940
- return " " + chalk3.red("Changes requested");
2949
+ return " " + chalk4.red("Changes requested");
2941
2950
  case "REVIEW_REQUIRED":
2942
- return " " + chalk3.yellow("Review pending");
2951
+ return " " + chalk4.yellow("Review pending");
2943
2952
  default:
2944
2953
  return "";
2945
2954
  }
2946
2955
  }
2947
2956
  var shortcutColors = [
2948
- chalk3.cyan.bold,
2949
- chalk3.green.bold,
2950
- chalk3.yellow.bold,
2951
- chalk3.magenta.bold,
2952
- chalk3.blue.bold,
2953
- chalk3.red.bold
2957
+ chalk4.cyan.bold,
2958
+ chalk4.green.bold,
2959
+ chalk4.yellow.bold,
2960
+ chalk4.magenta.bold,
2961
+ chalk4.blue.bold,
2962
+ chalk4.red.bold
2954
2963
  ];
2955
2964
  function formatAction(a, color) {
2956
2965
  const idx = a.label.indexOf(a.shortcut);
2957
- const styledKey = color(chalk3.underline(a.shortcut));
2966
+ const styledKey = color(chalk4.underline(a.shortcut));
2958
2967
  if (idx >= 0) {
2959
2968
  const before = a.label.slice(0, idx);
2960
2969
  const after = a.label.slice(idx + a.shortcut.length);
2961
- return chalk3.dim(before) + styledKey + chalk3.dim(after);
2970
+ return chalk4.dim(before) + styledKey + chalk4.dim(after);
2962
2971
  }
2963
- return styledKey + " " + chalk3.dim(a.label);
2972
+ return styledKey + " " + chalk4.dim(a.label);
2964
2973
  }
2965
2974
  function formatCommandBar(actions, w) {
2966
2975
  const parts = actions.map((a, i) => {
@@ -2992,16 +3001,16 @@ function renderActionPanel(title, content) {
2992
3001
  }
2993
3002
  function renderSettingsTo(state, w, out, versionCheck) {
2994
3003
  const provider = getProviderDisplayName(state.config.ai.provider);
2995
- const provTag = state.isAIProviderAvailable ? chalk3.green(provider) : chalk3.red(provider);
2996
- const ghTag = state.isGhAuthenticated ? chalk3.green("authenticated") : chalk3.red("not authenticated");
2997
- out(row(chalk3.dim("Provider: ") + provTag, w));
2998
- out(row(chalk3.dim("GitHub: ") + ghTag, w));
3004
+ const provTag = state.isAIProviderAvailable ? chalk4.green(provider) : chalk4.red(provider);
3005
+ const ghTag = state.isGhAuthenticated ? chalk4.green("authenticated") : chalk4.red("not authenticated");
3006
+ out(row(chalk4.dim("Provider: ") + provTag, w));
3007
+ out(row(chalk4.dim("GitHub: ") + ghTag, w));
2999
3008
  if (versionCheck?.updateAvailable && versionCheck.latestVersion) {
3000
3009
  out(
3001
3010
  row(
3002
- chalk3.yellow(
3011
+ chalk4.yellow(
3003
3012
  `Update available: ${versionCheck.currentVersion} \u2192 ${versionCheck.latestVersion}`
3004
- ) + chalk3.dim(' \u2014 run "npm install -g @rotorsoft/gent" to upgrade'),
3013
+ ) + chalk4.dim(' \u2014 run "npm install -g @rotorsoft/gent" to upgrade'),
3005
3014
  w
3006
3015
  )
3007
3016
  );
@@ -3017,8 +3026,8 @@ function buildDashboardLines(state, actions, hint, refreshing, versionCheck) {
3017
3026
  out(topRow(titleLabel, w));
3018
3027
  renderSettingsTo(state, w, out, versionCheck);
3019
3028
  if (!state.isGitRepo) {
3020
- out(row(chalk3.yellow("Not a git repository"), w));
3021
- out(row(chalk3.dim("Navigate to a git repository to get started"), w));
3029
+ out(row(chalk4.yellow("Not a git repository"), w));
3030
+ out(row(chalk4.dim("Navigate to a git repository to get started"), w));
3022
3031
  out(divRow(w));
3023
3032
  for (const line of formatCommandBar(actions, w)) {
3024
3033
  out(row(line, w));
@@ -3027,8 +3036,8 @@ function buildDashboardLines(state, actions, hint, refreshing, versionCheck) {
3027
3036
  return lines;
3028
3037
  }
3029
3038
  if (!state.isGhAuthenticated) {
3030
- out(row(chalk3.red("GitHub CLI not authenticated"), w));
3031
- out(row(chalk3.dim("Run: gh auth login"), w));
3039
+ out(row(chalk4.red("GitHub CLI not authenticated"), w));
3040
+ out(row(chalk4.dim("Run: gh auth login"), w));
3032
3041
  out(divRow(w));
3033
3042
  for (const line of formatCommandBar(actions, w)) {
3034
3043
  out(row(line, w));
@@ -3044,48 +3053,48 @@ function buildDashboardLines(state, actions, hint, refreshing, versionCheck) {
3044
3053
  if (state.issue) {
3045
3054
  out(
3046
3055
  row(
3047
- chalk3.dim("\xB7 ") + chalk3.cyan(`#${state.issue.number}`) + " " + chalk3.bold(state.issue.title),
3056
+ chalk4.dim("\xB7 ") + chalk4.cyan(`#${state.issue.number}`) + " " + chalk4.bold(state.issue.title),
3048
3057
  w
3049
3058
  )
3050
3059
  );
3051
3060
  const descLines = extractDescriptionLines(state.issue.body, descMax);
3052
3061
  for (const desc of descLines) {
3053
- out(row(" " + chalk3.dim(desc), w));
3062
+ out(row(" " + chalk4.dim(desc), w));
3054
3063
  }
3055
3064
  const tags = [];
3056
3065
  if (state.workflowStatus !== "none")
3057
3066
  tags.push(workflowBadge(state.workflowStatus));
3058
3067
  for (const prefix of ["type:", "priority:", "risk:", "area:"]) {
3059
3068
  const l = state.issue.labels.find((x) => x.startsWith(prefix));
3060
- if (l) tags.push(chalk3.dim(l));
3069
+ if (l) tags.push(chalk4.dim(l));
3061
3070
  }
3062
3071
  if (tags.length) out(row(" " + tags.join(" "), w));
3063
3072
  } else {
3064
- out(row(chalk3.dim(" No linked issue"), w));
3073
+ out(row(chalk4.dim(" No linked issue"), w));
3065
3074
  }
3066
3075
  }
3067
3076
  section("Branch");
3068
- let branchLine = chalk3.dim("\xB7 ") + chalk3.magenta(state.branch);
3077
+ let branchLine = chalk4.dim("\xB7 ") + chalk4.magenta(state.branch);
3069
3078
  if (state.isOnMain && !state.hasUncommittedChanges) {
3070
- branchLine += chalk3.dim(" \xB7 ready to start new work");
3079
+ branchLine += chalk4.dim(" \xB7 ready to start new work");
3071
3080
  }
3072
3081
  out(row(branchLine, w));
3073
3082
  const bits = [];
3074
3083
  if (state.commits.length > 0)
3075
- bits.push(chalk3.dim(`${state.commits.length} ahead`));
3076
- if (state.hasUncommittedChanges) bits.push(chalk3.yellow("\u25CF uncommitted"));
3077
- if (state.hasUnpushedCommits) bits.push(chalk3.yellow("\u25CF unpushed"));
3084
+ bits.push(chalk4.dim(`${state.commits.length} ahead`));
3085
+ if (state.hasUncommittedChanges) bits.push(chalk4.yellow("\u25CF uncommitted"));
3086
+ if (state.hasUnpushedCommits) bits.push(chalk4.yellow("\u25CF unpushed"));
3078
3087
  if (!state.hasUncommittedChanges && !state.hasUnpushedCommits && state.commits.length > 0) {
3079
- bits.push(chalk3.green("\u25CF synced"));
3088
+ bits.push(chalk4.green("\u25CF synced"));
3080
3089
  }
3081
- if (bits.length) out(row(" " + bits.join(chalk3.dim(" \xB7 ")), w));
3090
+ if (bits.length) out(row(" " + bits.join(chalk4.dim(" \xB7 ")), w));
3082
3091
  if (state.pr || !state.isOnMain) {
3083
3092
  section("Pull Request");
3084
3093
  if (state.pr) {
3085
3094
  const titleText = state.pr.title ? " " + state.pr.title : "";
3086
3095
  out(
3087
3096
  row(
3088
- chalk3.dim("\xB7 ") + chalk3.cyan(`#${state.pr.number}`) + titleText,
3097
+ chalk4.dim("\xB7 ") + chalk4.cyan(`#${state.pr.number}`) + titleText,
3089
3098
  w
3090
3099
  )
3091
3100
  );
@@ -3099,7 +3108,7 @@ function buildDashboardLines(state, actions, hint, refreshing, versionCheck) {
3099
3108
  const n = state.reviewFeedback.length;
3100
3109
  out(
3101
3110
  row(
3102
- " " + chalk3.yellow(`${n} actionable comment${n !== 1 ? "s" : ""} pending`),
3111
+ " " + chalk4.yellow(`${n} actionable comment${n !== 1 ? "s" : ""} pending`),
3103
3112
  w
3104
3113
  )
3105
3114
  );
@@ -3107,14 +3116,14 @@ function buildDashboardLines(state, actions, hint, refreshing, versionCheck) {
3107
3116
  if (state.hasUIChanges && state.isPlaywrightAvailable && state.config.video.enabled && state.pr.state === "open") {
3108
3117
  out(
3109
3118
  row(
3110
- " " + chalk3.cyan("UI changes detected") + chalk3.dim(" \xB7 video capture available"),
3119
+ " " + chalk4.cyan("UI changes detected") + chalk4.dim(" \xB7 video capture available"),
3111
3120
  w
3112
3121
  )
3113
3122
  );
3114
3123
  }
3115
- out(row(" " + chalk3.dim(state.pr.url), w));
3124
+ out(row(" " + chalk4.dim(state.pr.url), w));
3116
3125
  } else {
3117
- out(row(chalk3.dim(" No PR created"), w));
3126
+ out(row(chalk4.dim(" No PR created"), w));
3118
3127
  }
3119
3128
  }
3120
3129
  if (state.commits.length > 0 || !state.isOnMain) {
@@ -3122,33 +3131,33 @@ function buildDashboardLines(state, actions, hint, refreshing, versionCheck) {
3122
3131
  if (state.commits.length > 0) {
3123
3132
  const max = 6;
3124
3133
  for (const c of state.commits.slice(0, max)) {
3125
- out(row(chalk3.dim("\xB7 ") + c, w));
3134
+ out(row(chalk4.dim("\xB7 ") + c, w));
3126
3135
  }
3127
3136
  if (state.commits.length > max) {
3128
- out(row(chalk3.dim(` \u2026 and ${state.commits.length - max} more`), w));
3137
+ out(row(chalk4.dim(` \u2026 and ${state.commits.length - max} more`), w));
3129
3138
  }
3130
3139
  } else {
3131
- out(row(chalk3.dim(" No commits"), w));
3140
+ out(row(chalk4.dim(" No commits"), w));
3132
3141
  }
3133
3142
  }
3134
3143
  if (!state.hasValidRemote) {
3135
3144
  section("Setup");
3136
- out(row(chalk3.yellow("Step 1: Create a GitHub repository"), w));
3137
- out(row(chalk3.dim("Press [g] to create a GitHub repo and push"), w));
3145
+ out(row(chalk4.yellow("Step 1: Create a GitHub repository"), w));
3146
+ out(row(chalk4.dim("Press [g] to create a GitHub repo and push"), w));
3138
3147
  } else if (!state.hasLabels) {
3139
3148
  section("Setup");
3140
- out(row(chalk3.yellow("Step 2: Create workflow labels"), w));
3141
- out(row(chalk3.dim("Press [b] to set up labels"), w));
3149
+ out(row(chalk4.yellow("Step 2: Create workflow labels"), w));
3150
+ out(row(chalk4.dim("Press [b] to set up labels"), w));
3142
3151
  } else if (!state.hasConfig) {
3143
3152
  section("Tip");
3144
- out(row(chalk3.dim("Press [i] to customize configuration (optional)"), w));
3153
+ out(row(chalk4.dim("Press [i] to customize configuration (optional)"), w));
3145
3154
  } else if (hint) {
3146
3155
  section("Hint");
3147
- out(row(chalk3.yellow(hint), w));
3156
+ out(row(chalk4.yellow(hint), w));
3148
3157
  }
3149
3158
  out(divRow(w));
3150
3159
  if (refreshing) {
3151
- out(row(chalk3.yellow("Refreshing\u2026"), w));
3160
+ out(row(chalk4.yellow("Refreshing\u2026"), w));
3152
3161
  } else {
3153
3162
  for (const line of formatCommandBar(actions, w)) {
3154
3163
  out(row(line, w));
@@ -3168,7 +3177,7 @@ function clearScreen() {
3168
3177
  }
3169
3178
 
3170
3179
  // src/tui/modal.ts
3171
- import chalk8 from "chalk";
3180
+ import chalk9 from "chalk";
3172
3181
 
3173
3182
  // src/tui/key-reader.ts
3174
3183
  function readKey() {
@@ -3229,7 +3238,7 @@ function readKey() {
3229
3238
  }
3230
3239
 
3231
3240
  // src/tui/select-dialog.ts
3232
- import chalk4 from "chalk";
3241
+ import chalk5 from "chalk";
3233
3242
  function isSeparator(entry) {
3234
3243
  return "separator" in entry;
3235
3244
  }
@@ -3238,14 +3247,14 @@ function buildSelectContent(items, selectedIndex, maxWidth, currentIndex) {
3238
3247
  let selectableIdx = 0;
3239
3248
  for (const item of items) {
3240
3249
  if (isSeparator(item)) {
3241
- lines.push(chalk4.dim(item.separator));
3250
+ lines.push(chalk5.dim(item.separator));
3242
3251
  } else {
3243
3252
  const isSelected = selectableIdx === selectedIndex;
3244
3253
  const isCurrent = currentIndex != null && selectableIdx === currentIndex;
3245
- const prefix = isSelected ? chalk4.cyan.bold("> ") : " ";
3246
- const bullet = chalk4.dim("\xB7 ");
3254
+ const prefix = isSelected ? chalk5.cyan.bold("> ") : " ";
3255
+ const bullet = chalk5.dim("\xB7 ");
3247
3256
  const label = truncateAnsi(item.name, maxWidth - 4);
3248
- const styledLabel = isSelected ? chalk4.bold(label) : isCurrent ? chalk4.cyan(label) : label;
3257
+ const styledLabel = isSelected ? chalk5.bold(label) : isCurrent ? chalk5.cyan(label) : label;
3249
3258
  lines.push(prefix + bullet + styledLabel);
3250
3259
  selectableIdx++;
3251
3260
  }
@@ -3297,10 +3306,10 @@ async function showSelect(opts) {
3297
3306
  }
3298
3307
 
3299
3308
  // src/tui/confirm-dialog.ts
3300
- import chalk5 from "chalk";
3309
+ import chalk6 from "chalk";
3301
3310
  function buildConfirmContent(message, selectedYes) {
3302
- const yes = selectedYes ? chalk5.cyan.bold("> Yes") : chalk5.dim(" Yes");
3303
- const no = !selectedYes ? chalk5.cyan.bold("> No") : chalk5.dim(" No");
3311
+ const yes = selectedYes ? chalk6.cyan.bold("> Yes") : chalk6.dim(" Yes");
3312
+ const no = !selectedYes ? chalk6.cyan.bold("> No") : chalk6.dim(" No");
3304
3313
  return [message, "", yes, no];
3305
3314
  }
3306
3315
  async function showConfirm(opts) {
@@ -3339,10 +3348,10 @@ async function showConfirm(opts) {
3339
3348
  }
3340
3349
 
3341
3350
  // src/tui/input-dialog.ts
3342
- import chalk6 from "chalk";
3351
+ import chalk7 from "chalk";
3343
3352
  function buildInputContent(label, value, cursorVisible) {
3344
- const cursorChar = cursorVisible ? chalk6.inverse(" ") : "";
3345
- return [label, "", chalk6.cyan("> ") + value + cursorChar];
3353
+ const cursorChar = cursorVisible ? chalk7.inverse(" ") : "";
3354
+ return [label, "", chalk7.cyan("> ") + value + cursorChar];
3346
3355
  }
3347
3356
  async function showInput(opts) {
3348
3357
  const w = modalWidth();
@@ -3384,7 +3393,7 @@ async function showInput(opts) {
3384
3393
  }
3385
3394
 
3386
3395
  // src/tui/multiline-input.ts
3387
- import chalk7 from "chalk";
3396
+ import chalk8 from "chalk";
3388
3397
  function wrapLineWithMap(text, width) {
3389
3398
  if (width <= 0) return [{ text, offset: 0 }];
3390
3399
  if (text.length <= width) return [{ text, offset: 0 }];
@@ -3479,10 +3488,10 @@ function buildMultilineInputContent(label, value, cursorVisible, maxWidth, curso
3479
3488
  if (i === cursorRow && cursorVisible) {
3480
3489
  const charUnderCursor = cursorCol < text.length ? text[cursorCol] : " ";
3481
3490
  lines.push(
3482
- chalk7.cyan(" ") + text.slice(0, cursorCol) + chalk7.inverse(charUnderCursor) + text.slice(cursorCol + 1)
3491
+ chalk8.cyan(" ") + text.slice(0, cursorCol) + chalk8.inverse(charUnderCursor) + text.slice(cursorCol + 1)
3483
3492
  );
3484
3493
  } else {
3485
- lines.push(chalk7.cyan(" ") + text);
3494
+ lines.push(chalk8.cyan(" ") + text);
3486
3495
  }
3487
3496
  }
3488
3497
  return lines;
@@ -3582,19 +3591,19 @@ async function showMultilineInput(opts) {
3582
3591
  function modalTopRow(title, w) {
3583
3592
  const label = ` ${title} `;
3584
3593
  const fill = w - 2 - label.length;
3585
- return chalk8.bold("\u250C") + chalk8.bold.cyan(label) + chalk8.bold("\u2500".repeat(Math.max(0, fill)) + "\u2510");
3594
+ return chalk9.bold("\u250C") + chalk9.bold.cyan(label) + chalk9.bold("\u2500".repeat(Math.max(0, fill)) + "\u2510");
3586
3595
  }
3587
3596
  function modalDivRow(w) {
3588
- return chalk8.bold("\u251C" + "\u2500".repeat(w - 2) + "\u2524");
3597
+ return chalk9.bold("\u251C" + "\u2500".repeat(w - 2) + "\u2524");
3589
3598
  }
3590
3599
  function modalBotRow(w) {
3591
- return chalk8.bold("\u2514" + "\u2500".repeat(w - 2) + "\u2518");
3600
+ return chalk9.bold("\u2514" + "\u2500".repeat(w - 2) + "\u2518");
3592
3601
  }
3593
3602
  function modalRow(text, w) {
3594
3603
  const inner = w - 4;
3595
3604
  const fitted = truncateAnsi(text, inner);
3596
3605
  const pad = Math.max(0, inner - visibleLen(fitted));
3597
- return chalk8.bold("\u2502") + " " + fitted + " ".repeat(pad) + " " + chalk8.bold("\u2502");
3606
+ return chalk9.bold("\u2502") + " " + fitted + " ".repeat(pad) + " " + chalk9.bold("\u2502");
3598
3607
  }
3599
3608
  function modalEmptyRow(w) {
3600
3609
  return modalRow("", w);
@@ -3608,7 +3617,7 @@ function buildModalFrame(title, contentLines, footerText, width) {
3608
3617
  }
3609
3618
  lines.push(modalEmptyRow(width));
3610
3619
  lines.push(modalDivRow(width));
3611
- lines.push(modalRow(chalk8.dim(footerText), width));
3620
+ lines.push(modalRow(chalk9.dim(footerText), width));
3612
3621
  lines.push(modalBotRow(width));
3613
3622
  return lines;
3614
3623
  }
@@ -3637,7 +3646,7 @@ function renderOverlay(dashboardLines, modalLines, mWidth) {
3637
3646
  process.stdout.write(hideCursor());
3638
3647
  for (let i = 0; i < dashboardLines.length && i < rows; i++) {
3639
3648
  process.stdout.write(
3640
- moveTo(i + 1, 1) + chalk8.dim(stripAnsi(dashboardLines[i]))
3649
+ moveTo(i + 1, 1) + chalk9.dim(stripAnsi(dashboardLines[i]))
3641
3650
  );
3642
3651
  }
3643
3652
  const startRow = Math.max(1, Math.floor((rows - modalLines.length) / 2));
@@ -3763,6 +3772,36 @@ async function executeAction(actionId, state, dashboardLines) {
3763
3772
  return SKIP_REFRESH;
3764
3773
  }
3765
3774
  }
3775
+ function drainStdin() {
3776
+ return new Promise((resolve) => {
3777
+ const { stdin } = process;
3778
+ if (!stdin.isTTY) {
3779
+ resolve();
3780
+ return;
3781
+ }
3782
+ stdin.setRawMode(true);
3783
+ stdin.resume();
3784
+ const discard = () => {
3785
+ };
3786
+ stdin.on("data", discard);
3787
+ setTimeout(() => {
3788
+ stdin.removeListener("data", discard);
3789
+ stdin.pause();
3790
+ stdin.setRawMode(false);
3791
+ resolve();
3792
+ }, 50);
3793
+ });
3794
+ }
3795
+ async function runAISession(prompt, config) {
3796
+ try {
3797
+ await runInteractiveSession(prompt, config);
3798
+ } finally {
3799
+ try {
3800
+ await drainStdin();
3801
+ } catch {
3802
+ }
3803
+ }
3804
+ }
3766
3805
  async function handleCommit(state, dashboardLines) {
3767
3806
  try {
3768
3807
  const { stdout: status } = await execa4("git", ["status", "--short"]);
@@ -3817,8 +3856,7 @@ Co-Authored-By: ${providerName} <${providerEmail}>`;
3817
3856
  ]);
3818
3857
  logger.newline();
3819
3858
  try {
3820
- const { result } = await invokeAIInteractive(prompt, state.config);
3821
- await result;
3859
+ await runAISession(prompt, state.config);
3822
3860
  return true;
3823
3861
  } catch (error) {
3824
3862
  logger.error(`${providerName} commit failed: ${error}`);
@@ -3869,8 +3907,7 @@ ${feedbackLines}`);
3869
3907
  ]);
3870
3908
  console.log();
3871
3909
  try {
3872
- const { result } = await invokeAIInteractive(prompt, state.config);
3873
- await result;
3910
+ await runAISession(prompt, state.config);
3874
3911
  } catch (error) {
3875
3912
  logger.error(`${providerName} session failed: ${error}`);
3876
3913
  }