@rotorsoft/gent 1.14.0 → 1.14.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
@@ -13,7 +13,6 @@ import {
13
13
  configExists,
14
14
  createIssue,
15
15
  createPullRequest,
16
- createSpinner,
17
16
  extractTypeFromLabels,
18
17
  generateDefaultConfig,
19
18
  getConfigPath,
@@ -37,7 +36,7 @@ import {
37
36
  sortByPriority,
38
37
  updateIssueLabels,
39
38
  withSpinner
40
- } from "./chunk-SKWCS6Z2.js";
39
+ } from "./chunk-JHIW57FY.js";
41
40
 
42
41
  // src/index.ts
43
42
  import { Command } from "commander";
@@ -201,7 +200,7 @@ async function initCommand(options) {
201
200
  }
202
201
  ]);
203
202
  if (setupLabels) {
204
- const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-C6YHJ637.js");
203
+ const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-LB7X6A3K.js");
205
204
  await setupLabelsCommand2();
206
205
  }
207
206
  }
@@ -1556,9 +1555,6 @@ No commits were created. Please retry later.`
1556
1555
  );
1557
1556
  }
1558
1557
 
1559
- // src/commands/pr.ts
1560
- import inquirer5 from "inquirer";
1561
-
1562
1558
  // src/lib/playwright.ts
1563
1559
  import { execa as execa3 } from "execa";
1564
1560
  var UI_FILE_PATTERNS = [
@@ -1616,17 +1612,17 @@ async function prCommand(options) {
1616
1612
  ]);
1617
1613
  if (!ghAuth) {
1618
1614
  logger.error("Not authenticated with GitHub. Run 'gh auth login' first.");
1619
- process.exit(1);
1615
+ return;
1620
1616
  }
1621
1617
  if (!aiOk) {
1622
1618
  logger.error(
1623
1619
  `${providerName} CLI not found. Please install ${provider} CLI first.`
1624
1620
  );
1625
- process.exit(1);
1621
+ return;
1626
1622
  }
1627
1623
  if (await isOnMainBranch()) {
1628
1624
  logger.error("Cannot create PR from main/master branch.");
1629
- process.exit(1);
1625
+ return;
1630
1626
  }
1631
1627
  const existingPr = await getPrForBranch();
1632
1628
  if (existingPr) {
@@ -1641,21 +1637,10 @@ async function prCommand(options) {
1641
1637
  logger.info(`Base: ${colors.branch(baseBranch)}`);
1642
1638
  const hasUnpushed = await getUnpushedCommits();
1643
1639
  if (hasUnpushed) {
1644
- logger.warning("Branch has unpushed commits.");
1645
- const { push } = await inquirer5.prompt([
1646
- {
1647
- type: "confirm",
1648
- name: "push",
1649
- message: "Push to remote before creating PR?",
1650
- default: true
1651
- }
1652
- ]);
1653
- if (push) {
1654
- await withSpinner("Pushing branch...", async () => {
1655
- await pushBranch();
1656
- });
1657
- logger.success("Branch pushed");
1658
- }
1640
+ await withSpinner("Pushing branch...", async () => {
1641
+ await pushBranch();
1642
+ });
1643
+ logger.success("Branch pushed");
1659
1644
  }
1660
1645
  const issueNumber = extractIssueNumber(currentBranch);
1661
1646
  let issue = null;
@@ -1808,7 +1793,7 @@ function generateFallbackBody(issue, commits) {
1808
1793
  }
1809
1794
 
1810
1795
  // src/commands/fix.ts
1811
- import inquirer6 from "inquirer";
1796
+ import inquirer5 from "inquirer";
1812
1797
 
1813
1798
  // src/lib/review-feedback.ts
1814
1799
  var ACTIONABLE_KEYWORDS = [
@@ -2005,7 +1990,7 @@ async function fixCommand(options) {
2005
1990
  const hasChanges = await hasUncommittedChanges();
2006
1991
  if (hasChanges) {
2007
1992
  logger.warning("You have uncommitted changes.");
2008
- const { proceed } = await inquirer6.prompt([
1993
+ const { proceed } = await inquirer5.prompt([
2009
1994
  {
2010
1995
  type: "confirm",
2011
1996
  name: "proceed",
@@ -2165,7 +2150,7 @@ import { homedir } from "os";
2165
2150
  // package.json
2166
2151
  var package_default = {
2167
2152
  name: "@rotorsoft/gent",
2168
- version: "1.14.0",
2153
+ version: "1.14.2",
2169
2154
  description: "AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs",
2170
2155
  keywords: [
2171
2156
  "cli",
@@ -2587,7 +2572,6 @@ async function statusCommand() {
2587
2572
  }
2588
2573
 
2589
2574
  // src/commands/tui.ts
2590
- import inquirer7 from "inquirer";
2591
2575
  import { execa as execa4 } from "execa";
2592
2576
 
2593
2577
  // src/tui/state.ts
@@ -2868,75 +2852,73 @@ function renderActionPanel(title, content) {
2868
2852
  }
2869
2853
  console.log(botRow(w));
2870
2854
  }
2871
- function renderSettings(state, w) {
2855
+ function renderSettingsTo(state, w, out) {
2872
2856
  const provider = getProviderDisplayName(state.config.ai.provider);
2873
2857
  const provTag = state.isAIProviderAvailable ? chalk3.green(provider) : chalk3.red(provider);
2874
2858
  const ghTag = state.isGhAuthenticated ? chalk3.green("authenticated") : chalk3.red("not authenticated");
2875
- const videoTag = state.config.video.enabled ? chalk3.green("on") : chalk3.dim("off");
2876
- console.log(row(chalk3.dim("Provider: ") + provTag, w));
2877
- console.log(row(chalk3.dim("GitHub: ") + ghTag, w));
2878
- console.log(row(chalk3.dim("Video: ") + videoTag, w));
2859
+ out(row(chalk3.dim("Provider: ") + provTag, w));
2860
+ out(row(chalk3.dim("GitHub: ") + ghTag, w));
2879
2861
  }
2880
- function renderDashboard(state, actions, hint, refreshing) {
2862
+ function buildDashboardLines(state, actions, hint, refreshing) {
2863
+ const lines = [];
2864
+ const out = (line) => lines.push(line);
2881
2865
  const w = termWidth();
2882
2866
  const descMax = w - 8;
2883
2867
  const version2 = getVersion();
2884
2868
  const titleLabel = `gent v${version2}`;
2885
- console.log(topRow(titleLabel, w));
2886
- renderSettings(state, w);
2869
+ out(topRow(titleLabel, w));
2870
+ renderSettingsTo(state, w, out);
2887
2871
  if (!state.isGitRepo) {
2888
- console.log(row(chalk3.red("Not a git repository"), w));
2889
- console.log(
2890
- row(chalk3.dim("Run gent init in a git repo to get started"), w)
2891
- );
2892
- console.log(botRow(w));
2893
- return;
2872
+ out(row(chalk3.red("Not a git repository"), w));
2873
+ out(row(chalk3.dim("Run gent init in a git repo to get started"), w));
2874
+ out(botRow(w));
2875
+ return lines;
2894
2876
  }
2895
2877
  if (!state.isGhAuthenticated) {
2896
- console.log(row(chalk3.red("GitHub CLI not authenticated"), w));
2897
- console.log(row(chalk3.dim("Run: gh auth login"), w));
2898
- console.log(botRow(w));
2899
- return;
2878
+ out(row(chalk3.red("GitHub CLI not authenticated"), w));
2879
+ out(row(chalk3.dim("Run: gh auth login"), w));
2880
+ out(botRow(w));
2881
+ return lines;
2900
2882
  }
2901
2883
  if (state.isOnMain) {
2902
- console.log(
2884
+ out(
2903
2885
  row(
2904
2886
  chalk3.magenta(state.branch) + chalk3.dim(" \xB7 ready to start new work"),
2905
2887
  w
2906
2888
  )
2907
2889
  );
2908
2890
  if (state.hasUncommittedChanges) {
2909
- console.log(row(chalk3.yellow("\u25CF uncommitted changes"), w));
2891
+ out(row(chalk3.yellow("\u25CF uncommitted changes"), w));
2910
2892
  }
2911
2893
  if (hint) {
2912
- console.log(midRow("Hint", w));
2913
- console.log(row(chalk3.yellow(hint), w));
2894
+ out(midRow("Hint", w));
2895
+ out(row(chalk3.yellow(hint), w));
2914
2896
  }
2915
- console.log(divRow(w));
2897
+ out(divRow(w));
2916
2898
  if (refreshing) {
2917
- console.log(row(chalk3.yellow("Refreshing\u2026"), w));
2899
+ out(row(chalk3.yellow("Refreshing\u2026"), w));
2918
2900
  } else {
2919
2901
  for (const line of formatCommandBar(actions, w)) {
2920
- console.log(row(line, w));
2902
+ out(row(line, w));
2921
2903
  }
2922
2904
  }
2923
- console.log(botRow(w));
2924
- console.log();
2925
- return;
2905
+ out(botRow(w));
2906
+ out("");
2907
+ return lines;
2926
2908
  }
2927
2909
  const section = (title) => {
2928
- console.log(midRow(title, w));
2910
+ out(midRow(title, w));
2929
2911
  };
2930
2912
  section("Ticket");
2931
2913
  if (state.issue) {
2932
- console.log(
2914
+ out(
2933
2915
  row(
2934
2916
  chalk3.cyan(`#${state.issue.number}`) + " " + chalk3.bold(truncate(state.issue.title, descMax - 6)),
