clairo 1.0.9 → 1.2.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.
Files changed (3) hide show
  1. package/README.md +13 -13
  2. package/dist/cli.js +551 -372
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -4,13 +4,14 @@
4
4
  import meow from "meow";
5
5
 
6
6
  // src/app.tsx
7
- import { useCallback as useCallback10, useMemo as useMemo2, useState as useState17 } from "react";
7
+ import { useCallback as useCallback9, useMemo as useMemo2, useState as useState16 } from "react";
8
8
  import { Box as Box16, useApp, useInput as useInput13 } from "ink";
9
9
 
10
10
  // src/components/github/GitHubView.tsx
11
- import { useCallback as useCallback9, useEffect as useEffect9, useRef as useRef5, useState as useState12 } from "react";
11
+ import { useCallback as useCallback8, useEffect as useEffect8, useRef as useRef5, useState as useState11 } from "react";
12
12
  import { TitledBox as TitledBox3 } from "@mishieck/ink-titled-box";
13
13
  import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
14
+ import { ScrollView as ScrollView4 } from "ink-scroll-view";
14
15
 
15
16
  // src/hooks/github/useGitRepo.ts
16
17
  import { useCallback, useEffect as useEffect2, useMemo, useState as useState2 } from "react";
@@ -170,9 +171,40 @@ function findRemoteWithBranch(branch) {
170
171
  }
171
172
 
172
173
  // src/lib/github/index.ts
173
- import { exec } from "child_process";
174
+ import { exec, execFile } from "child_process";
174
175
  import { promisify } from "util";
175
176
  var execAsync = promisify(exec);
177
+ function resolveCheckStatus(check) {
178
+ const conclusion = check.conclusion ?? check.state;
179
+ if (conclusion === "SUCCESS" || check.status === "COMPLETED") return "success";
180
+ if (conclusion === "FAILURE" || conclusion === "ERROR") return "failure";
181
+ if (conclusion === "SKIPPED" || conclusion === "NEUTRAL") return "skipped";
182
+ if (conclusion === "PENDING" || check.status === "IN_PROGRESS" || check.status === "QUEUED" || check.status === "WAITING")
183
+ return "pending";
184
+ return "pending";
185
+ }
186
+ var CHECK_COLORS = {
187
+ success: "green",
188
+ failure: "red",
189
+ pending: "yellow",
190
+ skipped: "gray"
191
+ };
192
+ var CHECK_ICONS = { success: "\u2713", failure: "\u2717", pending: "\u25CF", skipped: "\u25CB" };
193
+ var CHECK_SORT_ORDER = { failure: 0, pending: 1, skipped: 2, success: 3 };
194
+ function resolveReviewDisplay(reviewDecision) {
195
+ const status = reviewDecision ?? "PENDING";
196
+ if (status === "APPROVED") return { text: status, color: "green" };
197
+ if (status === "CHANGES_REQUESTED") return { text: status, color: "red" };
198
+ return { text: status, color: "yellow" };
199
+ }
200
+ function resolveMergeDisplay(pr) {
201
+ if (!pr) return { text: "UNKNOWN", color: "yellow" };
202
+ if (pr.state === "MERGED") return { text: "MERGED", color: "magenta" };
203
+ if (pr.state === "CLOSED") return { text: "CLOSED", color: "red" };
204
+ if (pr.mergeable === "MERGEABLE") return { text: "MERGEABLE", color: "green" };
205
+ if (pr.mergeable === "CONFLICTING") return { text: "CONFLICTING", color: "red" };
206
+ return { text: pr.mergeable ?? "UNKNOWN", color: "yellow" };
207
+ }
176
208
  async function isGhInstalled() {
177
209
  try {
178
210
  await execAsync("gh --version");
@@ -220,12 +252,10 @@ async function listPRsForBranch(branch, repo) {
220
252
  }
221
253
  try {
222
254
  const { stdout } = await execAsync(
223
- `gh pr list --state open --json ${fields},headRefName --repo "${repo}" 2>/dev/null`
255
+ `gh pr list --state open --head "${branch}" --json ${fields} --repo "${repo}" 2>/dev/null`
224
256
  );
225
- const allPrs = JSON.parse(stdout);
226
- const prs = allPrs.filter((pr) => pr.headRefName === branch || pr.headRefName.endsWith(`:${branch}`));
227
- const result = prs.map(({ headRefName: _headRefName, ...rest }) => rest);
228
- return { success: true, data: result };
257
+ const prs = JSON.parse(stdout);
258
+ return { success: true, data: prs };
229
259
  } catch {
230
260
  return { success: false, error: "Failed to fetch PRs", errorType: "api_error" };
231
261
  }
@@ -282,6 +312,34 @@ function openPRCreationPage(owner, branch, onComplete) {
282
312
  onComplete == null ? void 0 : onComplete(error);
283
313
  });
284
314
  }
315
+ function openPRCreationPageWithContent(owner, branch, title, body, onComplete) {
316
+ const headFlag = `${owner}:${branch}`;
317
+ const args = ["pr", "create", "--web", "--head", headFlag, "--title", title, "--body", body];
318
+ execFile("gh", args, (error) => {
319
+ process.stdout.emit("resize");
320
+ onComplete == null ? void 0 : onComplete(error);
321
+ });
322
+ }
323
+ function editPRDescription(prNumber, repo, title, body, onComplete) {
324
+ const args = [
325
+ "api",
326
+ `repos/${repo}/pulls/${prNumber}`,
327
+ "--method",
328
+ "PATCH",
329
+ "--field",
330
+ `title=${title}`,
331
+ "--field",
332
+ `body=${body}`
333
+ ];
334
+ execFile("gh", args, (error, _stdout, stderr) => {
335
+ if (error) {
336
+ const message = (stderr == null ? void 0 : stderr.trim()) || error.message;
337
+ onComplete == null ? void 0 : onComplete(new Error(message));
338
+ } else {
339
+ onComplete == null ? void 0 : onComplete(null);
340
+ }
341
+ });
342
+ }
285
343
 
286
344
  // src/hooks/useTerminalFocus.ts
287
345
  import { useEffect, useState } from "react";
@@ -393,6 +451,9 @@ function useGitRepo() {
393
451
  };
394
452
  }
395
453
 
454
+ // src/hooks/github/usePullRequests.ts
455
+ import { useCallback as useCallback3, useState as useState4 } from "react";
456
+
396
457
  // src/hooks/github/usePRPolling.ts
397
458
  import { useCallback as useCallback2, useEffect as useEffect3, useRef, useState as useState3 } from "react";
