@rotorsoft/gent 1.11.0 → 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/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-3N6U3NTN.js";
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-HKXXO65J.js");
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) {
@@ -1836,7 +1894,7 @@ import { homedir } from "os";
1836
1894
  // package.json
1837
1895
  var package_default = {
1838
1896
  name: "@rotorsoft/gent",
1839
- version: "1.11.0",
1897
+ version: "1.12.0",
1840
1898
  description: "AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs",
1841
1899
  keywords: [
1842
1900
  "cli",
@@ -2245,6 +2303,722 @@ async function statusCommand() {
2245
2303
  }
2246
2304
  }
2247
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
+
2248
3022
  // src/index.ts
2249
3023
  var version = getVersion();
2250
3024
  function startVersionCheck() {
@@ -2295,5 +3069,11 @@ program.command("fix").description("Apply PR review feedback using AI").option("
2295
3069
  program.command("status").description("Show current workflow status").action(async () => {
2296
3070
  await statusCommand();
2297
3071
  });
3072
+ program.command("ui").description("Launch interactive dashboard").action(async () => {
3073
+ await tuiCommand();
3074
+ });
3075
+ program.action(async () => {
3076
+ await tuiCommand();
3077
+ });
2298
3078
  program.parse();
2299
3079
  //# sourceMappingURL=index.js.map