2935
2917
  w
2936
2918
  )
2937
2919
  );
2938
2920
  const desc = extractDescription(state.issue.body, descMax);
2939
- if (desc) console.log(row(chalk3.dim(desc), w));
2921
+ if (desc) out(row(chalk3.dim(desc), w));
2940
2922
  const tags = [];
2941
2923
  if (state.workflowStatus !== "none")
2942
2924
  tags.push(workflowBadge(state.workflowStatus));
@@ -2944,12 +2926,12 @@ function renderDashboard(state, actions, hint, refreshing) {
2944
2926
  const l = state.issue.labels.find((x) => x.startsWith(prefix));
2945
2927
  if (l) tags.push(chalk3.dim(l));
2946
2928
  }
2947
- if (tags.length) console.log(row(tags.join(" "), w));
2929
+ if (tags.length) out(row(tags.join(" "), w));
2948
2930
  } else {
2949
- console.log(row(chalk3.dim("No linked issue"), w));
2931
+ out(row(chalk3.dim("No linked issue"), w));
2950
2932
  }
2951
2933
  section("Branch");
2952
- console.log(row(chalk3.magenta(state.branch), w));
2934
+ out(row(chalk3.magenta(state.branch), w));
2953
2935
  const bits = [];
2954
2936
  if (state.commits.length > 0)
2955
2937
  bits.push(chalk3.dim(`${state.commits.length} ahead`));