398
459
  function usePRPolling() {
@@ -455,11 +516,11 @@ function usePRPolling() {
455
516
  }
456
517
 
457
518
  // src/hooks/github/usePullRequests.ts
458
- import { useCallback as useCallback3, useState as useState4 } from "react";
459
519
  function usePullRequests() {
460
520
  const [prs, setPrs] = useState4([]);
461
521
  const [selectedPR, setSelectedPR] = useState4(null);
462
522
  const [prDetails, setPrDetails] = useState4(null);
523
+ const polling = usePRPolling();
463
524
  const [loading, setLoading] = useState4({
464
525
  prs: false,
465
526
  details: false
@@ -527,6 +588,26 @@ function usePullRequests() {
527
588
  const setError = useCallback3((key, message) => {
528
589
  setErrors((prev) => ({ ...prev, [key]: message }));
529
590
  }, []);
591
+ const pollForNewPR = useCallback3(
592
+ (options) => {
593
+ const existingPRNumbers = prs.map((pr) => pr.number);
594
+ polling.startPolling({
595
+ branch: options.branch,
596
+ repoSlug: options.repoSlug,
597
+ existingPRNumbers,
598
+ onPRsUpdated: (updatedPrs) => {
599
+ setPrs(updatedPrs);
600
+ },
601
+ onNewPR: (newPR) => {
602
+ var _a;
603
+ setSelectedPR(newPR);
604
+ refreshDetails(newPR, options.repoSlug);
605
+ (_a = options.onNewPR) == null ? void 0 : _a.call(options, newPR);
606
+ }
607
+ });
608
+ },
609
+ [prs, polling.startPolling, refreshDetails]
610
+ );
530
611
  return {
531
612
  prs,
532
613
  selectedPR,
@@ -538,12 +619,105 @@ function usePullRequests() {
538
619
  loading,
539
620
  errors,
540
621
  setError,
541
- // Expose setters for cases where external code needs to update state directly
542
- setPrs,
543
- setSelectedPR
622
+ pollForNewPR,
623
+ stopPolling: polling.stopPolling,
624
+ isPolling: polling.isPolling
544
625
  };
545
626
  }
546
627
 
628
+ // src/lib/claude/api.ts
629
+ import { exec as exec2 } from "child_process";
630
+ function runClaudePrompt(prompt) {
631
+ let childProcess = null;
632
+ let cancelled = false;
633
+ const promise = new Promise((resolve) => {
634
+ const escapedPrompt = prompt.replace(/'/g, "'\\''").replace(/\n/g, "\\n");
635
+ const command = `claude -p $'${escapedPrompt}' --output-format json < /dev/null`;
636
+ childProcess = exec2(command, { maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
637
+ if (cancelled) {
638
+ resolve({
639
+ success: false,
640
+ error: "Cancelled",
641
+ errorType: "execution_error"
642
+ });
643
+ return;
644
+ }
645
+ if (error) {
646
+ if (error.message.includes("command not found") || error.message.includes("ENOENT") || error.code === "ENOENT") {
647
+ resolve({
648
+ success: false,
649
+ error: "Claude CLI not installed. Run: npm install -g @anthropic-ai/claude-code",
650
+ errorType: "not_installed"
651
+ });
652
+ return;
653
+ }
654
+ resolve({
655
+ success: false,
656
+ error: (stderr == null ? void 0 : stderr.trim()) || error.message,
657
+ errorType: "execution_error"
658
+ });
659
+ return;
660
+ }
661
+ if (!(stdout == null ? void 0 : stdout.trim())) {
662
+ resolve({
663
+ success: false,
664
+ error: (stderr == null ? void 0 : stderr.trim()) || "Claude returned empty response",
665
+ errorType: "execution_error"
666
+ });
667
+ return;
668
+ }
669
+ try {
670
+ const json = JSON.parse(stdout.trim());
671
+ if (json.is_error) {
672
+ resolve({
673
+ success: false,
674
+ error: json.result || "Claude returned an error",
675
+ errorType: "execution_error"
676
+ });
677
+ return;
678
+ }
679
+ resolve({
680
+ success: true,
681
+ data: json.result || stdout.trim()
682
+ });
683
+ } catch {
684
+ resolve({
685
+ success: true,
686
+ data: stdout.trim()
687
+ });
688
+ }
689
+ });
690
+ });
691
+ const cancel = () => {
692
+ cancelled = true;
693
+ if (childProcess) {
694
+ childProcess.kill("SIGTERM");
695
+ }
696
+ };
697
+ return { promise, cancel };
698
+ }
699
+ function generatePRContent() {
700
+ const prompt = `Your output will be used directly to pre-fill a GitHub pull request creation form. Analyze the current branch's changes and return ONLY a raw JSON object (no markdown fences, no explanation) with "title" and "body" fields.
701
+
702
+ Rules:
703
+ - "title": a concise PR title in imperative mood, under 72 characters
704
+ - "body": a markdown PR description summarizing what changed and why. Use bullet points for multiple changes.
705
+ - Do not include any text outside the JSON object
706
+ - Do not add any "Co-Authored-By" or attribution lines`;
707
+ return runClaudePrompt(prompt);
708
+ }
709
+ function generateStandupNotes(logContent) {
710
+ const prompt = `You are helping a developer prepare standup notes. Based on the following log entries, generate concise standup notes that summarize what was accomplished.
711
+
712
+ Format the output as bullet points grouped by category (e.g., Features, Bug Fixes, Refactoring, etc.). Keep it brief and professional.
713
+
714
+ Log entries:
715
+ ${logContent}
716
+
717
+ Generate the standup notes:`;
718
+ return runClaudePrompt(prompt);
719
+ }
720
+
547
721
  // src/lib/jira/parser.ts
548
722
  var TICKET_KEY_PATTERN = /^[A-Z][A-Z0-9]+-\d+$/;
549
723
  function isValidTicketKeyFormat(key) {
@@ -1042,52 +1216,14 @@ function renderInlineToString(tokens) {
1042
1216
 
1043
1217
  // src/components/github/PRDetailsBox.tsx
1044
1218
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1045
- function getCheckColor(check) {
1046
- const conclusion = check.conclusion ?? check.state;
1047
- if (conclusion === "SUCCESS") return "green";
1048
- if (conclusion === "FAILURE" || conclusion === "ERROR") return "red";
1049
- if (conclusion === "SKIPPED" || conclusion === "NEUTRAL") return "gray";
1050
- if (conclusion === "PENDING" || check.status === "IN_PROGRESS" || check.status === "QUEUED" || check.status === "WAITING")
1051
- return "yellow";
1052
- if (check.status === "COMPLETED") return "green";
1053
- return void 0;
1054
- }
1055
- function getCheckIcon(check) {
1056
- const conclusion = check.conclusion ?? check.state;
1057
- if (conclusion === "SUCCESS") return "\u2713";
1058
- if (conclusion === "FAILURE" || conclusion === "ERROR") return "\u2717";
1059
- if (conclusion === "SKIPPED" || conclusion === "NEUTRAL") return "\u25CB";
1060
- if (conclusion === "PENDING" || check.status === "IN_PROGRESS" || check.status === "QUEUED" || check.status === "WAITING")
1061
- return "\u25CF";
1062
- if (check.status === "COMPLETED") return "\u2713";
1063
- return "?";
1064
- }
1065
- function getCheckSortOrder(check) {
1066
- const conclusion = check.conclusion ?? check.state;
1067
- if (conclusion === "FAILURE" || conclusion === "ERROR") return 0;
1068
- if (conclusion === "PENDING" || check.status === "IN_PROGRESS" || check.status === "QUEUED" || check.status === "WAITING")
1069
- return 1;
1070
- if (conclusion === "SKIPPED" || conclusion === "NEUTRAL") return 2;
1071
- if (conclusion === "SUCCESS" || check.status === "COMPLETED") return 3;
1072
- return 4;
1073
- }
1074
- function PRDetailsBox({ pr, loading, error, isFocused }) {
1219
+ function PRDetailsBox({ pr, loading, error, isActive }) {
1075
1220
  var _a, _b, _c, _d, _e, _f, _g;
1076
1221
  const scrollRef = useRef2(null);
1077
1222
  const title = "[3] PR Details";
1078
- const borderColor = isFocused ? "yellow" : void 0;
1223
+ const borderColor = isActive ? "yellow" : void 0;
1079
1224
  const displayTitle = pr ? `${title} - #${pr.number}` : title;
1080
- const reviewStatus = (pr == null ? void 0 : pr.reviewDecision) ?? "PENDING";
1081
- const reviewColor = reviewStatus === "APPROVED" ? "green" : reviewStatus === "CHANGES_REQUESTED" ? "red" : "yellow";
1082
- const getMergeDisplay = () => {
1083
- if (!pr) return { text: "UNKNOWN", color: "yellow" };
1084
- if (pr.state === "MERGED") return { text: "MERGED", color: "magenta" };
1085
- if (pr.state === "CLOSED") return { text: "CLOSED", color: "red" };
1086
- if (pr.mergeable === "MERGEABLE") return { text: "MERGEABLE", color: "green" };
1087
- if (pr.mergeable === "CONFLICTING") return { text: "CONFLICTING", color: "red" };
1088
- return { text: pr.mergeable ?? "UNKNOWN", color: "yellow" };
1089
- };
1090
- const mergeDisplay = getMergeDisplay();
1225
+ const reviewDisplay = resolveReviewDisplay((pr == null ? void 0 : pr.reviewDecision) ?? null);
1226
+ const mergeDisplay = resolveMergeDisplay(pr);
1091
1227
  useInput(
1092
1228
  (input, key) => {
1093
1229
  var _a2, _b2;
@@ -1102,7 +1238,7 @@ function PRDetailsBox({ pr, loading, error, isFocused }) {
1102
1238
  });
1103
1239
  }
1104
1240
  },
1105
- { isActive: isFocused }
1241
+ { isActive }
1106
1242
  );
1107
1243
  const { stdout } = useStdout();
1108
1244
  const terminalWidth = (stdout == null ? void 0 : stdout.columns) ?? 80;
@@ -1137,7 +1273,7 @@ function PRDetailsBox({ pr, loading, error, isFocused }) {
1137
1273
  ] }),
1138
1274
  /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
1139
1275
  /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Review: " }),
1140
- /* @__PURE__ */ jsx2(Text2, { color: reviewColor, children: reviewStatus }),
1276
+ /* @__PURE__ */ jsx2(Text2, { color: reviewDisplay.color, children: reviewDisplay.text }),
1141
1277
  /* @__PURE__ */ jsx2(Text2, { children: " | " }),
1142
1278
  /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Status: " }),
1143
1279
  /* @__PURE__ */ jsx2(Text2, { color: mergeDisplay.color, children: mergeDisplay.text })
@@ -1174,12 +1310,13 @@ function PRDetailsBox({ pr, loading, error, isFocused }) {
1174
1310
  }
1175
1311
  return acc;
1176
1312
  }, /* @__PURE__ */ new Map()).values()) ?? []
1177
- ).sort((a, b) => getCheckSortOrder(a) - getCheckSortOrder(b)).map((check, idx) => {
1313
+ ).sort((a, b) => CHECK_SORT_ORDER[resolveCheckStatus(a)] - CHECK_SORT_ORDER[resolveCheckStatus(b)]).map((check, idx) => {
1178
1314
  const jobName = check.name ?? check.context;
1179
1315
  const displayName = check.workflowName ? `${check.workflowName} / ${jobName}` : jobName;
1180
- return /* @__PURE__ */ jsxs2(Text2, { color: getCheckColor(check), children: [
1316
+ const status = resolveCheckStatus(check);
1317
+ return /* @__PURE__ */ jsxs2(Text2, { color: CHECK_COLORS[status], children: [
1181
1318
  " ",
1182
- getCheckIcon(check),
1319
+ CHECK_ICONS[status],
1183
1320
  " ",
1184
1321
  displayName
1185
1322
  ] }, idx);
@@ -1198,9 +1335,9 @@ function PRDetailsBox({ pr, loading, error, isFocused }) {
1198
1335
 
1199
1336
  // src/components/github/PullRequestsBox.tsx
1200
1337
  import open2 from "open";
1201
- import { useEffect as useEffect7, useState as useState10 } from "react";
1338
+ import { useState as useState10 } from "react";
1202
1339
  import { TitledBox } from "@mishieck/ink-titled-box";
1203
- import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
1340
+ import { Box as Box3, Text as Text3, useInput as useInput3 } from "ink";
1204
1341
  import { ScrollView as ScrollView2 } from "ink-scroll-view";
1205
1342
 
1206
1343
  // src/hooks/jira/useJiraTickets.ts
@@ -1458,25 +1595,8 @@ function useModal() {
1458
1595
  }
1459
1596
 
1460
1597
  // src/hooks/useListNavigation.ts
1461
- import { useCallback as useCallback7, useState as useState8 } from "react";
1462
- function useListNavigation(length) {
1463
- const [index, setIndex] = useState8(0);
1464
- const prev = useCallback7(() => {
1465
- setIndex((i) => Math.max(0, i - 1));
1466
- }, []);
1467
- const next = useCallback7(() => {
1468
- setIndex((i) => Math.min(length - 1, i + 1));
1469
- }, [length]);
1470
- const clampedIndex = Math.min(index, Math.max(0, length - 1));
1471
- const reset = useCallback7(() => setIndex(0), []);
1472
- return {
1473
- index: length === 0 ? 0 : clampedIndex,
1474
- prev,
1475
- next,
1476
- reset,
1477
- setIndex
1478
- };
1479
- }
1598
+ import { useEffect as useEffect6, useState as useState8 } from "react";
1599
+ import { useInput as useInput2 } from "ink";
1480
1600
 
1481
1601
  // src/hooks/useScrollToIndex.ts
1482
1602
  import { useEffect as useEffect5, useRef as useRef4 } from "react";
@@ -1498,8 +1618,53 @@ function useScrollToIndex(index) {
1498
1618
  return scrollRef;
1499
1619
  }
1500
1620
 
1621
+ // src/hooks/useListNavigation.ts
1622
+ function useListNavigation({
1623
+ items,
1624
+ totalItems,
1625
+ selectedIndex,
1626
+ onSelect,
1627
+ isActive
1628
+ }) {
1629
+ const navLength = totalItems ?? items.length;
1630
+ const [highlightedIndex, setHighlightedIndex] = useState8(0);
1631
+ const scrollRef = useScrollToIndex(highlightedIndex);
1632
+ useEffect6(() => {
1633
+ if (selectedIndex !== void 0 && selectedIndex >= 0) {
1634
+ setHighlightedIndex(selectedIndex);
1635
+ }
1636
+ }, [selectedIndex]);
1637
+ const prev = () => setHighlightedIndex((i) => Math.max(0, i - 1));
1638
+ const next = () => setHighlightedIndex((i) => Math.min(navLength - 1, i + 1));
1639
+ useInput2(
1640
+ (input, key) => {
1641
+ if (navLength === 0) return;
1642
+ if (key.upArrow || input === "k") {
1643
+ prev();
1644
+ }
1645
+ if (key.downArrow || input === "j") {
1646
+ next();
1647
+ }
1648
+ if (input === " " && onSelect) {
1649
+ onSelect(highlightedIndex);
1650
+ }
1651
+ },
1652
+ { isActive: isActive === true }
1653
+ );
1654
+ const clampedIndex = navLength === 0 ? 0 : Math.min(highlightedIndex, Math.max(0, navLength - 1));
1655
+ return {
1656
+ highlightedIndex: clampedIndex,
1657
+ index: clampedIndex,
1658
+ scrollRef,
1659
+ prev,
1660
+ next,
1661
+ reset: () => setHighlightedIndex(0),
1662
+ setIndex: setHighlightedIndex
1663
+ };
1664
+ }
1665
+
1501
1666
  // src/hooks/useRubberDuck.ts
1502
- import { useCallback as useCallback8, useEffect as useEffect6, useState as useState9 } from "react";
1667
+ import { useCallback as useCallback7, useEffect as useEffect7, useState as useState9 } from "react";
1503
1668
  var DUCK_MESSAGES = [
1504
1669
  "Quack.",
1505
1670
  "Quack quack quack.",
@@ -1537,18 +1702,18 @@ function useRubberDuck() {
1537
1702
  visible: false,
1538
1703
  message: DUCK_MESSAGES[0]
1539
1704
  });
1540
- const getRandomMessage = useCallback8(() => {
1705
+ const getRandomMessage = useCallback7(() => {
1541
1706
  const index = Math.floor(Math.random() * DUCK_MESSAGES.length);
1542
1707
  return DUCK_MESSAGES[index];
1543
1708
  }, []);
1544
- const toggleDuck = useCallback8(() => {
1709
+ const toggleDuck = useCallback7(() => {
1545
1710
  setState((prev) => ({
1546
1711
  ...prev,
1547
1712
  visible: !prev.visible,
1548
1713
  message: !prev.visible ? getRandomMessage() : prev.message
1549
1714
  }));
1550
1715
  }, [getRandomMessage]);
1551
- const quack = useCallback8(() => {
1716
+ const quack = useCallback7(() => {
1552
1717
  if (state.visible) {
1553
1718
  setState((prev) => ({
1554
1719
  ...prev,
@@ -1556,11 +1721,11 @@ function useRubberDuck() {
1556
1721
  }));
1557
1722
  }
1558
1723
  }, [state.visible, getRandomMessage]);
1559
- const getReactionMessage = useCallback8((event) => {
1724
+ const getReactionMessage = useCallback7((event) => {
1560
1725
  const messages = REACTION_MESSAGES[event];
1561
1726
  return messages[Math.floor(Math.random() * messages.length)];
1562
1727
  }, []);
1563
- useEffect6(() => {
1728
+ useEffect7(() => {
1564
1729
  const unsubscribe = duckEvents.subscribe((event) => {
1565
1730
  setState((prev) => ({
1566
1731
  ...prev,
@@ -1579,12 +1744,12 @@ function useRubberDuck() {
1579
1744
  }
1580
1745
 
1581
1746
  // src/lib/clipboard.ts
1582
- import { exec as exec2 } from "child_process";
1747
+ import { exec as exec3 } from "child_process";
1583
1748
  async function copyToClipboard(text) {
1584
1749
  var _a, _b;
1585
1750
  const command = process.platform === "darwin" ? "pbcopy" : process.platform === "win32" ? "clip" : "xclip -selection clipboard";
1586
1751
  try {
1587
- const child = exec2(command);
1752
+ const child = exec3(command);
1588
1753
  (_a = child.stdin) == null ? void 0 : _a.write(text);
1589
1754
  (_b = child.stdin) == null ? void 0 : _b.end();
1590
1755
  await new Promise((resolve, reject) => {
@@ -1610,32 +1775,26 @@ function PullRequestsBox({
1610
1775
  error,
1611
1776
  branch,
1612
1777
  repoSlug,
1613
- isFocused
1778
+ isActive,
1779
+ isGeneratingPR
1614
1780
  }) {
1615
- const [highlightedIndex, setHighlightedIndex] = useState10(0);
1616
1781
  const [copied, setCopied] = useState10(false);
1617
- const scrollRef = useScrollToIndex(highlightedIndex);
1618
- const totalItems = prs.length + 1;
1619
- useEffect7(() => {
1620
- const idx = prs.findIndex((p) => p.number === (selectedPR == null ? void 0 : selectedPR.number));
1621
- if (idx >= 0) setHighlightedIndex(idx);
1622
- }, [selectedPR, prs]);
1623
- useInput2(
1624
- (input, key) => {
1625
- if (!isFocused) return;
1626
- if (key.upArrow || input === "k") {
1627
- setHighlightedIndex((prev) => Math.max(0, prev - 1));
1628
- }
1629
- if (key.downArrow || input === "j") {
1630
- setHighlightedIndex((prev) => Math.min(totalItems - 1, prev + 1));
1631
- }
1632
- if (input === " ") {
1633
- if (highlightedIndex === prs.length) {
1634
- onCreatePR();
1635
- } else if (prs[highlightedIndex]) {
1636
- onSelect(prs[highlightedIndex]);
1637
- }
1782
+ const selectedIndex = prs.findIndex((p) => p.number === (selectedPR == null ? void 0 : selectedPR.number));
1783
+ const { highlightedIndex, scrollRef } = useListNavigation({
1784
+ items: prs,
1785
+ totalItems: prs.length + 1,
1786
+ selectedIndex: selectedIndex >= 0 ? selectedIndex : void 0,
1787
+ onSelect: (index) => {
1788
+ if (index === prs.length) {
1789
+ onCreatePR();
1790
+ } else if (prs[index]) {
1791
+ onSelect(prs[index]);
1638
1792
  }
1793
+ },
1794
+ isActive
1795
+ });
1796
+ useInput3(
1797
+ (input) => {
1639
1798
  if (input === "y" && repoSlug && prs[highlightedIndex]) {
1640
1799
  const pr = prs[highlightedIndex];
1641
1800
  const url = `https://github.com/${repoSlug}/pull/${pr.number}`;
@@ -1650,12 +1809,12 @@ function PullRequestsBox({
1650
1809
  });
1651
1810
  }
1652
1811
  },
1653
- { isActive: isFocused }
1812
+ { isActive }
1654
1813
  );
1655
1814
  const title = "[2] Pull Requests";
1656
1815
  const subtitle = branch ? ` (${branch})` : "";
1657
1816
  const copiedIndicator = copied ? " [Copied!]" : "";
1658
- const borderColor = isFocused ? "yellow" : void 0;
1817
+ const borderColor = isActive ? "yellow" : void 0;
1659
1818
  return /* @__PURE__ */ jsx3(
1660
1819
  TitledBox,
1661
1820
  {
@@ -1666,10 +1825,11 @@ function PullRequestsBox({
1666
1825
  children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: [
1667
1826
  loading && /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Loading PRs..." }),
1668
1827
  error && /* @__PURE__ */ jsx3(Text3, { color: "red", children: error }),
1828
+ isGeneratingPR && /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "Generating PR with Claude... (Esc to cancel)" }),
1669
1829
  !loading && !error && /* @__PURE__ */ jsxs3(ScrollView2, { ref: scrollRef, children: [
1670
1830
  prs.length === 0 && /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "No PRs for this branch" }, "empty"),
1671
1831
  prs.map((pr, idx) => {
1672
- const isHighlighted = isFocused && idx === highlightedIndex;
1832
+ const isHighlighted = isActive && idx === highlightedIndex;
1673
1833
  const isSelected = pr.number === (selectedPR == null ? void 0 : selectedPR.number);
1674
1834
  const cursor = isHighlighted ? ">" : " ";
1675
1835
  const indicator = isSelected ? " *" : "";
@@ -1689,7 +1849,7 @@ function PullRequestsBox({
1689
1849
  ] }, pr.number);
1690
1850
  }),
1691
1851
  /* @__PURE__ */ jsxs3(Text3, { color: "blue", children: [
1692
- isFocused && highlightedIndex === prs.length ? "> " : " ",
1852
+ isActive && highlightedIndex === prs.length ? "> " : " ",
1693
1853
  "+ Create new PR"
1694
1854
  ] }, "create")
1695
1855
  ] })
@@ -1699,41 +1859,26 @@ function PullRequestsBox({
1699
1859
  }
1700
1860
 
1701
1861
  // src/components/github/RemotesBox.tsx
1702
- import { useEffect as useEffect8, useState as useState11 } from "react";
1703
1862
  import { TitledBox as TitledBox2 } from "@mishieck/ink-titled-box";
1704
- import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
1863
+ import { Box as Box4, Text as Text4 } from "ink";
1705
1864
  import { ScrollView as ScrollView3 } from "ink-scroll-view";
1706
1865
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1707
- function RemotesBox({ remotes, selectedRemote, onSelect, loading, error, isFocused }) {
1708
- const [highlightedIndex, setHighlightedIndex] = useState11(0);
1709
- const scrollRef = useScrollToIndex(highlightedIndex);
1710
- useEffect8(() => {
1711
- const idx = remotes.findIndex((r) => r.name === selectedRemote);
1712
- if (idx >= 0) setHighlightedIndex(idx);
1713
- }, [selectedRemote, remotes]);
1714
- useInput3(
1715
- (input, key) => {
1716
- if (!isFocused || remotes.length === 0) return;
1717
- if (key.upArrow || input === "k") {
1718
- setHighlightedIndex((prev) => Math.max(0, prev - 1));
1719
- }
1720
- if (key.downArrow || input === "j") {
1721
- setHighlightedIndex((prev) => Math.min(remotes.length - 1, prev + 1));
1722
- }
1723
- if (input === " ") {
1724
- onSelect(remotes[highlightedIndex].name);
1725
- }
1726
- },
1727
- { isActive: isFocused }
1728
- );
1866
+ function RemotesBox({ remotes, selectedRemote, onSelect, loading, error, isActive }) {
1867
+ const selectedIndex = remotes.findIndex((r) => r.name === selectedRemote);
1868
+ const { highlightedIndex, scrollRef } = useListNavigation({
1869
+ items: remotes,
1870
+ selectedIndex: selectedIndex >= 0 ? selectedIndex : void 0,
1871
+ onSelect: (index) => onSelect(remotes[index].name),
1872
+ isActive
1873
+ });
1729
1874
  const title = "[1] Remotes";
1730
- const borderColor = isFocused ? "yellow" : void 0;
1875
+ const borderColor = isActive ? "yellow" : void 0;
1731
1876
  return /* @__PURE__ */ jsx4(TitledBox2, { borderStyle: "round", titles: [title], borderColor, height: 5, children: /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: [
1732
1877
  loading && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Loading..." }),
1733
1878
  error && /* @__PURE__ */ jsx4(Text4, { color: "red", children: error }),
1734
1879
  !loading && !error && remotes.length === 0 && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "No remotes configured" }),
1735
1880
  !loading && !error && remotes.length > 0 && /* @__PURE__ */ jsx4(ScrollView3, { ref: scrollRef, children: remotes.map((remote, idx) => {
1736
- const isHighlighted = isFocused && idx === highlightedIndex;
1881
+ const isHighlighted = isActive && idx === highlightedIndex;
1737
1882
  const isSelected = remote.name === selectedRemote;
1738
1883
  const cursor = isHighlighted ? ">" : " ";
1739
1884
  const indicator = isSelected ? " *" : "";
@@ -1756,91 +1901,199 @@ function RemotesBox({ remotes, selectedRemote, onSelect, loading, error, isFocus
1756
1901
 
1757
1902
  // src/components/github/GitHubView.tsx
1758
1903
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1759
- function GitHubView({ isFocused, onFocusedBoxChange, onLogUpdated }) {
1904
+ function GitHubView({ isActive, onFocusedBoxChange, onLogUpdated }) {
1760
1905
  const repo = useGitRepo();
1761
1906
  const pullRequests = usePullRequests();
1762
- const polling = usePRPolling();
1763
- const [focusedBox, setFocusedBox] = useState12("remotes");
1764
- const lastFetchedRef = useRef5(null);
1765
- useEffect9(() => {
1907
+ const [focusedBox, setFocusedBox] = useState11("remotes");
1908
+ const [isGeneratingPR, setIsGeneratingPR] = useState11(false);
1909
+ const claudeProcessRef = useRef5(null);
1910
+ const previewScrollRef = useRef5(null);
1911
+ const [prPreview, setPrPreview] = useState11(null);
1912
+ useEffect8(() => {
1766
1913
  if (repo.loading || !repo.currentBranch || !repo.currentRepoSlug) return;
1767
- const current = { branch: repo.currentBranch, repoSlug: repo.currentRepoSlug };
1768
- const last = lastFetchedRef.current;
1769
- if (last && last.branch === current.branch && last.repoSlug === current.repoSlug) return;
1770
- lastFetchedRef.current = current;
1771
1914
  pullRequests.fetchPRsAndDetails(repo.currentBranch, repo.currentRepoSlug);
1772
1915
  }, [repo.loading, repo.currentBranch, repo.currentRepoSlug, pullRequests.fetchPRsAndDetails]);
1773
- useEffect9(() => {
1774
- if (isFocused) {
1916
+ useEffect8(() => {
1917
+ if (isActive) {
1775
1918
  repo.refreshBranch();
1776
1919
  }
1777
- }, [isFocused, repo.refreshBranch]);
1778
- useEffect9(() => {
1920
+ }, [isActive, repo.refreshBranch]);
1921
+ useEffect8(() => {
1779
1922
  onFocusedBoxChange == null ? void 0 : onFocusedBoxChange(focusedBox);
1780
1923
  }, [focusedBox, onFocusedBoxChange]);
1781
- const handleRemoteSelect = useCallback9(
1924
+ const handleRemoteSelect = useCallback8(
1782
1925
  (remoteName) => {
1783
1926
  repo.selectRemote(remoteName);
1784
- const remote = repo.remotes.find((r) => r.name === remoteName);
1785
- if (!remote || !repo.currentBranch) return;
1786
- const repoSlug = getRepoFromRemote(remote.url);
1787
- if (!repoSlug) return;
1788
- lastFetchedRef.current = { branch: repo.currentBranch, repoSlug };
1789
- pullRequests.fetchPRsAndDetails(repo.currentBranch, repoSlug);
1790
1927
  },
1791
- [repo.selectRemote, repo.remotes, repo.currentBranch, pullRequests.fetchPRsAndDetails]
1928
+ [repo.selectRemote]
1792
1929
  );
1793
- const handlePRSelect = useCallback9(
1930
+ const handlePRSelect = useCallback8(
1794
1931
  (pr) => {
1795
1932
  pullRequests.selectPR(pr, repo.currentRepoSlug);
1796
1933
  },
1797
1934
  [pullRequests.selectPR, repo.currentRepoSlug]
1798
1935
  );
1799
- const createPRContext = useRef5({ repo, pullRequests, onLogUpdated });
1800
- createPRContext.current = { repo, pullRequests, onLogUpdated };
1801
- const handleCreatePR = useCallback9(() => {
1802
- const { repo: repo2, pullRequests: pullRequests2 } = createPRContext.current;
1803
- if (!repo2.currentBranch) {
1804
- pullRequests2.setError("prs", "No branch detected");
1936
+ const onLogUpdatedRef = useRef5(onLogUpdated);
1937
+ onLogUpdatedRef.current = onLogUpdated;
1938
+ const prsRef = useRef5(pullRequests.prs);
1939
+ prsRef.current = pullRequests.prs;
1940
+ useEffect8(() => {
1941
+ return () => {
1942
+ var _a;
1943
+ (_a = claudeProcessRef.current) == null ? void 0 : _a.cancel();
1944
+ };
1945
+ }, []);
1946
+ const handleAICreatePR = useCallback8(() => {
1947
+ if (!repo.currentBranch || !repo.currentRepoSlug) {
1948
+ pullRequests.setError("prs", "No branch or repo detected");
1949
+ duckEvents.emit("error");
1950
+ return;
1951
+ }
1952
+ const remoteResult = findRemoteWithBranch(repo.currentBranch);
1953
+ if (!remoteResult.success) {
1954
+ pullRequests.setError("prs", "Push your branch to a remote first");
1955
+ duckEvents.emit("error");
1956
+ return;
1957
+ }
1958
+ setIsGeneratingPR(true);
1959
+ const repoSlug = repo.currentRepoSlug;
1960
+ const branch = repo.currentBranch;
1961
+ const repoPath = repo.repoPath;
1962
+ const owner = remoteResult.data.owner;
1963
+ const process2 = generatePRContent();
1964
+ claudeProcessRef.current = process2;
1965
+ process2.promise.then((result) => {
1966
+ claudeProcessRef.current = null;
1967
+ setIsGeneratingPR(false);
1968
+ if (!result.success) {
1969
+ if (result.error !== "Cancelled") {
1970
+ pullRequests.setError("prs", result.error);
1971
+ duckEvents.emit("error");
1972
+ }
1973
+ return;
1974
+ }
1975
+ let title;
1976
+ let body;
1977
+ try {
1978
+ const cleaned = result.data.replace(/^```(?:json)?\s*\n?/m, "").replace(/\n?```\s*$/m, "").trim();
1979
+ const parsed = JSON.parse(cleaned);
1980
+ title = parsed.title;
1981
+ body = parsed.body;
1982
+ } catch {
1983
+ pullRequests.setError("prs", "Failed to parse AI response");
1984
+ duckEvents.emit("error");
1985
+ return;
1986
+ }
1987
+ const tickets = repoPath && branch ? getLinkedTickets(repoPath, branch) : [];
1988
+ if (tickets.length > 0) {
1989
+ const siteUrl = repoPath ? getJiraSiteUrl(repoPath) : null;
1990
+ const prefix = tickets.map((t) => t.key).join(" ");
1991
+ title = `${prefix} ${title}`;
1992
+ if (siteUrl) {
1993
+ const baseUrl = siteUrl.replace(/\/$/, "");
1994
+ const links = tickets.map((t) => `[${t.key}](${baseUrl}/browse/${t.key})`).join(", ");
1995
+ const label = tickets.length === 1 ? "Ticket" : "Tickets";
1996
+ body = `${label}: ${links}
1997
+
1998
+ ${body}`;
1999
+ }
2000
+ }
2001
+ const currentPrs = prsRef.current;
2002
+ const existingPR = currentPrs.length > 0 ? currentPrs[0] : null;
2003
+ if (existingPR) {
2004
+ setPrPreview({ title, body, prNumber: existingPR.number, repoSlug, branch });
2005
+ } else {
2006
+ openPRCreationPageWithContent(owner, branch, title, body, (error) => {
2007
+ if (error) {
2008
+ pullRequests.setError("prs", `Failed to create PR: ${error.message}`);
2009
+ duckEvents.emit("error");
2010
+ }
2011
+ });
2012
+ pullRequests.pollForNewPR({
2013
+ branch,
2014
+ repoSlug,
2015
+ onNewPR: (newPR) => {
2016
+ var _a;
2017
+ const ticketKeys = repoPath && branch ? getLinkedTickets(repoPath, branch).map((t) => t.key) : [];
2018
+ logPRCreated(newPR.number, newPR.title, ticketKeys);
2019
+ duckEvents.emit("pr:opened");
2020
+ (_a = onLogUpdatedRef.current) == null ? void 0 : _a.call(onLogUpdatedRef);
2021
+ }
2022
+ });
2023
+ }
2024
+ });
2025
+ }, [repo.currentBranch, repo.currentRepoSlug, repo.repoPath, pullRequests.setError, pullRequests.pollForNewPR]);
2026
+ const handleCreatePR = useCallback8(() => {
2027
+ if (!repo.currentBranch) {
2028
+ pullRequests.setError("prs", "No branch detected");
1805
2029
  duckEvents.emit("error");
1806
2030
  return;
1807
2031
  }
1808
- const remoteResult = findRemoteWithBranch(repo2.currentBranch);
2032
+ const remoteResult = findRemoteWithBranch(repo.currentBranch);
1809
2033
  if (!remoteResult.success) {
1810
- pullRequests2.setError("prs", "Push your branch to a remote first");
2034
+ pullRequests.setError("prs", "Push your branch to a remote first");
1811
2035
  duckEvents.emit("error");
1812
2036
  return;
1813
2037
  }
1814
- openPRCreationPage(remoteResult.data.owner, repo2.currentBranch, (error) => {
2038
+ openPRCreationPage(remoteResult.data.owner, repo.currentBranch, (error) => {
1815
2039
  if (error) {
1816
- pullRequests2.setError("prs", `Failed to create PR: ${error.message}`);
2040
+ pullRequests.setError("prs", `Failed to create PR: ${error.message}`);
1817
2041
  duckEvents.emit("error");
1818
2042
  }
1819
2043
  });
1820
- if (!repo2.currentRepoSlug) return;
1821
- polling.startPolling({
1822
- branch: repo2.currentBranch,
1823
- repoSlug: repo2.currentRepoSlug,
1824
- existingPRNumbers: pullRequests2.prs.map((pr) => pr.number),
1825
- onPRsUpdated: (prs) => {
1826
- pullRequests2.setPrs(prs);
1827
- },
2044
+ if (!repo.currentRepoSlug) return;
2045
+ const repoPath = repo.repoPath;
2046
+ const branch = repo.currentBranch;
2047
+ const repoSlug = repo.currentRepoSlug;
2048
+ pullRequests.pollForNewPR({
2049
+ branch,
2050
+ repoSlug,
1828
2051
  onNewPR: (newPR) => {
1829
2052
  var _a;
1830
- const ctx = createPRContext.current;
1831
- const tickets = ctx.repo.repoPath && ctx.repo.currentBranch ? getLinkedTickets(ctx.repo.repoPath, ctx.repo.currentBranch).map((t) => t.key) : [];
2053
+ const tickets = repoPath && branch ? getLinkedTickets(repoPath, branch).map((t) => t.key) : [];
1832
2054
  logPRCreated(newPR.number, newPR.title, tickets);
1833
2055
  duckEvents.emit("pr:opened");
1834
- (_a = ctx.onLogUpdated) == null ? void 0 : _a.call(ctx);
1835
- ctx.pullRequests.setSelectedPR(newPR);
1836
- if (ctx.repo.currentRepoSlug) {
1837
- ctx.pullRequests.refreshDetails(newPR, ctx.repo.currentRepoSlug);
1838
- }
2056
+ (_a = onLogUpdatedRef.current) == null ? void 0 : _a.call(onLogUpdatedRef);
1839
2057
  }
1840
2058
  });
1841
- }, [polling.startPolling]);
2059
+ }, [repo.currentBranch, repo.currentRepoSlug, repo.repoPath, pullRequests.setError, pullRequests.pollForNewPR]);
1842
2060
  useInput4(
1843
- (input) => {
2061
+ (input, key) => {
2062
+ var _a, _b, _c;
2063
+ if (key.escape && isGeneratingPR) {
2064
+ (_a = claudeProcessRef.current) == null ? void 0 : _a.cancel();
2065
+ claudeProcessRef.current = null;
2066
+ setIsGeneratingPR(false);
2067
+ return;
2068
+ }
2069
+ if (prPreview) {
2070
+ if (key.escape) {
2071
+ setPrPreview(null);
2072
+ return;
2073
+ }
2074
+ if (key.upArrow || input === "k") {
2075
+ (_b = previewScrollRef.current) == null ? void 0 : _b.scrollBy(-1);
2076
+ return;
2077
+ }
2078
+ if (key.downArrow || input === "j") {
2079
+ (_c = previewScrollRef.current) == null ? void 0 : _c.scrollBy(1);
2080
+ return;
2081
+ }
2082
+ if (key.return) {
2083
+ const { prNumber, repoSlug, branch, title, body } = prPreview;
2084
+ setPrPreview(null);
2085
+ editPRDescription(prNumber, repoSlug, title, body, (error) => {
2086
+ if (error) {
2087
+ pullRequests.setError("prs", `Failed to update PR: ${error.message}`);
2088
+ duckEvents.emit("error");
2089
+ } else {
2090
+ pullRequests.fetchPRsAndDetails(branch, repoSlug);
2091
+ }
2092
+ });
2093
+ return;
2094
+ }
2095
+ return;
2096
+ }
1844
2097
  if (input === "1") setFocusedBox("remotes");
1845
2098
  if (input === "2") setFocusedBox("prs");
1846
2099
  if (input === "3") setFocusedBox("details");
@@ -1853,11 +2106,14 @@ function GitHubView({ isFocused, onFocusedBoxChange, onLogUpdated }) {
1853
2106
  pullRequests.refreshDetails(pullRequests.selectedPR, repo.currentRepoSlug);
1854
2107
  }
1855
2108
  }
2109
+ if (input === "c" && focusedBox === "prs" && !isGeneratingPR) {
2110
+ handleAICreatePR();
2111
+ }
1856
2112
  if (input === "n" && focusedBox === "prs") {
1857
2113
  handleCreatePR();
1858
2114
  }
1859
2115
  },
1860
- { isActive: isFocused }
2116
+ { isActive }
1861
2117
  );
1862
2118
  if (repo.isRepo === false) {
1863
2119
  return /* @__PURE__ */ jsx5(TitledBox3, { borderStyle: "round", titles: ["Error"], flexGrow: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "red", children: "Current directory is not a git repository" }) });
@@ -1871,7 +2127,7 @@ function GitHubView({ isFocused, onFocusedBoxChange, onLogUpdated }) {
1871
2127
  onSelect: handleRemoteSelect,
1872
2128
  loading: repo.loading,
1873
2129
  error: repo.error,
1874
- isFocused: isFocused && focusedBox === "remotes"
2130
+ isActive: isActive && !prPreview && focusedBox === "remotes"
1875
2131
  }
1876
2132
  ),
1877
2133
  /* @__PURE__ */ jsx5(
@@ -1885,7 +2141,8 @@ function GitHubView({ isFocused, onFocusedBoxChange, onLogUpdated }) {
1885
2141
  error: pullRequests.errors.prs,
1886
2142
  branch: repo.currentBranch,
1887
2143
  repoSlug: repo.currentRepoSlug,
1888
- isFocused: isFocused && focusedBox === "prs"
2144
+ isActive: isActive && !prPreview && focusedBox === "prs",
2145
+ isGeneratingPR
1889
2146
  }
1890
2147
  ),
1891
2148
  /* @__PURE__ */ jsx5(
@@ -1894,18 +2151,26 @@ function GitHubView({ isFocused, onFocusedBoxChange, onLogUpdated }) {
1894
2151
  pr: pullRequests.prDetails,
1895
2152
  loading: pullRequests.loading.details,
1896
2153
  error: pullRequests.errors.details,
1897
- isFocused: isFocused && focusedBox === "details"
2154
+ isActive: isActive && !prPreview && focusedBox === "details"
1898
2155
  }
1899
- )
2156
+ ),
2157
+ prPreview && /* @__PURE__ */ jsx5(TitledBox3, { borderStyle: "round", titles: ["PR Preview"], borderColor: "yellow", flexGrow: 1, flexBasis: 0, children: /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 1, flexGrow: 1, flexBasis: 0, overflow: "hidden", children: [
2158
+ /* @__PURE__ */ jsx5(Box5, { flexGrow: 1, flexBasis: 0, overflow: "hidden", children: /* @__PURE__ */ jsx5(ScrollView4, { ref: previewScrollRef, children: /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
2159
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: prPreview.title }),
2160
+ /* @__PURE__ */ jsx5(Text5, { children: "" }),
2161
+ /* @__PURE__ */ jsx5(Text5, { children: prPreview.body })
2162
+ ] }) }) }),
2163
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Enter to apply, Esc to dismiss, j/k to scroll" })
2164
+ ] }) })
1900
2165
  ] });
1901
2166
  }
1902
2167
 
1903
2168
  // src/components/jira/JiraView.tsx
1904
2169
  import open3 from "open";
1905
- import { useEffect as useEffect11, useRef as useRef6 } from "react";
2170
+ import { useEffect as useEffect10, useRef as useRef6 } from "react";
1906
2171
 
1907
2172
  // src/components/jira/LinkTicketModal.tsx
1908
- import { useState as useState13 } from "react";
2173
+ import { useState as useState12 } from "react";
1909
2174
  import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
1910
2175
 
1911
2176
  // src/components/ui/TextInput.tsx
@@ -1940,7 +2205,7 @@ function TextInput({ value, onChange, placeholder, isActive, mask }) {
1940
2205
  // src/components/jira/LinkTicketModal.tsx
1941
2206
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1942
2207
  function LinkTicketModal({ onSubmit, onCancel, loading, error }) {
1943
- const [ticketInput, setTicketInput] = useState13("");
2208
+ const [ticketInput, setTicketInput] = useState12("");
1944
2209
  const canSubmit = ticketInput.trim().length > 0;
1945
2210
  useInput6(
1946
2211
  (_input, key) => {
@@ -1974,16 +2239,16 @@ import { TitledBox as TitledBox4 } from "@mishieck/ink-titled-box";
1974
2239
  import { Box as Box11, Text as Text11, useInput as useInput9 } from "ink";
1975
2240
 
1976
2241
  // src/components/jira/ChangeStatusModal.tsx
1977
- import { useEffect as useEffect10, useState as useState14 } from "react";
2242
+ import { useEffect as useEffect9, useState as useState13 } from "react";
1978
2243
  import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
1979
2244
  import SelectInput from "ink-select-input";
1980
2245
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1981
2246
  function ChangeStatusModal({ repoPath, ticketKey, currentStatus, onComplete, onCancel }) {
1982
- const [transitions, setTransitions] = useState14([]);
1983
- const [loading, setLoading] = useState14(true);
1984
- const [applying, setApplying] = useState14(false);
1985
- const [error, setError] = useState14(null);
1986
- useEffect10(() => {
2247
+ const [transitions, setTransitions] = useState13([]);
2248
+ const [loading, setLoading] = useState13(true);
2249
+ const [applying, setApplying] = useState13(false);
2250
+ const [error, setError] = useState13(null);
2251
+ useEffect9(() => {
1987
2252
  const fetchTransitions = async () => {
1988
2253
  const siteUrl = getJiraSiteUrl(repoPath);
1989
2254
  const creds = getJiraCredentials(repoPath);
@@ -2060,9 +2325,9 @@ function ChangeStatusModal({ repoPath, ticketKey, currentStatus, onComplete, onC
2060
2325
  }
2061
2326
 
2062
2327
  // src/components/jira/ConfigureJiraSiteModal.tsx
2063
- import { useState as useState15 } from "react";
2328
+ import { useState as useState14 } from "react";
2064
2329
  import { Box as Box9, Text as Text9, useInput as useInput8 } from "ink";
2065
- import { ScrollView as ScrollView4 } from "ink-scroll-view";
2330
+ import { ScrollView as ScrollView5 } from "ink-scroll-view";
2066
2331
 
2067
2332
  // src/lib/editor.ts
2068
2333
  import { spawnSync as spawnSync2 } from "child_process";
@@ -2105,13 +2370,13 @@ function ConfigureJiraSiteModal({
2105
2370
  error
2106
2371
  }) {
2107
2372
  const hasExisting = existingConfigs.length > 0;
2108
- const [mode, setMode] = useState15(hasExisting ? "choose" : "manual");
2109
- const [selectedExisting, setSelectedExisting] = useState15(0);
2373
+ const [mode, setMode] = useState14(hasExisting ? "choose" : "manual");
2374
+ const [selectedExisting, setSelectedExisting] = useState14(0);
2110
2375
  const scrollRef = useScrollToIndex(selectedExisting);
2111
- const [siteUrl, setSiteUrl] = useState15(initialSiteUrl ?? "");
2112
- const [email, setEmail] = useState15(initialEmail ?? "");
2113
- const [apiToken, setApiToken] = useState15("");
2114
- const [selectedItem, setSelectedItem] = useState15("siteUrl");
2376
+ const [siteUrl, setSiteUrl] = useState14(initialSiteUrl ?? "");
2377
+ const [email, setEmail] = useState14(initialEmail ?? "");
2378
+ const [apiToken, setApiToken] = useState14("");
2379
+ const [selectedItem, setSelectedItem] = useState14("siteUrl");
2115
2380
  const items = ["siteUrl", "email", "apiToken", "submit"];
2116
2381
  const canSubmit = siteUrl.trim() && email.trim() && apiToken.trim();
2117
2382
  const chooseItems = existingConfigs.length + 1;
@@ -2203,7 +2468,7 @@ function ConfigureJiraSiteModal({
2203
2468
  /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Select an existing configuration or enter new credentials" }),
2204
2469
  /* @__PURE__ */ jsx9(Box9, { marginTop: 1 }),
2205
2470
  error && /* @__PURE__ */ jsx9(Box9, { marginBottom: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "red", children: error }) }),
2206
- /* @__PURE__ */ jsx9(Box9, { height: listHeight, overflow: "hidden", children: /* @__PURE__ */ jsxs9(ScrollView4, { ref: scrollRef, children: [
2471
+ /* @__PURE__ */ jsx9(Box9, { height: listHeight, overflow: "hidden", children: /* @__PURE__ */ jsxs9(ScrollView5, { ref: scrollRef, children: [
2207
2472
  existingConfigs.map((config, idx) => {
2208
2473
  const isSelected = selectedExisting === idx;
2209
2474
  return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
@@ -2212,7 +2477,7 @@ function ConfigureJiraSiteModal({
2212
2477
  config.siteUrl
2213
2478
  ] }),
2214
2479
  /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
2215
- " ",
2480
+ " ",
2216
2481
  config.email
2217
2482
  ] })
2218
2483
  ] }, config.siteUrl + config.email);
@@ -2276,13 +2541,14 @@ function TicketItem({ ticketKey, summary, status, isHighlighted, isSelected }) {
2276
2541
 
2277
2542
  // src/components/jira/JiraView.tsx
2278
2543
  import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
2279
- function JiraView({ isFocused, onModalChange, onJiraStateChange, onLogUpdated }) {
2544
+ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated }) {
2280
2545
  const repo = useGitRepo();
2281
2546
  const jira = useJiraTickets();
2282
2547
  const modal = useModal();
2283
- const nav = useListNavigation(jira.tickets.length);
2548
+ const nav = useListNavigation({ items: jira.tickets });
2549
+ const currentTicket = jira.tickets[nav.index] ?? null;
2284
2550
  const lastInitRef = useRef6(null);
2285
- useEffect11(() => {
2551
+ useEffect10(() => {
2286
2552
  if (repo.loading || !repo.repoPath || !repo.currentBranch) return;
2287
2553
  const current = { branch: repo.currentBranch };
2288
2554
  const last = lastInitRef.current;
@@ -2290,17 +2556,17 @@ function JiraView({ isFocused, onModalChange, onJiraStateChange, onLogUpdated })
2290
2556
  lastInitRef.current = current;
2291
2557
  jira.initializeJiraState(repo.repoPath, repo.currentBranch, repo.currentRepoSlug);
2292
2558
  }, [repo.loading, repo.repoPath, repo.currentBranch, repo.currentRepoSlug, jira.initializeJiraState]);
2293
- useEffect11(() => {
2294
- if (isFocused) {
2559
+ useEffect10(() => {
2560
+ if (isActive) {
2295
2561
  repo.refreshBranch();
2296
2562
  } else {
2297
2563
  modal.close();
2298
2564
  }
2299
- }, [isFocused, repo.refreshBranch, modal.close]);
2300
- useEffect11(() => {
2565
+ }, [isActive, repo.refreshBranch, modal.close]);
2566
+ useEffect10(() => {
2301
2567
  onModalChange == null ? void 0 : onModalChange(modal.isOpen);
2302
2568
  }, [modal.isOpen, onModalChange]);
2303
- useEffect11(() => {
2569
+ useEffect10(() => {
2304
2570
  onJiraStateChange == null ? void 0 : onJiraStateChange(jira.jiraState);
2305
2571
  }, [jira.jiraState, onJiraStateChange]);
2306
2572
  const handleConfigureSubmit = async (siteUrl, email, apiToken) => {
@@ -2313,38 +2579,30 @@ function JiraView({ isFocused, onModalChange, onJiraStateChange, onLogUpdated })
2313
2579
  const success = await jira.linkTicket(repo.repoPath, repo.currentBranch, ticketInput);
2314
2580
  if (success) modal.close();
2315
2581
  };
2582
+ const getTicketUrl = () => {
2583
+ if (!repo.repoPath || !currentTicket) return null;
2584
+ const siteUrl = getJiraSiteUrl(repo.repoPath);
2585
+ return siteUrl ? `${siteUrl}/browse/${currentTicket.key}` : null;
2586
+ };
2316
2587
  const handleUnlinkTicket = () => {
2317
- if (!repo.repoPath || !repo.currentBranch || jira.tickets.length === 0) return;
2318
- const ticket = jira.tickets[nav.index];
2319
- if (ticket) {
2320
- jira.unlinkTicket(repo.repoPath, repo.currentBranch, ticket.key);
2321
- jira.refreshTickets(repo.repoPath, repo.currentBranch);
2322
- nav.prev();
2323
- }
2588
+ if (!repo.repoPath || !repo.currentBranch || !currentTicket) return;
2589
+ jira.unlinkTicket(repo.repoPath, repo.currentBranch, currentTicket.key);
2590
+ jira.refreshTickets(repo.repoPath, repo.currentBranch);
2591
+ nav.prev();
2324
2592
  };
2325
2593
  const handleOpenInBrowser = () => {
2326
- if (!repo.repoPath || jira.tickets.length === 0) return;
2327
- const ticket = jira.tickets[nav.index];
2328
- const siteUrl = getJiraSiteUrl(repo.repoPath);
2329
- if (ticket && siteUrl) {
2330
- open3(`${siteUrl}/browse/${ticket.key}`).catch(() => {
2331
- });
2332
- }
2594
+ const url = getTicketUrl();
2595
+ if (url) open3(url).catch(() => {
2596
+ });
2333
2597
  };
2334
2598
  const handleCopyLink = () => {
2335
- if (!repo.repoPath || jira.tickets.length === 0) return;
2336
- const ticket = jira.tickets[nav.index];
2337
- const siteUrl = getJiraSiteUrl(repo.repoPath);
2338
- if (ticket && siteUrl) {
2339
- copyToClipboard(`${siteUrl}/browse/${ticket.key}`);
2340
- }
2599
+ const url = getTicketUrl();
2600
+ if (url) copyToClipboard(url);
2341
2601
  };
2342
2602
  const handleStatusComplete = (newStatus) => {
2343
- if (!repo.repoPath || !repo.currentBranch) return;
2344
- const ticket = jira.tickets[nav.index];
2345
- if (!ticket) return;
2346
- updateTicketStatus(repo.repoPath, repo.currentBranch, ticket.key, newStatus);
2347
- logJiraStatusChanged(ticket.key, ticket.summary, ticket.status, newStatus);
2603
+ if (!repo.repoPath || !repo.currentBranch || !currentTicket) return;
2604
+ updateTicketStatus(repo.repoPath, repo.currentBranch, currentTicket.key, newStatus);
2605
+ logJiraStatusChanged(currentTicket.key, currentTicket.summary, currentTicket.status, newStatus);
2348
2606
  onLogUpdated == null ? void 0 : onLogUpdated();
2349
2607
  modal.close();
2350
2608
  jira.refreshTickets(repo.repoPath, repo.currentBranch);
@@ -2377,7 +2635,7 @@ function JiraView({ isFocused, onModalChange, onJiraStateChange, onLogUpdated })
2377
2635
  if (input === "y") handleCopyLink();
2378
2636
  }
2379
2637
  },
2380
- { isActive: isFocused && !modal.isOpen }
2638
+ { isActive: isActive && !modal.isOpen }
2381
2639
  );
2382
2640
  if (repo.isRepo === false) {
2383
2641
  return /* @__PURE__ */ jsx11(TitledBox4, { borderStyle: "round", titles: ["Jira"], flexShrink: 0, children: /* @__PURE__ */ jsx11(Text11, { color: "red", children: "Not a git repository" }) });
@@ -2416,21 +2674,20 @@ function JiraView({ isFocused, onModalChange, onJiraStateChange, onLogUpdated })
2416
2674
  }
2417
2675
  ) });
2418
2676
  }
2419
- if (modal.type === "status" && repo.repoPath && repo.currentBranch && jira.tickets[nav.index]) {
2420
- const ticket = jira.tickets[nav.index];
2677
+ if (modal.type === "status" && repo.repoPath && repo.currentBranch && currentTicket) {
2421
2678
  return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx11(
2422
2679
  ChangeStatusModal,
2423
2680
  {
2424
2681
  repoPath: repo.repoPath,
2425
- ticketKey: ticket.key,
2426
- currentStatus: ticket.status,
2682
+ ticketKey: currentTicket.key,
2683
+ currentStatus: currentTicket.status,
2427
2684
  onComplete: handleStatusComplete,
2428
2685
  onCancel: modal.close
2429
2686
  }
2430
2687
  ) });
2431
2688
  }
2432
2689
  const title = "[4] Jira";
2433
- const borderColor = isFocused ? "yellow" : void 0;
2690
+ const borderColor = isActive ? "yellow" : void 0;
2434
2691
  return /* @__PURE__ */ jsx11(TitledBox4, { borderStyle: "round", titles: [title], borderColor, flexShrink: 0, children: /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 1, children: [
2435
2692
  jira.jiraState === "not_configured" && /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "No Jira site configured" }),
2436
2693
  jira.jiraState === "no_tickets" && /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "No tickets linked to this branch" }),
@@ -2452,106 +2709,21 @@ import { useEffect as useEffect12 } from "react";
2452
2709
  import { Box as Box14, useInput as useInput12 } from "ink";
2453
2710
 
2454
2711
  // src/components/logs/LogViewerBox.tsx
2455
- import { useRef as useRef7, useState as useState16 } from "react";
2712
+ import { useEffect as useEffect11, useRef as useRef7, useState as useState15 } from "react";
2456
2713
  import { TitledBox as TitledBox5 } from "@mishieck/ink-titled-box";
2457
2714
  import { Box as Box12, Text as Text12, useInput as useInput10 } from "ink";
2458
- import { ScrollView as ScrollView5 } from "ink-scroll-view";
2715
+ import { ScrollView as ScrollView6 } from "ink-scroll-view";
2459
2716
  import TextInput2 from "ink-text-input";
2460
-
2461
- // src/lib/claude/api.ts
2462
- import { exec as exec3 } from "child_process";
2463
- function runClaudePrompt(prompt) {
2464
- let childProcess = null;
2465
- let cancelled = false;
2466
- const promise = new Promise((resolve) => {
2467
- const escapedPrompt = prompt.replace(/'/g, "'\\''").replace(/\n/g, "\\n");
2468
- const command = `claude -p $'${escapedPrompt}' --output-format json < /dev/null`;
2469
- childProcess = exec3(command, { maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
2470
- if (cancelled) {
2471
- resolve({
2472
- success: false,
2473
- error: "Cancelled",
2474
- errorType: "execution_error"
2475
- });
2476
- return;
2477
- }
2478
- if (error) {
2479
- if (error.message.includes("command not found") || error.message.includes("ENOENT") || error.code === "ENOENT") {
2480
- resolve({
2481
- success: false,
2482
- error: "Claude CLI not installed. Run: npm install -g @anthropic-ai/claude-code",
2483
- errorType: "not_installed"
2484
- });
2485
- return;
2486
- }
2487
- resolve({
2488
- success: false,
2489
- error: (stderr == null ? void 0 : stderr.trim()) || error.message,
2490
- errorType: "execution_error"
2491
- });
2492
- return;
2493
- }
2494
- if (!(stdout == null ? void 0 : stdout.trim())) {
2495
- resolve({
2496
- success: false,
2497
- error: (stderr == null ? void 0 : stderr.trim()) || "Claude returned empty response",
2498
- errorType: "execution_error"
2499
- });
2500
- return;
2501
- }
2502
- try {
2503
- const json = JSON.parse(stdout.trim());
2504
- if (json.is_error) {
2505
- resolve({
2506
- success: false,
2507
- error: json.result || "Claude returned an error",
2508
- errorType: "execution_error"
2509
- });
2510
- return;
2511
- }
2512
- resolve({
2513
- success: true,
2514
- data: json.result || stdout.trim()
2515
- });
2516
- } catch {
2517
- resolve({
2518
- success: true,
2519
- data: stdout.trim()
2520
- });
2521
- }
2522
- });
2523
- });
2524
- const cancel = () => {
2525
- cancelled = true;
2526
- if (childProcess) {
2527
- childProcess.kill("SIGTERM");
2528
- }
2529
- };
2530
- return { promise, cancel };
2531
- }
2532
- function generateStandupNotes(logContent) {
2533
- const prompt = `You are helping a developer prepare standup notes. Based on the following log entries, generate concise standup notes that summarize what was accomplished.
2534
-
2535
- Format the output as bullet points grouped by category (e.g., Features, Bug Fixes, Refactoring, etc.). Keep it brief and professional.
2536
-
2537
- Log entries:
2538
- ${logContent}
2539
-
2540
- Generate the standup notes:`;
2541
- return runClaudePrompt(prompt);
2542
- }
2543
-
2544
- // src/components/logs/LogViewerBox.tsx
2545
2717
  import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
2546
- function LogViewerBox({ date, content, isFocused, onRefresh, onLogCreated }) {
2718
+ function LogViewerBox({ date, content, isActive, onRefresh, onLogCreated }) {
2547
2719
  const scrollRef = useRef7(null);
2548
- const [isInputMode, setIsInputMode] = useState16(false);
2549
- const [inputValue, setInputValue] = useState16("");
2550
- const [isGeneratingStandup, setIsGeneratingStandup] = useState16(false);
2551
- const [standupResult, setStandupResult] = useState16(null);
2720
+ const [isInputMode, setIsInputMode] = useState15(false);
2721
+ const [inputValue, setInputValue] = useState15("");
2722
+ const [isGeneratingStandup, setIsGeneratingStandup] = useState15(false);
2723
+ const [standupResult, setStandupResult] = useState15(null);
2552
2724
  const claudeProcessRef = useRef7(null);
2553
2725
  const title = "[6] Log Content";
2554
- const borderColor = isFocused ? "yellow" : void 0;
2726
+ const borderColor = isActive ? "yellow" : void 0;
2555
2727
  const displayTitle = date ? `${title} - ${date}.md` : title;
2556
2728
  useInput10(
2557
2729
  (input, key) => {
@@ -2613,8 +2785,14 @@ function LogViewerBox({ date, content, isFocused, onRefresh, onLogCreated }) {
2613
2785
  });
2614
2786
  }
2615
2787
  },
2616
- { isActive: isFocused }
2788
+ { isActive }
2617
2789
  );
2790
+ useEffect11(() => {
2791
+ return () => {
2792
+ var _a;
2793
+ (_a = claudeProcessRef.current) == null ? void 0 : _a.cancel();
2794
+ };
2795
+ }, []);
2618
2796
  const handleInputSubmit = (value) => {
2619
2797
  if (!date || !value.trim()) {
2620
2798
  setIsInputMode(false);
@@ -2633,7 +2811,7 @@ ${value.trim()}
2633
2811
  onRefresh();
2634
2812
  };
2635
2813
  return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", flexGrow: 1, children: [
2636
- /* @__PURE__ */ jsx12(TitledBox5, { borderStyle: "round", titles: [displayTitle], borderColor, flexGrow: 1, children: /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx12(ScrollView5, { ref: scrollRef, children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingX: 1, children: [
2814
+ /* @__PURE__ */ jsx12(TitledBox5, { borderStyle: "round", titles: [displayTitle], borderColor, flexGrow: 1, children: /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx12(ScrollView6, { ref: scrollRef, children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingX: 1, children: [
2637
2815
  !date && /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "Select a log file to view" }),
2638
2816
  date && content === null && /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "Log file not found" }),
2639
2817
  date && content !== null && content.trim() === "" && /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "Empty log file" }),
@@ -2669,7 +2847,7 @@ ${value.trim()}
2669
2847
  // src/components/logs/LogsHistoryBox.tsx
2670
2848
  import { TitledBox as TitledBox6 } from "@mishieck/ink-titled-box";
2671
2849
  import { Box as Box13, Text as Text13, useInput as useInput11 } from "ink";
2672
- import { ScrollView as ScrollView6 } from "ink-scroll-view";
2850
+ import { ScrollView as ScrollView7 } from "ink-scroll-view";
2673
2851
  import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
2674
2852
  function LogsHistoryBox({
2675
2853
  logFiles,
@@ -2677,11 +2855,11 @@ function LogsHistoryBox({
2677
2855
  highlightedIndex,
2678
2856
  onHighlight,
2679
2857
  onSelect,
2680
- isFocused
2858
+ isActive
2681
2859
  }) {
2682
2860
  const scrollRef = useScrollToIndex(highlightedIndex);
2683
2861
  const title = "[5] Logs";
2684
- const borderColor = isFocused ? "yellow" : void 0;
2862
+ const borderColor = isActive ? "yellow" : void 0;
2685
2863
  useInput11(
2686
2864
  (input, key) => {
2687
2865
  if (logFiles.length === 0) return;
@@ -2691,18 +2869,18 @@ function LogsHistoryBox({
2691
2869
  if (key.downArrow || input === "j") {
2692
2870
  onHighlight(Math.min(logFiles.length - 1, highlightedIndex + 1));
2693
2871
  }
2694
- if (key.return) {
2872
+ if (input === " ") {
2695
2873
  const file = logFiles[highlightedIndex];
2696
2874
  if (file) {
2697
2875
  onSelect(file.date);
2698
2876
  }
2699
2877
  }
2700
2878
  },
2701
- { isActive: isFocused }
2879
+ { isActive }
2702
2880
  );
2703
2881
  return /* @__PURE__ */ jsx13(TitledBox6, { borderStyle: "round", titles: [title], borderColor, height: 5, children: /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: [
2704
2882
  logFiles.length === 0 && /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: "No logs yet" }),
2705
- logFiles.length > 0 && /* @__PURE__ */ jsx13(ScrollView6, { ref: scrollRef, children: logFiles.map((file, idx) => {
2883
+ logFiles.length > 0 && /* @__PURE__ */ jsx13(ScrollView7, { ref: scrollRef, children: logFiles.map((file, idx) => {
2706
2884
  const isHighlighted = idx === highlightedIndex;
2707
2885
  const isSelected = file.date === selectedDate;
2708
2886
  const cursor = isHighlighted ? ">" : " ";
@@ -2722,7 +2900,7 @@ function LogsHistoryBox({
2722
2900
 
2723
2901
  // src/components/logs/LogsView.tsx
2724
2902
  import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
2725
- function LogsView({ isFocused, refreshKey, focusedBox, onFocusedBoxChange }) {
2903
+ function LogsView({ isActive, refreshKey, focusedBox, onFocusedBoxChange }) {
2726
2904
  const logs = useLogs();
2727
2905
  useEffect12(() => {
2728
2906
  if (refreshKey !== void 0 && refreshKey > 0) {
@@ -2734,7 +2912,7 @@ function LogsView({ isFocused, refreshKey, focusedBox, onFocusedBoxChange }) {
2734
2912
  if (input === "5") onFocusedBoxChange("history");
2735
2913
  if (input === "6") onFocusedBoxChange("viewer");
2736
2914
  },
2737
- { isActive: isFocused }
2915
+ { isActive }
2738
2916
  );
2739
2917
  return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", flexGrow: 1, children: [
2740
2918
  /* @__PURE__ */ jsx14(
@@ -2745,7 +2923,7 @@ function LogsView({ isFocused, refreshKey, focusedBox, onFocusedBoxChange }) {
2745
2923
  highlightedIndex: logs.highlightedIndex,
2746
2924
  onHighlight: logs.setHighlightedIndex,
2747
2925
  onSelect: logs.selectDate,
2748
- isFocused: isFocused && focusedBox === "history"
2926
+ isActive: isActive && focusedBox === "history"
2749
2927
  }
2750
2928
  ),
2751
2929
  /* @__PURE__ */ jsx14(
@@ -2753,7 +2931,7 @@ function LogsView({ isFocused, refreshKey, focusedBox, onFocusedBoxChange }) {
2753
2931
  {
2754
2932
  date: logs.selectedDate,
2755
2933
  content: logs.logContent,
2756
- isFocused: isFocused && focusedBox === "viewer",
2934
+ isActive: isActive && focusedBox === "viewer",
2757
2935
  onRefresh: logs.refresh,
2758
2936
  onLogCreated: logs.handleLogCreated
2759
2937
  }
@@ -2790,6 +2968,7 @@ var GITHUB_KEYBINDINGS = {
2790
2968
  remotes: [{ key: "Space", label: "Select Remote" }],
2791
2969
  prs: [
2792
2970
  { key: "Space", label: "Select" },
2971
+ { key: "c", label: "Generate PR", color: "green" },
2793
2972
  { key: "n", label: "New PR", color: "green" },
2794
2973
  { key: "r", label: "Refresh" },
2795
2974
  { key: "o", label: "Open", color: "green" },
@@ -2820,7 +2999,7 @@ var JIRA_KEYBINDINGS = {
2820
2999
 
2821
3000
  // src/constants/logs.ts
2822
3001
  var LOGS_KEYBINDINGS = {
2823
- history: [{ key: "Enter", label: "Select" }],
3002
+ history: [{ key: "Space", label: "Select" }],
2824
3003
  viewer: [
2825
3004
  { key: "i", label: "Add Entry" },
2826
3005
  { key: "e", label: "Edit" },
@@ -2849,13 +3028,13 @@ function computeKeybindings(focusedView, state) {
2849
3028
  import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
2850
3029
  function App() {
2851
3030
  const { exit } = useApp();
2852
- const [focusedView, setFocusedView] = useState17("github");
2853
- const [modalOpen, setModalOpen] = useState17(false);
2854
- const [logRefreshKey, setLogRefreshKey] = useState17(0);
3031
+ const [focusedView, setFocusedView] = useState16("github");
3032
+ const [modalOpen, setModalOpen] = useState16(false);
3033
+ const [logRefreshKey, setLogRefreshKey] = useState16(0);
2855
3034
  const duck = useRubberDuck();
2856
- const [githubFocusedBox, setGithubFocusedBox] = useState17("remotes");
2857
- const [jiraState, setJiraState] = useState17("not_configured");
2858
- const [logsFocusedBox, setLogsFocusedBox] = useState17("history");
3035
+ const [githubFocusedBox, setGithubFocusedBox] = useState16("remotes");
3036
+ const [jiraState, setJiraState] = useState16("not_configured");
3037
+ const [logsFocusedBox, setLogsFocusedBox] = useState16("history");
2859
3038
  const keybindings = useMemo2(
2860
3039
  () => computeKeybindings(focusedView, {
2861
3040
  github: { focusedBox: githubFocusedBox },
@@ -2864,7 +3043,7 @@ function App() {
2864
3043
  }),
2865
3044
  [focusedView, githubFocusedBox, jiraState, modalOpen, logsFocusedBox]
2866
3045
  );
2867
- const handleLogUpdated = useCallback10(() => {
3046
+ const handleLogUpdated = useCallback9(() => {
2868
3047
  setLogRefreshKey((prev) => prev + 1);
2869
3048
  }, []);
2870
3049
  useInput13(
@@ -2901,7 +3080,7 @@ function App() {
2901
3080
  /* @__PURE__ */ jsx16(
2902
3081
  GitHubView,
2903
3082
  {
2904
- isFocused: focusedView === "github",
3083
+ isActive: focusedView === "github",
2905
3084
  onFocusedBoxChange: setGithubFocusedBox,
2906
3085
  onLogUpdated: handleLogUpdated
2907
3086
  }
@@ -2909,7 +3088,7 @@ function App() {
2909
3088
  /* @__PURE__ */ jsx16(
2910
3089
  JiraView,
2911
3090
  {
2912
- isFocused: focusedView === "jira",
3091
+ isActive: focusedView === "jira",
2913
3092
  onModalChange: setModalOpen,
2914
3093
  onJiraStateChange: setJiraState,
2915
3094
  onLogUpdated: handleLogUpdated
@@ -2919,7 +3098,7 @@ function App() {
2919
3098
  /* @__PURE__ */ jsx16(Box16, { flexDirection: "column", flexGrow: 1, flexBasis: 0, children: /* @__PURE__ */ jsx16(
2920
3099
  LogsView,
2921
3100
  {
2922
- isFocused: focusedView === "logs",
3101
+ isActive: focusedView === "logs",
2923
3102
  refreshKey: logRefreshKey,
2924
3103
  focusedBox: logsFocusedBox,
2925
3104
  onFocusedBoxChange: setLogsFocusedBox
@@ -2941,13 +3120,13 @@ function App() {
2941
3120
  import { render as inkRender } from "ink";
2942
3121
 
2943
3122
  // src/lib/Screen.tsx
2944
- import { useCallback as useCallback11, useEffect as useEffect13, useState as useState18 } from "react";
3123
+ import { useCallback as useCallback10, useEffect as useEffect13, useState as useState17 } from "react";
2945
3124
  import { Box as Box17, useStdout as useStdout2 } from "ink";
2946
3125
  import { jsx as jsx17 } from "react/jsx-runtime";
2947
3126
  function Screen({ children }) {
2948
3127
  const { stdout } = useStdout2();
2949
- const getSize = useCallback11(() => ({ height: stdout.rows, width: stdout.columns }), [stdout]);
2950
- const [size, setSize] = useState18(getSize);
3128
+ const getSize = useCallback10(() => ({ height: stdout.rows, width: stdout.columns }), [stdout]);
3129
+ const [size, setSize] = useState17(getSize);
2951
3130
  useEffect13(() => {
2952
3131
  const onResize = () => setSize(getSize());
2953
3132
  stdout.on("resize", onResize);