@rotorsoft/gent 1.10.1 → 1.12.0
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/README.md +45 -0
- package/dist/{chunk-6O2G7EPT.js → chunk-TLGYE7J2.js} +36 -3
- package/dist/chunk-TLGYE7J2.js.map +1 -0
- package/dist/index.js +860 -6
- package/dist/index.js.map +1 -1
- package/dist/{setup-labels-PZK2T3WA.js → setup-labels-FLCGVPFY.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-6O2G7EPT.js.map +0 -1
- /package/dist/{setup-labels-PZK2T3WA.js.map → setup-labels-FLCGVPFY.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
configExists,
|
|
14
14
|
createIssue,
|
|
15
15
|
createPullRequest,
|
|
16
|
+
createSpinner,
|
|
16
17
|
extractPriorityFromLabels,
|
|
17
18
|
extractTypeFromLabels,
|
|
18
19
|
generateDefaultConfig,
|
|
@@ -32,9 +33,10 @@ import {
|
|
|
32
33
|
sanitizeSlug,
|
|
33
34
|
setupLabelsCommand,
|
|
34
35
|
sortByPriority,
|
|
36
|
+
updateConfigProvider,
|
|
35
37
|
updateIssueLabels,
|
|
36
38
|
withSpinner
|
|
37
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-TLGYE7J2.js";
|
|
38
40
|
|
|
39
41
|
// src/index.ts
|
|
40
42
|
import { Command } from "commander";
|
|
@@ -195,7 +197,7 @@ async function initCommand(options) {
|
|
|
195
197
|
}
|
|
196
198
|
]);
|
|
197
199
|
if (setupLabels) {
|
|
198
|
-
const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-
|
|
200
|
+
const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-FLCGVPFY.js");
|
|
199
201
|
await setupLabelsCommand2();
|
|
200
202
|
}
|
|
201
203
|
}
|
|
@@ -540,6 +542,22 @@ ${issue ? `Closes #${issue.number}` : ""}
|
|
|
540
542
|
|
|
541
543
|
Only output the PR description, nothing else.`;
|
|
542
544
|
}
|
|
545
|
+
function buildCommitMessagePrompt(diff, issueNumber, issueTitle) {
|
|
546
|
+
const issueContext = issueNumber ? `
|
|
547
|
+
Related Issue: #${issueNumber}${issueTitle ? ` - ${issueTitle}` : ""}
|
|
548
|
+
` : "";
|
|
549
|
+
return `Generate a concise git commit message for the following changes.
|
|
550
|
+
${issueContext}
|
|
551
|
+
## Diff
|
|
552
|
+
${diff}
|
|
553
|
+
|
|
554
|
+
Rules:
|
|
555
|
+
- Use conventional commit format: <type>: <short description>
|
|
556
|
+
- Types: feat, fix, refactor, chore, docs, test, style, perf
|
|
557
|
+
- Keep the first line under 72 characters
|
|
558
|
+
- Do NOT include a body or footer
|
|
559
|
+
- Output ONLY the commit message, nothing else`;
|
|
560
|
+
}
|
|
543
561
|
function parseTicketMeta(output) {
|
|
544
562
|
const metaMatch = output.match(
|
|
545
563
|
/META:type=(\w+),priority=(\w+),risk=(\w+),area=(\w+)/
|
|
@@ -592,6 +610,46 @@ function generateFallbackTitle(description) {
|
|
|
592
610
|
}
|
|
593
611
|
return truncated;
|
|
594
612
|
}
|
|
613
|
+
function buildVideoPrompt(issueNumber, issueTitle, videoConfig, agentInstructions) {
|
|
614
|
+
return `You are helping capture a Playwright video demonstration of UI changes for GitHub Issue #${issueNumber}: ${issueTitle}
|
|
615
|
+
|
|
616
|
+
${agentInstructions ? `## Project-Specific Instructions
|
|
617
|
+
${agentInstructions}
|
|
618
|
+
|
|
619
|
+
` : ""}
|
|
620
|
+
|
|
621
|
+
## Task: Record UI Demo Video
|
|
622
|
+
|
|
623
|
+
Create a short video (max ${videoConfig.max_duration}s) demonstrating the UI changes made for this issue.
|
|
624
|
+
|
|
625
|
+
### Video Requirements
|
|
626
|
+
- Resolution: ${videoConfig.width}x${videoConfig.height}
|
|
627
|
+
- Format: WebM or MP4
|
|
628
|
+
- Duration: Under ${videoConfig.max_duration} seconds
|
|
629
|
+
- Show the key UI interactions and visual changes
|
|
630
|
+
|
|
631
|
+
### Steps
|
|
632
|
+
|
|
633
|
+
1. **Start the development server** if not already running
|
|
634
|
+
2. **Use Playwright to record video** of the relevant UI interactions:
|
|
635
|
+
- Navigate to the affected pages/components
|
|
636
|
+
- Demonstrate the new or changed functionality
|
|
637
|
+
- Show before/after if applicable
|
|
638
|
+
|
|
639
|
+
3. **Upload video to GitHub** as a release asset or use GitHub's drag-drop upload:
|
|
640
|
+
- Create a GitHub release or upload to issue comments
|
|
641
|
+
- Get the permanent URL for the video
|
|
642
|
+
- Do NOT commit video files to the repository
|
|
643
|
+
|
|
644
|
+
4. **Add video to PR** by commenting with the video URL or embedding it
|
|
645
|
+
|
|
646
|
+
### Important
|
|
647
|
+
- Upload video to GitHub assets, NOT to the repository
|
|
648
|
+
- Keep the video concise - focus on demonstrating the changes
|
|
649
|
+
- Ensure the video clearly shows the UI improvements
|
|
650
|
+
|
|
651
|
+
Output the GitHub URL where the video was uploaded when complete.`;
|
|
652
|
+
}
|
|
595
653
|
|
|
596
654
|
// src/commands/create.ts
|
|
597
655
|
async function createCommand(description, options) {
|
|
@@ -1300,6 +1358,52 @@ async function autoSelectIssue(readyLabel) {
|
|
|
1300
1358
|
|
|
1301
1359
|
// src/commands/pr.ts
|
|
1302
1360
|
import inquirer4 from "inquirer";
|
|
1361
|
+
|
|
1362
|
+
// src/lib/playwright.ts
|
|
1363
|
+
import { execa as execa3 } from "execa";
|
|
1364
|
+
var UI_FILE_PATTERNS = [
|
|
1365
|
+
/\.(tsx|jsx)$/,
|
|
1366
|
+
/\.(vue|svelte)$/,
|
|
1367
|
+
/\.css$/,
|
|
1368
|
+
/\.scss$/,
|
|
1369
|
+
/\.less$/,
|
|
1370
|
+
/\.styled\.(ts|js)$/,
|
|
1371
|
+
/components?\//i,
|
|
1372
|
+
/pages?\//i,
|
|
1373
|
+
/views?\//i,
|
|
1374
|
+
/layouts?\//i,
|
|
1375
|
+
/ui\//i,
|
|
1376
|
+
/styles?\//i
|
|
1377
|
+
];
|
|
1378
|
+
async function isPlaywrightAvailable() {
|
|
1379
|
+
try {
|
|
1380
|
+
const { exitCode } = await execa3("npx", ["playwright", "--version"], {
|
|
1381
|
+
reject: false
|
|
1382
|
+
});
|
|
1383
|
+
return exitCode === 0;
|
|
1384
|
+
} catch {
|
|
1385
|
+
return false;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
function hasUIChanges(changedFiles) {
|
|
1389
|
+
return changedFiles.some(
|
|
1390
|
+
(file) => UI_FILE_PATTERNS.some((pattern) => pattern.test(file))
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
async function getChangedFiles(baseBranch = "main") {
|
|
1394
|
+
try {
|
|
1395
|
+
const { stdout } = await execa3("git", [
|
|
1396
|
+
"diff",
|
|
1397
|
+
`${baseBranch}...HEAD`,
|
|
1398
|
+
"--name-only"
|
|
1399
|
+
]);
|
|
1400
|
+
return stdout.trim().split("\n").filter(Boolean);
|
|
1401
|
+
} catch {
|
|
1402
|
+
return [];
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// src/commands/pr.ts
|
|
1303
1407
|
async function prCommand(options) {
|
|
1304
1408
|
logger.bold("Creating AI-enhanced pull request...");
|
|
1305
1409
|
logger.newline();
|
|
@@ -1369,7 +1473,31 @@ async function prCommand(options) {
|
|
|
1369
1473
|
}
|
|
1370
1474
|
logger.info(`Commits: ${commits.length}`);
|
|
1371
1475
|
logger.newline();
|
|
1372
|
-
const
|
|
1476
|
+
const shouldCaptureVideo = options.video !== false && config.video.enabled;
|
|
1477
|
+
let captureVideoInstructions = "";
|
|
1478
|
+
if (shouldCaptureVideo) {
|
|
1479
|
+
const changedFiles = await getChangedFiles(baseBranch);
|
|
1480
|
+
const uiChangesDetected = hasUIChanges(changedFiles);
|
|
1481
|
+
if (uiChangesDetected) {
|
|
1482
|
+
logger.info("UI changes detected in this branch");
|
|
1483
|
+
const playwrightAvailable = await isPlaywrightAvailable();
|
|
1484
|
+
if (!playwrightAvailable) {
|
|
1485
|
+
logger.warning("Playwright not available. Skipping video capture.");
|
|
1486
|
+
logger.dim("Install Playwright with: npm install -D playwright");
|
|
1487
|
+
} else {
|
|
1488
|
+
logger.info("Playwright available - AI will capture demo video via MCP");
|
|
1489
|
+
captureVideoInstructions = `
|
|
1490
|
+
|
|
1491
|
+
IMPORTANT: This PR contains UI changes. Use the Playwright MCP plugin to:
|
|
1492
|
+
1. Start the dev server if needed
|
|
1493
|
+
2. Navigate to the relevant pages showing the UI changes
|
|
1494
|
+
3. Capture a short demo video (max ${config.video.max_duration}s) showcasing the changes
|
|
1495
|
+
4. Upload the video to GitHub and include it in the PR description under a "## Demo Video" section
|
|
1496
|
+
`;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
const prompt = buildPrPrompt(issue, commits, diffSummary) + captureVideoInstructions;
|
|
1373
1501
|
let prBody;
|
|
1374
1502
|
try {
|
|
1375
1503
|
logger.info(`Generating PR description with ${colors.provider(providerName)}...`);
|
|
@@ -1766,7 +1894,7 @@ import { homedir } from "os";
|
|
|
1766
1894
|
// package.json
|
|
1767
1895
|
var package_default = {
|
|
1768
1896
|
name: "@rotorsoft/gent",
|
|
1769
|
-
version: "1.
|
|
1897
|
+
version: "1.12.0",
|
|
1770
1898
|
description: "AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs",
|
|
1771
1899
|
keywords: [
|
|
1772
1900
|
"cli",
|
|
@@ -2175,6 +2303,722 @@ async function statusCommand() {
|
|
|
2175
2303
|
}
|
|
2176
2304
|
}
|
|
2177
2305
|
|
|
2306
|
+
// src/commands/tui.ts
|
|
2307
|
+
import inquirer6 from "inquirer";
|
|
2308
|
+
import { execa as execa4 } from "execa";
|
|
2309
|
+
|
|
2310
|
+
// src/tui/state.ts
|
|
2311
|
+
async function aggregateState() {
|
|
2312
|
+
const isGitRepo = await checkGitRepo();
|
|
2313
|
+
if (!isGitRepo) {
|
|
2314
|
+
const config2 = loadConfig();
|
|
2315
|
+
return {
|
|
2316
|
+
isGitRepo: false,
|
|
2317
|
+
isGhAuthenticated: false,
|
|
2318
|
+
isAIProviderAvailable: false,
|
|
2319
|
+
config: config2,
|
|
2320
|
+
hasConfig: false,
|
|
2321
|
+
hasProgress: false,
|
|
2322
|
+
branch: "",
|
|
2323
|
+
branchInfo: null,
|
|
2324
|
+
isOnMain: false,
|
|
2325
|
+
hasUncommittedChanges: false,
|
|
2326
|
+
hasUnpushedCommits: false,
|
|
2327
|
+
commits: [],
|
|
2328
|
+
baseBranch: "main",
|
|
2329
|
+
issue: null,
|
|
2330
|
+
workflowStatus: "none",
|
|
2331
|
+
pr: null,
|
|
2332
|
+
reviewFeedback: [],
|
|
2333
|
+
hasActionableFeedback: false,
|
|
2334
|
+
hasUIChanges: false,
|
|
2335
|
+
isPlaywrightAvailable: false
|
|
2336
|
+
};
|
|
2337
|
+
}
|
|
2338
|
+
const config = loadConfig();
|
|
2339
|
+
const workflowLabels = getWorkflowLabels(config);
|
|
2340
|
+
const [
|
|
2341
|
+
isGhAuthenticated,
|
|
2342
|
+
isAIProviderAvailable,
|
|
2343
|
+
branch,
|
|
2344
|
+
isOnMain,
|
|
2345
|
+
uncommitted,
|
|
2346
|
+
baseBranch
|
|
2347
|
+
] = await Promise.all([
|
|
2348
|
+
checkGhAuth(),
|
|
2349
|
+
checkAIProvider(config.ai.provider),
|
|
2350
|
+
getCurrentBranch(),
|
|
2351
|
+
isOnMainBranch(),
|
|
2352
|
+
hasUncommittedChanges(),
|
|
2353
|
+
getDefaultBranch()
|
|
2354
|
+
]);
|
|
2355
|
+
const hasConfig = configExists();
|
|
2356
|
+
const hasProgress = progressExists(config);
|
|
2357
|
+
const branchInfo = parseBranchName(branch);
|
|
2358
|
+
const [commits, unpushed] = await Promise.all([
|
|
2359
|
+
getCommitsSinceBase(baseBranch),
|
|
2360
|
+
getUnpushedCommits()
|
|
2361
|
+
]);
|
|
2362
|
+
let issue = null;
|
|
2363
|
+
let workflowStatus = "none";
|
|
2364
|
+
let pr = null;
|
|
2365
|
+
let reviewFeedback = [];
|
|
2366
|
+
let hasActionableFeedback = false;
|
|
2367
|
+
let uiChanges = false;
|
|
2368
|
+
let playwrightAvailable = false;
|
|
2369
|
+
if (!isOnMain) {
|
|
2370
|
+
const issueNumber = extractIssueNumber(branch);
|
|
2371
|
+
const [issueResult, prResult, changedFiles, playwrightResult] = await Promise.all([
|
|
2372
|
+
issueNumber ? getIssue(issueNumber).catch(() => null) : Promise.resolve(null),
|
|
2373
|
+
getPrStatus().catch(() => null),
|
|
2374
|
+
getChangedFiles(baseBranch),
|
|
2375
|
+
isPlaywrightAvailable()
|
|
2376
|
+
]);
|
|
2377
|
+
issue = issueResult;
|
|
2378
|
+
pr = prResult;
|
|
2379
|
+
uiChanges = hasUIChanges(changedFiles);
|
|
2380
|
+
playwrightAvailable = playwrightResult;
|
|
2381
|
+
if (issue) {
|
|
2382
|
+
if (issue.labels.includes(workflowLabels.ready)) {
|
|
2383
|
+
workflowStatus = "ready";
|
|
2384
|
+
} else if (issue.labels.includes(workflowLabels.inProgress)) {
|
|
2385
|
+
workflowStatus = "in-progress";
|
|
2386
|
+
} else if (issue.labels.includes(workflowLabels.completed)) {
|
|
2387
|
+
workflowStatus = "completed";
|
|
2388
|
+
} else if (issue.labels.includes(workflowLabels.blocked)) {
|
|
2389
|
+
workflowStatus = "blocked";
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
if (pr && pr.state === "open") {
|
|
2393
|
+
try {
|
|
2394
|
+
const lastCommitTimestamp = await getLastCommitTimestamp();
|
|
2395
|
+
const reviewData = await getPrReviewData(pr.number);
|
|
2396
|
+
const { items } = summarizeReviewFeedback(reviewData, { afterTimestamp: lastCommitTimestamp });
|
|
2397
|
+
reviewFeedback = items;
|
|
2398
|
+
hasActionableFeedback = items.length > 0;
|
|
2399
|
+
} catch {
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
return {
|
|
2404
|
+
isGitRepo,
|
|
2405
|
+
isGhAuthenticated,
|
|
2406
|
+
isAIProviderAvailable,
|
|
2407
|
+
config,
|
|
2408
|
+
hasConfig,
|
|
2409
|
+
hasProgress,
|
|
2410
|
+
branch,
|
|
2411
|
+
branchInfo,
|
|
2412
|
+
isOnMain,
|
|
2413
|
+
hasUncommittedChanges: uncommitted,
|
|
2414
|
+
hasUnpushedCommits: unpushed,
|
|
2415
|
+
commits,
|
|
2416
|
+
baseBranch,
|
|
2417
|
+
issue,
|
|
2418
|
+
workflowStatus,
|
|
2419
|
+
pr,
|
|
2420
|
+
reviewFeedback,
|
|
2421
|
+
hasActionableFeedback,
|
|
2422
|
+
hasUIChanges: uiChanges,
|
|
2423
|
+
isPlaywrightAvailable: playwrightAvailable
|
|
2424
|
+
};
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
// src/tui/actions.ts
|
|
2428
|
+
function getAvailableActions(state) {
|
|
2429
|
+
const actions = [];
|
|
2430
|
+
if (!state.isGitRepo || !state.isGhAuthenticated) {
|
|
2431
|
+
actions.push({ id: "quit", label: "quit", shortcut: "q" });
|
|
2432
|
+
return actions;
|
|
2433
|
+
}
|
|
2434
|
+
if (state.isOnMain) {
|
|
2435
|
+
actions.push({ id: "create", label: "new", shortcut: "n" });
|
|
2436
|
+
actions.push({ id: "run-auto", label: "run next", shortcut: "r" });
|
|
2437
|
+
actions.push({ id: "list", label: "list", shortcut: "l" });
|
|
2438
|
+
actions.push({ id: "switch-provider", label: "switch", shortcut: "s" });
|
|
2439
|
+
actions.push({ id: "quit", label: "quit", shortcut: "q" });
|
|
2440
|
+
return actions;
|
|
2441
|
+
}
|
|
2442
|
+
if (state.hasUncommittedChanges) {
|
|
2443
|
+
actions.push({ id: "commit", label: "commit", shortcut: "c" });
|
|
2444
|
+
}
|
|
2445
|
+
if (state.hasUnpushedCommits) {
|
|
2446
|
+
actions.push({ id: "push", label: "Push", shortcut: "P" });
|
|
2447
|
+
}
|
|
2448
|
+
if (!state.pr && state.commits.length > 0) {
|
|
2449
|
+
actions.push({ id: "pr", label: "Create pr", shortcut: "C" });
|
|
2450
|
+
}
|
|
2451
|
+
if (state.pr && state.pr.state === "open") {
|
|
2452
|
+
if (state.hasActionableFeedback) {
|
|
2453
|
+
actions.push({ id: "fix", label: "fix feedback", shortcut: "f" });
|
|
2454
|
+
}
|
|
2455
|
+
if (state.hasUIChanges && state.isPlaywrightAvailable && state.config.video.enabled) {
|
|
2456
|
+
actions.push({ id: "video", label: "video", shortcut: "v" });
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
if (state.pr && (state.pr.state === "merged" || state.pr.state === "closed")) {
|
|
2460
|
+
actions.push({ id: "checkout-main", label: "main", shortcut: "m" });
|
|
2461
|
+
}
|
|
2462
|
+
actions.push({ id: "switch-provider", label: "switch", shortcut: "s" });
|
|
2463
|
+
actions.push({ id: "quit", label: "quit", shortcut: "q" });
|
|
2464
|
+
return actions;
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
// src/tui/display.ts
|
|
2468
|
+
import chalk3 from "chalk";
|
|
2469
|
+
var stripAnsi = (str) => str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2470
|
+
var visibleLen = (str) => stripAnsi(str).length;
|
|
2471
|
+
function termWidth() {
|
|
2472
|
+
return Math.min(process.stdout.columns || 80, 90);
|
|
2473
|
+
}
|
|
2474
|
+
function termHeight() {
|
|
2475
|
+
return process.stdout.rows || 24;
|
|
2476
|
+
}
|
|
2477
|
+
function truncate(text, max) {
|
|
2478
|
+
if (text.length <= max) return text;
|
|
2479
|
+
return text.slice(0, max - 1) + "\u2026";
|
|
2480
|
+
}
|
|
2481
|
+
function extractDescription(body, maxLen) {
|
|
2482
|
+
const lines = body.split("\n");
|
|
2483
|
+
for (const line of lines) {
|
|
2484
|
+
const trimmed = line.trim();
|
|
2485
|
+
if (!trimmed) continue;
|
|
2486
|
+
if (trimmed.startsWith("#")) continue;
|
|
2487
|
+
if (trimmed.startsWith("---")) continue;
|
|
2488
|
+
if (trimmed.startsWith("META:")) continue;
|
|
2489
|
+
if (trimmed.startsWith("**Type:**")) continue;
|
|
2490
|
+
const clean = trimmed.replace(/\*\*/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
2491
|
+
return truncate(clean, maxLen);
|
|
2492
|
+
}
|
|
2493
|
+
return "";
|
|
2494
|
+
}
|
|
2495
|
+
function topRow(title, w) {
|
|
2496
|
+
const label = ` ${title} `;
|
|
2497
|
+
const fill = w - 2 - label.length;
|
|
2498
|
+
return chalk3.dim("\u250C") + chalk3.bold.cyan(label) + chalk3.dim("\u2500".repeat(Math.max(0, fill)) + "\u2510");
|
|
2499
|
+
}
|
|
2500
|
+
function midRow(title, w) {
|
|
2501
|
+
const label = ` ${title} `;
|
|
2502
|
+
const fill = w - 2 - label.length;
|
|
2503
|
+
return chalk3.dim("\u251C") + chalk3.bold.cyan(label) + chalk3.dim("\u2500".repeat(Math.max(0, fill)) + "\u2524");
|
|
2504
|
+
}
|
|
2505
|
+
function divRow(w) {
|
|
2506
|
+
return chalk3.dim("\u251C" + "\u2500".repeat(w - 2) + "\u2524");
|
|
2507
|
+
}
|
|
2508
|
+
function botRow(w) {
|
|
2509
|
+
return chalk3.dim("\u2514" + "\u2500".repeat(w - 2) + "\u2518");
|
|
2510
|
+
}
|
|
2511
|
+
function row(text, w) {
|
|
2512
|
+
const inner = w - 4;
|
|
2513
|
+
const pad = Math.max(0, inner - visibleLen(text));
|
|
2514
|
+
return chalk3.dim("\u2502") + " " + text + " ".repeat(pad) + " " + chalk3.dim("\u2502");
|
|
2515
|
+
}
|
|
2516
|
+
function workflowBadge(status) {
|
|
2517
|
+
switch (status) {
|
|
2518
|
+
case "ready":
|
|
2519
|
+
return chalk3.bgGreen.black(" READY ");
|
|
2520
|
+
case "in-progress":
|
|
2521
|
+
return chalk3.bgYellow.black(" IN PROGRESS ");
|
|
2522
|
+
case "completed":
|
|
2523
|
+
return chalk3.bgBlue.white(" COMPLETED ");
|
|
2524
|
+
case "blocked":
|
|
2525
|
+
return chalk3.bgRed.white(" BLOCKED ");
|
|
2526
|
+
default:
|
|
2527
|
+
return "";
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
function prBadge(state, draft) {
|
|
2531
|
+
if (state === "merged") return chalk3.bgMagenta.white(" MERGED ");
|
|
2532
|
+
if (state === "closed") return chalk3.bgRed.white(" CLOSED ");
|
|
2533
|
+
return draft ? chalk3.bgYellow.black(" DRAFT ") : chalk3.bgGreen.black(" OPEN ");
|
|
2534
|
+
}
|
|
2535
|
+
function reviewBadge(decision) {
|
|
2536
|
+
if (!decision) return "";
|
|
2537
|
+
switch (decision) {
|
|
2538
|
+
case "APPROVED":
|
|
2539
|
+
return " " + chalk3.green("Approved");
|
|
2540
|
+
case "CHANGES_REQUESTED":
|
|
2541
|
+
return " " + chalk3.red("Changes requested");
|
|
2542
|
+
case "REVIEW_REQUIRED":
|
|
2543
|
+
return " " + chalk3.yellow("Review pending");
|
|
2544
|
+
default:
|
|
2545
|
+
return "";
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
var shortcutColors = [
|
|
2549
|
+
chalk3.cyan.bold,
|
|
2550
|
+
chalk3.green.bold,
|
|
2551
|
+
chalk3.yellow.bold,
|
|
2552
|
+
chalk3.magenta.bold,
|
|
2553
|
+
chalk3.blue.bold,
|
|
2554
|
+
chalk3.red.bold
|
|
2555
|
+
];
|
|
2556
|
+
function formatAction(a, color) {
|
|
2557
|
+
const key = a.shortcut;
|
|
2558
|
+
const idx = a.label.indexOf(key);
|
|
2559
|
+
if (idx >= 0) {
|
|
2560
|
+
const before = a.label.slice(0, idx);
|
|
2561
|
+
const after = a.label.slice(idx + key.length);
|
|
2562
|
+
return chalk3.dim(before) + color(key) + chalk3.dim(after);
|
|
2563
|
+
}
|
|
2564
|
+
return color(key) + " " + chalk3.dim(a.label);
|
|
2565
|
+
}
|
|
2566
|
+
function formatCommandBar(actions, w) {
|
|
2567
|
+
const parts = actions.map((a, i) => {
|
|
2568
|
+
const color = shortcutColors[i % shortcutColors.length];
|
|
2569
|
+
return formatAction(a, color);
|
|
2570
|
+
});
|
|
2571
|
+
const inner = w - 4;
|
|
2572
|
+
const lines = [];
|
|
2573
|
+
let cur = "";
|
|
2574
|
+
for (const part of parts) {
|
|
2575
|
+
const next = cur + (cur.length > 0 ? " " : "") + part;
|
|
2576
|
+
if (visibleLen(next) > inner) {
|
|
2577
|
+
lines.push(cur);
|
|
2578
|
+
cur = part;
|
|
2579
|
+
} else {
|
|
2580
|
+
cur = next;
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
if (cur.length > 0) lines.push(cur);
|
|
2584
|
+
return lines;
|
|
2585
|
+
}
|
|
2586
|
+
function renderModal(message) {
|
|
2587
|
+
const w = termWidth();
|
|
2588
|
+
const h = termHeight();
|
|
2589
|
+
const modalW = Math.min(w - 4, Math.max(30, visibleLen(message) + 8));
|
|
2590
|
+
const padX = Math.max(0, Math.floor((w - modalW) / 2));
|
|
2591
|
+
const padY = Math.max(0, Math.floor((h - 5) / 2));
|
|
2592
|
+
const indent = " ".repeat(padX);
|
|
2593
|
+
const inner = modalW - 4;
|
|
2594
|
+
const textPad = Math.max(0, inner - visibleLen(message));
|
|
2595
|
+
const lines = [
|
|
2596
|
+
indent + chalk3.dim("\u250C" + "\u2500".repeat(modalW - 2) + "\u2510"),
|
|
2597
|
+
indent + chalk3.dim("\u2502") + " ".repeat(modalW - 2) + chalk3.dim("\u2502"),
|
|
2598
|
+
indent + chalk3.dim("\u2502") + " " + chalk3.bold(message) + " ".repeat(textPad) + " " + chalk3.dim("\u2502"),
|
|
2599
|
+
indent + chalk3.dim("\u2502") + " ".repeat(modalW - 2) + chalk3.dim("\u2502"),
|
|
2600
|
+
indent + chalk3.dim("\u2514" + "\u2500".repeat(modalW - 2) + "\u2518")
|
|
2601
|
+
];
|
|
2602
|
+
for (let i = 0; i < padY; i++) console.log();
|
|
2603
|
+
for (const line of lines) console.log(line);
|
|
2604
|
+
}
|
|
2605
|
+
function renderSettings(state, w) {
|
|
2606
|
+
const provider = getProviderDisplayName(state.config.ai.provider);
|
|
2607
|
+
const provTag = state.isAIProviderAvailable ? chalk3.green(provider) : chalk3.red(provider);
|
|
2608
|
+
const ghTag = state.isGhAuthenticated ? chalk3.green("authenticated") : chalk3.red("not authenticated");
|
|
2609
|
+
const videoTag = state.config.video.enabled ? chalk3.green("on") : chalk3.dim("off");
|
|
2610
|
+
console.log(row(chalk3.dim("Provider: ") + provTag, w));
|
|
2611
|
+
console.log(row(chalk3.dim("GitHub: ") + ghTag, w));
|
|
2612
|
+
console.log(row(chalk3.dim("Video: ") + videoTag, w));
|
|
2613
|
+
}
|
|
2614
|
+
function renderDashboard(state, actions, hint) {
|
|
2615
|
+
const w = termWidth();
|
|
2616
|
+
const descMax = w - 8;
|
|
2617
|
+
const version2 = getVersion();
|
|
2618
|
+
const titleLabel = `gent v${version2}`;
|
|
2619
|
+
console.log(topRow(titleLabel, w));
|
|
2620
|
+
if (!state.isGitRepo) {
|
|
2621
|
+
console.log(row(chalk3.red("Not a git repository"), w));
|
|
2622
|
+
console.log(row(chalk3.dim("Run gent init in a git repo to get started"), w));
|
|
2623
|
+
console.log(botRow(w));
|
|
2624
|
+
return;
|
|
2625
|
+
}
|
|
2626
|
+
if (!state.isGhAuthenticated) {
|
|
2627
|
+
console.log(row(chalk3.red("GitHub CLI not authenticated"), w));
|
|
2628
|
+
console.log(row(chalk3.dim("Run: gh auth login"), w));
|
|
2629
|
+
console.log(botRow(w));
|
|
2630
|
+
return;
|
|
2631
|
+
}
|
|
2632
|
+
if (state.isOnMain) {
|
|
2633
|
+
console.log(row(chalk3.magenta(state.branch) + chalk3.dim(" \xB7 ready to start new work"), w));
|
|
2634
|
+
if (state.hasUncommittedChanges) {
|
|
2635
|
+
console.log(row(chalk3.yellow("\u25CF uncommitted changes"), w));
|
|
2636
|
+
}
|
|
2637
|
+
console.log(midRow("Settings", w));
|
|
2638
|
+
renderSettings(state, w);
|
|
2639
|
+
if (hint) {
|
|
2640
|
+
console.log(midRow("Hint", w));
|
|
2641
|
+
console.log(row(chalk3.yellow(hint), w));
|
|
2642
|
+
}
|
|
2643
|
+
console.log(divRow(w));
|
|
2644
|
+
for (const line of formatCommandBar(actions, w)) {
|
|
2645
|
+
console.log(row(line, w));
|
|
2646
|
+
}
|
|
2647
|
+
console.log(botRow(w));
|
|
2648
|
+
console.log();
|
|
2649
|
+
return;
|
|
2650
|
+
}
|
|
2651
|
+
const section = (title) => {
|
|
2652
|
+
console.log(midRow(title, w));
|
|
2653
|
+
};
|
|
2654
|
+
section("Ticket");
|
|
2655
|
+
if (state.issue) {
|
|
2656
|
+
console.log(row(
|
|
2657
|
+
chalk3.cyan(`#${state.issue.number}`) + " " + chalk3.bold(truncate(state.issue.title, descMax - 6)),
|
|
2658
|
+
w
|
|
2659
|
+
));
|
|
2660
|
+
const desc = extractDescription(state.issue.body, descMax);
|
|
2661
|
+
if (desc) console.log(row(chalk3.dim(desc), w));
|
|
2662
|
+
const tags = [];
|
|
2663
|
+
if (state.workflowStatus !== "none") tags.push(workflowBadge(state.workflowStatus));
|
|
2664
|
+
for (const prefix of ["type:", "priority:", "risk:", "area:"]) {
|
|
2665
|
+
const l = state.issue.labels.find((x) => x.startsWith(prefix));
|
|
2666
|
+
if (l) tags.push(chalk3.dim(l));
|
|
2667
|
+
}
|
|
2668
|
+
if (tags.length) console.log(row(tags.join(" "), w));
|
|
2669
|
+
} else {
|
|
2670
|
+
console.log(row(chalk3.dim("No linked issue"), w));
|
|
2671
|
+
}
|
|
2672
|
+
section("Branch");
|
|
2673
|
+
console.log(row(chalk3.magenta(state.branch), w));
|
|
2674
|
+
const bits = [];
|
|
2675
|
+
if (state.commits.length > 0) bits.push(chalk3.dim(`${state.commits.length} ahead`));
|
|
2676
|
+
if (state.hasUncommittedChanges) bits.push(chalk3.yellow("\u25CF uncommitted"));
|
|
2677
|
+
if (state.hasUnpushedCommits) bits.push(chalk3.yellow("\u25CF unpushed"));
|
|
2678
|
+
if (!state.hasUncommittedChanges && !state.hasUnpushedCommits && state.commits.length > 0) {
|
|
2679
|
+
bits.push(chalk3.green("\u25CF synced"));
|
|
2680
|
+
}
|
|
2681
|
+
if (bits.length) console.log(row(bits.join(chalk3.dim(" \xB7 ")), w));
|
|
2682
|
+
section("Pull Request");
|
|
2683
|
+
if (state.pr) {
|
|
2684
|
+
const titleText = state.pr.title ? " " + truncate(state.pr.title, descMax - 12) : "";
|
|
2685
|
+
console.log(row(
|
|
2686
|
+
chalk3.cyan(`#${state.pr.number}`) + titleText,
|
|
2687
|
+
w
|
|
2688
|
+
));
|
|
2689
|
+
console.log(row(
|
|
2690
|
+
prBadge(state.pr.state, state.pr.isDraft) + reviewBadge(state.pr.reviewDecision),
|
|
2691
|
+
w
|
|
2692
|
+
));
|
|
2693
|
+
if (state.hasActionableFeedback) {
|
|
2694
|
+
const n = state.reviewFeedback.length;
|
|
2695
|
+
console.log(row(chalk3.yellow(`${n} actionable comment${n !== 1 ? "s" : ""} pending`), w));
|
|
2696
|
+
}
|
|
2697
|
+
if (state.hasUIChanges && state.isPlaywrightAvailable && state.config.video.enabled && state.pr.state === "open") {
|
|
2698
|
+
console.log(row(chalk3.cyan("UI changes detected") + chalk3.dim(" \xB7 video capture available"), w));
|
|
2699
|
+
}
|
|
2700
|
+
console.log(row(chalk3.dim(state.pr.url), w));
|
|
2701
|
+
} else {
|
|
2702
|
+
console.log(row(chalk3.dim("No PR created"), w));
|
|
2703
|
+
}
|
|
2704
|
+
section("Commits");
|
|
2705
|
+
if (state.commits.length > 0) {
|
|
2706
|
+
const max = 6;
|
|
2707
|
+
for (const c of state.commits.slice(0, max)) {
|
|
2708
|
+
console.log(row(c, w));
|
|
2709
|
+
}
|
|
2710
|
+
if (state.commits.length > max) {
|
|
2711
|
+
console.log(row(chalk3.dim(`\u2026 and ${state.commits.length - max} more`), w));
|
|
2712
|
+
}
|
|
2713
|
+
} else {
|
|
2714
|
+
console.log(row(chalk3.dim("No commits"), w));
|
|
2715
|
+
}
|
|
2716
|
+
section("Settings");
|
|
2717
|
+
renderSettings(state, w);
|
|
2718
|
+
if (hint) {
|
|
2719
|
+
section("Hint");
|
|
2720
|
+
console.log(row(chalk3.yellow(hint), w));
|
|
2721
|
+
}
|
|
2722
|
+
console.log(divRow(w));
|
|
2723
|
+
for (const line of formatCommandBar(actions, w)) {
|
|
2724
|
+
console.log(row(line, w));
|
|
2725
|
+
}
|
|
2726
|
+
console.log(botRow(w));
|
|
2727
|
+
}
|
|
2728
|
+
function clearScreen() {
|
|
2729
|
+
process.stdout.write("\x1B[2J\x1B[0f");
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
// src/commands/tui.ts
|
|
2733
|
+
var CANCEL = /* @__PURE__ */ Symbol("cancel");
|
|
2734
|
+
async function confirm(message) {
|
|
2735
|
+
const { ok } = await inquirer6.prompt([
|
|
2736
|
+
{
|
|
2737
|
+
type: "confirm",
|
|
2738
|
+
name: "ok",
|
|
2739
|
+
message,
|
|
2740
|
+
default: true
|
|
2741
|
+
}
|
|
2742
|
+
]);
|
|
2743
|
+
return ok;
|
|
2744
|
+
}
|
|
2745
|
+
async function loadState() {
|
|
2746
|
+
clearScreen();
|
|
2747
|
+
renderModal("Loading workflow state...");
|
|
2748
|
+
const state = await aggregateState();
|
|
2749
|
+
return state;
|
|
2750
|
+
}
|
|
2751
|
+
async function waitForKey(validKeys) {
|
|
2752
|
+
return new Promise((resolve) => {
|
|
2753
|
+
const { stdin } = process;
|
|
2754
|
+
const wasRaw = stdin.isRaw;
|
|
2755
|
+
stdin.setRawMode(true);
|
|
2756
|
+
stdin.resume();
|
|
2757
|
+
stdin.setEncoding("utf8");
|
|
2758
|
+
const onData = (key) => {
|
|
2759
|
+
if (key === "") {
|
|
2760
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
2761
|
+
stdin.pause();
|
|
2762
|
+
stdin.removeListener("data", onData);
|
|
2763
|
+
resolve("q");
|
|
2764
|
+
return;
|
|
2765
|
+
}
|
|
2766
|
+
if (validKeys.includes(key)) {
|
|
2767
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
2768
|
+
stdin.pause();
|
|
2769
|
+
stdin.removeListener("data", onData);
|
|
2770
|
+
resolve(key);
|
|
2771
|
+
}
|
|
2772
|
+
};
|
|
2773
|
+
stdin.on("data", onData);
|
|
2774
|
+
});
|
|
2775
|
+
}
|
|
2776
|
+
async function executeAction(actionId, state) {
|
|
2777
|
+
switch (actionId) {
|
|
2778
|
+
case "quit":
|
|
2779
|
+
return false;
|
|
2780
|
+
case "list":
|
|
2781
|
+
clearScreen();
|
|
2782
|
+
await listCommand({ status: "ready", limit: 20 });
|
|
2783
|
+
await promptContinue();
|
|
2784
|
+
return true;
|
|
2785
|
+
case "run-auto": {
|
|
2786
|
+
clearScreen();
|
|
2787
|
+
if (!await confirm("Start AI agent to implement next ticket?")) return true;
|
|
2788
|
+
await runCommand(void 0, { auto: true });
|
|
2789
|
+
return false;
|
|
2790
|
+
}
|
|
2791
|
+
case "create": {
|
|
2792
|
+
clearScreen();
|
|
2793
|
+
const { description } = await inquirer6.prompt([
|
|
2794
|
+
{
|
|
2795
|
+
type: "input",
|
|
2796
|
+
name: "description",
|
|
2797
|
+
message: "Describe the ticket (empty to cancel):"
|
|
2798
|
+
}
|
|
2799
|
+
]);
|
|
2800
|
+
if (!description.trim()) {
|
|
2801
|
+
logger.info("Cancelled");
|
|
2802
|
+
return true;
|
|
2803
|
+
}
|
|
2804
|
+
await createCommand(description, {});
|
|
2805
|
+
await promptContinue();
|
|
2806
|
+
return true;
|
|
2807
|
+
}
|
|
2808
|
+
case "commit":
|
|
2809
|
+
clearScreen();
|
|
2810
|
+
await handleCommit(state);
|
|
2811
|
+
await promptContinue();
|
|
2812
|
+
return true;
|
|
2813
|
+
case "push":
|
|
2814
|
+
clearScreen();
|
|
2815
|
+
await handlePush();
|
|
2816
|
+
await promptContinue();
|
|
2817
|
+
return true;
|
|
2818
|
+
case "pr": {
|
|
2819
|
+
clearScreen();
|
|
2820
|
+
if (!await confirm("Create a pull request?")) return true;
|
|
2821
|
+
await prCommand({});
|
|
2822
|
+
await promptContinue();
|
|
2823
|
+
return true;
|
|
2824
|
+
}
|
|
2825
|
+
case "fix": {
|
|
2826
|
+
clearScreen();
|
|
2827
|
+
if (!await confirm("Start AI agent to fix review feedback?")) return true;
|
|
2828
|
+
await fixCommand({});
|
|
2829
|
+
return false;
|
|
2830
|
+
}
|
|
2831
|
+
case "video": {
|
|
2832
|
+
clearScreen();
|
|
2833
|
+
if (!await confirm("Record video of UI changes?")) return true;
|
|
2834
|
+
await handleVideoCapture(state);
|
|
2835
|
+
await promptContinue();
|
|
2836
|
+
return true;
|
|
2837
|
+
}
|
|
2838
|
+
case "switch-provider":
|
|
2839
|
+
clearScreen();
|
|
2840
|
+
await handleSwitchProvider(state);
|
|
2841
|
+
return true;
|
|
2842
|
+
case "checkout-main": {
|
|
2843
|
+
clearScreen();
|
|
2844
|
+
if (!await confirm("Switch to main branch?")) return true;
|
|
2845
|
+
await handleCheckoutMain();
|
|
2846
|
+
return true;
|
|
2847
|
+
}
|
|
2848
|
+
default:
|
|
2849
|
+
return true;
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
async function handleCommit(state) {
|
|
2853
|
+
try {
|
|
2854
|
+
const { stdout: status } = await execa4("git", ["status", "--short"]);
|
|
2855
|
+
if (!status.trim()) {
|
|
2856
|
+
logger.info("No changes to commit");
|
|
2857
|
+
return;
|
|
2858
|
+
}
|
|
2859
|
+
logger.info("Changes:");
|
|
2860
|
+
console.log(status);
|
|
2861
|
+
console.log();
|
|
2862
|
+
await execa4("git", ["add", "-A"]);
|
|
2863
|
+
const { stdout: diffStat } = await execa4("git", ["diff", "--cached", "--stat"]);
|
|
2864
|
+
const { stdout: diffPatch } = await execa4("git", ["diff", "--cached"]);
|
|
2865
|
+
const diffContent = (diffStat + "\n\n" + diffPatch).slice(0, 4e3);
|
|
2866
|
+
const issueNumber = state.issue?.number ?? null;
|
|
2867
|
+
const issueTitle = state.issue?.title ?? null;
|
|
2868
|
+
const provider = state.config.ai.provider;
|
|
2869
|
+
const providerName = getProviderDisplayName(provider);
|
|
2870
|
+
logger.info(`Generating commit message with ${providerName}...`);
|
|
2871
|
+
const message = await generateCommitMessage(diffContent, issueNumber, issueTitle, state);
|
|
2872
|
+
if (message === CANCEL) {
|
|
2873
|
+
await execa4("git", ["reset", "HEAD"]);
|
|
2874
|
+
logger.info("Cancelled");
|
|
2875
|
+
return;
|
|
2876
|
+
}
|
|
2877
|
+
console.log();
|
|
2878
|
+
logger.info(`Message: ${message}`);
|
|
2879
|
+
console.log();
|
|
2880
|
+
if (!await confirm("Commit with this message?")) {
|
|
2881
|
+
await execa4("git", ["reset", "HEAD"]);
|
|
2882
|
+
logger.info("Commit cancelled");
|
|
2883
|
+
return;
|
|
2884
|
+
}
|
|
2885
|
+
const providerEmail = getProviderEmail(provider);
|
|
2886
|
+
const fullMessage = `${message}
|
|
2887
|
+
|
|
2888
|
+
Co-Authored-By: ${providerName} <${providerEmail}>`;
|
|
2889
|
+
const spinner = createSpinner("Committing...");
|
|
2890
|
+
spinner.start();
|
|
2891
|
+
await execa4("git", ["commit", "-m", fullMessage]);
|
|
2892
|
+
spinner.succeed("Changes committed");
|
|
2893
|
+
} catch (error) {
|
|
2894
|
+
logger.error(`Commit failed: ${error}`);
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
async function generateCommitMessage(diffContent, issueNumber, issueTitle, state) {
|
|
2898
|
+
try {
|
|
2899
|
+
const prompt = buildCommitMessagePrompt(diffContent, issueNumber, issueTitle);
|
|
2900
|
+
const result = await invokeAI({ prompt, streamOutput: true }, state.config);
|
|
2901
|
+
let message = result.output.trim().split("\n")[0].trim();
|
|
2902
|
+
for (const q of ['"', "'", "`"]) {
|
|
2903
|
+
if (message.startsWith(q) && message.endsWith(q)) {
|
|
2904
|
+
message = message.slice(1, -1);
|
|
2905
|
+
break;
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
message = message.replace(/^```\w*\s*/, "").replace(/\s*```$/, "");
|
|
2909
|
+
return message;
|
|
2910
|
+
} catch {
|
|
2911
|
+
logger.warning("AI commit message generation failed");
|
|
2912
|
+
console.log();
|
|
2913
|
+
const { message } = await inquirer6.prompt([
|
|
2914
|
+
{
|
|
2915
|
+
type: "input",
|
|
2916
|
+
name: "message",
|
|
2917
|
+
message: "Commit message (empty to cancel):"
|
|
2918
|
+
}
|
|
2919
|
+
]);
|
|
2920
|
+
return message.trim() || CANCEL;
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
async function handlePush() {
|
|
2924
|
+
try {
|
|
2925
|
+
const { stdout: branch } = await execa4("git", ["branch", "--show-current"]);
|
|
2926
|
+
if (!await confirm(`Push ${branch.trim()} to remote?`)) return;
|
|
2927
|
+
const spinner = createSpinner("Pushing...");
|
|
2928
|
+
spinner.start();
|
|
2929
|
+
await execa4("git", ["push", "-u", "origin", branch.trim()]);
|
|
2930
|
+
spinner.succeed("Pushed to remote");
|
|
2931
|
+
} catch (error) {
|
|
2932
|
+
logger.error(`Push failed: ${error}`);
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
var PROVIDERS = ["claude", "gemini", "codex"];
|
|
2936
|
+
async function handleSwitchProvider(state) {
|
|
2937
|
+
const current = state.config.ai.provider;
|
|
2938
|
+
const { provider } = await inquirer6.prompt([
|
|
2939
|
+
{
|
|
2940
|
+
type: "list",
|
|
2941
|
+
name: "provider",
|
|
2942
|
+
message: "Select AI provider:",
|
|
2943
|
+
choices: PROVIDERS.map((p) => ({
|
|
2944
|
+
name: p.charAt(0).toUpperCase() + p.slice(1) + (p === current ? " (current)" : ""),
|
|
2945
|
+
value: p
|
|
2946
|
+
})),
|
|
2947
|
+
default: current
|
|
2948
|
+
}
|
|
2949
|
+
]);
|
|
2950
|
+
if (provider === current) return;
|
|
2951
|
+
try {
|
|
2952
|
+
updateConfigProvider(provider);
|
|
2953
|
+
logger.success(`Provider switched to ${provider}`);
|
|
2954
|
+
} catch (error) {
|
|
2955
|
+
logger.error(`Failed to switch provider: ${error}`);
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
async function handleCheckoutMain() {
|
|
2959
|
+
try {
|
|
2960
|
+
const spinner = createSpinner("Switching to main...");
|
|
2961
|
+
spinner.start();
|
|
2962
|
+
await execa4("git", ["checkout", "main"]);
|
|
2963
|
+
await execa4("git", ["pull"]);
|
|
2964
|
+
spinner.succeed("Switched to main");
|
|
2965
|
+
} catch (error) {
|
|
2966
|
+
logger.error(`Checkout failed: ${error}`);
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
async function handleVideoCapture(state) {
|
|
2970
|
+
if (!state.issue) {
|
|
2971
|
+
logger.error("No linked issue found");
|
|
2972
|
+
return;
|
|
2973
|
+
}
|
|
2974
|
+
try {
|
|
2975
|
+
const agentInstructions = loadAgentInstructions();
|
|
2976
|
+
const videoPrompt = buildVideoPrompt(
|
|
2977
|
+
state.issue.number,
|
|
2978
|
+
state.issue.title,
|
|
2979
|
+
state.config.video,
|
|
2980
|
+
agentInstructions
|
|
2981
|
+
);
|
|
2982
|
+
await invokeAIInteractive(videoPrompt, state.config);
|
|
2983
|
+
logger.success("Video capture completed");
|
|
2984
|
+
} catch (error) {
|
|
2985
|
+
logger.error(`Video capture failed: ${error}`);
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
async function promptContinue() {
|
|
2989
|
+
console.log();
|
|
2990
|
+
await inquirer6.prompt([
|
|
2991
|
+
{
|
|
2992
|
+
type: "input",
|
|
2993
|
+
name: "continue",
|
|
2994
|
+
message: "Press Enter to continue..."
|
|
2995
|
+
}
|
|
2996
|
+
]);
|
|
2997
|
+
}
|
|
2998
|
+
async function tuiCommand() {
|
|
2999
|
+
let running = true;
|
|
3000
|
+
while (running) {
|
|
3001
|
+
const state = await loadState();
|
|
3002
|
+
clearScreen();
|
|
3003
|
+
const actions = getAvailableActions(state);
|
|
3004
|
+
let hint;
|
|
3005
|
+
if (state.isOnMain) {
|
|
3006
|
+
hint = "Select an action to get started";
|
|
3007
|
+
} else if (state.hasUncommittedChanges && !state.pr) {
|
|
3008
|
+
hint = "Commit your changes before creating a PR";
|
|
3009
|
+
} else if (state.hasActionableFeedback) {
|
|
3010
|
+
hint = "Review feedback needs attention";
|
|
3011
|
+
}
|
|
3012
|
+
renderDashboard(state, actions, hint);
|
|
3013
|
+
const validKeys = actions.map((a) => a.shortcut);
|
|
3014
|
+
const key = await waitForKey(validKeys);
|
|
3015
|
+
const action = actions.find((a) => a.shortcut === key);
|
|
3016
|
+
if (action) {
|
|
3017
|
+
running = await executeAction(action.id, state);
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
|
|
2178
3022
|
// src/index.ts
|
|
2179
3023
|
var version = getVersion();
|
|
2180
3024
|
function startVersionCheck() {
|
|
@@ -2212,8 +3056,12 @@ program.command("list").description("List GitHub issues by label/status").option
|
|
|
2212
3056
|
program.command("run [issue-number]").description("Run AI to implement a GitHub issue").option("-a, --auto", "Auto-select highest priority ai-ready issue").option("-p, --provider <provider>", "AI provider to use (claude, gemini, or codex)").action(async (issueNumber, options) => {
|
|
2213
3057
|
await runCommand(issueNumber, { auto: options.auto, provider: options.provider });
|
|
2214
3058
|
});
|
|
2215
|
-
program.command("pr").description("Create an AI-enhanced pull request").option("-d, --draft", "Create as draft PR").option("-p, --provider <provider>", "AI provider to use (claude, gemini, or codex)").action(async (options) => {
|
|
2216
|
-
await prCommand({
|
|
3059
|
+
program.command("pr").description("Create an AI-enhanced pull request").option("-d, --draft", "Create as draft PR").option("-p, --provider <provider>", "AI provider to use (claude, gemini, or codex)").option("--no-video", "Disable video capture for UI changes").action(async (options) => {
|
|
3060
|
+
await prCommand({
|
|
3061
|
+
draft: options.draft,
|
|
3062
|
+
provider: options.provider,
|
|
3063
|
+
video: options.video
|
|
3064
|
+
});
|
|
2217
3065
|
});
|
|
2218
3066
|
program.command("fix").description("Apply PR review feedback using AI").option("-p, --provider <provider>", "AI provider to use (claude, gemini, or codex)").action(async (options) => {
|
|
2219
3067
|
await fixCommand({ provider: options.provider });
|
|
@@ -2221,5 +3069,11 @@ program.command("fix").description("Apply PR review feedback using AI").option("
|
|
|
2221
3069
|
program.command("status").description("Show current workflow status").action(async () => {
|
|
2222
3070
|
await statusCommand();
|
|
2223
3071
|
});
|
|
3072
|
+
program.command("ui").description("Launch interactive dashboard").action(async () => {
|
|
3073
|
+
await tuiCommand();
|
|
3074
|
+
});
|
|
3075
|
+
program.action(async () => {
|
|
3076
|
+
await tuiCommand();
|
|
3077
|
+
});
|
|
2224
3078
|
program.parse();
|
|
2225
3079
|
//# sourceMappingURL=index.js.map
|