@rotorsoft/gent 1.14.0 → 1.14.1
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/{chunk-SKWCS6Z2.js → chunk-JHIW57FY.js} +1 -2
- package/dist/index.js +602 -246
- package/dist/index.js.map +1 -1
- package/dist/{setup-labels-C6YHJ637.js → setup-labels-LB7X6A3K.js} +2 -2
- package/package.json +1 -1
- /package/dist/{chunk-SKWCS6Z2.js.map → chunk-JHIW57FY.js.map} +0 -0
- /package/dist/{setup-labels-C6YHJ637.js.map → setup-labels-LB7X6A3K.js.map} +0 -0
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-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
1621
|
+
return;
|
|
1626
1622
|
}
|
|
1627
1623
|
if (await isOnMainBranch()) {
|
|
1628
1624
|
logger.error("Cannot create PR from main/master branch.");
|
|
1629
|
-
|
|
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
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
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
|
|
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
|
|
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.
|
|
2153
|
+
version: "1.14.1",
|
|
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
|
|
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
|
-
|
|
2876
|
-
|
|
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
|
|
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
|
-
|
|
2886
|
-
|
|
2869
|
+
out(topRow(titleLabel, w));
|
|
2870
|
+
renderSettingsTo(state, w, out);
|
|
2887
2871
|
if (!state.isGitRepo) {
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
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
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2891
|
+
out(row(chalk3.yellow("\u25CF uncommitted changes"), w));
|
|
2910
2892
|
}
|
|
2911
2893
|
if (hint) {
|
|
2912
|
-
|
|
2913
|
-
|
|
2894
|
+
out(midRow("Hint", w));
|
|
2895
|
+
out(row(chalk3.yellow(hint), w));
|
|
2914
2896
|
}
|
|
2915
|
-
|
|
2897
|
+
out(divRow(w));
|
|
2916
2898
|
if (refreshing) {
|
|
2917
|
-
|
|
2899
|
+
out(row(chalk3.yellow("Refreshing\u2026"), w));
|
|
2918
2900
|
} else {
|
|
2919
2901
|
for (const line of formatCommandBar(actions, w)) {
|
|
2920
|
-
|
|
2902
|
+
out(row(line, w));
|
|
2921
2903
|
}
|
|
2922
2904
|
}
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
return;
|
|
2905
|
+
out(botRow(w));
|
|
2906
|
+
out("");
|
|
2907
|
+
return lines;
|
|
2926
2908
|
}
|
|
2927
2909
|
const section = (title) => {
|
|
2928
|
-
|
|
2910
|
+
out(midRow(title, w));
|
|
2929
2911
|
};
|
|
2930
2912
|
section("Ticket");
|
|
2931
2913
|
if (state.issue) {
|
|
2932
|
-
|
|
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)
|
|
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)
|
|
2929
|
+
if (tags.length) out(row(tags.join(" "), w));
|
|
2948
2930
|
} else {
|
|
2949
|
-
|
|
2931
|
+
out(row(chalk3.dim("No linked issue"), w));
|
|
2950
2932
|
}
|
|
2951
2933
|
section("Branch");
|
|
2952
|
-
|
|
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)
|
|
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
|
-
|
|
2966
|
-
|
|
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
|
-
|
|
2956
|
+
out(
|
|
2975
2957
|
row(
|
|
2976
2958
|
chalk3.yellow(`${n} actionable comment${n !== 1 ? "s" : ""} pending`),
|
|
2977
2959
|
w
|
|
@@ -2979,62 +2961,336 @@ 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
|
-
|
|
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
|
-
|
|
2971
|
+
out(row(chalk3.dim(state.pr.url), w));
|
|
2990
2972
|
} else {
|
|
2991
|
-
|
|
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
|
-
|
|
2979
|
+
out(row(c.substring(0, w - 5), w));
|
|
2998
2980
|
}
|
|
2999
2981
|
if (state.commits.length > max) {
|
|
3000
|
-
|
|
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
|
-
|
|
2985
|
+
out(row(chalk3.dim("No commits"), w));
|
|
3006
2986
|
}
|
|
3007
2987
|
if (hint) {
|
|
3008
2988
|
section("Hint");
|
|
3009
|
-
|
|
2989
|
+
out(row(chalk3.yellow(hint), w));
|
|
3010
2990
|
}
|
|
3011
|
-
|
|
2991
|
+
out(divRow(w));
|
|
3012
2992
|
if (refreshing) {
|
|
3013
|
-
|
|
2993
|
+
out(row(chalk3.yellow("Refreshing\u2026"), w));
|
|
3014
2994
|
} else {
|
|
3015
2995
|
for (const line of formatCommandBar(actions, w)) {
|
|
3016
|
-
|
|
2996
|
+
out(row(line, w));
|
|
3017
2997
|
}
|
|
3018
2998
|
}
|
|
3019
|
-
|
|
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/
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
const
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
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
|
-
|
|
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);
|
|
3037
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 termSize() {
|
|
3095
|
+
return {
|
|
3096
|
+
cols: process.stdout.columns || 80,
|
|
3097
|
+
rows: process.stdout.rows || 24
|
|
3098
|
+
};
|
|
3099
|
+
}
|
|
3100
|
+
function moveTo(row2, col) {
|
|
3101
|
+
return `\x1B[${row2};${col}H`;
|
|
3102
|
+
}
|
|
3103
|
+
function hideCursor() {
|
|
3104
|
+
return "\x1B[?25l";
|
|
3105
|
+
}
|
|
3106
|
+
function showCursor() {
|
|
3107
|
+
return "\x1B[?25h";
|
|
3108
|
+
}
|
|
3109
|
+
function modalWidth() {
|
|
3110
|
+
const cols = process.stdout.columns || 80;
|
|
3111
|
+
return Math.min(60, cols - 4);
|
|
3112
|
+
}
|
|
3113
|
+
function renderOverlay(dashboardLines, modalLines, mWidth) {
|
|
3114
|
+
const { cols, rows } = termSize();
|
|
3115
|
+
process.stdout.write("\x1B[2J\x1B[0f");
|
|
3116
|
+
process.stdout.write(hideCursor());
|
|
3117
|
+
for (let i = 0; i < dashboardLines.length && i < rows; i++) {
|
|
3118
|
+
process.stdout.write(
|
|
3119
|
+
moveTo(i + 1, 1) + chalk4.dim(stripAnsi(dashboardLines[i]))
|
|
3120
|
+
);
|
|
3121
|
+
}
|
|
3122
|
+
const startRow = Math.max(1, Math.floor((rows - modalLines.length) / 2));
|
|
3123
|
+
const startCol = Math.max(1, Math.floor((cols - mWidth) / 2));
|
|
3124
|
+
for (let i = 0; i < modalLines.length; i++) {
|
|
3125
|
+
process.stdout.write(moveTo(startRow + i, startCol) + modalLines[i]);
|
|
3126
|
+
}
|
|
3127
|
+
process.stdout.write(moveTo(startRow + modalLines.length + 1, 1));
|
|
3128
|
+
}
|
|
3129
|
+
function readKey() {
|
|
3130
|
+
return new Promise((resolve) => {
|
|
3131
|
+
const { stdin } = process;
|
|
3132
|
+
const wasRaw = stdin.isRaw;
|
|
3133
|
+
stdin.setRawMode(true);
|
|
3134
|
+
stdin.resume();
|
|
3135
|
+
stdin.setEncoding("utf8");
|
|
3136
|
+
const onData = (data) => {
|
|
3137
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
3138
|
+
stdin.pause();
|
|
3139
|
+
stdin.removeListener("data", onData);
|
|
3140
|
+
if (data === "") {
|
|
3141
|
+
resolve({ name: "escape", raw: data });
|
|
3142
|
+
} else if (data === "\x1B" || data === "\x1B\x1B") {
|
|
3143
|
+
resolve({ name: "escape", raw: data });
|
|
3144
|
+
} else if (data === "\x1B[A") {
|
|
3145
|
+
resolve({ name: "up", raw: data });
|
|
3146
|
+
} else if (data === "\x1B[B") {
|
|
3147
|
+
resolve({ name: "down", raw: data });
|
|
3148
|
+
} else if (data === "\x1B[C") {
|
|
3149
|
+
resolve({ name: "right", raw: data });
|
|
3150
|
+
} else if (data === "\x1B[D") {
|
|
3151
|
+
resolve({ name: "left", raw: data });
|
|
3152
|
+
} else if (data === "\r" || data === "\n") {
|
|
3153
|
+
resolve({ name: "enter", raw: data });
|
|
3154
|
+
} else if (data === "\x7F" || data === "\b") {
|
|
3155
|
+
resolve({ name: "backspace", raw: data });
|
|
3156
|
+
} else if (data === " ") {
|
|
3157
|
+
resolve({ name: "tab", raw: data });
|
|
3158
|
+
} else if (data.length === 1 && data.charCodeAt(0) >= 32) {
|
|
3159
|
+
resolve({ name: data, raw: data });
|
|
3160
|
+
} else {
|
|
3161
|
+
stdin.setRawMode(true);
|
|
3162
|
+
stdin.resume();
|
|
3163
|
+
stdin.on("data", onData);
|
|
3164
|
+
}
|
|
3165
|
+
};
|
|
3166
|
+
stdin.on("data", onData);
|
|
3167
|
+
});
|
|
3168
|
+
}
|
|
3169
|
+
async function showConfirm(opts) {
|
|
3170
|
+
const w = modalWidth();
|
|
3171
|
+
let selectedYes = true;
|
|
3172
|
+
const render = () => {
|
|
3173
|
+
const content = buildConfirmContent(opts.message, selectedYes);
|
|
3174
|
+
const footer = "\u2191\u2193 Select Enter Confirm Esc Cancel";
|
|
3175
|
+
const lines = buildModalFrame(opts.title, content, footer, w);
|
|
3176
|
+
renderOverlay(opts.dashboardLines, lines, w);
|
|
3177
|
+
};
|
|
3178
|
+
render();
|
|
3179
|
+
while (true) {
|
|
3180
|
+
const key = await readKey();
|
|
3181
|
+
switch (key.name) {
|
|
3182
|
+
case "up":
|
|
3183
|
+
case "down":
|
|
3184
|
+
case "tab":
|
|
3185
|
+
selectedYes = !selectedYes;
|
|
3186
|
+
render();
|
|
3187
|
+
break;
|
|
3188
|
+
case "enter":
|
|
3189
|
+
process.stdout.write(showCursor());
|
|
3190
|
+
return selectedYes;
|
|
3191
|
+
case "escape":
|
|
3192
|
+
process.stdout.write(showCursor());
|
|
3193
|
+
return false;
|
|
3194
|
+
case "y":
|
|
3195
|
+
process.stdout.write(showCursor());
|
|
3196
|
+
return true;
|
|
3197
|
+
case "n":
|
|
3198
|
+
process.stdout.write(showCursor());
|
|
3199
|
+
return false;
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
function selectableCount(items) {
|
|
3204
|
+
return items.filter((i) => !isSeparator(i)).length;
|
|
3205
|
+
}
|
|
3206
|
+
async function showSelect(opts) {
|
|
3207
|
+
const w = modalWidth();
|
|
3208
|
+
const maxItems = selectableCount(opts.items);
|
|
3209
|
+
if (maxItems === 0) return null;
|
|
3210
|
+
let selectedIndex = 0;
|
|
3211
|
+
const render = () => {
|
|
3212
|
+
const content = buildSelectContent(opts.items, selectedIndex, w - 6);
|
|
3213
|
+
const footer = "\u2191\u2193 Navigate Enter Select Esc Cancel";
|
|
3214
|
+
const lines = buildModalFrame(opts.title, content, footer, w);
|
|
3215
|
+
renderOverlay(opts.dashboardLines, lines, w);
|
|
3216
|
+
};
|
|
3217
|
+
render();
|
|
3218
|
+
while (true) {
|
|
3219
|
+
const key = await readKey();
|
|
3220
|
+
switch (key.name) {
|
|
3221
|
+
case "up":
|
|
3222
|
+
selectedIndex = (selectedIndex - 1 + maxItems) % maxItems;
|
|
3223
|
+
render();
|
|
3224
|
+
break;
|
|
3225
|
+
case "down":
|
|
3226
|
+
selectedIndex = (selectedIndex + 1) % maxItems;
|
|
3227
|
+
render();
|
|
3228
|
+
break;
|
|
3229
|
+
case "enter": {
|
|
3230
|
+
process.stdout.write(showCursor());
|
|
3231
|
+
let idx = 0;
|
|
3232
|
+
for (const item of opts.items) {
|
|
3233
|
+
if (!isSeparator(item)) {
|
|
3234
|
+
if (idx === selectedIndex) return item.value;
|
|
3235
|
+
idx++;
|
|
3236
|
+
}
|
|
3237
|
+
}
|
|
3238
|
+
return null;
|
|
3239
|
+
}
|
|
3240
|
+
case "escape":
|
|
3241
|
+
process.stdout.write(showCursor());
|
|
3242
|
+
return null;
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
async function showInput(opts) {
|
|
3247
|
+
const w = modalWidth();
|
|
3248
|
+
let value = "";
|
|
3249
|
+
let cursorBlink = true;
|
|
3250
|
+
const render = () => {
|
|
3251
|
+
const maxLen = w - 10;
|
|
3252
|
+
const displayValue = value.length > maxLen ? value.slice(value.length - maxLen) : value;
|
|
3253
|
+
const content = buildInputContent(opts.label, displayValue, cursorBlink);
|
|
3254
|
+
const footer = "Enter Submit Esc Cancel";
|
|
3255
|
+
const lines = buildModalFrame(opts.title, content, footer, w);
|
|
3256
|
+
renderOverlay(opts.dashboardLines, lines, w);
|
|
3257
|
+
};
|
|
3258
|
+
render();
|
|
3259
|
+
while (true) {
|
|
3260
|
+
const key = await readKey();
|
|
3261
|
+
switch (key.name) {
|
|
3262
|
+
case "enter":
|
|
3263
|
+
process.stdout.write(showCursor());
|
|
3264
|
+
return value.trim() || null;
|
|
3265
|
+
case "escape":
|
|
3266
|
+
process.stdout.write(showCursor());
|
|
3267
|
+
return null;
|
|
3268
|
+
case "backspace":
|
|
3269
|
+
if (value.length > 0) {
|
|
3270
|
+
value = value.slice(0, -1);
|
|
3271
|
+
}
|
|
3272
|
+
render();
|
|
3273
|
+
break;
|
|
3274
|
+
default:
|
|
3275
|
+
if (key.raw.length === 1 && key.raw.charCodeAt(0) >= 32) {
|
|
3276
|
+
value += key.raw;
|
|
3277
|
+
cursorBlink = true;
|
|
3278
|
+
render();
|
|
3279
|
+
}
|
|
3280
|
+
break;
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
function showStatus(title, message, dashboardLines) {
|
|
3285
|
+
const w = modalWidth();
|
|
3286
|
+
const content = [message];
|
|
3287
|
+
const footer = "";
|
|
3288
|
+
const lines = buildModalFrame(title, content, footer, w);
|
|
3289
|
+
renderOverlay(dashboardLines, lines, w);
|
|
3290
|
+
}
|
|
3291
|
+
|
|
3292
|
+
// src/commands/tui.ts
|
|
3293
|
+
var CANCEL = /* @__PURE__ */ Symbol("cancel");
|
|
3038
3294
|
async function waitForKey(validKeys) {
|
|
3039
3295
|
return new Promise((resolve) => {
|
|
3040
3296
|
const { stdin } = process;
|
|
@@ -3060,99 +3316,66 @@ async function waitForKey(validKeys) {
|
|
|
3060
3316
|
stdin.on("data", onData);
|
|
3061
3317
|
});
|
|
3062
3318
|
}
|
|
3063
|
-
|
|
3319
|
+
var CONTINUE = { running: true, refresh: true };
|
|
3320
|
+
var SKIP_REFRESH = { running: true, refresh: false };
|
|
3321
|
+
var QUIT = { running: false, refresh: false };
|
|
3322
|
+
async function executeAction(actionId, state, dashboardLines) {
|
|
3064
3323
|
switch (actionId) {
|
|
3065
3324
|
case "quit":
|
|
3066
|
-
return
|
|
3325
|
+
return QUIT;
|
|
3067
3326
|
case "list": {
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
await listCommand({});
|
|
3071
|
-
} catch (error) {
|
|
3072
|
-
logger.error(`List failed: ${error}`);
|
|
3073
|
-
}
|
|
3074
|
-
await promptContinue();
|
|
3075
|
-
return true;
|
|
3327
|
+
const switched = await handleList(dashboardLines);
|
|
3328
|
+
return switched ? CONTINUE : SKIP_REFRESH;
|
|
3076
3329
|
}
|
|
3077
3330
|
case "create": {
|
|
3331
|
+
const description = await showInput({
|
|
3332
|
+
title: "New Ticket",
|
|
3333
|
+
label: "Describe the ticket:",
|
|
3334
|
+
dashboardLines
|
|
3335
|
+
});
|
|
3336
|
+
if (!description) return SKIP_REFRESH;
|
|
3078
3337
|
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
3338
|
try {
|
|
3091
3339
|
await createCommand(description, {});
|
|
3092
3340
|
} catch (error) {
|
|
3093
3341
|
logger.error(`Create failed: ${error}`);
|
|
3094
3342
|
}
|
|
3095
|
-
|
|
3096
|
-
|
|
3343
|
+
return CONTINUE;
|
|
3344
|
+
}
|
|
3345
|
+
case "commit": {
|
|
3346
|
+
const committed = await handleCommit(state, dashboardLines);
|
|
3347
|
+
return committed ? CONTINUE : SKIP_REFRESH;
|
|
3097
3348
|
}
|
|
3098
|
-
case "commit":
|
|
3099
|
-
clearScreen();
|
|
3100
|
-
await handleCommit(state);
|
|
3101
|
-
await promptContinue();
|
|
3102
|
-
return true;
|
|
3103
3349
|
case "push":
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
await promptContinue();
|
|
3107
|
-
return true;
|
|
3350
|
+
await handlePush(dashboardLines);
|
|
3351
|
+
return CONTINUE;
|
|
3108
3352
|
case "pr": {
|
|
3109
3353
|
clearScreen();
|
|
3110
|
-
if (!await confirm("Create a pull request?")) return true;
|
|
3111
3354
|
await prCommand({});
|
|
3112
|
-
|
|
3113
|
-
return true;
|
|
3355
|
+
return CONTINUE;
|
|
3114
3356
|
}
|
|
3115
3357
|
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
3358
|
await handleRun(state);
|
|
3129
|
-
|
|
3130
|
-
return true;
|
|
3359
|
+
return CONTINUE;
|
|
3131
3360
|
}
|
|
3132
3361
|
case "switch-provider":
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
if (!await confirm("Switch to main branch?")) return true;
|
|
3139
|
-
await handleCheckoutMain();
|
|
3140
|
-
return true;
|
|
3141
|
-
}
|
|
3362
|
+
await handleSwitchProvider(state, dashboardLines);
|
|
3363
|
+
return SKIP_REFRESH;
|
|
3364
|
+
case "checkout-main":
|
|
3365
|
+
await handleCheckoutMain(dashboardLines);
|
|
3366
|
+
return CONTINUE;
|
|
3142
3367
|
default:
|
|
3143
|
-
return
|
|
3368
|
+
return SKIP_REFRESH;
|
|
3144
3369
|
}
|
|
3145
3370
|
}
|
|
3146
|
-
async function handleCommit(state) {
|
|
3371
|
+
async function handleCommit(state, dashboardLines) {
|
|
3147
3372
|
try {
|
|
3148
3373
|
const { stdout: status } = await execa4("git", ["status", "--short"]);
|
|
3149
3374
|
if (!status.trim()) {
|
|
3150
|
-
|
|
3151
|
-
|
|
3375
|
+
showStatus("Commit", "No changes to commit", dashboardLines);
|
|
3376
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
3377
|
+
return false;
|
|
3152
3378
|
}
|
|
3153
|
-
logger.info("Changes:");
|
|
3154
|
-
console.log(status);
|
|
3155
|
-
console.log();
|
|
3156
3379
|
await execa4("git", ["add", "-A"]);
|
|
3157
3380
|
const { stdout: diffStat } = await execa4("git", [
|
|
3158
3381
|
"diff",
|
|
@@ -3165,69 +3388,69 @@ async function handleCommit(state) {
|
|
|
3165
3388
|
const issueTitle = state.issue?.title ?? null;
|
|
3166
3389
|
const provider = state.config.ai.provider;
|
|
3167
3390
|
const providerName = getProviderDisplayName(provider);
|
|
3168
|
-
const
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
name: "
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3391
|
+
const mode = await showSelect({
|
|
3392
|
+
title: "Commit",
|
|
3393
|
+
items: [
|
|
3394
|
+
{ name: `Generate with ${providerName}`, value: "ai" },
|
|
3395
|
+
{ name: "Enter manually", value: "manual" }
|
|
3396
|
+
],
|
|
3397
|
+
dashboardLines
|
|
3398
|
+
});
|
|
3399
|
+
if (!mode) {
|
|
3400
|
+
await execa4("git", ["reset", "HEAD"]);
|
|
3401
|
+
return false;
|
|
3402
|
+
}
|
|
3179
3403
|
let message;
|
|
3180
3404
|
if (mode === "manual") {
|
|
3181
|
-
const
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
]);
|
|
3188
|
-
message = manualInput.trim() || CANCEL;
|
|
3405
|
+
const input = await showInput({
|
|
3406
|
+
title: "Commit Message",
|
|
3407
|
+
label: "Enter commit message:",
|
|
3408
|
+
dashboardLines
|
|
3409
|
+
});
|
|
3410
|
+
message = input || CANCEL;
|
|
3189
3411
|
} else {
|
|
3190
|
-
|
|
3412
|
+
showStatus("Generating", `Generating commit message with ${providerName}...`, dashboardLines);
|
|
3191
3413
|
message = await generateCommitMessage(
|
|
3192
3414
|
diffContent,
|
|
3193
3415
|
issueNumber,
|
|
3194
3416
|
issueTitle,
|
|
3195
|
-
state
|
|
3417
|
+
state,
|
|
3418
|
+
dashboardLines
|
|
3196
3419
|
);
|
|
3197
3420
|
}
|
|
3198
3421
|
if (message === CANCEL) {
|
|
3199
3422
|
await execa4("git", ["reset", "HEAD"]);
|
|
3200
|
-
|
|
3201
|
-
return;
|
|
3423
|
+
return false;
|
|
3202
3424
|
}
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3425
|
+
const confirmed = await showConfirm({
|
|
3426
|
+
title: "Commit",
|
|
3427
|
+
message: `Message: ${message.length > 50 ? message.slice(0, 50) + "\u2026" : message}`,
|
|
3428
|
+
dashboardLines
|
|
3429
|
+
});
|
|
3430
|
+
if (!confirmed) {
|
|
3207
3431
|
await execa4("git", ["reset", "HEAD"]);
|
|
3208
|
-
|
|
3209
|
-
return;
|
|
3432
|
+
return false;
|
|
3210
3433
|
}
|
|
3211
3434
|
const providerEmail = getProviderEmail(provider);
|
|
3212
3435
|
const fullMessage = `${message}
|
|
3213
3436
|
|
|
3214
3437
|
Co-Authored-By: ${providerName} <${providerEmail}>`;
|
|
3215
|
-
|
|
3216
|
-
spinner.start();
|
|
3438
|
+
showStatus("Committing", "Committing changes...", dashboardLines);
|
|
3217
3439
|
await execa4("git", ["commit", "-m", fullMessage]);
|
|
3218
|
-
|
|
3440
|
+
return true;
|
|
3219
3441
|
} catch (error) {
|
|
3220
3442
|
logger.error(`Commit failed: ${error}`);
|
|
3443
|
+
return false;
|
|
3221
3444
|
}
|
|
3222
3445
|
}
|
|
3223
|
-
async function generateCommitMessage(diffContent, issueNumber, issueTitle, state) {
|
|
3446
|
+
async function generateCommitMessage(diffContent, issueNumber, issueTitle, state, dashboardLines) {
|
|
3224
3447
|
try {
|
|
3225
3448
|
const prompt = buildCommitMessagePrompt(
|
|
3226
3449
|
diffContent,
|
|
3227
3450
|
issueNumber,
|
|
3228
3451
|
issueTitle
|
|
3229
3452
|
);
|
|
3230
|
-
const result = await invokeAI({ prompt, streamOutput:
|
|
3453
|
+
const result = await invokeAI({ prompt, streamOutput: false }, state.config);
|
|
3231
3454
|
let message = result.output.trim().split("\n")[0].trim();
|
|
3232
3455
|
for (const q of ['"', "'", "`"]) {
|
|
3233
3456
|
if (message.startsWith(q) && message.endsWith(q)) {
|
|
@@ -3238,16 +3461,12 @@ async function generateCommitMessage(diffContent, issueNumber, issueTitle, state
|
|
|
3238
3461
|
message = message.replace(/^```\w*\s*/, "").replace(/\s*```$/, "");
|
|
3239
3462
|
return message;
|
|
3240
3463
|
} catch {
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
message: "Commit message (empty to cancel):"
|
|
3248
|
-
}
|
|
3249
|
-
]);
|
|
3250
|
-
return message.trim() || CANCEL;
|
|
3464
|
+
const input = await showInput({
|
|
3465
|
+
title: "Commit Message",
|
|
3466
|
+
label: "AI generation failed. Enter commit message:",
|
|
3467
|
+
dashboardLines
|
|
3468
|
+
});
|
|
3469
|
+
return input || CANCEL;
|
|
3251
3470
|
}
|
|
3252
3471
|
}
|
|
3253
3472
|
async function handleRun(state) {
|
|
@@ -3295,61 +3514,190 @@ ${feedbackLines}`);
|
|
|
3295
3514
|
logger.error(`${providerName} session failed: ${error}`);
|
|
3296
3515
|
}
|
|
3297
3516
|
}
|
|
3298
|
-
async function handlePush() {
|
|
3517
|
+
async function handlePush(dashboardLines) {
|
|
3299
3518
|
try {
|
|
3300
3519
|
const { stdout: branch } = await execa4("git", ["branch", "--show-current"]);
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
await execa4("git", ["push", "-u", "origin", branch.trim()]);
|
|
3305
|
-
spinner.succeed("Pushed to remote");
|
|
3520
|
+
const branchName = branch.trim();
|
|
3521
|
+
showStatus("Pushing", `Pushing ${branchName} to remote...`, dashboardLines);
|
|
3522
|
+
await execa4("git", ["push", "-u", "origin", branchName]);
|
|
3306
3523
|
} catch (error) {
|
|
3307
3524
|
logger.error(`Push failed: ${error}`);
|
|
3308
3525
|
}
|
|
3309
3526
|
}
|
|
3310
3527
|
var PROVIDERS = ["claude", "gemini", "codex"];
|
|
3311
|
-
async function handleSwitchProvider(state) {
|
|
3528
|
+
async function handleSwitchProvider(state, dashboardLines) {
|
|
3312
3529
|
const current = state.config.ai.provider;
|
|
3313
|
-
const
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
}
|
|
3324
|
-
]);
|
|
3325
|
-
if (provider === current) return;
|
|
3530
|
+
const items = PROVIDERS.map((p) => ({
|
|
3531
|
+
name: p.charAt(0).toUpperCase() + p.slice(1) + (p === current ? " (current)" : ""),
|
|
3532
|
+
value: p
|
|
3533
|
+
}));
|
|
3534
|
+
const provider = await showSelect({
|
|
3535
|
+
title: "AI Provider",
|
|
3536
|
+
items,
|
|
3537
|
+
dashboardLines
|
|
3538
|
+
});
|
|
3539
|
+
if (!provider || provider === current) return;
|
|
3326
3540
|
setRuntimeProvider(provider);
|
|
3327
|
-
|
|
3541
|
+
state.config.ai.provider = provider;
|
|
3328
3542
|
}
|
|
3329
|
-
async function handleCheckoutMain() {
|
|
3543
|
+
async function handleCheckoutMain(dashboardLines) {
|
|
3330
3544
|
try {
|
|
3331
|
-
|
|
3332
|
-
spinner.start();
|
|
3545
|
+
showStatus("Switching", "Switching to main...", dashboardLines);
|
|
3333
3546
|
await execa4("git", ["checkout", "main"]);
|
|
3334
3547
|
await execa4("git", ["pull"]);
|
|
3335
|
-
spinner.succeed("Switched to main");
|
|
3336
3548
|
} catch (error) {
|
|
3337
3549
|
logger.error(`Checkout failed: ${error}`);
|
|
3338
3550
|
}
|
|
3339
3551
|
}
|
|
3340
|
-
async function
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3552
|
+
async function handleList(dashboardLines) {
|
|
3553
|
+
try {
|
|
3554
|
+
showStatus("Loading", "Fetching tickets...", dashboardLines);
|
|
3555
|
+
const config = loadConfig();
|
|
3556
|
+
const workflowLabels = getWorkflowLabels(config);
|
|
3557
|
+
const [inProgress, ready, prs, localBranches] = await Promise.all([
|
|
3558
|
+
listIssues({
|
|
3559
|
+
labels: [workflowLabels.inProgress],
|
|
3560
|
+
state: "open",
|
|
3561
|
+
limit: 20
|
|
3562
|
+
}),
|
|
3563
|
+
listIssues({
|
|
3564
|
+
labels: [workflowLabels.ready],
|
|
3565
|
+
state: "open",
|
|
3566
|
+
limit: 20
|
|
3567
|
+
}),
|
|
3568
|
+
listOpenPrs(30),
|
|
3569
|
+
listLocalBranches()
|
|
3570
|
+
]);
|
|
3571
|
+
sortByPriority(inProgress);
|
|
3572
|
+
sortByPriority(ready);
|
|
3573
|
+
const choices = buildTicketChoices(inProgress, ready, prs, localBranches);
|
|
3574
|
+
const currentBranch = await getCurrentBranch();
|
|
3575
|
+
const defaultBranch = await getDefaultBranch();
|
|
3576
|
+
const items = [];
|
|
3577
|
+
const mainLabel = defaultBranch + (currentBranch === defaultBranch ? " (current)" : "");
|
|
3578
|
+
items.push({ name: mainLabel, value: "__main__" });
|
|
3579
|
+
const inProgressChoices = choices.filter(
|
|
3580
|
+
(c) => c.category === "in-progress"
|
|
3581
|
+
);
|
|
3582
|
+
const openPrChoices = choices.filter((c) => c.category === "open-pr");
|
|
3583
|
+
const readyChoices = choices.filter((c) => c.category === "ready");
|
|
3584
|
+
if (inProgressChoices.length > 0) {
|
|
3585
|
+
items.push({ separator: "\u2500\u2500 In Progress \u2500\u2500" });
|
|
3586
|
+
for (const c of inProgressChoices) {
|
|
3587
|
+
items.push({
|
|
3588
|
+
name: `#${c.issueNumber} ${c.title}`,
|
|
3589
|
+
value: String(c.issueNumber)
|
|
3590
|
+
});
|
|
3591
|
+
}
|
|
3347
3592
|
}
|
|
3348
|
-
|
|
3593
|
+
if (openPrChoices.length > 0) {
|
|
3594
|
+
items.push({ separator: "\u2500\u2500 Open PRs \u2500\u2500" });
|
|
3595
|
+
for (const c of openPrChoices) {
|
|
3596
|
+
items.push({
|
|
3597
|
+
name: `#${c.issueNumber} ${c.title}`,
|
|
3598
|
+
value: String(c.issueNumber)
|
|
3599
|
+
});
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
if (readyChoices.length > 0) {
|
|
3603
|
+
items.push({ separator: "\u2500\u2500 Ready \u2500\u2500" });
|
|
3604
|
+
for (const c of readyChoices) {
|
|
3605
|
+
items.push({
|
|
3606
|
+
name: `#${c.issueNumber} ${c.title}`,
|
|
3607
|
+
value: String(c.issueNumber)
|
|
3608
|
+
});
|
|
3609
|
+
}
|
|
3610
|
+
}
|
|
3611
|
+
if (choices.length === 0) {
|
|
3612
|
+
showStatus("List", "No tickets found", dashboardLines);
|
|
3613
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
3614
|
+
return false;
|
|
3615
|
+
}
|
|
3616
|
+
const selected = await showSelect({
|
|
3617
|
+
title: "Switch Ticket",
|
|
3618
|
+
items,
|
|
3619
|
+
dashboardLines
|
|
3620
|
+
});
|
|
3621
|
+
if (!selected) return false;
|
|
3622
|
+
if (selected === "__main__") {
|
|
3623
|
+
if (currentBranch === defaultBranch) return false;
|
|
3624
|
+
const dirty2 = await hasUncommittedChanges();
|
|
3625
|
+
if (dirty2) {
|
|
3626
|
+
const ok = await showConfirm({
|
|
3627
|
+
title: "Uncommitted Changes",
|
|
3628
|
+
message: "You have uncommitted changes. Continue?",
|
|
3629
|
+
dashboardLines
|
|
3630
|
+
});
|
|
3631
|
+
if (!ok) return false;
|
|
3632
|
+
}
|
|
3633
|
+
showStatus("Switching", `Switching to ${defaultBranch}...`, dashboardLines);
|
|
3634
|
+
await checkoutBranch(defaultBranch);
|
|
3635
|
+
return true;
|
|
3636
|
+
}
|
|
3637
|
+
const issueNumber = parseInt(selected, 10);
|
|
3638
|
+
const ticket = choices.find((c) => c.issueNumber === issueNumber);
|
|
3639
|
+
if (!ticket) return false;
|
|
3640
|
+
const dirty = await hasUncommittedChanges();
|
|
3641
|
+
if (dirty) {
|
|
3642
|
+
const ok = await showConfirm({
|
|
3643
|
+
title: "Uncommitted Changes",
|
|
3644
|
+
message: "You have uncommitted changes. Continue?",
|
|
3645
|
+
dashboardLines
|
|
3646
|
+
});
|
|
3647
|
+
if (!ok) return false;
|
|
3648
|
+
}
|
|
3649
|
+
const targetBranch = ticket.branch;
|
|
3650
|
+
if (targetBranch) {
|
|
3651
|
+
if (await branchExists(targetBranch)) {
|
|
3652
|
+
showStatus("Switching", `Switching to ${targetBranch}...`, dashboardLines);
|
|
3653
|
+
await checkoutBranch(targetBranch);
|
|
3654
|
+
return true;
|
|
3655
|
+
} else if (await remoteBranchExists(targetBranch)) {
|
|
3656
|
+
showStatus("Fetching", `Fetching ${targetBranch}...`, dashboardLines);
|
|
3657
|
+
await fetchAndCheckout(targetBranch);
|
|
3658
|
+
return true;
|
|
3659
|
+
} else {
|
|
3660
|
+
return await offerCreateBranch2(
|
|
3661
|
+
issueNumber,
|
|
3662
|
+
ticket.title,
|
|
3663
|
+
dashboardLines
|
|
3664
|
+
);
|
|
3665
|
+
}
|
|
3666
|
+
} else {
|
|
3667
|
+
return await offerCreateBranch2(
|
|
3668
|
+
issueNumber,
|
|
3669
|
+
ticket.title,
|
|
3670
|
+
dashboardLines
|
|
3671
|
+
);
|
|
3672
|
+
}
|
|
3673
|
+
} catch (error) {
|
|
3674
|
+
logger.error(`List failed: ${error}`);
|
|
3675
|
+
return false;
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
async function offerCreateBranch2(issueNumber, title, dashboardLines) {
|
|
3679
|
+
const config = loadConfig();
|
|
3680
|
+
const branchName = await generateBranchName(
|
|
3681
|
+
config,
|
|
3682
|
+
issueNumber,
|
|
3683
|
+
title,
|
|
3684
|
+
"feature"
|
|
3685
|
+
);
|
|
3686
|
+
const create = await showConfirm({
|
|
3687
|
+
title: "Create Branch",
|
|
3688
|
+
message: `No branch found. Create ${branchName}?`,
|
|
3689
|
+
dashboardLines
|
|
3690
|
+
});
|
|
3691
|
+
if (!create) return false;
|
|
3692
|
+
const defaultBranch = await getDefaultBranch();
|
|
3693
|
+
showStatus("Creating", `Creating ${branchName}...`, dashboardLines);
|
|
3694
|
+
await createBranch(branchName, defaultBranch);
|
|
3695
|
+
return true;
|
|
3349
3696
|
}
|
|
3350
3697
|
async function tuiCommand() {
|
|
3351
3698
|
let running = true;
|
|
3352
3699
|
let lastActions = [];
|
|
3700
|
+
let lastDashboardLines = [];
|
|
3353
3701
|
const config = loadConfig();
|
|
3354
3702
|
let lastState = {
|
|
3355
3703
|
isGitRepo: true,
|
|
@@ -3373,28 +3721,36 @@ async function tuiCommand() {
|
|
|
3373
3721
|
hasUIChanges: false,
|
|
3374
3722
|
isPlaywrightAvailable: false
|
|
3375
3723
|
};
|
|
3724
|
+
let needsRefresh = true;
|
|
3376
3725
|
while (running) {
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3726
|
+
if (needsRefresh) {
|
|
3727
|
+
clearScreen();
|
|
3728
|
+
renderDashboard(lastState, lastActions, void 0, true);
|
|
3729
|
+
const state = await aggregateState();
|
|
3730
|
+
const actions = getAvailableActions(state);
|
|
3731
|
+
lastState = state;
|
|
3732
|
+
lastActions = actions;
|
|
3733
|
+
}
|
|
3384
3734
|
let hint;
|
|
3385
|
-
if (
|
|
3735
|
+
if (lastState.isOnMain) {
|
|
3386
3736
|
hint = "Select an action to get started";
|
|
3387
|
-
} else if (
|
|
3737
|
+
} else if (lastState.hasUncommittedChanges && !lastState.pr) {
|
|
3388
3738
|
hint = "Commit your changes before creating a PR";
|
|
3389
|
-
} else if (
|
|
3739
|
+
} else if (lastState.hasActionableFeedback) {
|
|
3390
3740
|
hint = "Review feedback needs attention";
|
|
3391
3741
|
}
|
|
3392
|
-
|
|
3393
|
-
|
|
3742
|
+
lastDashboardLines = buildDashboardLines(lastState, lastActions, hint);
|
|
3743
|
+
clearScreen();
|
|
3744
|
+
for (const line of lastDashboardLines) {
|
|
3745
|
+
console.log(line);
|
|
3746
|
+
}
|
|
3747
|
+
const validKeys = lastActions.map((a) => a.shortcut);
|
|
3394
3748
|
const key = await waitForKey(validKeys);
|
|
3395
|
-
const action =
|
|
3749
|
+
const action = lastActions.find((a) => a.shortcut === key);
|
|
3396
3750
|
if (action) {
|
|
3397
|
-
|
|
3751
|
+
const result = await executeAction(action.id, lastState, lastDashboardLines);
|
|
3752
|
+
running = result.running;
|
|
3753
|
+
needsRefresh = result.refresh;
|
|
3398
3754
|
}
|
|
3399
3755
|
}
|
|
3400
3756
|
}
|