@@ -2958,12 +2940,12 @@ function renderDashboard(state, actions, hint, refreshing) {
2958
2940
  if (!state.hasUncommittedChanges && !state.hasUnpushedCommits && state.commits.length > 0) {
2959
2941
  bits.push(chalk3.green("\u25CF synced"));
2960
2942
  }
2961
- if (bits.length) console.log(row(bits.join(chalk3.dim(" \xB7 ")), w));
2943
+ if (bits.length) out(row(bits.join(chalk3.dim(" \xB7 ")), w));
2962
2944
  section("Pull Request");
2963
2945
  if (state.pr) {
2964
2946
  const titleText = state.pr.title ? " " + truncate(state.pr.title, descMax - 12) : "";
2965
- console.log(row(chalk3.cyan(`#${state.pr.number}`) + titleText, w));
2966
- console.log(
2947
+ out(row(chalk3.cyan(`#${state.pr.number}`) + titleText, w));
2948
+ out(
2967
2949
  row(
2968
2950
  prBadge(state.pr.state, state.pr.isDraft) + reviewBadge(state.pr.reviewDecision),
2969
2951
  w
@@ -2971,7 +2953,7 @@ function renderDashboard(state, actions, hint, refreshing) {
2971
2953
  );
2972
2954
  if (state.hasActionableFeedback) {
2973
2955
  const n = state.reviewFeedback.length;
2974
- console.log(
2956
+ out(
2975
2957
  row(
2976
2958
  chalk3.yellow(`${n} actionable comment${n !== 1 ? "s" : ""} pending`),
2977
2959
  w
@@ -2979,62 +2961,421 @@ function renderDashboard(state, actions, hint, refreshing) {
2979
2961
  );
2980
2962
  }
2981
2963
  if (state.hasUIChanges && state.isPlaywrightAvailable && state.config.video.enabled && state.pr.state === "open") {
2982
- console.log(
2964
+ out(
2983
2965
  row(
2984
2966
  chalk3.cyan("UI changes detected") + chalk3.dim(" \xB7 video capture available"),
2985
2967
  w
2986
2968
  )
2987
2969
  );
2988
2970
  }
2989
- console.log(row(chalk3.dim(state.pr.url), w));
2971
+ out(row(chalk3.dim(state.pr.url), w));
2990
2972
  } else {
2991
- console.log(row(chalk3.dim("No PR created"), w));
2973
+ out(row(chalk3.dim("No PR created"), w));
2992
2974
  }
2993
2975
  section("Commits");
2994
2976
  if (state.commits.length > 0) {
2995
2977
  const max = 6;
2996
2978
  for (const c of state.commits.slice(0, max)) {
2997
- console.log(row(c.substring(0, w - 5), w));
2979
+ out(row(c.substring(0, w - 5), w));
2998
2980
  }
2999
2981
  if (state.commits.length > max) {
3000
- console.log(
3001
- row(chalk3.dim(`\u2026 and ${state.commits.length - max} more`), w)
3002
- );
2982
+ out(row(chalk3.dim(`\u2026 and ${state.commits.length - max} more`), w));
3003
2983
  }
3004
2984
  } else {
3005
- console.log(row(chalk3.dim("No commits"), w));
2985
+ out(row(chalk3.dim("No commits"), w));
3006
2986
  }
3007
2987
  if (hint) {
3008
2988
  section("Hint");
3009
- console.log(row(chalk3.yellow(hint), w));
2989
+ out(row(chalk3.yellow(hint), w));
3010
2990
  }
3011
- console.log(divRow(w));
2991
+ out(divRow(w));
3012
2992
  if (refreshing) {
3013
- console.log(row(chalk3.yellow("Refreshing\u2026"), w));
2993
+ out(row(chalk3.yellow("Refreshing\u2026"), w));
3014
2994
  } else {
3015
2995
  for (const line of formatCommandBar(actions, w)) {
3016
- console.log(row(line, w));
2996
+ out(row(line, w));
3017
2997
  }
3018
2998
  }
3019
- console.log(botRow(w));
2999
+ out(botRow(w));
3000
+ return lines;
3001
+ }
3002
+ function renderDashboard(state, actions, hint, refreshing) {
3003
+ const lines = buildDashboardLines(state, actions, hint, refreshing);
3004
+ for (const line of lines) {
3005
+ console.log(line);
3006
+ }
3020
3007
  }
3021
3008
  function clearScreen() {
3022
3009
  process.stdout.write("\x1B[2J\x1B[0f");
3023
3010
  }
3024
3011
 
3025
- // src/commands/tui.ts
3026
- var CANCEL = /* @__PURE__ */ Symbol("cancel");
3027
- async function confirm(message) {
3028
- const { ok } = await inquirer7.prompt([
3029
- {
3030
- type: "confirm",
3031
- name: "ok",
3032
- message,
3033
- default: true
3012
+ // src/tui/modal.ts
3013
+ import chalk4 from "chalk";
3014
+ function modalTopRow(title, w) {
3015
+ const label = ` ${title} `;
3016
+ const fill = w - 2 - label.length;
3017
+ return chalk4.bold("\u250C") + chalk4.bold.cyan(label) + chalk4.bold("\u2500".repeat(Math.max(0, fill)) + "\u2510");
3018
+ }
3019
+ function modalDivRow(w) {
3020
+ return chalk4.bold("\u251C" + "\u2500".repeat(w - 2) + "\u2524");
3021
+ }
3022
+ function modalBotRow(w) {
3023
+ return chalk4.bold("\u2514" + "\u2500".repeat(w - 2) + "\u2518");
3024
+ }
3025
+ function truncateAnsi(text, max) {
3026
+ if (visibleLen(text) <= max) return text;
3027
+ let visible = 0;
3028
+ let i = 0;
3029
+ while (i < text.length && visible < max - 1) {
3030
+ if (text[i] === "\x1B") {
3031
+ const end = text.indexOf("m", i);
3032
+ if (end !== -1) {
3033
+ i = end + 1;
3034
+ continue;
3035
+ }
3034
3036
  }
3035
- ]);
3036
- return ok;
3037
+ visible++;
3038
+ i++;
3039
+ }
3040
+ return text.slice(0, i) + "\x1B[0m\u2026";
3041
+ }
3042
+ function modalRow(text, w) {
3043
+ const inner = w - 4;
3044
+ const fitted = truncateAnsi(text, inner);
3045
+ const pad = Math.max(0, inner - visibleLen(fitted));
3046
+ return chalk4.bold("\u2502") + " " + fitted + " ".repeat(pad) + " " + chalk4.bold("\u2502");
3047
+ }
3048
+ function modalEmptyRow(w) {
3049
+ return modalRow("", w);
3050
+ }
3051
+ function buildModalFrame(title, contentLines, footerText, width) {
3052
+ const lines = [];
3053
+ lines.push(modalTopRow(title, width));
3054
+ lines.push(modalEmptyRow(width));
3055
+ for (const line of contentLines) {
3056
+ lines.push(modalRow(line, width));
3057
+ }
3058
+ lines.push(modalEmptyRow(width));
3059
+ lines.push(modalDivRow(width));
3060
+ lines.push(modalRow(chalk4.dim(footerText), width));
3061
+ lines.push(modalBotRow(width));
3062
+ return lines;
3063
+ }
3064
+ function isSeparator(entry) {
3065
+ return "separator" in entry;
3066
+ }
3067
+ function buildSelectContent(items, selectedIndex, maxWidth) {
3068
+ const lines = [];
3069
+ let selectableIdx = 0;
3070
+ for (const item of items) {
3071
+ if (isSeparator(item)) {
3072
+ lines.push(chalk4.dim(item.separator));
3073
+ } else {
3074
+ const isSelected = selectableIdx === selectedIndex;
3075
+ const prefix = isSelected ? chalk4.cyan.bold("> ") : " ";
3076
+ const label = item.name.length > maxWidth - 2 ? item.name.slice(0, maxWidth - 3) + "\u2026" : item.name;
3077
+ lines.push(
3078
+ prefix + (isSelected ? chalk4.bold(label) : label)
3079
+ );
3080
+ selectableIdx++;
3081
+ }
3082
+ }
3083
+ return lines;
3084
+ }
3085
+ function buildConfirmContent(message, selectedYes) {
3086
+ const yes = selectedYes ? chalk4.cyan.bold("> Yes") : chalk4.dim(" Yes");
3087
+ const no = !selectedYes ? chalk4.cyan.bold("> No") : chalk4.dim(" No");
3088
+ return [message, "", yes, no];
3089
+ }
3090
+ function buildInputContent(label, value, cursorVisible) {
3091
+ const cursor = cursorVisible ? chalk4.cyan("_") : " ";
3092
+ return [label, "", chalk4.cyan("> ") + value + cursor];
3093
+ }
3094
+ function buildMultilineInputContent(label, value, cursorVisible, maxWidth) {
3095
+ const cursor = cursorVisible ? chalk4.cyan("_") : " ";
3096
+ const lines = [label, ""];
3097
+ if (value === "") {
3098
+ lines.push(chalk4.cyan(" ") + cursor);
3099
+ } else {
3100
+ const inputLines = value.split("\n");
3101
+ const contentWidth = maxWidth - 2;
3102
+ for (let i = 0; i < inputLines.length; i++) {
3103
+ const raw = inputLines[i];
3104
+ const wrapped = wrapLine(raw, contentWidth);
3105
+ for (let j = 0; j < wrapped.length; j++) {
3106
+ const isLastLine = i === inputLines.length - 1 && j === wrapped.length - 1;
3107
+ const text = wrapped[j] + (isLastLine ? cursor : "");
3108
+ lines.push(chalk4.cyan(" ") + text);
3109
+ }
3110
+ }
3111
+ }
3112
+ return lines;
3037
3113
  }
3114
+ function wrapLine(text, width) {
3115
+ if (width <= 0) return [text];
3116
+ if (text.length <= width) return [text];
3117
+ const result = [];
3118
+ let remaining = text;
3119
+ while (remaining.length > width) {
3120
+ let breakAt = remaining.lastIndexOf(" ", width);
3121
+ if (breakAt <= 0) breakAt = width;
3122
+ result.push(remaining.slice(0, breakAt));
3123
+ remaining = remaining.slice(breakAt).replace(/^ /, "");
3124
+ }
3125
+ if (remaining.length > 0 || result.length === 0) {
3126
+ result.push(remaining);
3127
+ }
3128
+ return result;
3129
+ }
3130
+ function termSize() {
3131
+ return {
3132
+ cols: process.stdout.columns || 80,
3133
+ rows: process.stdout.rows || 24
3134
+ };
3135
+ }
3136
+ function moveTo(row2, col) {
3137
+ return `\x1B[${row2};${col}H`;
3138
+ }
3139
+ function hideCursor() {
3140
+ return "\x1B[?25l";
3141
+ }
3142
+ function showCursor() {
3143
+ return "\x1B[?25h";
3144
+ }
3145
+ function modalWidth() {
3146
+ const cols = process.stdout.columns || 80;
3147
+ return Math.min(60, cols - 4);
3148
+ }
3149
+ function renderOverlay(dashboardLines, modalLines, mWidth) {
3150
+ const { cols, rows } = termSize();
3151
+ process.stdout.write("\x1B[2J\x1B[0f");
3152
+ process.stdout.write(hideCursor());
3153
+ for (let i = 0; i < dashboardLines.length && i < rows; i++) {
3154
+ process.stdout.write(
3155
+ moveTo(i + 1, 1) + chalk4.dim(stripAnsi(dashboardLines[i]))
3156
+ );
3157
+ }
3158
+ const startRow = Math.max(1, Math.floor((rows - modalLines.length) / 2));
3159
+ const startCol = Math.max(1, Math.floor((cols - mWidth) / 2));
3160
+ for (let i = 0; i < modalLines.length; i++) {
3161
+ process.stdout.write(moveTo(startRow + i, startCol) + modalLines[i]);
3162
+ }
3163
+ process.stdout.write(moveTo(startRow + modalLines.length + 1, 1));
3164
+ }
3165
+ function readKey() {
3166
+ return new Promise((resolve) => {
3167
+ const { stdin } = process;
3168
+ const wasRaw = stdin.isRaw;
3169
+ stdin.setRawMode(true);
3170
+ stdin.resume();
3171
+ stdin.setEncoding("utf8");
3172
+ const onData = (data) => {
3173
+ stdin.setRawMode(wasRaw ?? false);
3174
+ stdin.pause();
3175
+ stdin.removeListener("data", onData);
3176
+ if (data === "") {
3177
+ resolve({ name: "escape", raw: data });
3178
+ } else if (data === "\x1B" || data === "\x1B\x1B") {
3179
+ resolve({ name: "escape", raw: data });
3180
+ } else if (data === "\x1B[A") {
3181
+ resolve({ name: "up", raw: data });
3182
+ } else if (data === "\x1B[B") {
3183
+ resolve({ name: "down", raw: data });
3184
+ } else if (data === "\x1B[C") {
3185
+ resolve({ name: "right", raw: data });
3186
+ } else if (data === "\x1B[D") {
3187
+ resolve({ name: "left", raw: data });
3188
+ } else if (data === "\r" || data === "\n") {
3189
+ resolve({ name: "enter", raw: data });
3190
+ } else if (data === "\x7F" || data === "\b") {
3191
+ resolve({ name: "backspace", raw: data });
3192
+ } else if (data === " ") {
3193
+ resolve({ name: "tab", raw: data });
3194
+ } else if (data === "") {
3195
+ resolve({ name: "ctrl-s", raw: data });
3196
+ } else if (data.length === 1 && data.charCodeAt(0) >= 32) {
3197
+ resolve({ name: data, raw: data });
3198
+ } else {
3199
+ stdin.setRawMode(true);
3200
+ stdin.resume();
3201
+ stdin.on("data", onData);
3202
+ }
3203
+ };
3204
+ stdin.on("data", onData);
3205
+ });
3206
+ }
3207
+ async function showConfirm(opts) {
3208
+ const w = modalWidth();
3209
+ let selectedYes = true;
3210
+ const render = () => {
3211
+ const content = buildConfirmContent(opts.message, selectedYes);
3212
+ const footer = "\u2191\u2193 Select Enter Confirm Esc Cancel";
3213
+ const lines = buildModalFrame(opts.title, content, footer, w);
3214
+ renderOverlay(opts.dashboardLines, lines, w);
3215
+ };
3216
+ render();
3217
+ while (true) {
3218
+ const key = await readKey();
3219
+ switch (key.name) {
3220
+ case "up":
3221
+ case "down":
3222
+ case "tab":
3223
+ selectedYes = !selectedYes;
3224
+ render();
3225
+ break;
3226
+ case "enter":
3227
+ process.stdout.write(showCursor());
3228
+ return selectedYes;
3229
+ case "escape":
3230
+ process.stdout.write(showCursor());
3231
+ return false;
3232
+ case "y":
3233
+ process.stdout.write(showCursor());
3234
+ return true;
3235
+ case "n":
3236
+ process.stdout.write(showCursor());
3237
+ return false;
3238
+ }
3239
+ }
3240
+ }
3241
+ function selectableCount(items) {
3242
+ return items.filter((i) => !isSeparator(i)).length;
3243
+ }
3244
+ async function showSelect(opts) {
3245
+ const w = modalWidth();
3246
+ const maxItems = selectableCount(opts.items);
3247
+ if (maxItems === 0) return null;
3248
+ let selectedIndex = 0;
3249
+ const render = () => {
3250
+ const content = buildSelectContent(opts.items, selectedIndex, w - 6);
3251
+ const footer = "\u2191\u2193 Navigate Enter Select Esc Cancel";
3252
+ const lines = buildModalFrame(opts.title, content, footer, w);
3253
+ renderOverlay(opts.dashboardLines, lines, w);
3254
+ };
3255
+ render();
3256
+ while (true) {
3257
+ const key = await readKey();
3258
+ switch (key.name) {
3259
+ case "up":
3260
+ selectedIndex = (selectedIndex - 1 + maxItems) % maxItems;
3261
+ render();
3262
+ break;
3263
+ case "down":
3264
+ selectedIndex = (selectedIndex + 1) % maxItems;
3265
+ render();
3266
+ break;
3267
+ case "enter": {
3268
+ process.stdout.write(showCursor());
3269
+ let idx = 0;
3270
+ for (const item of opts.items) {
3271
+ if (!isSeparator(item)) {
3272
+ if (idx === selectedIndex) return item.value;
3273
+ idx++;
3274
+ }
3275
+ }
3276
+ return null;
3277
+ }
3278
+ case "escape":
3279
+ process.stdout.write(showCursor());
3280
+ return null;
3281
+ }
3282
+ }
3283
+ }
3284
+ async function showInput(opts) {
3285
+ const w = modalWidth();
3286
+ let value = "";
3287
+ let cursorBlink = true;
3288
+ const render = () => {
3289
+ const maxLen = w - 10;
3290
+ const displayValue = value.length > maxLen ? value.slice(value.length - maxLen) : value;
3291
+ const content = buildInputContent(opts.label, displayValue, cursorBlink);
3292
+ const footer = "Enter Submit Esc Cancel";
3293
+ const lines = buildModalFrame(opts.title, content, footer, w);
3294
+ renderOverlay(opts.dashboardLines, lines, w);
3295
+ };
3296
+ render();
3297
+ while (true) {
3298
+ const key = await readKey();
3299
+ switch (key.name) {
3300
+ case "enter":
3301
+ process.stdout.write(showCursor());
3302
+ return value.trim() || null;
3303
+ case "escape":
3304
+ process.stdout.write(showCursor());
3305
+ return null;
3306
+ case "backspace":
3307
+ if (value.length > 0) {
3308
+ value = value.slice(0, -1);
3309
+ }
3310
+ render();
3311
+ break;
3312
+ default:
3313
+ if (key.raw.length === 1 && key.raw.charCodeAt(0) >= 32) {
3314
+ value += key.raw;
3315
+ cursorBlink = true;
3316
+ render();
3317
+ }
3318
+ break;
3319
+ }
3320
+ }
3321
+ }
3322
+ async function showMultilineInput(opts) {
3323
+ const w = modalWidth();
3324
+ let value = "";
3325
+ let cursorBlink = true;
3326
+ const contentWidth = w - 6;
3327
+ const render = () => {
3328
+ const content = buildMultilineInputContent(
3329
+ opts.label,
3330
+ value,
3331
+ cursorBlink,
3332
+ contentWidth
3333
+ );
3334
+ const footer = "Enter Newline Ctrl+S Submit Esc Cancel";
3335
+ const lines = buildModalFrame(opts.title, content, footer, w);
3336
+ renderOverlay(opts.dashboardLines, lines, w);
3337
+ };
3338
+ render();
3339
+ while (true) {
3340
+ const key = await readKey();
3341
+ switch (key.name) {
3342
+ case "ctrl-s":
3343
+ process.stdout.write(showCursor());
3344
+ return value.trim() || null;
3345
+ case "enter":
3346
+ value += "\n";
3347
+ cursorBlink = true;
3348
+ render();
3349
+ break;
3350
+ case "escape":
3351
+ process.stdout.write(showCursor());
3352
+ return null;
3353
+ case "backspace":
3354
+ if (value.length > 0) {
3355
+ value = value.slice(0, -1);
3356
+ }
3357
+ render();
3358
+ break;
3359
+ default:
3360
+ if (key.raw.length === 1 && key.raw.charCodeAt(0) >= 32) {
3361
+ value += key.raw;
3362
+ cursorBlink = true;
3363
+ render();
3364
+ }
3365
+ break;
3366
+ }
3367
+ }
3368
+ }
3369
+ function showStatus(title, message, dashboardLines) {
3370
+ const w = modalWidth();
3371
+ const content = [message];
3372
+ const footer = "";
3373
+ const lines = buildModalFrame(title, content, footer, w);
3374
+ renderOverlay(dashboardLines, lines, w);
3375
+ }
3376
+
3377
+ // src/commands/tui.ts
3378
+ var CANCEL = /* @__PURE__ */ Symbol("cancel");
3038
3379
  async function waitForKey(validKeys) {
3039
3380
  return new Promise((resolve) => {
3040
3381
  const { stdin } = process;
@@ -3060,99 +3401,66 @@ async function waitForKey(validKeys) {
3060
3401
  stdin.on("data", onData);
3061
3402
  });
3062
3403
  }
3063
- async function executeAction(actionId, state) {
3404
+ var CONTINUE = { running: true, refresh: true };
3405
+ var SKIP_REFRESH = { running: true, refresh: false };
3406
+ var QUIT = { running: false, refresh: false };
3407
+ async function executeAction(actionId, state, dashboardLines) {
3064
3408
  switch (actionId) {
3065
3409
  case "quit":
3066
- return false;
3410
+ return QUIT;
3067
3411
  case "list": {
3068
- clearScreen();
3069
- try {
3070
- await listCommand({});
3071
- } catch (error) {
3072
- logger.error(`List failed: ${error}`);
3073
- }
3074
- await promptContinue();
3075
- return true;
3412
+ const switched = await handleList(dashboardLines);
3413
+ return switched ? CONTINUE : SKIP_REFRESH;
3076
3414
  }
3077
3415
  case "create": {
3416
+ const description = await showMultilineInput({
3417
+ title: "New Ticket",
3418
+ label: "Describe the ticket:",
3419
+ dashboardLines
3420
+ });
3421
+ if (!description) return SKIP_REFRESH;
3078
3422
  clearScreen();
3079
- const { description } = await inquirer7.prompt([
3080
- {
3081
- type: "input",
3082
- name: "description",
3083
- message: "Describe the ticket (empty to cancel):"
3084
- }
3085
- ]);
3086
- if (!description.trim()) {
3087
- logger.info("Cancelled");
3088
- return true;
3089
- }
3090
3423
  try {
3091
3424
  await createCommand(description, {});
3092
3425
  } catch (error) {
3093
3426
  logger.error(`Create failed: ${error}`);
3094
3427
  }
3095
- await promptContinue();
3096
- return true;
3428
+ return CONTINUE;
3429
+ }
3430
+ case "commit": {
3431
+ const committed = await handleCommit(state, dashboardLines);
3432
+ return committed ? CONTINUE : SKIP_REFRESH;
3097
3433
  }
3098
- case "commit":
3099
- clearScreen();
3100
- await handleCommit(state);
3101
- await promptContinue();
3102
- return true;
3103
3434
  case "push":
3104
- clearScreen();
3105
- await handlePush();
3106
- await promptContinue();
3107
- return true;
3435
+ await handlePush(dashboardLines);
3436
+ return CONTINUE;
3108
3437
  case "pr": {
3109
3438
  clearScreen();
3110
- if (!await confirm("Create a pull request?")) return true;
3111
3439
  await prCommand({});
3112
- await promptContinue();
3113
- return true;
3440
+ return CONTINUE;
3114
3441
  }
3115
3442
  case "run": {
3116
- clearScreen();
3117
- const hasCommits = state.commits.length > 0;
3118
- const hasFeedback = state.hasActionableFeedback;
3119
- let msg;
3120
- if (hasFeedback && hasCommits) {
3121
- msg = "Start AI agent to address review feedback?";
3122
- } else if (hasCommits) {
3123
- msg = "Start AI agent to continue implementation from existing commits?";
3124
- } else {
3125
- msg = "Start AI agent to implement this ticket from scratch?";
3126
- }
3127
- if (!await confirm(msg)) return true;
3128
3443
  await handleRun(state);
3129
- await promptContinue();
3130
- return true;
3444
+ return CONTINUE;
3131
3445
  }
3132
3446
  case "switch-provider":
3133
- clearScreen();
3134
- await handleSwitchProvider(state);
3135
- return true;
3136
- case "checkout-main": {
3137
- clearScreen();
3138
- if (!await confirm("Switch to main branch?")) return true;
3139
- await handleCheckoutMain();
3140
- return true;
3141
- }
3447
+ await handleSwitchProvider(state, dashboardLines);
3448
+ return SKIP_REFRESH;
3449
+ case "checkout-main":
3450
+ await handleCheckoutMain(dashboardLines);
3451
+ return CONTINUE;
3142
3452
  default:
3143
- return true;
3453
+ return SKIP_REFRESH;
3144
3454
  }
3145
3455
  }
3146
- async function handleCommit(state) {
3456
+ async function handleCommit(state, dashboardLines) {
3147
3457
  try {
3148
3458
  const { stdout: status } = await execa4("git", ["status", "--short"]);
3149
3459
  if (!status.trim()) {
3150
- logger.info("No changes to commit");
3151
- return;
3460
+ showStatus("Commit", "No changes to commit", dashboardLines);
3461
+ await new Promise((r) => setTimeout(r, 1500));
3462
+ return false;
3152
3463
  }
3153
- logger.info("Changes:");
3154
- console.log(status);
3155
- console.log();
3156
3464
  await execa4("git", ["add", "-A"]);
3157
3465
  const { stdout: diffStat } = await execa4("git", [
3158
3466
  "diff",
@@ -3165,69 +3473,69 @@ async function handleCommit(state) {
3165
3473
  const issueTitle = state.issue?.title ?? null;
3166
3474
  const provider = state.config.ai.provider;
3167
3475
  const providerName = getProviderDisplayName(provider);
3168
- const { mode } = await inquirer7.prompt([
3169
- {
3170
- type: "list",
3171
- name: "mode",
3172
- message: "How would you like to provide the commit message?",
3173
- choices: [
3174
- { name: `Generate with ${providerName}`, value: "ai" },
3175
- { name: "Enter manually", value: "manual" }
3176
- ]
3177
- }
3178
- ]);
3476
+ const mode = await showSelect({
3477
+ title: "Commit",
3478
+ items: [
3479
+ { name: `Generate with ${providerName}`, value: "ai" },
3480
+ { name: "Enter manually", value: "manual" }
3481
+ ],
3482
+ dashboardLines
3483
+ });
3484
+ if (!mode) {
3485
+ await execa4("git", ["reset", "HEAD"]);
3486
+ return false;
3487
+ }
3179
3488
  let message;
3180
3489
  if (mode === "manual") {
3181
- const { manualInput } = await inquirer7.prompt([
3182
- {
3183
- type: "input",
3184
- name: "manualInput",
3185
- message: "Commit message (empty to cancel):"
3186
- }
3187
- ]);
3188
- message = manualInput.trim() || CANCEL;
3490
+ const input = await showInput({
3491
+ title: "Commit Message",
3492
+ label: "Enter commit message:",
3493
+ dashboardLines
3494
+ });
3495
+ message = input || CANCEL;
3189
3496
  } else {
3190
- logger.info(`Generating commit message with ${providerName}...`);
3497
+ showStatus("Generating", `Generating commit message with ${providerName}...`, dashboardLines);
3191
3498
  message = await generateCommitMessage(
3192
3499
  diffContent,
3193
3500
  issueNumber,
3194
3501
  issueTitle,
3195
- state
3502
+ state,
3503
+ dashboardLines
3196
3504
  );
3197
3505
  }
3198
3506
  if (message === CANCEL) {
3199
3507
  await execa4("git", ["reset", "HEAD"]);
3200
- logger.info("Cancelled");
3201
- return;
3508
+ return false;
3202
3509
  }
3203
- console.log();
3204
- logger.info(`Message: ${message}`);
3205
- console.log();
3206
- if (!await confirm("Commit with this message?")) {
3510
+ const confirmed = await showConfirm({
3511
+ title: "Commit",
3512
+ message: `Message: ${message.length > 50 ? message.slice(0, 50) + "\u2026" : message}`,
3513
+ dashboardLines
3514
+ });
3515
+ if (!confirmed) {
3207
3516
  await execa4("git", ["reset", "HEAD"]);
3208
- logger.info("Commit cancelled");
3209
- return;
3517
+ return false;
3210
3518
  }
3211
3519
  const providerEmail = getProviderEmail(provider);
3212
3520
  const fullMessage = `${message}
3213
3521
 
3214
3522
  Co-Authored-By: ${providerName} <${providerEmail}>`;
3215
- const spinner = createSpinner("Committing...");
3216
- spinner.start();
3523
+ showStatus("Committing", "Committing changes...", dashboardLines);
3217
3524
  await execa4("git", ["commit", "-m", fullMessage]);
3218
- spinner.succeed("Changes committed");
3525
+ return true;
3219
3526
  } catch (error) {
3220
3527
  logger.error(`Commit failed: ${error}`);
3528
+ return false;
3221
3529
  }
3222
3530
  }
3223
- async function generateCommitMessage(diffContent, issueNumber, issueTitle, state) {
3531
+ async function generateCommitMessage(diffContent, issueNumber, issueTitle, state, dashboardLines) {
3224
3532
  try {
3225
3533
  const prompt = buildCommitMessagePrompt(
3226
3534
  diffContent,
3227
3535
  issueNumber,
3228
3536
  issueTitle
3229
3537
  );
3230
- const result = await invokeAI({ prompt, streamOutput: true }, state.config);
3538
+ const result = await invokeAI({ prompt, streamOutput: false }, state.config);
3231
3539
  let message = result.output.trim().split("\n")[0].trim();
3232
3540
  for (const q of ['"', "'", "`"]) {
3233
3541
  if (message.startsWith(q) && message.endsWith(q)) {
@@ -3238,16 +3546,12 @@ async function generateCommitMessage(diffContent, issueNumber, issueTitle, state
3238
3546
  message = message.replace(/^```\w*\s*/, "").replace(/\s*```$/, "");
3239
3547
  return message;
3240
3548
  } catch {
3241
- logger.warning("AI commit message generation failed");
3242
- console.log();
3243
- const { message } = await inquirer7.prompt([
3244
- {
3245
- type: "input",
3246
- name: "message",
3247
- message: "Commit message (empty to cancel):"
3248
- }
3249
- ]);
3250
- return message.trim() || CANCEL;
3549
+ const input = await showInput({
3550
+ title: "Commit Message",
3551
+ label: "AI generation failed. Enter commit message:",
3552
+ dashboardLines
3553
+ });
3554
+ return input || CANCEL;
3251
3555
  }
3252
3556
  }
3253
3557
  async function handleRun(state) {
@@ -3295,61 +3599,190 @@ ${feedbackLines}`);
3295
3599
  logger.error(`${providerName} session failed: ${error}`);
3296
3600
  }
3297
3601
  }
3298
- async function handlePush() {
3602
+ async function handlePush(dashboardLines) {
3299
3603
  try {
3300
3604
  const { stdout: branch } = await execa4("git", ["branch", "--show-current"]);
3301
- if (!await confirm(`Push ${branch.trim()} to remote?`)) return;
3302
- const spinner = createSpinner("Pushing...");
3303
- spinner.start();
3304
- await execa4("git", ["push", "-u", "origin", branch.trim()]);
3305
- spinner.succeed("Pushed to remote");
3605
+ const branchName = branch.trim();
3606
+ showStatus("Pushing", `Pushing ${branchName} to remote...`, dashboardLines);
3607
+ await execa4("git", ["push", "-u", "origin", branchName]);
3306
3608
  } catch (error) {
3307
3609
  logger.error(`Push failed: ${error}`);
3308
3610
  }
3309
3611
  }
3310
3612
  var PROVIDERS = ["claude", "gemini", "codex"];
3311
- async function handleSwitchProvider(state) {
3613
+ async function handleSwitchProvider(state, dashboardLines) {
3312
3614
  const current = state.config.ai.provider;
3313
- const { provider } = await inquirer7.prompt([
3314
- {
3315
- type: "list",
3316
- name: "provider",
3317
- message: "Select AI provider:",
3318
- choices: PROVIDERS.map((p) => ({
3319
- name: p.charAt(0).toUpperCase() + p.slice(1) + (p === current ? " (current)" : ""),
3320
- value: p
3321
- })),
3322
- default: current
3323
- }
3324
- ]);
3325
- if (provider === current) return;
3615
+ const items = PROVIDERS.map((p) => ({
3616
+ name: p.charAt(0).toUpperCase() + p.slice(1) + (p === current ? " (current)" : ""),
3617
+ value: p
3618
+ }));
3619
+ const provider = await showSelect({
3620
+ title: "AI Provider",
3621
+ items,
3622
+ dashboardLines
3623
+ });
3624
+ if (!provider || provider === current) return;
3326
3625
  setRuntimeProvider(provider);
3327
- logger.success(`Provider switched to ${provider} (session only)`);
3626
+ state.config.ai.provider = provider;
3328
3627
  }
3329
- async function handleCheckoutMain() {
3628
+ async function handleCheckoutMain(dashboardLines) {
3330
3629
  try {
3331
- const spinner = createSpinner("Switching to main...");
3332
- spinner.start();
3630
+ showStatus("Switching", "Switching to main...", dashboardLines);
3333
3631
  await execa4("git", ["checkout", "main"]);
3334
3632
  await execa4("git", ["pull"]);
3335
- spinner.succeed("Switched to main");
3336
3633
  } catch (error) {
3337
3634
  logger.error(`Checkout failed: ${error}`);
3338
3635
  }
3339
3636
  }
3340
- async function promptContinue() {
3341
- console.log();
3342
- await inquirer7.prompt([
3343
- {
3344
- type: "input",
3345
- name: "continue",
3346
- message: "Press Enter to continue..."
3637
+ async function handleList(dashboardLines) {
3638
+ try {
3639
+ showStatus("Loading", "Fetching tickets...", dashboardLines);
3640
+ const config = loadConfig();
3641
+ const workflowLabels = getWorkflowLabels(config);
3642
+ const [inProgress, ready, prs, localBranches] = await Promise.all([
3643
+ listIssues({
3644
+ labels: [workflowLabels.inProgress],
3645
+ state: "open",
3646
+ limit: 20
3647
+ }),
3648
+ listIssues({
3649
+ labels: [workflowLabels.ready],
3650
+ state: "open",
3651
+ limit: 20
3652
+ }),
3653
+ listOpenPrs(30),
3654
+ listLocalBranches()
3655
+ ]);
3656
+ sortByPriority(inProgress);
3657
+ sortByPriority(ready);
3658
+ const choices = buildTicketChoices(inProgress, ready, prs, localBranches);
3659
+ const currentBranch = await getCurrentBranch();
3660
+ const defaultBranch = await getDefaultBranch();
3661
+ const items = [];
3662
+ const mainLabel = defaultBranch + (currentBranch === defaultBranch ? " (current)" : "");
3663
+ items.push({ name: mainLabel, value: "__main__" });
3664
+ const inProgressChoices = choices.filter(
3665
+ (c) => c.category === "in-progress"
3666
+ );
3667
+ const openPrChoices = choices.filter((c) => c.category === "open-pr");
3668
+ const readyChoices = choices.filter((c) => c.category === "ready");
3669
+ if (inProgressChoices.length > 0) {
3670
+ items.push({ separator: "\u2500\u2500 In Progress \u2500\u2500" });
3671
+ for (const c of inProgressChoices) {
3672
+ items.push({
3673
+ name: `#${c.issueNumber} ${c.title}`,
3674
+ value: String(c.issueNumber)
3675
+ });
3676
+ }
3347
3677
  }
3348
- ]);
3678
+ if (openPrChoices.length > 0) {
3679
+ items.push({ separator: "\u2500\u2500 Open PRs \u2500\u2500" });
3680
+ for (const c of openPrChoices) {
3681
+ items.push({
3682
+ name: `#${c.issueNumber} ${c.title}`,
3683
+ value: String(c.issueNumber)
3684
+ });
3685
+ }
3686
+ }
3687
+ if (readyChoices.length > 0) {
3688
+ items.push({ separator: "\u2500\u2500 Ready \u2500\u2500" });
3689
+ for (const c of readyChoices) {
3690
+ items.push({
3691
+ name: `#${c.issueNumber} ${c.title}`,
3692
+ value: String(c.issueNumber)
3693
+ });
3694
+ }
3695
+ }
3696
+ if (choices.length === 0) {
3697
+ showStatus("List", "No tickets found", dashboardLines);
3698
+ await new Promise((r) => setTimeout(r, 1500));
3699
+ return false;
3700
+ }
3701
+ const selected = await showSelect({
3702
+ title: "Switch Ticket",
3703
+ items,
3704
+ dashboardLines
3705
+ });
3706
+ if (!selected) return false;
3707
+ if (selected === "__main__") {
3708
+ if (currentBranch === defaultBranch) return false;
3709
+ const dirty2 = await hasUncommittedChanges();
3710
+ if (dirty2) {
3711
+ const ok = await showConfirm({
3712
+ title: "Uncommitted Changes",
3713
+ message: "You have uncommitted changes. Continue?",
3714
+ dashboardLines
3715
+ });
3716
+ if (!ok) return false;
3717
+ }
3718
+ showStatus("Switching", `Switching to ${defaultBranch}...`, dashboardLines);
3719
+ await checkoutBranch(defaultBranch);
3720
+ return true;
3721
+ }
3722
+ const issueNumber = parseInt(selected, 10);
3723
+ const ticket = choices.find((c) => c.issueNumber === issueNumber);
3724
+ if (!ticket) return false;
3725
+ const dirty = await hasUncommittedChanges();
3726
+ if (dirty) {
3727
+ const ok = await showConfirm({
3728
+ title: "Uncommitted Changes",
3729
+ message: "You have uncommitted changes. Continue?",
3730
+ dashboardLines
3731
+ });
3732
+ if (!ok) return false;
3733
+ }
3734
+ const targetBranch = ticket.branch;
3735
+ if (targetBranch) {
3736
+ if (await branchExists(targetBranch)) {
3737
+ showStatus("Switching", `Switching to ${targetBranch}...`, dashboardLines);
3738
+ await checkoutBranch(targetBranch);
3739
+ return true;
3740
+ } else if (await remoteBranchExists(targetBranch)) {
3741
+ showStatus("Fetching", `Fetching ${targetBranch}...`, dashboardLines);
3742
+ await fetchAndCheckout(targetBranch);
3743
+ return true;
3744
+ } else {
3745
+ return await offerCreateBranch2(
3746
+ issueNumber,
3747
+ ticket.title,
3748
+ dashboardLines
3749
+ );
3750
+ }
3751
+ } else {
3752
+ return await offerCreateBranch2(
3753
+ issueNumber,
3754
+ ticket.title,
3755
+ dashboardLines
3756
+ );
3757
+ }
3758
+ } catch (error) {
3759
+ logger.error(`List failed: ${error}`);
3760
+ return false;
3761
+ }
3762
+ }
3763
+ async function offerCreateBranch2(issueNumber, title, dashboardLines) {
3764
+ const config = loadConfig();
3765
+ const branchName = await generateBranchName(
3766
+ config,
3767
+ issueNumber,
3768
+ title,
3769
+ "feature"
3770
+ );
3771
+ const create = await showConfirm({
3772
+ title: "Create Branch",
3773
+ message: `No branch found. Create ${branchName}?`,
3774
+ dashboardLines
3775
+ });
3776
+ if (!create) return false;
3777
+ const defaultBranch = await getDefaultBranch();
3778
+ showStatus("Creating", `Creating ${branchName}...`, dashboardLines);
3779
+ await createBranch(branchName, defaultBranch);
3780
+ return true;
3349
3781
  }
3350
3782
  async function tuiCommand() {
3351
3783
  let running = true;
3352
3784
  let lastActions = [];
3785
+ let lastDashboardLines = [];
3353
3786
  const config = loadConfig();
3354
3787
  let lastState = {
3355
3788
  isGitRepo: true,
@@ -3373,28 +3806,36 @@ async function tuiCommand() {
3373
3806
  hasUIChanges: false,
3374
3807
  isPlaywrightAvailable: false
3375
3808
  };
3809
+ let needsRefresh = true;
3376
3810
  while (running) {
3377
- clearScreen();
3378
- renderDashboard(lastState, lastActions, void 0, true);
3379
- const state = await aggregateState();
3380
- const actions = getAvailableActions(state);
3381
- lastState = state;
3382
- lastActions = actions;
3383
- clearScreen();
3811
+ if (needsRefresh) {
3812
+ clearScreen();
3813
+ renderDashboard(lastState, lastActions, void 0, true);
3814
+ const state = await aggregateState();
3815
+ const actions = getAvailableActions(state);
3816
+ lastState = state;
3817
+ lastActions = actions;
3818
+ }
3384
3819
  let hint;
3385
- if (state.isOnMain) {
3820
+ if (lastState.isOnMain) {
3386
3821
  hint = "Select an action to get started";
3387
- } else if (state.hasUncommittedChanges && !state.pr) {
3822
+ } else if (lastState.hasUncommittedChanges && !lastState.pr) {
3388
3823
  hint = "Commit your changes before creating a PR";
3389
- } else if (state.hasActionableFeedback) {
3824
+ } else if (lastState.hasActionableFeedback) {
3390
3825
  hint = "Review feedback needs attention";
3391
3826
  }
3392
- renderDashboard(state, actions, hint);
3393
- const validKeys = actions.map((a) => a.shortcut);
3827
+ lastDashboardLines = buildDashboardLines(lastState, lastActions, hint);
3828
+ clearScreen();
3829
+ for (const line of lastDashboardLines) {
3830
+ console.log(line);
3831
+ }
3832
+ const validKeys = lastActions.map((a) => a.shortcut);
3394
3833
  const key = await waitForKey(validKeys);
3395
- const action = actions.find((a) => a.shortcut === key);
3834
+ const action = lastActions.find((a) => a.shortcut === key);
3396
3835
  if (action) {
3397
- running = await executeAction(action.id, state);
3836
+ const result = await executeAction(action.id, lastState, lastDashboardLines);
3837
+ running = result.running;
3838
+ needsRefresh = result.refresh;
3398
3839
  }
3399
3840
  }
3400
3841
  }