@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/{chunk-PHZJIEY6.js → chunk-FML3SVK5.js} +2 -2
- package/dist/{chunk-MRF5FZO6.js → chunk-SBG4BMMM.js} +2 -2
- package/dist/{chunk-ENNNKNLI.js → chunk-XBUSPWBB.js} +2 -1
- package/dist/github-remote-GJIXMSQK.js +9 -0
- package/dist/index.js +323 -286
- package/dist/index.js.map +1 -1
- package/dist/setup-labels-3IV5GAOW.js +9 -0
- package/package.json +1 -1
- package/dist/github-remote-YD46C4M7.js +0 -9
- package/dist/setup-labels-W4Z3EJ43.js +0 -9
- /package/dist/{chunk-PHZJIEY6.js.map → chunk-FML3SVK5.js.map} +0 -0
- /package/dist/{chunk-MRF5FZO6.js.map → chunk-SBG4BMMM.js.map} +0 -0
- /package/dist/{chunk-ENNNKNLI.js.map → chunk-XBUSPWBB.js.map} +0 -0
- /package/dist/{github-remote-YD46C4M7.js.map → github-remote-GJIXMSQK.js.map} +0 -0
- /package/dist/{setup-labels-W4Z3EJ43.js.map → setup-labels-3IV5GAOW.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
githubRemoteCommand
|
|
4
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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.
|
|
272
|
-
"
|
|
273
|
-
`
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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-
|
|
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-
|
|
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.
|
|
308
|
-
"
|
|
309
|
-
`
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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-
|
|
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() ? [
|
|
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: ["
|
|
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.
|
|
971
|
-
"Issue
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
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
|
|
1547
|
+
const session = await runInteractiveSession(
|
|
1517
1548
|
prompt,
|
|
1518
1549
|
config,
|
|
1519
1550
|
options.provider
|
|
1520
1551
|
);
|
|
1521
|
-
|
|
1522
|
-
|
|
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.
|
|
1602
|
-
"
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
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.
|
|
1849
|
-
|
|
1850
|
-
"
|
|
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
|
|
2170
|
+
const session = await runInteractiveSession(
|
|
2155
2171
|
prompt,
|
|
2156
2172
|
config,
|
|
2157
2173
|
options.provider
|
|
2158
2174
|
);
|
|
2159
|
-
aiExitCode =
|
|
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.
|
|
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
|
-
|
|
2470
|
-
|
|
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
|
-
|
|
2488
|
+
progressStatus = chalk3.green(`Found (${lines} lines)`);
|
|
2479
2489
|
} else {
|
|
2480
|
-
|
|
2490
|
+
progressStatus = chalk3.yellow("Not found");
|
|
2481
2491
|
}
|
|
2482
|
-
logger.
|
|
2483
|
-
|
|
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
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
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
|
-
|
|
2506
|
-
return suffix;
|
|
2511
|
+
return isActive ? " (active)" : isFallback ? " (fallback)" : "";
|
|
2507
2512
|
};
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
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
|
-
|
|
2530
|
+
const gitEntries = [
|
|
2531
|
+
{ key: "Branch", value: colors.branch(currentBranch) }
|
|
2532
|
+
];
|
|
2525
2533
|
if (onMain) {
|
|
2526
|
-
|
|
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
|
-
|
|
2531
|
-
|
|
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
|
-
|
|
2542
|
+
gitEntries.push({ key: `Commits ahead of ${baseBranch}`, value: String(commits.length) });
|
|
2535
2543
|
const unpushed = await getUnpushedCommits();
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
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
|
-
|
|
2550
|
+
gitEntries.push({ key: "Working tree", value: chalk3.yellow("Has uncommitted changes") });
|
|
2544
2551
|
}
|
|
2545
|
-
logger.
|
|
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
|
-
|
|
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
|
-
|
|
2562
|
+
workflowValue = colors.label("ai-ready");
|
|
2559
2563
|
} else if (issue.labels.includes(workflowLabels.inProgress)) {
|
|
2560
|
-
|
|
2564
|
+
workflowValue = colors.label("ai-in-progress");
|
|
2561
2565
|
} else if (issue.labels.includes(workflowLabels.completed)) {
|
|
2562
|
-
|
|
2566
|
+
workflowValue = colors.label("ai-completed");
|
|
2563
2567
|
} else if (issue.labels.includes(workflowLabels.blocked)) {
|
|
2564
|
-
|
|
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.
|
|
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
|
|
2575
|
-
|
|
2576
|
-
|
|
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
|
-
|
|
2587
|
-
` Review: ${formatReviewDecision(prStatus.reviewDecision)}`
|
|
2588
|
-
);
|
|
2597
|
+
prEntries.push({ key: "Review", value: formatReviewDecision(prStatus.reviewDecision) });
|
|
2589
2598
|
}
|
|
2590
2599
|
if (items.length > 0) {
|
|
2591
|
-
|
|
2592
|
-
|
|
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
|
-
|
|
2607
|
+
prEntries.push({ key: "", value: chalk3.dim(`${location}: ${body}`) });
|
|
2598
2608
|
}
|
|
2599
2609
|
} else if (prStatus.reviewDecision === "APPROVED") {
|
|
2600
|
-
|
|
2610
|
+
prEntries.push({ key: "Status", value: chalk3.green("Ready to merge!") });
|
|
2601
2611
|
} else {
|
|
2602
|
-
|
|
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
|
-
|
|
2608
|
-
|
|
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
|
-
|
|
2613
|
-
|
|
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.
|
|
2619
|
-
|
|
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
|
-
|
|
2631
|
+
let suggestions = [];
|
|
2624
2632
|
if (onMain) {
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
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
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
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
|
-
|
|
2637
|
-
|
|
2638
|
-
]
|
|
2644
|
+
suggestions = [
|
|
2645
|
+
{ key: "git checkout main && git pull", value: "Sync with merged changes" }
|
|
2646
|
+
];
|
|
2639
2647
|
} else if (prStatus.state === "closed") {
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
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
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
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
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2913
|
+
return chalk4.dim("\u251C" + "\u2500".repeat(w - 2) + "\u2524");
|
|
2905
2914
|
}
|
|
2906
2915
|
function botRow(w) {
|
|
2907
|
-
return
|
|
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
|
|
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
|
|
2927
|
+
return chalk4.bgGreen.black(" READY ");
|
|
2919
2928
|
case "in-progress":
|
|
2920
|
-
return
|
|
2929
|
+
return chalk4.bgYellow.black(" IN PROGRESS ");
|
|
2921
2930
|
case "completed":
|
|
2922
|
-
return
|
|
2931
|
+
return chalk4.bgBlue.white(" COMPLETED ");
|
|
2923
2932
|
case "blocked":
|
|
2924
|
-
return
|
|
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
|
|
2931
|
-
if (state === "closed") return
|
|
2932
|
-
return draft ?
|
|
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 " " +
|
|
2947
|
+
return " " + chalk4.green("Approved");
|
|
2939
2948
|
case "CHANGES_REQUESTED":
|
|
2940
|
-
return " " +
|
|
2949
|
+
return " " + chalk4.red("Changes requested");
|
|
2941
2950
|
case "REVIEW_REQUIRED":
|
|
2942
|
-
return " " +
|
|
2951
|
+
return " " + chalk4.yellow("Review pending");
|
|
2943
2952
|
default:
|
|
2944
2953
|
return "";
|
|
2945
2954
|
}
|
|
2946
2955
|
}
|
|
2947
2956
|
var shortcutColors = [
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
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(
|
|
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
|
|
2970
|
+
return chalk4.dim(before) + styledKey + chalk4.dim(after);
|
|
2962
2971
|
}
|
|
2963
|
-
return styledKey + " " +
|
|
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 ?
|
|
2996
|
-
const ghTag = state.isGhAuthenticated ?
|
|
2997
|
-
out(row(
|
|
2998
|
-
out(row(
|
|
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
|
-
|
|
3011
|
+
chalk4.yellow(
|
|
3003
3012
|
`Update available: ${versionCheck.currentVersion} \u2192 ${versionCheck.latestVersion}`
|
|
3004
|
-
) +
|
|
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(
|
|
3021
|
-
out(row(
|
|
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(
|
|
3031
|
-
out(row(
|
|
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
|
-
|
|
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(" " +
|
|
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(
|
|
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(
|
|
3073
|
+
out(row(chalk4.dim(" No linked issue"), w));
|
|
3065
3074
|
}
|
|
3066
3075
|
}
|
|
3067
3076
|
section("Branch");
|
|
3068
|
-
let branchLine =
|
|
3077
|
+
let branchLine = chalk4.dim("\xB7 ") + chalk4.magenta(state.branch);
|
|
3069
3078
|
if (state.isOnMain && !state.hasUncommittedChanges) {
|
|
3070
|
-
branchLine +=
|
|
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(
|
|
3076
|
-
if (state.hasUncommittedChanges) bits.push(
|
|
3077
|
-
if (state.hasUnpushedCommits) bits.push(
|
|
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(
|
|
3088
|
+
bits.push(chalk4.green("\u25CF synced"));
|
|
3080
3089
|
}
|
|
3081
|
-
if (bits.length) out(row(" " + bits.join(
|
|
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
|
-
|
|
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
|
-
" " +
|
|
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
|
-
" " +
|
|
3119
|
+
" " + chalk4.cyan("UI changes detected") + chalk4.dim(" \xB7 video capture available"),
|
|
3111
3120
|
w
|
|
3112
3121
|
)
|
|
3113
3122
|
);
|
|
3114
3123
|
}
|
|
3115
|
-
out(row(" " +
|
|
3124
|
+
out(row(" " + chalk4.dim(state.pr.url), w));
|
|
3116
3125
|
} else {
|
|
3117
|
-
out(row(
|
|
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(
|
|
3134
|
+
out(row(chalk4.dim("\xB7 ") + c, w));
|
|
3126
3135
|
}
|
|
3127
3136
|
if (state.commits.length > max) {
|
|
3128
|
-
out(row(
|
|
3137
|
+
out(row(chalk4.dim(` \u2026 and ${state.commits.length - max} more`), w));
|
|
3129
3138
|
}
|
|
3130
3139
|
} else {
|
|
3131
|
-
out(row(
|
|
3140
|
+
out(row(chalk4.dim(" No commits"), w));
|
|
3132
3141
|
}
|
|
3133
3142
|
}
|
|
3134
3143
|
if (!state.hasValidRemote) {
|
|
3135
3144
|
section("Setup");
|
|
3136
|
-
out(row(
|
|
3137
|
-
out(row(
|
|
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(
|
|
3141
|
-
out(row(
|
|
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(
|
|
3153
|
+
out(row(chalk4.dim("Press [i] to customize configuration (optional)"), w));
|
|
3145
3154
|
} else if (hint) {
|
|
3146
3155
|
section("Hint");
|
|
3147
|
-
out(row(
|
|
3156
|
+
out(row(chalk4.yellow(hint), w));
|
|
3148
3157
|
}
|
|
3149
3158
|
out(divRow(w));
|
|
3150
3159
|
if (refreshing) {
|
|
3151
|
-
out(row(
|
|
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
|
|
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
|
|
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(
|
|
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 ?
|
|
3246
|
-
const bullet =
|
|
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 ?
|
|
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
|
|
3309
|
+
import chalk6 from "chalk";
|
|
3301
3310
|
function buildConfirmContent(message, selectedYes) {
|
|
3302
|
-
const yes = selectedYes ?
|
|
3303
|
-
const no = !selectedYes ?
|
|
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
|
|
3351
|
+
import chalk7 from "chalk";
|
|
3343
3352
|
function buildInputContent(label, value, cursorVisible) {
|
|
3344
|
-
const cursorChar = cursorVisible ?
|
|
3345
|
-
return [label, "",
|
|
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
|
|
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
|
-
|
|
3491
|
+
chalk8.cyan(" ") + text.slice(0, cursorCol) + chalk8.inverse(charUnderCursor) + text.slice(cursorCol + 1)
|
|
3483
3492
|
);
|
|
3484
3493
|
} else {
|
|
3485
|
-
lines.push(
|
|
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
|
|
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
|
|
3597
|
+
return chalk9.bold("\u251C" + "\u2500".repeat(w - 2) + "\u2524");
|
|
3589
3598
|
}
|
|
3590
3599
|
function modalBotRow(w) {
|
|
3591
|
-
return
|
|
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
|
|
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(
|
|
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) +
|
|
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
|
-
|
|
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
|
-
|
|
3873
|
-
await result;
|
|
3910
|
+
await runAISession(prompt, state.config);
|
|
3874
3911
|
} catch (error) {
|
|
3875
3912
|
logger.error(`${providerName} session failed: ${error}`);
|
|
3876
3913
|
}
|