clairo 0.3.0 → 0.5.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 (2) hide show
  1. package/dist/cli.js +1266 -741
  2. package/package.json +4 -1
package/dist/cli.js CHANGED
@@ -4,14 +4,14 @@
4
4
  import meow from "meow";
5
5
 
6
6
  // src/app.tsx
7
- import { useState as useState8 } from "react";
8
- import { Box as Box12, useApp, useInput as useInput10 } from "ink";
7
+ import { useCallback as useCallback4, useState as useState9 } from "react";
8
+ import { Box as Box16, useApp, useInput as useInput13 } from "ink";
9
9
 
10
10
  // src/components/github/GitHubView.tsx
11
11
  import { exec as exec3 } from "child_process";
12
- import { useCallback, useEffect as useEffect3, useState as useState3 } from "react";
12
+ import { useCallback, useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
13
13
  import { TitledBox as TitledBox4 } from "@mishieck/ink-titled-box";
14
- import { Box as Box4, Text as Text4, useInput as useInput4 } from "ink";
14
+ import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
15
15
 
16
16
  // src/lib/config/index.ts
17
17
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -241,697 +241,970 @@ async function getPRDetails(prNumber, repo) {
241
241
  }
242
242
  }
243
243
 
244
- // src/components/github/PRDetailsBox.tsx
245
- import { useRef } from "react";
246
- import open from "open";
247
- import { TitledBox } from "@mishieck/ink-titled-box";
248
- import { Box, Text, useInput } from "ink";
249
- import { ScrollView } from "ink-scroll-view";
250
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
251
- function getCheckColor(check) {
252
- const conclusion = check.conclusion ?? check.state;
253
- if (conclusion === "SUCCESS") return "green";
254
- if (conclusion === "FAILURE" || conclusion === "ERROR") return "red";
255
- if (conclusion === "SKIPPED" || conclusion === "NEUTRAL") return "gray";
256
- if (conclusion === "PENDING" || check.status === "IN_PROGRESS" || check.status === "QUEUED" || check.status === "WAITING")
257
- return "yellow";
258
- if (check.status === "COMPLETED") return "green";
259
- return void 0;
244
+ // src/lib/jira/parser.ts
245
+ var TICKET_KEY_PATTERN = /^[A-Z][A-Z0-9]+-\d+$/;
246
+ function isValidTicketKeyFormat(key) {
247
+ return TICKET_KEY_PATTERN.test(key.toUpperCase());
260
248
  }
261
- function getCheckIcon(check) {
262
- const conclusion = check.conclusion ?? check.state;
263
- if (conclusion === "SUCCESS") return "\u2713";
264
- if (conclusion === "FAILURE" || conclusion === "ERROR") return "\u2717";
265
- if (conclusion === "SKIPPED" || conclusion === "NEUTRAL") return "\u25CB";
266
- if (conclusion === "PENDING" || check.status === "IN_PROGRESS" || check.status === "QUEUED" || check.status === "WAITING")
267
- return "\u25CF";
268
- if (check.status === "COMPLETED") return "\u2713";
269
- return "?";
249
+ function parseTicketKey(input) {
250
+ const trimmed = input.trim();
251
+ const urlMatch = trimmed.match(/\/browse\/([A-Za-z][A-Za-z0-9]+-\d+)/i);
252
+ if (urlMatch) {
253
+ return urlMatch[1].toUpperCase();
254
+ }
255
+ const upperInput = trimmed.toUpperCase();
256
+ if (isValidTicketKeyFormat(upperInput)) {
257
+ return upperInput;
258
+ }
259
+ return null;
270
260
  }
271
- function PRDetailsBox({ pr, loading, error, isFocused }) {
272
- var _a, _b, _c, _d, _e, _f, _g;
273
- const scrollRef = useRef(null);
274
- const title = "[3] PR Details";
275
- const borderColor = isFocused ? "yellow" : void 0;
276
- const displayTitle = pr ? `${title} - #${pr.number}` : title;
277
- const reviewStatus = (pr == null ? void 0 : pr.reviewDecision) ?? "PENDING";
278
- const reviewColor = reviewStatus === "APPROVED" ? "green" : reviewStatus === "CHANGES_REQUESTED" ? "red" : "yellow";
279
- const getMergeDisplay = () => {
280
- if (!pr) return { text: "UNKNOWN", color: "yellow" };
281
- if (pr.state === "MERGED") return { text: "MERGED", color: "magenta" };
282
- if (pr.state === "CLOSED") return { text: "CLOSED", color: "red" };
283
- if (pr.mergeable === "MERGEABLE") return { text: "MERGEABLE", color: "green" };
284
- if (pr.mergeable === "CONFLICTING") return { text: "CONFLICTING", color: "red" };
285
- return { text: pr.mergeable ?? "UNKNOWN", color: "yellow" };
286
- };
287
- const mergeDisplay = getMergeDisplay();
288
- useInput(
289
- (input, key) => {
290
- var _a2, _b2;
291
- if (key.upArrow || input === "k") {
292
- (_a2 = scrollRef.current) == null ? void 0 : _a2.scrollBy(-1);
293
- }
294
- if (key.downArrow || input === "j") {
295
- (_b2 = scrollRef.current) == null ? void 0 : _b2.scrollBy(1);
296
- }
297
- if (input === "o" && (pr == null ? void 0 : pr.url)) {
298
- open(pr.url).catch(() => {
299
- });
300
- }
301
- },
302
- { isActive: isFocused }
303
- );
304
- return /* @__PURE__ */ jsx(TitledBox, { borderStyle: "round", titles: [displayTitle], borderColor, flexGrow: 2, children: /* @__PURE__ */ jsx(Box, { flexGrow: 1, overflow: "hidden", children: /* @__PURE__ */ jsx(ScrollView, { ref: scrollRef, flexGrow: 1, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
305
- loading && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading details..." }),
306
- error && /* @__PURE__ */ jsx(Text, { color: "red", children: error }),
307
- !loading && !error && !pr && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Select a PR to view details" }),
308
- !loading && !error && pr && /* @__PURE__ */ jsxs(Fragment, { children: [
309
- /* @__PURE__ */ jsx(Text, { bold: true, children: pr.title }),
310
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
311
- "by ",
312
- ((_a = pr.author) == null ? void 0 : _a.login) ?? "unknown",
313
- " | ",
314
- ((_b = pr.commits) == null ? void 0 : _b.length) ?? 0,
315
- " commits"
316
- ] }),
317
- /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
318
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Review: " }),
319
- /* @__PURE__ */ jsx(Text, { color: reviewColor, children: reviewStatus }),
320
- /* @__PURE__ */ jsx(Text, { children: " | " }),
321
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Status: " }),
322
- /* @__PURE__ */ jsx(Text, { color: mergeDisplay.color, children: mergeDisplay.text })
323
- ] }),
324
- (((_c = pr.assignees) == null ? void 0 : _c.length) ?? 0) > 0 && /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
325
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Assignees: " }),
326
- /* @__PURE__ */ jsx(Text, { children: pr.assignees.map((a) => a.login).join(", ") })
327
- ] }),
328
- (((_d = pr.reviews) == null ? void 0 : _d.length) ?? 0) > 0 && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
329
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Reviews:" }),
330
- pr.reviews.map((review, idx) => {
331
- const color = review.state === "APPROVED" ? "green" : review.state === "CHANGES_REQUESTED" ? "red" : review.state === "COMMENTED" ? "blue" : "yellow";
332
- const icon = review.state === "APPROVED" ? "\u2713" : review.state === "CHANGES_REQUESTED" ? "\u2717" : review.state === "COMMENTED" ? "\u{1F4AC}" : "\u25CB";
333
- return /* @__PURE__ */ jsxs(Text, { color, children: [
334
- " ",
335
- icon,
336
- " ",
337
- review.author.login
338
- ] }, idx);
339
- })
340
- ] }),
341
- (((_e = pr.reviewRequests) == null ? void 0 : _e.length) ?? 0) > 0 && /* @__PURE__ */ jsxs(Box, { children: [
342
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Pending: " }),
343
- /* @__PURE__ */ jsx(Text, { color: "yellow", children: pr.reviewRequests.map((r) => r.login ?? r.name ?? r.slug ?? "Team").join(", ") })
344
- ] }),
345
- (((_f = pr.statusCheckRollup) == null ? void 0 : _f.length) ?? 0) > 0 && /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
346
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Checks:" }),
347
- (_g = pr.statusCheckRollup) == null ? void 0 : _g.map((check, idx) => /* @__PURE__ */ jsxs(Text, { color: getCheckColor(check), children: [
348
- " ",
349
- getCheckIcon(check),
350
- " ",
351
- check.name ?? check.context
352
- ] }, idx))
353
- ] }),
354
- pr.body && /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
355
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Description:" }),
356
- /* @__PURE__ */ jsx(Text, { children: pr.body })
357
- ] })
358
- ] })
359
- ] }) }) }) });
261
+ function extractTicketKeyFromBranch(branchName) {
262
+ const match = branchName.match(/([A-Za-z][A-Za-z0-9]+-\d+)/);
263
+ if (match) {
264
+ const candidate = match[1].toUpperCase();
265
+ if (isValidTicketKeyFormat(candidate)) {
266
+ return candidate;
267
+ }
268
+ }
269
+ return null;
360
270
  }
361
271
 
362
- // src/components/github/PullRequestsBox.tsx
363
- import { useEffect, useState } from "react";
364
- import { TitledBox as TitledBox2 } from "@mishieck/ink-titled-box";
365
- import { Box as Box2, Text as Text2, useInput as useInput2 } from "ink";
366
-
367
- // src/lib/clipboard.ts
368
- import { exec as exec2 } from "child_process";
369
- async function copyToClipboard(text) {
370
- var _a, _b;
371
- const command = process.platform === "darwin" ? "pbcopy" : process.platform === "win32" ? "clip" : "xclip -selection clipboard";
372
- try {
373
- const child = exec2(command);
374
- (_a = child.stdin) == null ? void 0 : _a.write(text);
375
- (_b = child.stdin) == null ? void 0 : _b.end();
376
- await new Promise((resolve, reject) => {
377
- child.on("close", (code) => {
378
- if (code === 0) resolve();
379
- else reject(new Error(`Clipboard command exited with code ${code}`));
380
- });
381
- });
382
- return true;
383
- } catch {
384
- return false;
272
+ // src/lib/jira/config.ts
273
+ function isJiraConfigured(repoPath) {
274
+ const config = getRepoConfig(repoPath);
275
+ return !!(config.jiraSiteUrl && config.jiraEmail && config.jiraApiToken);
276
+ }
277
+ function getJiraSiteUrl(repoPath) {
278
+ const config = getRepoConfig(repoPath);
279
+ return config.jiraSiteUrl ?? null;
280
+ }
281
+ function setJiraSiteUrl(repoPath, siteUrl) {
282
+ updateRepoConfig(repoPath, { jiraSiteUrl: siteUrl });
283
+ }
284
+ function getJiraCredentials(repoPath) {
285
+ const config = getRepoConfig(repoPath);
286
+ return {
287
+ email: config.jiraEmail ?? null,
288
+ apiToken: config.jiraApiToken ?? null
289
+ };
290
+ }
291
+ function setJiraCredentials(repoPath, email, apiToken) {
292
+ updateRepoConfig(repoPath, { jiraEmail: email, jiraApiToken: apiToken });
293
+ }
294
+ function getLinkedTickets(repoPath, branch) {
295
+ var _a;
296
+ const config = getRepoConfig(repoPath);
297
+ return ((_a = config.branchTickets) == null ? void 0 : _a[branch]) ?? [];
298
+ }
299
+ function addLinkedTicket(repoPath, branch, ticket) {
300
+ const config = getRepoConfig(repoPath);
301
+ const branchTickets = config.branchTickets ?? {};
302
+ const tickets = branchTickets[branch] ?? [];
303
+ if (tickets.some((t) => t.key === ticket.key)) {
304
+ return;
385
305
  }
306
+ updateRepoConfig(repoPath, {
307
+ branchTickets: {
308
+ ...branchTickets,
309
+ [branch]: [...tickets, ticket]
310
+ }
311
+ });
386
312
  }
387
-
388
- // src/components/github/PullRequestsBox.tsx
389
- import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
390
- function PullRequestsBox({
391
- prs,
392
- selectedPR,
393
- onSelect,
394
- onCreatePR,
395
- loading,
396
- error,
397
- branch,
398
- repoSlug,
399
- isFocused
400
- }) {
401
- const [highlightedIndex, setHighlightedIndex] = useState(0);
402
- const totalItems = prs.length + 1;
403
- useEffect(() => {
404
- const idx = prs.findIndex((p) => p.number === (selectedPR == null ? void 0 : selectedPR.number));
405
- if (idx >= 0) setHighlightedIndex(idx);
406
- }, [selectedPR, prs]);
407
- useInput2(
408
- (input, key) => {
409
- if (!isFocused) return;
410
- if (key.upArrow || input === "k") {
411
- setHighlightedIndex((prev) => Math.max(0, prev - 1));
412
- }
413
- if (key.downArrow || input === "j") {
414
- setHighlightedIndex((prev) => Math.min(totalItems - 1, prev + 1));
415
- }
416
- if (key.return) {
417
- if (highlightedIndex === prs.length) {
418
- onCreatePR();
419
- } else if (prs[highlightedIndex]) {
420
- onSelect(prs[highlightedIndex]);
421
- }
422
- }
423
- if (input === "y" && repoSlug && prs[highlightedIndex]) {
424
- const pr = prs[highlightedIndex];
425
- const url = `https://github.com/${repoSlug}/pull/${pr.number}`;
426
- copyToClipboard(url);
427
- }
428
- },
429
- { isActive: isFocused }
430
- );
431
- const title = "[2] Pull Requests";
432
- const subtitle = branch ? ` (${branch})` : "";
433
- const borderColor = isFocused ? "yellow" : void 0;
434
- return /* @__PURE__ */ jsx2(TitledBox2, { borderStyle: "round", titles: [`${title}${subtitle}`], borderColor, flexShrink: 0, children: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, overflow: "hidden", children: [
435
- loading && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Loading PRs..." }),
436
- error && /* @__PURE__ */ jsx2(Text2, { color: "red", children: error }),
437
- !loading && !error && /* @__PURE__ */ jsxs2(Fragment2, { children: [
438
- prs.length === 0 && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "No PRs for this branch" }),
439
- prs.map((pr, idx) => {
440
- const isHighlighted = isFocused && idx === highlightedIndex;
441
- const isSelected = pr.number === (selectedPR == null ? void 0 : selectedPR.number);
442
- const prefix = isHighlighted ? "> " : isSelected ? "\u25CF " : " ";
443
- return /* @__PURE__ */ jsxs2(Text2, { color: isSelected ? "green" : void 0, children: [
444
- prefix,
445
- "#",
446
- pr.number,
447
- " ",
448
- pr.isDraft ? "[Draft] " : "",
449
- pr.title
450
- ] }, pr.number);
451
- }),
452
- /* @__PURE__ */ jsxs2(Text2, { color: "blue", children: [
453
- isFocused && highlightedIndex === prs.length ? "> " : " ",
454
- "+ Create new PR"
455
- ] })
456
- ] })
457
- ] }) });
313
+ function removeLinkedTicket(repoPath, branch, ticketKey) {
314
+ const config = getRepoConfig(repoPath);
315
+ const branchTickets = config.branchTickets ?? {};
316
+ const tickets = branchTickets[branch] ?? [];
317
+ updateRepoConfig(repoPath, {
318
+ branchTickets: {
319
+ ...branchTickets,
320
+ [branch]: tickets.filter((t) => t.key !== ticketKey)
321
+ }
322
+ });
458
323
  }
459
-
460
- // src/components/github/RemotesBox.tsx
461
- import { useEffect as useEffect2, useState as useState2 } from "react";
462
- import { TitledBox as TitledBox3 } from "@mishieck/ink-titled-box";
463
- import { Box as Box3, Text as Text3, useInput as useInput3 } from "ink";
464
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
465
- function RemotesBox({ remotes, selectedRemote, onSelect, loading, error, isFocused }) {
466
- const [highlightedIndex, setHighlightedIndex] = useState2(0);
467
- useEffect2(() => {
468
- const idx = remotes.findIndex((r) => r.name === selectedRemote);
469
- if (idx >= 0) setHighlightedIndex(idx);
470
- }, [selectedRemote, remotes]);
471
- useInput3(
472
- (input, key) => {
473
- if (!isFocused || remotes.length === 0) return;
474
- if (key.upArrow || input === "k") {
475
- setHighlightedIndex((prev) => Math.max(0, prev - 1));
476
- }
477
- if (key.downArrow || input === "j") {
478
- setHighlightedIndex((prev) => Math.min(remotes.length - 1, prev + 1));
479
- }
480
- if (key.return) {
481
- onSelect(remotes[highlightedIndex].name);
482
- }
483
- },
484
- { isActive: isFocused }
485
- );
486
- const title = "[1] Remotes";
487
- const borderColor = isFocused ? "yellow" : void 0;
488
- return /* @__PURE__ */ jsx3(TitledBox3, { borderStyle: "round", titles: [title], borderColor, flexShrink: 0, children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 1, overflow: "hidden", children: [
489
- loading && /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Loading..." }),
490
- error && /* @__PURE__ */ jsx3(Text3, { color: "red", children: error }),
491
- !loading && !error && remotes.length === 0 && /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "No remotes configured" }),
492
- !loading && !error && remotes.map((remote, idx) => {
493
- const isHighlighted = isFocused && idx === highlightedIndex;
494
- const isSelected = remote.name === selectedRemote;
495
- const prefix = isHighlighted ? "> " : isSelected ? "\u25CF " : " ";
496
- return /* @__PURE__ */ jsxs3(Text3, { color: isSelected ? "green" : void 0, children: [
497
- prefix,
498
- remote.name,
499
- " (",
500
- remote.url,
501
- ")"
502
- ] }, remote.name);
503
- })
504
- ] }) });
324
+ function updateTicketStatus(repoPath, branch, ticketKey, newStatus) {
325
+ const config = getRepoConfig(repoPath);
326
+ const branchTickets = config.branchTickets ?? {};
327
+ const tickets = branchTickets[branch] ?? [];
328
+ updateRepoConfig(repoPath, {
329
+ branchTickets: {
330
+ ...branchTickets,
331
+ [branch]: tickets.map((t) => t.key === ticketKey ? { ...t, status: newStatus } : t)
332
+ }
333
+ });
505
334
  }
506
335
 
507
- // src/components/github/GitHubView.tsx
508
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
509
- function GitHubView({ isFocused, onKeybindingsChange }) {
510
- const [isRepo, setIsRepo] = useState3(null);
511
- const [repoPath, setRepoPath] = useState3(null);
512
- const [remotes, setRemotes] = useState3([]);
513
- const [currentBranch, setCurrentBranch] = useState3(null);
514
- const [currentRepoSlug, setCurrentRepoSlug] = useState3(null);
515
- const [selectedRemote, setSelectedRemote] = useState3(null);
516
- const [selectedPR, setSelectedPR] = useState3(null);
517
- const [prs, setPrs] = useState3([]);
518
- const [prDetails, setPrDetails] = useState3(null);
519
- const [loading, setLoading] = useState3({
520
- remotes: true,
521
- prs: false,
522
- details: false
523
- });
524
- const [errors, setErrors] = useState3({});
525
- const [focusedBox, setFocusedBox] = useState3("remotes");
526
- useEffect3(() => {
527
- if (!isFocused) {
528
- onKeybindingsChange == null ? void 0 : onKeybindingsChange([]);
529
- return;
336
+ // src/lib/jira/api.ts
337
+ function createAuthHeader(email, apiToken) {
338
+ const credentials = Buffer.from(`${email}:${apiToken}`).toString("base64");
339
+ return `Basic ${credentials}`;
340
+ }
341
+ async function jiraFetch(auth, endpoint, options) {
342
+ const url = `${auth.siteUrl}/rest/api/3${endpoint}`;
343
+ const method = (options == null ? void 0 : options.method) ?? "GET";
344
+ try {
345
+ const headers = {
346
+ Authorization: createAuthHeader(auth.email, auth.apiToken),
347
+ Accept: "application/json"
348
+ };
349
+ const fetchOptions = { method, headers };
350
+ if (options == null ? void 0 : options.body) {
351
+ headers["Content-Type"] = "application/json";
352
+ fetchOptions.body = JSON.stringify(options.body);
530
353
  }
531
- const bindings = [];
532
- if (focusedBox === "remotes") {
533
- bindings.push({ key: "Enter", label: "Select Remote" });
534
- } else if (focusedBox === "prs") {
535
- bindings.push({ key: "n", label: "New PR", color: "green" });
536
- bindings.push({ key: "r", label: "Refresh" });
537
- bindings.push({ key: "o", label: "Open", color: "green" });
538
- bindings.push({ key: "y", label: "Copy Link" });
539
- } else if (focusedBox === "details") {
540
- bindings.push({ key: "r", label: "Refresh" });
541
- bindings.push({ key: "o", label: "Open", color: "green" });
354
+ const response = await fetch(url, fetchOptions);
355
+ if (!response.ok) {
356
+ const text = await response.text();
357
+ return { ok: false, status: response.status, error: text };
542
358
  }
543
- onKeybindingsChange == null ? void 0 : onKeybindingsChange(bindings);
544
- }, [isFocused, focusedBox, onKeybindingsChange]);
545
- useEffect3(() => {
546
- const gitRepoCheck = isGitRepo();
547
- setIsRepo(gitRepoCheck);
548
- if (!gitRepoCheck) {
549
- setLoading((prev) => ({ ...prev, remotes: false }));
550
- setErrors((prev) => ({ ...prev, remotes: "Not a git repository" }));
551
- return;
359
+ if (response.status === 204) {
360
+ return { ok: true, status: response.status, data: null };
552
361
  }
553
- const rootResult = getRepoRoot();
554
- if (rootResult.success) {
555
- setRepoPath(rootResult.data);
362
+ const data = await response.json();
363
+ return { ok: true, status: response.status, data };
364
+ } catch (err) {
365
+ const message = err instanceof Error ? err.message : "Network error";
366
+ return { ok: false, status: 0, error: message };
367
+ }
368
+ }
369
+ async function validateCredentials(auth) {
370
+ const result = await jiraFetch(auth, "/myself");
371
+ if (!result.ok) {
372
+ if (result.status === 401 || result.status === 403) {
373
+ return {
374
+ success: false,
375
+ error: "Invalid credentials. Check your email and API token.",
376
+ errorType: "auth_error"
377
+ };
556
378
  }
557
- const branchResult = getCurrentBranch();
558
- if (branchResult.success) {
559
- setCurrentBranch(branchResult.data);
379
+ return {
380
+ success: false,
381
+ error: result.error ?? "Failed to connect to Jira",
382
+ errorType: "api_error"
383
+ };
384
+ }
385
+ return { success: true, data: result.data };
386
+ }
387
+ async function getIssue(auth, ticketKey) {
388
+ const result = await jiraFetch(auth, `/issue/${ticketKey}?fields=summary,status`);
389
+ if (!result.ok) {
390
+ if (result.status === 401 || result.status === 403) {
391
+ return {
392
+ success: false,
393
+ error: "Authentication failed",
394
+ errorType: "auth_error"
395
+ };
560
396
  }
561
- const remotesResult = listRemotes();
562
- if (remotesResult.success) {
563
- setRemotes(remotesResult.data);
564
- const remoteNames = remotesResult.data.map((r) => r.name);
565
- const defaultRemote = getSelectedRemote(rootResult.success ? rootResult.data : "", remoteNames);
566
- setSelectedRemote(defaultRemote);
567
- } else {
568
- setErrors((prev) => ({ ...prev, remotes: remotesResult.error }));
397
+ if (result.status === 404) {
398
+ return {
399
+ success: false,
400
+ error: `Ticket ${ticketKey} not found`,
401
+ errorType: "invalid_ticket"
402
+ };
569
403
  }
570
- setLoading((prev) => ({ ...prev, remotes: false }));
571
- }, []);
572
- const refreshPRs = useCallback(async () => {
573
- if (!currentBranch || !currentRepoSlug) return;
574
- setLoading((prev) => ({ ...prev, prs: true }));
575
- try {
576
- const result = await listPRsForBranch(currentBranch, currentRepoSlug);
577
- if (result.success) {
578
- setPrs(result.data);
579
- if (result.data.length > 0) {
580
- setSelectedPR((prev) => prev ?? result.data[0]);
581
- }
582
- setErrors((prev) => ({ ...prev, prs: void 0 }));
583
- } else {
584
- setErrors((prev) => ({ ...prev, prs: result.error }));
585
- }
586
- } catch (err) {
587
- setErrors((prev) => ({ ...prev, prs: String(err) }));
588
- } finally {
589
- setLoading((prev) => ({ ...prev, prs: false }));
404
+ return {
405
+ success: false,
406
+ error: result.error ?? "Failed to fetch issue",
407
+ errorType: "api_error"
408
+ };
409
+ }
410
+ return { success: true, data: result.data };
411
+ }
412
+ async function getTransitions(auth, ticketKey) {
413
+ const result = await jiraFetch(auth, `/issue/${ticketKey}/transitions`);
414
+ if (!result.ok) {
415
+ if (result.status === 401 || result.status === 403) {
416
+ return {
417
+ success: false,
418
+ error: "Authentication failed",
419
+ errorType: "auth_error"
420
+ };
590
421
  }
591
- }, [currentBranch, currentRepoSlug]);
592
- const refreshDetails = useCallback(async () => {
593
- if (!selectedPR || !currentRepoSlug) return;
594
- setLoading((prev) => ({ ...prev, details: true }));
595
- try {
596
- const result = await getPRDetails(selectedPR.number, currentRepoSlug);
597
- if (result.success) {
598
- setPrDetails(result.data);
599
- setErrors((prev) => ({ ...prev, details: void 0 }));
600
- } else {
601
- setErrors((prev) => ({ ...prev, details: result.error }));
602
- }
603
- } catch (err) {
604
- setErrors((prev) => ({ ...prev, details: String(err) }));
605
- } finally {
606
- setLoading((prev) => ({ ...prev, details: false }));
422
+ if (result.status === 404) {
423
+ return {
424
+ success: false,
425
+ error: `Ticket ${ticketKey} not found`,
426
+ errorType: "invalid_ticket"
427
+ };
607
428
  }
608
- }, [selectedPR, currentRepoSlug]);
609
- useEffect3(() => {
610
- if (!selectedRemote || !currentBranch) return;
611
- const remote = remotes.find((r) => r.name === selectedRemote);
612
- if (!remote) return;
613
- const repo = getRepoFromRemote(remote.url);
614
- if (!repo) return;
615
- setCurrentRepoSlug(repo);
616
- setPrs([]);
617
- setSelectedPR(null);
618
- }, [selectedRemote, currentBranch, remotes]);
619
- useEffect3(() => {
620
- if (currentRepoSlug && currentBranch) {
621
- refreshPRs();
429
+ return {
430
+ success: false,
431
+ error: result.error ?? "Failed to fetch transitions",
432
+ errorType: "api_error"
433
+ };
434
+ }
435
+ const data = result.data;
436
+ return { success: true, data: data.transitions };
437
+ }
438
+ async function applyTransition(auth, ticketKey, transitionId) {
439
+ const result = await jiraFetch(auth, `/issue/${ticketKey}/transitions`, {
440
+ method: "POST",
441
+ body: { transition: { id: transitionId } }
442
+ });
443
+ if (!result.ok) {
444
+ if (result.status === 401 || result.status === 403) {
445
+ return {
446
+ success: false,
447
+ error: "Authentication failed",
448
+ errorType: "auth_error"
449
+ };
622
450
  }
623
- }, [currentRepoSlug, currentBranch, refreshPRs]);
624
- useEffect3(() => {
625
- if (!selectedPR || !currentRepoSlug) {
626
- setPrDetails(null);
627
- return;
451
+ if (result.status === 404) {
452
+ return {
453
+ success: false,
454
+ error: `Ticket ${ticketKey} not found`,
455
+ errorType: "invalid_ticket"
456
+ };
628
457
  }
629
- refreshDetails();
630
- }, [selectedPR, currentRepoSlug, refreshDetails]);
631
- const handleRemoteSelect = useCallback(
632
- (remoteName) => {
633
- setSelectedRemote(remoteName);
634
- if (repoPath) {
635
- updateRepoConfig(repoPath, { selectedRemote: remoteName });
636
- }
637
- },
638
- [repoPath]
639
- );
640
- const handlePRSelect = useCallback((pr) => {
641
- setSelectedPR(pr);
642
- }, []);
643
- const handleCreatePR = useCallback(() => {
644
- exec3("gh pr create --web", () => {
645
- process.stdout.emit("resize");
646
- });
647
- }, []);
648
- useInput4(
649
- (input) => {
650
- if (input === "1") setFocusedBox("remotes");
651
- if (input === "2") setFocusedBox("prs");
652
- if (input === "3") setFocusedBox("details");
653
- if (input === "r") {
654
- if (focusedBox === "prs") refreshPRs();
655
- if (focusedBox === "details") refreshDetails();
656
- }
657
- },
658
- { isActive: isFocused }
659
- );
660
- if (isRepo === false) {
661
- return /* @__PURE__ */ jsx4(TitledBox4, { borderStyle: "round", titles: ["Error"], flexGrow: 1, children: /* @__PURE__ */ jsx4(Text4, { color: "red", children: "Current directory is not a git repository" }) });
662
- }
663
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", flexGrow: 1, children: [
664
- /* @__PURE__ */ jsx4(
665
- RemotesBox,
666
- {
667
- remotes,
668
- selectedRemote,
669
- onSelect: handleRemoteSelect,
670
- loading: loading.remotes,
671
- error: errors.remotes,
672
- isFocused: isFocused && focusedBox === "remotes"
673
- }
674
- ),
675
- /* @__PURE__ */ jsx4(
676
- PullRequestsBox,
677
- {
678
- prs,
679
- selectedPR,
680
- onSelect: handlePRSelect,
681
- onCreatePR: handleCreatePR,
682
- loading: loading.prs,
683
- error: errors.prs,
684
- branch: currentBranch,
685
- repoSlug: currentRepoSlug,
686
- isFocused: isFocused && focusedBox === "prs"
687
- }
688
- ),
689
- /* @__PURE__ */ jsx4(
690
- PRDetailsBox,
691
- {
692
- pr: prDetails,
693
- loading: loading.details,
694
- error: errors.details,
695
- isFocused: isFocused && focusedBox === "details"
696
- }
697
- )
698
- ] });
458
+ return {
459
+ success: false,
460
+ error: result.error ?? "Failed to apply transition",
461
+ errorType: "api_error"
462
+ };
463
+ }
464
+ return { success: true, data: null };
699
465
  }
700
466
 
701
- // src/components/jira/JiraView.tsx
702
- import { useCallback as useCallback2, useEffect as useEffect5, useState as useState7 } from "react";
703
- import open2 from "open";
704
- import { TitledBox as TitledBox5 } from "@mishieck/ink-titled-box";
705
- import { Box as Box10, Text as Text10, useInput as useInput9 } from "ink";
467
+ // src/lib/logs/index.ts
468
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, appendFileSync, writeFileSync as writeFileSync2 } from "fs";
469
+ import { homedir as homedir2 } from "os";
470
+ import { join as join2 } from "path";
471
+ import { spawnSync } from "child_process";
472
+ var LOGS_DIRECTORY = join2(homedir2(), ".clairo", "logs");
473
+ function ensureLogsDirectory() {
474
+ if (!existsSync2(LOGS_DIRECTORY)) {
475
+ mkdirSync2(LOGS_DIRECTORY, { recursive: true });
476
+ }
477
+ }
478
+ function getTodayDate() {
479
+ const now = /* @__PURE__ */ new Date();
480
+ const year = now.getFullYear();
481
+ const month = String(now.getMonth() + 1).padStart(2, "0");
482
+ const day = String(now.getDate()).padStart(2, "0");
483
+ return `${year}-${month}-${day}`;
484
+ }
485
+ function formatTimestamp() {
486
+ const now = /* @__PURE__ */ new Date();
487
+ const hours = String(now.getHours()).padStart(2, "0");
488
+ const minutes = String(now.getMinutes()).padStart(2, "0");
489
+ return `${hours}:${minutes}`;
490
+ }
491
+ function listLogFiles() {
492
+ ensureLogsDirectory();
493
+ try {
494
+ const files = readdirSync(LOGS_DIRECTORY);
495
+ const today = getTodayDate();
496
+ const logFiles = files.filter((file) => /^\d{4}-\d{2}-\d{2}\.md$/.test(file)).map((file) => {
497
+ const date = file.replace(".md", "");
498
+ return {
499
+ date,
500
+ filename: file,
501
+ isToday: date === today
502
+ };
503
+ }).sort((a, b) => b.date.localeCompare(a.date));
504
+ return logFiles;
505
+ } catch {
506
+ return [];
507
+ }
508
+ }
509
+ function readLog(date) {
510
+ const filePath = join2(LOGS_DIRECTORY, `${date}.md`);
511
+ try {
512
+ if (!existsSync2(filePath)) {
513
+ return null;
514
+ }
515
+ return readFileSync2(filePath, "utf-8");
516
+ } catch {
517
+ return null;
518
+ }
519
+ }
520
+ function getLogFilePath(date) {
521
+ return join2(LOGS_DIRECTORY, `${date}.md`);
522
+ }
523
+ function logExists(date) {
524
+ return existsSync2(getLogFilePath(date));
525
+ }
526
+ function createEmptyLog(date) {
527
+ ensureLogsDirectory();
528
+ const filePath = getLogFilePath(date);
529
+ if (existsSync2(filePath)) {
530
+ return;
531
+ }
532
+ const header = `# Log - ${date}
533
+ `;
534
+ writeFileSync2(filePath, header);
535
+ }
536
+ function appendToLog(date, entry) {
537
+ ensureLogsDirectory();
538
+ const filePath = getLogFilePath(date);
539
+ if (!existsSync2(filePath)) {
540
+ const header = `# Log - ${date}
706
541
 
707
- // src/lib/jira/parser.ts
708
- var TICKET_KEY_PATTERN = /^[A-Z][A-Z0-9]+-\d+$/;
709
- function isValidTicketKeyFormat(key) {
710
- return TICKET_KEY_PATTERN.test(key.toUpperCase());
542
+ `;
543
+ writeFileSync2(filePath, header);
544
+ }
545
+ appendFileSync(filePath, entry);
711
546
  }
712
- function parseTicketKey(input) {
713
- const trimmed = input.trim();
714
- const urlMatch = trimmed.match(/\/browse\/([A-Za-z][A-Za-z0-9]+-\d+)/i);
715
- if (urlMatch) {
716
- return urlMatch[1].toUpperCase();
547
+ function openLogInEditor(date) {
548
+ const filePath = getLogFilePath(date);
549
+ if (!existsSync2(filePath)) {
550
+ return false;
717
551
  }
718
- const upperInput = trimmed.toUpperCase();
719
- if (isValidTicketKeyFormat(upperInput)) {
720
- return upperInput;
552
+ const editor = process.env.VISUAL || process.env.EDITOR || "vi";
553
+ const result = spawnSync(editor, [filePath], {
554
+ stdio: "inherit"
555
+ });
556
+ process.stdout.write("\x1B[2J\x1B[H");
557
+ process.stdout.emit("resize");
558
+ return result.status === 0;
559
+ }
560
+
561
+ // src/lib/logs/logger.ts
562
+ function logPRCreated(prNumber, title, jiraTickets) {
563
+ const timestamp = formatTimestamp();
564
+ const today = getTodayDate();
565
+ let entry = `## ${timestamp} - Created PR #${prNumber}
566
+
567
+ ${title}
568
+ `;
569
+ if (jiraTickets.length > 0) {
570
+ entry += `Jira: ${jiraTickets.join(", ")}
571
+ `;
721
572
  }
722
- return null;
573
+ entry += "\n";
574
+ appendToLog(today, entry);
723
575
  }
724
- function extractTicketKeyFromBranch(branchName) {
725
- const match = branchName.match(/([A-Za-z][A-Za-z0-9]+-\d+)/);
726
- if (match) {
727
- const candidate = match[1].toUpperCase();
728
- if (isValidTicketKeyFormat(candidate)) {
729
- return candidate;
576
+ function logJiraStatusChanged(ticketKey, oldStatus, newStatus) {
577
+ const timestamp = formatTimestamp();
578
+ const today = getTodayDate();
579
+ const entry = `## ${timestamp} - Updated Jira ticket
580
+
581
+ ${ticketKey}: ${oldStatus} \u2192 ${newStatus}
582
+
583
+ `;
584
+ appendToLog(today, entry);
585
+ }
586
+
587
+ // src/components/github/PRDetailsBox.tsx
588
+ import { useRef } from "react";
589
+ import open from "open";
590
+ import { TitledBox } from "@mishieck/ink-titled-box";
591
+ import { Box as Box2, Text as Text2, useInput } from "ink";
592
+ import { ScrollView } from "ink-scroll-view";
593
+
594
+ // src/components/ui/Markdown.tsx
595
+ import { Box, Text } from "ink";
596
+ import Link from "ink-link";
597
+ import { marked } from "marked";
598
+ import Table from "cli-table3";
599
+ import { jsx, jsxs } from "react/jsx-runtime";
600
+ function Markdown({ children }) {
601
+ const tokens = marked.lexer(children);
602
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: tokens.map((token, idx) => /* @__PURE__ */ jsx(TokenRenderer, { token }, idx)) });
603
+ }
604
+ function TokenRenderer({ token }) {
605
+ var _a, _b;
606
+ switch (token.type) {
607
+ case "heading":
608
+ return /* @__PURE__ */ jsx(Box, { marginTop: token.depth === 1 ? 0 : 1, children: /* @__PURE__ */ jsx(Text, { bold: true, underline: token.depth === 1, children: renderInline(token.tokens) }) });
609
+ case "paragraph": {
610
+ const hasLinks = (_a = token.tokens) == null ? void 0 : _a.some((t) => {
611
+ var _a2;
612
+ return t.type === "link" || t.type === "strong" && "tokens" in t && ((_a2 = t.tokens) == null ? void 0 : _a2.some((st) => st.type === "link"));
613
+ });
614
+ if (hasLinks) {
615
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "row", flexWrap: "wrap", children: renderInline(token.tokens) });
616
+ }
617
+ return /* @__PURE__ */ jsx(Text, { children: renderInline(token.tokens) });
730
618
  }
619
+ case "code":
620
+ return /* @__PURE__ */ jsx(Box, { marginY: 1, paddingX: 1, borderStyle: "single", borderColor: "gray", children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: token.text }) });
621
+ case "blockquote":
622
+ return /* @__PURE__ */ jsxs(Box, { marginLeft: 2, children: [
623
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u2502 " }),
624
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: (_b = token.tokens) == null ? void 0 : _b.map((t, idx) => /* @__PURE__ */ jsx(TokenRenderer, { token: t }, idx)) })
625
+ ] });
626
+ case "list":
627
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginY: 1, children: token.items.map((item, idx) => /* @__PURE__ */ jsxs(Box, { children: [
628
+ /* @__PURE__ */ jsx(Text, { children: token.ordered ? `${idx + 1}. ` : "\u2022 " }),
629
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: item.tokens.map((t, i) => /* @__PURE__ */ jsx(TokenRenderer, { token: t }, i)) })
630
+ ] }, idx)) });
631
+ case "table":
632
+ return /* @__PURE__ */ jsx(TableRenderer, { token });
633
+ case "hr":
634
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(40) });
635
+ case "space":
636
+ return null;
637
+ default:
638
+ if ("text" in token && typeof token.text === "string") {
639
+ return /* @__PURE__ */ jsx(Text, { children: token.text });
640
+ }
641
+ return null;
731
642
  }
732
- return null;
733
643
  }
734
-
735
- // src/lib/jira/config.ts
736
- function isJiraConfigured(repoPath) {
737
- const config = getRepoConfig(repoPath);
738
- return !!(config.jiraSiteUrl && config.jiraEmail && config.jiraApiToken);
644
+ function TableRenderer({ token }) {
645
+ const table = new Table({
646
+ head: token.header.map((cell) => renderInlineToString(cell.tokens)),
647
+ style: { head: ["cyan"], border: ["gray"] }
648
+ });
649
+ for (const row of token.rows) {
650
+ table.push(row.map((cell) => renderInlineToString(cell.tokens)));
651
+ }
652
+ return /* @__PURE__ */ jsx(Text, { children: table.toString() });
739
653
  }
740
- function getJiraSiteUrl(repoPath) {
741
- const config = getRepoConfig(repoPath);
742
- return config.jiraSiteUrl ?? null;
654
+ function renderInline(tokens) {
655
+ if (!tokens) return null;
656
+ return tokens.map((token, idx) => {
657
+ switch (token.type) {
658
+ case "text":
659
+ return /* @__PURE__ */ jsx(Text, { children: token.text }, idx);
660
+ case "strong":
661
+ return /* @__PURE__ */ jsx(Text, { bold: true, children: renderInline(token.tokens) }, idx);
662
+ case "em":
663
+ return /* @__PURE__ */ jsx(Text, { italic: true, children: renderInline(token.tokens) }, idx);
664
+ case "codespan":
665
+ return /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
666
+ "`",
667
+ token.text,
668
+ "`"
669
+ ] }, idx);
670
+ case "link":
671
+ return /* @__PURE__ */ jsx(Link, { url: token.href, children: /* @__PURE__ */ jsx(Text, { color: "blue", children: renderInlineToString(token.tokens) }) }, idx);
672
+ case "image":
673
+ return /* @__PURE__ */ jsxs(Text, { color: "blue", children: [
674
+ "[Image: ",
675
+ token.text || token.href,
676
+ "]"
677
+ ] }, idx);
678
+ case "br":
679
+ return /* @__PURE__ */ jsx(Text, { children: "\n" }, idx);
680
+ case "del":
681
+ return /* @__PURE__ */ jsx(Text, { strikethrough: true, children: renderInline(token.tokens) }, idx);
682
+ default:
683
+ if ("text" in token && typeof token.text === "string") {
684
+ return /* @__PURE__ */ jsx(Text, { children: token.text }, idx);
685
+ }
686
+ return null;
687
+ }
688
+ });
743
689
  }
744
- function setJiraSiteUrl(repoPath, siteUrl) {
745
- updateRepoConfig(repoPath, { jiraSiteUrl: siteUrl });
690
+ function renderInlineToString(tokens) {
691
+ if (!tokens) return "";
692
+ return tokens.map((token) => {
693
+ if ("text" in token && typeof token.text === "string") {
694
+ return token.text;
695
+ }
696
+ if ("tokens" in token && Array.isArray(token.tokens)) {
697
+ return renderInlineToString(token.tokens);
698
+ }
699
+ return "";
700
+ }).join("");
746
701
  }
747
- function getJiraCredentials(repoPath) {
748
- const config = getRepoConfig(repoPath);
749
- return {
750
- email: config.jiraEmail ?? null,
751
- apiToken: config.jiraApiToken ?? null
702
+
703
+ // src/components/github/PRDetailsBox.tsx
704
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
705
+ function getCheckColor(check) {
706
+ const conclusion = check.conclusion ?? check.state;
707
+ if (conclusion === "SUCCESS") return "green";
708
+ if (conclusion === "FAILURE" || conclusion === "ERROR") return "red";
709
+ if (conclusion === "SKIPPED" || conclusion === "NEUTRAL") return "gray";
710
+ if (conclusion === "PENDING" || check.status === "IN_PROGRESS" || check.status === "QUEUED" || check.status === "WAITING")
711
+ return "yellow";
712
+ if (check.status === "COMPLETED") return "green";
713
+ return void 0;
714
+ }
715
+ function getCheckIcon(check) {
716
+ const conclusion = check.conclusion ?? check.state;
717
+ if (conclusion === "SUCCESS") return "\u2713";
718
+ if (conclusion === "FAILURE" || conclusion === "ERROR") return "\u2717";
719
+ if (conclusion === "SKIPPED" || conclusion === "NEUTRAL") return "\u25CB";
720
+ if (conclusion === "PENDING" || check.status === "IN_PROGRESS" || check.status === "QUEUED" || check.status === "WAITING")
721
+ return "\u25CF";
722
+ if (check.status === "COMPLETED") return "\u2713";
723
+ return "?";
724
+ }
725
+ function PRDetailsBox({ pr, loading, error, isFocused }) {
726
+ var _a, _b, _c, _d, _e, _f, _g;
727
+ const scrollRef = useRef(null);
728
+ const title = "[3] PR Details";
729
+ const borderColor = isFocused ? "yellow" : void 0;
730
+ const displayTitle = pr ? `${title} - #${pr.number}` : title;
731
+ const reviewStatus = (pr == null ? void 0 : pr.reviewDecision) ?? "PENDING";
732
+ const reviewColor = reviewStatus === "APPROVED" ? "green" : reviewStatus === "CHANGES_REQUESTED" ? "red" : "yellow";
733
+ const getMergeDisplay = () => {
734
+ if (!pr) return { text: "UNKNOWN", color: "yellow" };
735
+ if (pr.state === "MERGED") return { text: "MERGED", color: "magenta" };
736
+ if (pr.state === "CLOSED") return { text: "CLOSED", color: "red" };
737
+ if (pr.mergeable === "MERGEABLE") return { text: "MERGEABLE", color: "green" };
738
+ if (pr.mergeable === "CONFLICTING") return { text: "CONFLICTING", color: "red" };
739
+ return { text: pr.mergeable ?? "UNKNOWN", color: "yellow" };
752
740
  };
741
+ const mergeDisplay = getMergeDisplay();
742
+ useInput(
743
+ (input, key) => {
744
+ var _a2, _b2;
745
+ if (key.upArrow || input === "k") {
746
+ (_a2 = scrollRef.current) == null ? void 0 : _a2.scrollBy(-1);
747
+ }
748
+ if (key.downArrow || input === "j") {
749
+ (_b2 = scrollRef.current) == null ? void 0 : _b2.scrollBy(1);
750
+ }
751
+ if (input === "o" && (pr == null ? void 0 : pr.url)) {
752
+ open(pr.url).catch(() => {
753
+ });
754
+ }
755
+ },
756
+ { isActive: isFocused }
757
+ );
758
+ return /* @__PURE__ */ jsx2(TitledBox, { borderStyle: "round", titles: [displayTitle], borderColor, flexGrow: 1, children: /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx2(ScrollView, { ref: scrollRef, children: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
759
+ loading && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Loading details..." }),
760
+ error && /* @__PURE__ */ jsx2(Text2, { color: "red", children: error }),
761
+ !loading && !error && !pr && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Select a PR to view details" }),
762
+ !loading && !error && pr && /* @__PURE__ */ jsxs2(Fragment, { children: [
763
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: pr.title }),
764
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
765
+ "by ",
766
+ ((_a = pr.author) == null ? void 0 : _a.login) ?? "unknown",
767
+ " | ",
768
+ ((_b = pr.commits) == null ? void 0 : _b.length) ?? 0,
769
+ " commits"
770
+ ] }),
771
+ /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
772
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Review: " }),
773
+ /* @__PURE__ */ jsx2(Text2, { color: reviewColor, children: reviewStatus }),
774
+ /* @__PURE__ */ jsx2(Text2, { children: " | " }),
775
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Status: " }),
776
+ /* @__PURE__ */ jsx2(Text2, { color: mergeDisplay.color, children: mergeDisplay.text })
777
+ ] }),
778
+ (((_c = pr.assignees) == null ? void 0 : _c.length) ?? 0) > 0 && /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
779
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Assignees: " }),
780
+ /* @__PURE__ */ jsx2(Text2, { children: pr.assignees.map((a) => a.login).join(", ") })
781
+ ] }),
782
+ (((_d = pr.reviews) == null ? void 0 : _d.length) ?? 0) > 0 && /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
783
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Reviews:" }),
784
+ pr.reviews.map((review, idx) => {
785
+ const color = review.state === "APPROVED" ? "green" : review.state === "CHANGES_REQUESTED" ? "red" : review.state === "COMMENTED" ? "blue" : "yellow";
786
+ const icon = review.state === "APPROVED" ? "\u2713" : review.state === "CHANGES_REQUESTED" ? "\u2717" : review.state === "COMMENTED" ? "\u{1F4AC}" : "\u25CB";
787
+ return /* @__PURE__ */ jsxs2(Text2, { color, children: [
788
+ " ",
789
+ icon,
790
+ " ",
791
+ review.author.login
792
+ ] }, idx);
793
+ })
794
+ ] }),
795
+ (((_e = pr.reviewRequests) == null ? void 0 : _e.length) ?? 0) > 0 && /* @__PURE__ */ jsxs2(Box2, { children: [
796
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Pending: " }),
797
+ /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: pr.reviewRequests.map((r) => r.login ?? r.name ?? r.slug ?? "Team").join(", ") })
798
+ ] }),
799
+ (((_f = pr.statusCheckRollup) == null ? void 0 : _f.length) ?? 0) > 0 && /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
800
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Checks:" }),
801
+ (_g = pr.statusCheckRollup) == null ? void 0 : _g.map((check, idx) => /* @__PURE__ */ jsxs2(Text2, { color: getCheckColor(check), children: [
802
+ " ",
803
+ getCheckIcon(check),
804
+ " ",
805
+ check.name ?? check.context
806
+ ] }, idx))
807
+ ] }),
808
+ pr.body && /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
809
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Description:" }),
810
+ /* @__PURE__ */ jsx2(Markdown, { children: pr.body })
811
+ ] })
812
+ ] })
813
+ ] }) }) }) });
753
814
  }
754
- function setJiraCredentials(repoPath, email, apiToken) {
755
- updateRepoConfig(repoPath, { jiraEmail: email, jiraApiToken: apiToken });
815
+
816
+ // src/components/github/PullRequestsBox.tsx
817
+ import { useEffect, useState } from "react";
818
+ import { TitledBox as TitledBox2 } from "@mishieck/ink-titled-box";
819
+ import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
820
+
821
+ // src/lib/clipboard.ts
822
+ import { exec as exec2 } from "child_process";
823
+ async function copyToClipboard(text) {
824
+ var _a, _b;
825
+ const command = process.platform === "darwin" ? "pbcopy" : process.platform === "win32" ? "clip" : "xclip -selection clipboard";
826
+ try {
827
+ const child = exec2(command);
828
+ (_a = child.stdin) == null ? void 0 : _a.write(text);
829
+ (_b = child.stdin) == null ? void 0 : _b.end();
830
+ await new Promise((resolve, reject) => {
831
+ child.on("close", (code) => {
832
+ if (code === 0) resolve();
833
+ else reject(new Error(`Clipboard command exited with code ${code}`));
834
+ });
835
+ });
836
+ return true;
837
+ } catch {
838
+ return false;
839
+ }
756
840
  }
757
- function getLinkedTickets(repoPath, branch) {
758
- var _a;
759
- const config = getRepoConfig(repoPath);
760
- return ((_a = config.branchTickets) == null ? void 0 : _a[branch]) ?? [];
841
+
842
+ // src/components/github/PullRequestsBox.tsx
843
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
844
+ function PullRequestsBox({
845
+ prs,
846
+ selectedPR,
847
+ onSelect,
848
+ onCreatePR,
849
+ loading,
850
+ error,
851
+ branch,
852
+ repoSlug,
853
+ isFocused
854
+ }) {
855
+ const [highlightedIndex, setHighlightedIndex] = useState(0);
856
+ const totalItems = prs.length + 1;
857
+ useEffect(() => {
858
+ const idx = prs.findIndex((p) => p.number === (selectedPR == null ? void 0 : selectedPR.number));
859
+ if (idx >= 0) setHighlightedIndex(idx);
860
+ }, [selectedPR, prs]);
861
+ useInput2(
862
+ (input, key) => {
863
+ if (!isFocused) return;
864
+ if (key.upArrow || input === "k") {
865
+ setHighlightedIndex((prev) => Math.max(0, prev - 1));
866
+ }
867
+ if (key.downArrow || input === "j") {
868
+ setHighlightedIndex((prev) => Math.min(totalItems - 1, prev + 1));
869
+ }
870
+ if (key.return) {
871
+ if (highlightedIndex === prs.length) {
872
+ onCreatePR();
873
+ } else if (prs[highlightedIndex]) {
874
+ onSelect(prs[highlightedIndex]);
875
+ }
876
+ }
877
+ if (input === "y" && repoSlug && prs[highlightedIndex]) {
878
+ const pr = prs[highlightedIndex];
879
+ const url = `https://github.com/${repoSlug}/pull/${pr.number}`;
880
+ copyToClipboard(url);
881
+ }
882
+ },
883
+ { isActive: isFocused }
884
+ );
885
+ const title = "[2] Pull Requests";
886
+ const subtitle = branch ? ` (${branch})` : "";
887
+ const borderColor = isFocused ? "yellow" : void 0;
888
+ return /* @__PURE__ */ jsx3(TitledBox2, { borderStyle: "round", titles: [`${title}${subtitle}`], borderColor, flexShrink: 0, children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 1, overflow: "hidden", children: [
889
+ loading && /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Loading PRs..." }),
890
+ error && /* @__PURE__ */ jsx3(Text3, { color: "red", children: error }),
891
+ !loading && !error && /* @__PURE__ */ jsxs3(Fragment2, { children: [
892
+ prs.length === 0 && /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "No PRs for this branch" }),
893
+ prs.map((pr, idx) => {
894
+ const isHighlighted = isFocused && idx === highlightedIndex;
895
+ const isSelected = pr.number === (selectedPR == null ? void 0 : selectedPR.number);
896
+ const prefix = isHighlighted ? "> " : isSelected ? "\u25CF " : " ";
897
+ return /* @__PURE__ */ jsxs3(Text3, { color: isSelected ? "green" : void 0, children: [
898
+ prefix,
899
+ "#",
900
+ pr.number,
901
+ " ",
902
+ pr.isDraft ? "[Draft] " : "",
903
+ pr.title
904
+ ] }, pr.number);
905
+ }),
906
+ /* @__PURE__ */ jsxs3(Text3, { color: "blue", children: [
907
+ isFocused && highlightedIndex === prs.length ? "> " : " ",
908
+ "+ Create new PR"
909
+ ] })
910
+ ] })
911
+ ] }) });
761
912
  }
762
- function addLinkedTicket(repoPath, branch, ticket) {
763
- const config = getRepoConfig(repoPath);
764
- const branchTickets = config.branchTickets ?? {};
765
- const tickets = branchTickets[branch] ?? [];
766
- if (tickets.some((t) => t.key === ticket.key)) {
767
- return;
768
- }
769
- updateRepoConfig(repoPath, {
770
- branchTickets: {
771
- ...branchTickets,
772
- [branch]: [...tickets, ticket]
773
- }
774
- });
913
+
914
+ // src/components/github/RemotesBox.tsx
915
+ import { useEffect as useEffect2, useState as useState2 } from "react";
916
+ import { TitledBox as TitledBox3 } from "@mishieck/ink-titled-box";
917
+ import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
918
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
919
+ function RemotesBox({ remotes, selectedRemote, onSelect, loading, error, isFocused }) {
920
+ const [highlightedIndex, setHighlightedIndex] = useState2(0);
921
+ useEffect2(() => {
922
+ const idx = remotes.findIndex((r) => r.name === selectedRemote);
923
+ if (idx >= 0) setHighlightedIndex(idx);
924
+ }, [selectedRemote, remotes]);
925
+ useInput3(
926
+ (input, key) => {
927
+ if (!isFocused || remotes.length === 0) return;
928
+ if (key.upArrow || input === "k") {
929
+ setHighlightedIndex((prev) => Math.max(0, prev - 1));
930
+ }
931
+ if (key.downArrow || input === "j") {
932
+ setHighlightedIndex((prev) => Math.min(remotes.length - 1, prev + 1));
933
+ }
934
+ if (key.return) {
935
+ onSelect(remotes[highlightedIndex].name);
936
+ }
937
+ },
938
+ { isActive: isFocused }
939
+ );
940
+ const title = "[1] Remotes";
941
+ const borderColor = isFocused ? "yellow" : void 0;
942
+ return /* @__PURE__ */ jsx4(TitledBox3, { borderStyle: "round", titles: [title], borderColor, flexShrink: 0, children: /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingX: 1, overflow: "hidden", children: [
943
+ loading && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Loading..." }),
944
+ error && /* @__PURE__ */ jsx4(Text4, { color: "red", children: error }),
945
+ !loading && !error && remotes.length === 0 && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "No remotes configured" }),
946
+ !loading && !error && remotes.map((remote, idx) => {
947
+ const isHighlighted = isFocused && idx === highlightedIndex;
948
+ const isSelected = remote.name === selectedRemote;
949
+ const prefix = isHighlighted ? "> " : isSelected ? "\u25CF " : " ";
950
+ return /* @__PURE__ */ jsxs4(Text4, { color: isSelected ? "green" : void 0, children: [
951
+ prefix,
952
+ remote.name,
953
+ " (",
954
+ remote.url,
955
+ ")"
956
+ ] }, remote.name);
957
+ })
958
+ ] }) });
775
959
  }
776
- function removeLinkedTicket(repoPath, branch, ticketKey) {
777
- const config = getRepoConfig(repoPath);
778
- const branchTickets = config.branchTickets ?? {};
779
- const tickets = branchTickets[branch] ?? [];
780
- updateRepoConfig(repoPath, {
781
- branchTickets: {
782
- ...branchTickets,
783
- [branch]: tickets.filter((t) => t.key !== ticketKey)
784
- }
960
+
961
+ // src/components/github/GitHubView.tsx
962
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
963
+ function GitHubView({ isFocused, onKeybindingsChange, onLogUpdated }) {
964
+ const [isRepo, setIsRepo] = useState3(null);
965
+ const [repoPath, setRepoPath] = useState3(null);
966
+ const [remotes, setRemotes] = useState3([]);
967
+ const [currentBranch, setCurrentBranch] = useState3(null);
968
+ const [currentRepoSlug, setCurrentRepoSlug] = useState3(null);
969
+ const [selectedRemote, setSelectedRemote] = useState3(null);
970
+ const [selectedPR, setSelectedPR] = useState3(null);
971
+ const [prs, setPrs] = useState3([]);
972
+ const [prDetails, setPrDetails] = useState3(null);
973
+ const [loading, setLoading] = useState3({
974
+ remotes: true,
975
+ prs: false,
976
+ details: false
785
977
  });
786
- }
787
- function updateTicketStatus(repoPath, branch, ticketKey, newStatus) {
788
- const config = getRepoConfig(repoPath);
789
- const branchTickets = config.branchTickets ?? {};
790
- const tickets = branchTickets[branch] ?? [];
791
- updateRepoConfig(repoPath, {
792
- branchTickets: {
793
- ...branchTickets,
794
- [branch]: tickets.map((t) => t.key === ticketKey ? { ...t, status: newStatus } : t)
978
+ const [errors, setErrors] = useState3({});
979
+ const [focusedBox, setFocusedBox] = useState3("remotes");
980
+ useEffect3(() => {
981
+ if (!isFocused) {
982
+ onKeybindingsChange == null ? void 0 : onKeybindingsChange([]);
983
+ return;
795
984
  }
796
- });
797
- }
798
-
799
- // src/lib/jira/api.ts
800
- function createAuthHeader(email, apiToken) {
801
- const credentials = Buffer.from(`${email}:${apiToken}`).toString("base64");
802
- return `Basic ${credentials}`;
803
- }
804
- async function jiraFetch(auth, endpoint, options) {
805
- const url = `${auth.siteUrl}/rest/api/3${endpoint}`;
806
- const method = (options == null ? void 0 : options.method) ?? "GET";
807
- try {
808
- const headers = {
809
- Authorization: createAuthHeader(auth.email, auth.apiToken),
810
- Accept: "application/json"
811
- };
812
- const fetchOptions = { method, headers };
813
- if (options == null ? void 0 : options.body) {
814
- headers["Content-Type"] = "application/json";
815
- fetchOptions.body = JSON.stringify(options.body);
985
+ const bindings = [];
986
+ if (focusedBox === "remotes") {
987
+ bindings.push({ key: "Enter", label: "Select Remote" });
988
+ } else if (focusedBox === "prs") {
989
+ bindings.push({ key: "n", label: "New PR", color: "green" });
990
+ bindings.push({ key: "r", label: "Refresh" });
991
+ bindings.push({ key: "o", label: "Open", color: "green" });
992
+ bindings.push({ key: "y", label: "Copy Link" });
993
+ } else if (focusedBox === "details") {
994
+ bindings.push({ key: "r", label: "Refresh" });
995
+ bindings.push({ key: "o", label: "Open", color: "green" });
816
996
  }
817
- const response = await fetch(url, fetchOptions);
818
- if (!response.ok) {
819
- const text = await response.text();
820
- return { ok: false, status: response.status, error: text };
997
+ onKeybindingsChange == null ? void 0 : onKeybindingsChange(bindings);
998
+ }, [isFocused, focusedBox, onKeybindingsChange]);
999
+ useEffect3(() => {
1000
+ const gitRepoCheck = isGitRepo();
1001
+ setIsRepo(gitRepoCheck);
1002
+ if (!gitRepoCheck) {
1003
+ setLoading((prev) => ({ ...prev, remotes: false }));
1004
+ setErrors((prev) => ({ ...prev, remotes: "Not a git repository" }));
1005
+ return;
821
1006
  }
822
- if (response.status === 204) {
823
- return { ok: true, status: response.status, data: null };
1007
+ const rootResult = getRepoRoot();
1008
+ if (rootResult.success) {
1009
+ setRepoPath(rootResult.data);
824
1010
  }
825
- const data = await response.json();
826
- return { ok: true, status: response.status, data };
827
- } catch (err) {
828
- const message = err instanceof Error ? err.message : "Network error";
829
- return { ok: false, status: 0, error: message };
830
- }
831
- }
832
- async function validateCredentials(auth) {
833
- const result = await jiraFetch(auth, "/myself");
834
- if (!result.ok) {
835
- if (result.status === 401 || result.status === 403) {
836
- return {
837
- success: false,
838
- error: "Invalid credentials. Check your email and API token.",
839
- errorType: "auth_error"
840
- };
1011
+ const branchResult = getCurrentBranch();
1012
+ if (branchResult.success) {
1013
+ setCurrentBranch(branchResult.data);
841
1014
  }
842
- return {
843
- success: false,
844
- error: result.error ?? "Failed to connect to Jira",
845
- errorType: "api_error"
846
- };
847
- }
848
- return { success: true, data: result.data };
849
- }
850
- async function getIssue(auth, ticketKey) {
851
- const result = await jiraFetch(auth, `/issue/${ticketKey}?fields=summary,status`);
852
- if (!result.ok) {
853
- if (result.status === 401 || result.status === 403) {
854
- return {
855
- success: false,
856
- error: "Authentication failed",
857
- errorType: "auth_error"
858
- };
1015
+ const remotesResult = listRemotes();
1016
+ if (remotesResult.success) {
1017
+ setRemotes(remotesResult.data);
1018
+ const remoteNames = remotesResult.data.map((r) => r.name);
1019
+ const defaultRemote = getSelectedRemote(rootResult.success ? rootResult.data : "", remoteNames);
1020
+ setSelectedRemote(defaultRemote);
1021
+ } else {
1022
+ setErrors((prev) => ({ ...prev, remotes: remotesResult.error }));
859
1023
  }
860
- if (result.status === 404) {
861
- return {
862
- success: false,
863
- error: `Ticket ${ticketKey} not found`,
864
- errorType: "invalid_ticket"
865
- };
1024
+ setLoading((prev) => ({ ...prev, remotes: false }));
1025
+ }, []);
1026
+ const refreshPRs = useCallback(async () => {
1027
+ if (!currentBranch || !currentRepoSlug) return;
1028
+ setLoading((prev) => ({ ...prev, prs: true }));
1029
+ try {
1030
+ const result = await listPRsForBranch(currentBranch, currentRepoSlug);
1031
+ if (result.success) {
1032
+ setPrs(result.data);
1033
+ if (result.data.length > 0) {
1034
+ setSelectedPR((prev) => prev ?? result.data[0]);
1035
+ }
1036
+ setErrors((prev) => ({ ...prev, prs: void 0 }));
1037
+ } else {
1038
+ setErrors((prev) => ({ ...prev, prs: result.error }));
1039
+ }
1040
+ } catch (err) {
1041
+ setErrors((prev) => ({ ...prev, prs: String(err) }));
1042
+ } finally {
1043
+ setLoading((prev) => ({ ...prev, prs: false }));
866
1044
  }
867
- return {
868
- success: false,
869
- error: result.error ?? "Failed to fetch issue",
870
- errorType: "api_error"
871
- };
872
- }
873
- return { success: true, data: result.data };
874
- }
875
- async function getTransitions(auth, ticketKey) {
876
- const result = await jiraFetch(auth, `/issue/${ticketKey}/transitions`);
877
- if (!result.ok) {
878
- if (result.status === 401 || result.status === 403) {
879
- return {
880
- success: false,
881
- error: "Authentication failed",
882
- errorType: "auth_error"
883
- };
1045
+ }, [currentBranch, currentRepoSlug]);
1046
+ const refreshDetails = useCallback(async () => {
1047
+ if (!selectedPR || !currentRepoSlug) return;
1048
+ setLoading((prev) => ({ ...prev, details: true }));
1049
+ try {
1050
+ const result = await getPRDetails(selectedPR.number, currentRepoSlug);
1051
+ if (result.success) {
1052
+ setPrDetails(result.data);
1053
+ setErrors((prev) => ({ ...prev, details: void 0 }));
1054
+ } else {
1055
+ setErrors((prev) => ({ ...prev, details: result.error }));
1056
+ }
1057
+ } catch (err) {
1058
+ setErrors((prev) => ({ ...prev, details: String(err) }));
1059
+ } finally {
1060
+ setLoading((prev) => ({ ...prev, details: false }));
884
1061
  }
885
- if (result.status === 404) {
886
- return {
887
- success: false,
888
- error: `Ticket ${ticketKey} not found`,
889
- errorType: "invalid_ticket"
890
- };
1062
+ }, [selectedPR, currentRepoSlug]);
1063
+ useEffect3(() => {
1064
+ if (!selectedRemote || !currentBranch) return;
1065
+ const remote = remotes.find((r) => r.name === selectedRemote);
1066
+ if (!remote) return;
1067
+ const repo = getRepoFromRemote(remote.url);
1068
+ if (!repo) return;
1069
+ setCurrentRepoSlug(repo);
1070
+ setPrs([]);
1071
+ setSelectedPR(null);
1072
+ }, [selectedRemote, currentBranch, remotes]);
1073
+ useEffect3(() => {
1074
+ if (currentRepoSlug && currentBranch) {
1075
+ refreshPRs();
891
1076
  }
892
- return {
893
- success: false,
894
- error: result.error ?? "Failed to fetch transitions",
895
- errorType: "api_error"
896
- };
897
- }
898
- const data = result.data;
899
- return { success: true, data: data.transitions };
900
- }
901
- async function applyTransition(auth, ticketKey, transitionId) {
902
- const result = await jiraFetch(auth, `/issue/${ticketKey}/transitions`, {
903
- method: "POST",
904
- body: { transition: { id: transitionId } }
905
- });
906
- if (!result.ok) {
907
- if (result.status === 401 || result.status === 403) {
908
- return {
909
- success: false,
910
- error: "Authentication failed",
911
- errorType: "auth_error"
912
- };
1077
+ }, [currentRepoSlug, currentBranch, refreshPRs]);
1078
+ useEffect3(() => {
1079
+ if (!selectedPR || !currentRepoSlug) {
1080
+ setPrDetails(null);
1081
+ return;
913
1082
  }
914
- if (result.status === 404) {
915
- return {
916
- success: false,
917
- error: `Ticket ${ticketKey} not found`,
918
- errorType: "invalid_ticket"
919
- };
1083
+ refreshDetails();
1084
+ }, [selectedPR, currentRepoSlug, refreshDetails]);
1085
+ const handleRemoteSelect = useCallback(
1086
+ (remoteName) => {
1087
+ setSelectedRemote(remoteName);
1088
+ if (repoPath) {
1089
+ updateRepoConfig(repoPath, { selectedRemote: remoteName });
1090
+ }
1091
+ },
1092
+ [repoPath]
1093
+ );
1094
+ const handlePRSelect = useCallback((pr) => {
1095
+ setSelectedPR(pr);
1096
+ }, []);
1097
+ const prNumbersBeforeCreate = useRef2(/* @__PURE__ */ new Set());
1098
+ const pollingIntervalRef = useRef2(null);
1099
+ const handleCreatePR = useCallback(() => {
1100
+ prNumbersBeforeCreate.current = new Set(prs.map((pr) => pr.number));
1101
+ exec3("gh pr create --web", () => {
1102
+ process.stdout.emit("resize");
1103
+ });
1104
+ if (!currentBranch || !currentRepoSlug) return;
1105
+ let attempts = 0;
1106
+ const maxAttempts = 24;
1107
+ const pollInterval = 5e3;
1108
+ if (pollingIntervalRef.current) {
1109
+ clearInterval(pollingIntervalRef.current);
920
1110
  }
921
- return {
922
- success: false,
923
- error: result.error ?? "Failed to apply transition",
924
- errorType: "api_error"
1111
+ pollingIntervalRef.current = setInterval(async () => {
1112
+ attempts++;
1113
+ if (attempts > maxAttempts) {
1114
+ if (pollingIntervalRef.current) {
1115
+ clearInterval(pollingIntervalRef.current);
1116
+ pollingIntervalRef.current = null;
1117
+ }
1118
+ return;
1119
+ }
1120
+ const result = await listPRsForBranch(currentBranch, currentRepoSlug);
1121
+ if (result.success) {
1122
+ setPrs(result.data);
1123
+ const newPR = result.data.find((pr) => !prNumbersBeforeCreate.current.has(pr.number));
1124
+ if (newPR) {
1125
+ if (pollingIntervalRef.current) {
1126
+ clearInterval(pollingIntervalRef.current);
1127
+ pollingIntervalRef.current = null;
1128
+ }
1129
+ const tickets = repoPath && currentBranch ? getLinkedTickets(repoPath, currentBranch).map((t) => t.key) : [];
1130
+ logPRCreated(newPR.number, newPR.title, tickets);
1131
+ onLogUpdated == null ? void 0 : onLogUpdated();
1132
+ setSelectedPR(newPR);
1133
+ }
1134
+ }
1135
+ }, pollInterval);
1136
+ }, [prs, currentBranch, currentRepoSlug, repoPath, onLogUpdated]);
1137
+ useEffect3(() => {
1138
+ return () => {
1139
+ if (pollingIntervalRef.current) {
1140
+ clearInterval(pollingIntervalRef.current);
1141
+ }
925
1142
  };
1143
+ }, []);
1144
+ useInput4(
1145
+ (input) => {
1146
+ if (input === "1") setFocusedBox("remotes");
1147
+ if (input === "2") setFocusedBox("prs");
1148
+ if (input === "3") setFocusedBox("details");
1149
+ if (input === "r") {
1150
+ if (focusedBox === "prs") refreshPRs();
1151
+ if (focusedBox === "details") refreshDetails();
1152
+ }
1153
+ },
1154
+ { isActive: isFocused }
1155
+ );
1156
+ if (isRepo === false) {
1157
+ return /* @__PURE__ */ jsx5(TitledBox4, { borderStyle: "round", titles: ["Error"], flexGrow: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "red", children: "Current directory is not a git repository" }) });
926
1158
  }
927
- return { success: true, data: null };
1159
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", flexGrow: 1, children: [
1160
+ /* @__PURE__ */ jsx5(
1161
+ RemotesBox,
1162
+ {
1163
+ remotes,
1164
+ selectedRemote,
1165
+ onSelect: handleRemoteSelect,
1166
+ loading: loading.remotes,
1167
+ error: errors.remotes,
1168
+ isFocused: isFocused && focusedBox === "remotes"
1169
+ }
1170
+ ),
1171
+ /* @__PURE__ */ jsx5(
1172
+ PullRequestsBox,
1173
+ {
1174
+ prs,
1175
+ selectedPR,
1176
+ onSelect: handlePRSelect,
1177
+ onCreatePR: handleCreatePR,
1178
+ loading: loading.prs,
1179
+ error: errors.prs,
1180
+ branch: currentBranch,
1181
+ repoSlug: currentRepoSlug,
1182
+ isFocused: isFocused && focusedBox === "prs"
1183
+ }
1184
+ ),
1185
+ /* @__PURE__ */ jsx5(
1186
+ PRDetailsBox,
1187
+ {
1188
+ pr: prDetails,
1189
+ loading: loading.details,
1190
+ error: errors.details,
1191
+ isFocused: isFocused && focusedBox === "details"
1192
+ }
1193
+ )
1194
+ ] });
928
1195
  }
929
1196
 
1197
+ // src/components/jira/JiraView.tsx
1198
+ import { useCallback as useCallback2, useEffect as useEffect5, useState as useState7 } from "react";
1199
+ import open2 from "open";
1200
+ import { TitledBox as TitledBox5 } from "@mishieck/ink-titled-box";
1201
+ import { Box as Box11, Text as Text11, useInput as useInput9 } from "ink";
1202
+
930
1203
  // src/components/jira/ChangeStatusModal.tsx
931
1204
  import { useEffect as useEffect4, useState as useState4 } from "react";
932
- import { Box as Box5, Text as Text5, useInput as useInput5 } from "ink";
1205
+ import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
933
1206
  import SelectInput from "ink-select-input";
934
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1207
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
935
1208
  function ChangeStatusModal({ repoPath, ticketKey, currentStatus, onComplete, onCancel }) {
936
1209
  const [transitions, setTransitions] = useState4([]);
937
1210
  const [loading, setLoading] = useState4(true);
@@ -991,36 +1264,36 @@ function ChangeStatusModal({ repoPath, ticketKey, currentStatus, onComplete, onC
991
1264
  value: t.id
992
1265
  }));
993
1266
  const initialIndex = Math.max(0, transitions.findIndex((t) => t.to.name === currentStatus));
994
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 1, children: [
995
- /* @__PURE__ */ jsxs5(Text5, { bold: true, color: "yellow", children: [
1267
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 1, children: [
1268
+ /* @__PURE__ */ jsxs6(Text6, { bold: true, color: "yellow", children: [
996
1269
  "Change Status: ",
997
1270
  ticketKey
998
1271
  ] }),
999
- loading && /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Loading transitions..." }),
1000
- error && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "red", children: error }) }),
1001
- !loading && !error && transitions.length === 0 && /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "No available transitions" }),
1002
- !loading && !error && transitions.length > 0 && !applying && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx5(SelectInput, { items, initialIndex, onSelect: handleSelect }) }),
1003
- applying && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "Updating status..." }) }),
1004
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Esc to cancel" }) })
1272
+ loading && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Loading transitions..." }),
1273
+ error && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "red", children: error }) }),
1274
+ !loading && !error && transitions.length === 0 && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "No available transitions" }),
1275
+ !loading && !error && transitions.length > 0 && !applying && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx6(SelectInput, { items, initialIndex, onSelect: handleSelect }) }),
1276
+ applying && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "yellow", children: "Updating status..." }) }),
1277
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Esc to cancel" }) })
1005
1278
  ] });
1006
1279
  }
1007
1280
 
1008
1281
  // src/components/jira/ConfigureJiraSiteModal.tsx
1009
1282
  import { useState as useState5 } from "react";
1010
- import { Box as Box6, Text as Text6, useInput as useInput6 } from "ink";
1283
+ import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
1011
1284
 
1012
1285
  // src/lib/editor.ts
1013
- import { spawnSync } from "child_process";
1014
- import { mkdtempSync, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
1286
+ import { spawnSync as spawnSync2 } from "child_process";
1287
+ import { mkdtempSync, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
1015
1288
  import { tmpdir } from "os";
1016
- import { join as join2 } from "path";
1289
+ import { join as join3 } from "path";
1017
1290
  function openInEditor(content, filename) {
1018
1291
  const editor = process.env.VISUAL || process.env.EDITOR || "vi";
1019
- const tempDir = mkdtempSync(join2(tmpdir(), "clairo-"));
1020
- const tempFile = join2(tempDir, filename);
1292
+ const tempDir = mkdtempSync(join3(tmpdir(), "clairo-"));
1293
+ const tempFile = join3(tempDir, filename);
1021
1294
  try {
1022
- writeFileSync2(tempFile, content);
1023
- const result = spawnSync(editor, [tempFile], {
1295
+ writeFileSync3(tempFile, content);
1296
+ const result = spawnSync2(editor, [tempFile], {
1024
1297
  stdio: "inherit"
1025
1298
  });
1026
1299
  process.stdout.write("\x1B[2J\x1B[H");
@@ -1028,7 +1301,7 @@ function openInEditor(content, filename) {
1028
1301
  if (result.status !== 0) {
1029
1302
  return null;
1030
1303
  }
1031
- return readFileSync2(tempFile, "utf-8");
1304
+ return readFileSync3(tempFile, "utf-8");
1032
1305
  } finally {
1033
1306
  try {
1034
1307
  rmSync(tempDir, { recursive: true });
@@ -1038,7 +1311,7 @@ function openInEditor(content, filename) {
1038
1311
  }
1039
1312
 
1040
1313
  // src/components/jira/ConfigureJiraSiteModal.tsx
1041
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1314
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1042
1315
  function ConfigureJiraSiteModal({
1043
1316
  initialSiteUrl,
1044
1317
  initialEmail,
@@ -1102,41 +1375,41 @@ function ConfigureJiraSiteModal({
1102
1375
  const prefix = isSelected ? "> " : " ";
1103
1376
  const color = isSelected ? "yellow" : void 0;
1104
1377
  const displayValue = isSensitive && value ? "*".repeat(Math.min(value.length, 20)) : value;
1105
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
1106
- /* @__PURE__ */ jsxs6(Text6, { color, bold: isSelected, children: [
1378
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1379
+ /* @__PURE__ */ jsxs7(Text7, { color, bold: isSelected, children: [
1107
1380
  prefix,
1108
1381
  label
1109
1382
  ] }),
1110
- value !== void 0 && /* @__PURE__ */ jsx6(Box6, { marginLeft: 4, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: displayValue || "(empty - press Enter to edit)" }) })
1383
+ value !== void 0 && /* @__PURE__ */ jsx7(Box7, { marginLeft: 4, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: displayValue || "(empty - press Enter to edit)" }) })
1111
1384
  ] });
1112
1385
  };
1113
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, paddingY: 1, children: [
1114
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: "Configure Jira Site" }),
1115
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Up/Down to select, Enter to edit, Esc to cancel" }),
1116
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1 }),
1117
- error && /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "red", children: error }) }),
1386
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, paddingY: 1, children: [
1387
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "Configure Jira Site" }),
1388
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Up/Down to select, Enter to edit, Esc to cancel" }),
1389
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1 }),
1390
+ error && /* @__PURE__ */ jsx7(Box7, { marginBottom: 1, children: /* @__PURE__ */ jsx7(Text7, { color: "red", children: error }) }),
1118
1391
  renderItem("siteUrl", "Site URL (e.g., https://company.atlassian.net)", siteUrl),
1119
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1 }),
1392
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1 }),
1120
1393
  renderItem("email", "Email", email),
1121
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1 }),
1394
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1 }),
1122
1395
  renderItem("apiToken", "API Token", apiToken, true),
1123
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1 }),
1124
- /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsxs6(Text6, { color: selectedItem === "submit" ? "green" : void 0, bold: selectedItem === "submit", children: [
1396
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1 }),
1397
+ /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text7, { color: selectedItem === "submit" ? "green" : void 0, bold: selectedItem === "submit", children: [
1125
1398
  selectedItem === "submit" ? "> " : " ",
1126
1399
  canSubmit ? "[Save Configuration]" : "[Fill all fields first]"
1127
1400
  ] }) }),
1128
- loading && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "yellow", children: "Validating credentials..." }) }),
1129
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Get your API token from: https://id.atlassian.com/manage-profile/security/api-tokens" }) })
1401
+ loading && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { color: "yellow", children: "Validating credentials..." }) }),
1402
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Get your API token from: https://id.atlassian.com/manage-profile/security/api-tokens" }) })
1130
1403
  ] });
1131
1404
  }
1132
1405
 
1133
1406
  // src/components/jira/LinkTicketModal.tsx
1134
1407
  import { useState as useState6 } from "react";
1135
- import { Box as Box8, Text as Text8, useInput as useInput8 } from "ink";
1408
+ import { Box as Box9, Text as Text9, useInput as useInput8 } from "ink";
1136
1409
 
1137
1410
  // src/components/ui/TextInput.tsx
1138
- import { Box as Box7, Text as Text7, useInput as useInput7 } from "ink";
1139
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1411
+ import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
1412
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1140
1413
  function TextInput({ value, onChange, placeholder, isActive, mask }) {
1141
1414
  useInput7(
1142
1415
  (input, key) => {
@@ -1157,14 +1430,14 @@ function TextInput({ value, onChange, placeholder, isActive, mask }) {
1157
1430
  );
1158
1431
  const displayValue = mask ? "*".repeat(value.length) : value;
1159
1432
  const showPlaceholder = value.length === 0 && placeholder;
1160
- return /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text7, { children: [
1161
- showPlaceholder ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: placeholder }) : /* @__PURE__ */ jsx7(Text7, { children: displayValue }),
1162
- isActive && /* @__PURE__ */ jsx7(Text7, { backgroundColor: "yellow", children: " " })
1433
+ return /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { children: [
1434
+ showPlaceholder ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: placeholder }) : /* @__PURE__ */ jsx8(Text8, { children: displayValue }),
1435
+ isActive && /* @__PURE__ */ jsx8(Text8, { backgroundColor: "yellow", children: " " })
1163
1436
  ] }) });
1164
1437
  }
1165
1438
 
1166
1439
  // src/components/jira/LinkTicketModal.tsx
1167
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1440
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1168
1441
  function LinkTicketModal({ onSubmit, onCancel, loading, error }) {
1169
1442
  const [ticketInput, setTicketInput] = useState6("");
1170
1443
  const canSubmit = ticketInput.trim().length > 0;
@@ -1181,14 +1454,14 @@ function LinkTicketModal({ onSubmit, onCancel, loading, error }) {
1181
1454
  },
1182
1455
  { isActive: !loading }
1183
1456
  );
1184
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 1, children: [
1185
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: "yellow", children: "Link Jira Ticket" }),
1186
- /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Type ticket ID, Enter to submit, Esc to cancel" }),
1187
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1 }),
1188
- error && /* @__PURE__ */ jsx8(Box8, { marginBottom: 1, children: /* @__PURE__ */ jsx8(Text8, { color: "red", children: error }) }),
1189
- /* @__PURE__ */ jsxs8(Box8, { children: [
1190
- /* @__PURE__ */ jsx8(Text8, { color: "blue", children: "Ticket: " }),
1191
- /* @__PURE__ */ jsx8(
1457
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 1, children: [
1458
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: "yellow", children: "Link Jira Ticket" }),
1459
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Type ticket ID, Enter to submit, Esc to cancel" }),
1460
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1 }),
1461
+ error && /* @__PURE__ */ jsx9(Box9, { marginBottom: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "red", children: error }) }),
1462
+ /* @__PURE__ */ jsxs9(Box9, { children: [
1463
+ /* @__PURE__ */ jsx9(Text9, { color: "blue", children: "Ticket: " }),
1464
+ /* @__PURE__ */ jsx9(
1192
1465
  TextInput,
1193
1466
  {
1194
1467
  value: ticketInput,
@@ -1198,23 +1471,23 @@ function LinkTicketModal({ onSubmit, onCancel, loading, error }) {
1198
1471
  }
1199
1472
  )
1200
1473
  ] }),
1201
- loading && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { color: "yellow", children: "Fetching ticket..." }) }),
1202
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Examples: PROJ-123 or https://company.atlassian.net/browse/PROJ-123" }) })
1474
+ loading && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "Fetching ticket..." }) }),
1475
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Examples: PROJ-123 or https://company.atlassian.net/browse/PROJ-123" }) })
1203
1476
  ] });
1204
1477
  }
1205
1478
 
1206
1479
  // src/components/jira/TicketItem.tsx
1207
- import { Box as Box9, Text as Text9 } from "ink";
1208
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1480
+ import { Box as Box10, Text as Text10 } from "ink";
1481
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1209
1482
  function TicketItem({ ticketKey, summary, status, isHighlighted, isSelected }) {
1210
1483
  const prefix = isHighlighted ? "> " : isSelected ? "\u25CF " : " ";
1211
1484
  const textColor = isSelected ? "green" : void 0;
1212
- return /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsxs9(Text9, { color: textColor, children: [
1485
+ return /* @__PURE__ */ jsx10(Box10, { children: /* @__PURE__ */ jsxs10(Text10, { color: textColor, children: [
1213
1486
  prefix,
1214
- /* @__PURE__ */ jsx9(Text9, { bold: true, color: "blue", children: ticketKey }),
1487
+ /* @__PURE__ */ jsx10(Text10, { bold: true, color: "blue", children: ticketKey }),
1215
1488
  " ",
1216
1489
  summary,
1217
- status && /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
1490
+ status && /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
1218
1491
  " [",
1219
1492
  status,
1220
1493
  "]"
@@ -1223,8 +1496,8 @@ function TicketItem({ ticketKey, summary, status, isHighlighted, isSelected }) {
1223
1496
  }
1224
1497
 
1225
1498
  // src/components/jira/JiraView.tsx
1226
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1227
- function JiraView({ isFocused, onModalChange, onKeybindingsChange }) {
1499
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1500
+ function JiraView({ isFocused, onModalChange, onKeybindingsChange, onLogUpdated }) {
1228
1501
  const [repoPath, setRepoPath] = useState7(null);
1229
1502
  const [currentBranch, setCurrentBranch] = useState7(null);
1230
1503
  const [isRepo, setIsRepo] = useState7(null);
@@ -1438,12 +1711,12 @@ function JiraView({ isFocused, onModalChange, onKeybindingsChange }) {
1438
1711
  { isActive: isFocused && !showConfigureModal && !showLinkModal && !showStatusModal }
1439
1712
  );
1440
1713
  if (isRepo === false) {
1441
- return /* @__PURE__ */ jsx10(TitledBox5, { borderStyle: "round", titles: ["Jira"], flexShrink: 0, children: /* @__PURE__ */ jsx10(Text10, { color: "red", children: "Not a git repository" }) });
1714
+ return /* @__PURE__ */ jsx11(TitledBox5, { borderStyle: "round", titles: ["Jira"], flexShrink: 0, children: /* @__PURE__ */ jsx11(Text11, { color: "red", children: "Not a git repository" }) });
1442
1715
  }
1443
1716
  if (showConfigureModal) {
1444
1717
  const siteUrl = repoPath ? getJiraSiteUrl(repoPath) : void 0;
1445
1718
  const creds = repoPath ? getJiraCredentials(repoPath) : { email: null, apiToken: null };
1446
- return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx10(
1719
+ return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx11(
1447
1720
  ConfigureJiraSiteModal,
1448
1721
  {
1449
1722
  initialSiteUrl: siteUrl ?? void 0,
@@ -1459,7 +1732,7 @@ function JiraView({ isFocused, onModalChange, onKeybindingsChange }) {
1459
1732
  ) });
1460
1733
  }
1461
1734
  if (showLinkModal) {
1462
- return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx10(
1735
+ return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx11(
1463
1736
  LinkTicketModal,
1464
1737
  {
1465
1738
  onSubmit: handleLinkSubmit,
@@ -1474,14 +1747,17 @@ function JiraView({ isFocused, onModalChange, onKeybindingsChange }) {
1474
1747
  }
1475
1748
  if (showStatusModal && repoPath && currentBranch && tickets[highlightedIndex]) {
1476
1749
  const ticket = tickets[highlightedIndex];
1477
- return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx10(
1750
+ return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx11(
1478
1751
  ChangeStatusModal,
1479
1752
  {
1480
1753
  repoPath,
1481
1754
  ticketKey: ticket.key,
1482
1755
  currentStatus: ticket.status,
1483
1756
  onComplete: (newStatus) => {
1757
+ const oldStatus = ticket.status;
1484
1758
  updateTicketStatus(repoPath, currentBranch, ticket.key, newStatus);
1759
+ logJiraStatusChanged(ticket.key, oldStatus, newStatus);
1760
+ onLogUpdated == null ? void 0 : onLogUpdated();
1485
1761
  setShowStatusModal(false);
1486
1762
  refreshTickets();
1487
1763
  },
@@ -1491,10 +1767,10 @@ function JiraView({ isFocused, onModalChange, onKeybindingsChange }) {
1491
1767
  }
1492
1768
  const title = "[4] Jira";
1493
1769
  const borderColor = isFocused ? "yellow" : void 0;
1494
- return /* @__PURE__ */ jsx10(TitledBox5, { borderStyle: "round", titles: [title], borderColor, flexShrink: 0, children: /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingX: 1, children: [
1495
- jiraState === "not_configured" && /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "No Jira site configured" }),
1496
- jiraState === "no_tickets" && /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "No tickets linked to this branch" }),
1497
- jiraState === "has_tickets" && tickets.map((ticket, idx) => /* @__PURE__ */ jsx10(
1770
+ return /* @__PURE__ */ jsx11(TitledBox5, { borderStyle: "round", titles: [title], borderColor, flexShrink: 0, children: /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 1, children: [
1771
+ jiraState === "not_configured" && /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "No Jira site configured" }),
1772
+ jiraState === "no_tickets" && /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "No tickets linked to this branch" }),
1773
+ jiraState === "has_tickets" && tickets.map((ticket, idx) => /* @__PURE__ */ jsx11(
1498
1774
  TicketItem,
1499
1775
  {
1500
1776
  ticketKey: ticket.key,
@@ -1507,9 +1783,237 @@ function JiraView({ isFocused, onModalChange, onKeybindingsChange }) {
1507
1783
  ] }) });
1508
1784
  }
1509
1785
 
1786
+ // src/components/logs/LogsView.tsx
1787
+ import { useCallback as useCallback3, useEffect as useEffect6, useState as useState8 } from "react";
1788
+ import { Box as Box14, useInput as useInput12 } from "ink";
1789
+
1790
+ // src/components/logs/LogsHistoryBox.tsx
1791
+ import { TitledBox as TitledBox6 } from "@mishieck/ink-titled-box";
1792
+ import { Box as Box12, Text as Text12, useInput as useInput10 } from "ink";
1793
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1794
+ function LogsHistoryBox({
1795
+ logFiles,
1796
+ selectedDate,
1797
+ highlightedIndex,
1798
+ onHighlight,
1799
+ onSelect,
1800
+ isFocused
1801
+ }) {
1802
+ const title = "[5] Logs";
1803
+ const borderColor = isFocused ? "yellow" : void 0;
1804
+ useInput10(
1805
+ (input, key) => {
1806
+ if (logFiles.length === 0) return;
1807
+ if (key.upArrow || input === "k") {
1808
+ onHighlight(Math.max(0, highlightedIndex - 1));
1809
+ }
1810
+ if (key.downArrow || input === "j") {
1811
+ onHighlight(Math.min(logFiles.length - 1, highlightedIndex + 1));
1812
+ }
1813
+ if (key.return) {
1814
+ const file = logFiles[highlightedIndex];
1815
+ if (file) {
1816
+ onSelect(file.date);
1817
+ }
1818
+ }
1819
+ },
1820
+ { isActive: isFocused }
1821
+ );
1822
+ return /* @__PURE__ */ jsx12(TitledBox6, { borderStyle: "round", titles: [title], borderColor, flexShrink: 0, children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingX: 1, children: [
1823
+ logFiles.length === 0 && /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "No logs yet" }),
1824
+ logFiles.map((file, idx) => {
1825
+ const isHighlighted = idx === highlightedIndex;
1826
+ const isSelected = file.date === selectedDate;
1827
+ const cursor = isHighlighted ? ">" : " ";
1828
+ const indicator = isSelected ? " *" : "";
1829
+ return /* @__PURE__ */ jsxs12(Box12, { children: [
1830
+ /* @__PURE__ */ jsxs12(Text12, { color: isHighlighted ? "yellow" : void 0, children: [
1831
+ cursor,
1832
+ " "
1833
+ ] }),
1834
+ /* @__PURE__ */ jsx12(
1835
+ Text12,
1836
+ {
1837
+ color: file.isToday ? "green" : void 0,
1838
+ bold: file.isToday,
1839
+ children: file.date
1840
+ }
1841
+ ),
1842
+ file.isToday && /* @__PURE__ */ jsx12(Text12, { color: "green", children: " (today)" }),
1843
+ /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: indicator })
1844
+ ] }, file.date);
1845
+ })
1846
+ ] }) });
1847
+ }
1848
+
1849
+ // src/components/logs/LogViewerBox.tsx
1850
+ import { useRef as useRef3 } from "react";
1851
+ import { TitledBox as TitledBox7 } from "@mishieck/ink-titled-box";
1852
+ import { Box as Box13, Text as Text13, useInput as useInput11 } from "ink";
1853
+ import { ScrollView as ScrollView2 } from "ink-scroll-view";
1854
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1855
+ function LogViewerBox({ date, content, isFocused, onRefresh, onLogCreated }) {
1856
+ const scrollRef = useRef3(null);
1857
+ const title = "[6] Log Content";
1858
+ const borderColor = isFocused ? "yellow" : void 0;
1859
+ const displayTitle = date ? `${title} - ${date}.md` : title;
1860
+ useInput11(
1861
+ (input, key) => {
1862
+ var _a, _b;
1863
+ if (key.upArrow || input === "k") {
1864
+ (_a = scrollRef.current) == null ? void 0 : _a.scrollBy(-1);
1865
+ }
1866
+ if (key.downArrow || input === "j") {
1867
+ (_b = scrollRef.current) == null ? void 0 : _b.scrollBy(1);
1868
+ }
1869
+ if (input === "e" && date) {
1870
+ openLogInEditor(date);
1871
+ onRefresh();
1872
+ }
1873
+ if (input === "n") {
1874
+ const today = getTodayDate();
1875
+ if (!logExists(today)) {
1876
+ createEmptyLog(today);
1877
+ onLogCreated();
1878
+ }
1879
+ }
1880
+ if (input === "r") {
1881
+ onRefresh();
1882
+ }
1883
+ },
1884
+ { isActive: isFocused }
1885
+ );
1886
+ return /* @__PURE__ */ jsx13(TitledBox7, { borderStyle: "round", titles: [displayTitle], borderColor, flexGrow: 1, children: /* @__PURE__ */ jsx13(Box13, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx13(ScrollView2, { ref: scrollRef, children: /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", paddingX: 1, children: [
1887
+ !date && /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: "Select a log file to view" }),
1888
+ date && content === null && /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: "Log file not found" }),
1889
+ date && content !== null && content.trim() === "" && /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: "Empty log file" }),
1890
+ date && content && content.trim() !== "" && /* @__PURE__ */ jsx13(Markdown, { children: content })
1891
+ ] }) }) }) });
1892
+ }
1893
+
1894
+ // src/components/logs/LogsView.tsx
1895
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
1896
+ function LogsView({ isFocused, onKeybindingsChange, refreshKey }) {
1897
+ const [logFiles, setLogFiles] = useState8([]);
1898
+ const [selectedDate, setSelectedDate] = useState8(null);
1899
+ const [logContent, setLogContent] = useState8(null);
1900
+ const [highlightedIndex, setHighlightedIndex] = useState8(0);
1901
+ const [focusedBox, setFocusedBox] = useState8("history");
1902
+ useEffect6(() => {
1903
+ if (!isFocused) {
1904
+ onKeybindingsChange == null ? void 0 : onKeybindingsChange([]);
1905
+ return;
1906
+ }
1907
+ const bindings = [];
1908
+ if (focusedBox === "history") {
1909
+ bindings.push({ key: "Enter", label: "Select" });
1910
+ } else if (focusedBox === "viewer") {
1911
+ bindings.push({ key: "e", label: "Edit" });
1912
+ bindings.push({ key: "n", label: "New Log", color: "green" });
1913
+ bindings.push({ key: "r", label: "Refresh" });
1914
+ }
1915
+ onKeybindingsChange == null ? void 0 : onKeybindingsChange(bindings);
1916
+ }, [isFocused, focusedBox, onKeybindingsChange]);
1917
+ const refreshLogFiles = useCallback3(() => {
1918
+ const files = listLogFiles();
1919
+ setLogFiles(files);
1920
+ if (files.length > 0 && !selectedDate) {
1921
+ const today = getTodayDate();
1922
+ const todayFile = files.find((f) => f.date === today);
1923
+ if (todayFile) {
1924
+ setSelectedDate(todayFile.date);
1925
+ const idx = files.findIndex((f) => f.date === today);
1926
+ setHighlightedIndex(idx >= 0 ? idx : 0);
1927
+ } else {
1928
+ setSelectedDate(files[0].date);
1929
+ setHighlightedIndex(0);
1930
+ }
1931
+ }
1932
+ }, [selectedDate]);
1933
+ useEffect6(() => {
1934
+ refreshLogFiles();
1935
+ }, [refreshLogFiles]);
1936
+ useEffect6(() => {
1937
+ if (selectedDate) {
1938
+ const content = readLog(selectedDate);
1939
+ setLogContent(content);
1940
+ } else {
1941
+ setLogContent(null);
1942
+ }
1943
+ }, [selectedDate]);
1944
+ useEffect6(() => {
1945
+ if (refreshKey !== void 0 && refreshKey > 0) {
1946
+ const files = listLogFiles();
1947
+ setLogFiles(files);
1948
+ const today = getTodayDate();
1949
+ if (selectedDate === today) {
1950
+ const content = readLog(today);
1951
+ setLogContent(content);
1952
+ } else if (!selectedDate && files.length > 0) {
1953
+ const todayFile = files.find((f) => f.date === today);
1954
+ if (todayFile) {
1955
+ setSelectedDate(today);
1956
+ const idx = files.findIndex((f) => f.date === today);
1957
+ setHighlightedIndex(idx >= 0 ? idx : 0);
1958
+ }
1959
+ }
1960
+ }
1961
+ }, [refreshKey, selectedDate]);
1962
+ const handleSelectDate = useCallback3((date) => {
1963
+ setSelectedDate(date);
1964
+ }, []);
1965
+ const handleRefresh = useCallback3(() => {
1966
+ refreshLogFiles();
1967
+ if (selectedDate) {
1968
+ const content = readLog(selectedDate);
1969
+ setLogContent(content);
1970
+ }
1971
+ }, [refreshLogFiles, selectedDate]);
1972
+ const handleLogCreated = useCallback3(() => {
1973
+ const files = listLogFiles();
1974
+ setLogFiles(files);
1975
+ const today = getTodayDate();
1976
+ setSelectedDate(today);
1977
+ const idx = files.findIndex((f) => f.date === today);
1978
+ setHighlightedIndex(idx >= 0 ? idx : 0);
1979
+ const content = readLog(today);
1980
+ setLogContent(content);
1981
+ }, []);
1982
+ useInput12(
1983
+ (input) => {
1984
+ if (input === "5") setFocusedBox("history");
1985
+ if (input === "6") setFocusedBox("viewer");
1986
+ },
1987
+ { isActive: isFocused }
1988
+ );
1989
+ return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", flexGrow: 1, children: [
1990
+ /* @__PURE__ */ jsx14(
1991
+ LogsHistoryBox,
1992
+ {
1993
+ logFiles,
1994
+ selectedDate,
1995
+ highlightedIndex,
1996
+ onHighlight: setHighlightedIndex,
1997
+ onSelect: handleSelectDate,
1998
+ isFocused: isFocused && focusedBox === "history"
1999
+ }
2000
+ ),
2001
+ /* @__PURE__ */ jsx14(
2002
+ LogViewerBox,
2003
+ {
2004
+ date: selectedDate,
2005
+ content: logContent,
2006
+ isFocused: isFocused && focusedBox === "viewer",
2007
+ onRefresh: handleRefresh,
2008
+ onLogCreated: handleLogCreated
2009
+ }
2010
+ )
2011
+ ] });
2012
+ }
2013
+
1510
2014
  // src/components/ui/KeybindingsBar.tsx
1511
- import { Box as Box11, Text as Text11 } from "ink";
1512
- import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
2015
+ import { Box as Box15, Text as Text14 } from "ink";
2016
+ import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
1513
2017
  var globalBindings = [
1514
2018
  { key: "1-4", label: "Focus" },
1515
2019
  { key: "j/k", label: "Navigate" },
@@ -1520,20 +2024,24 @@ var modalBindings = [
1520
2024
  ];
1521
2025
  function KeybindingsBar({ contextBindings = [], modalOpen = false }) {
1522
2026
  const allBindings = modalOpen ? [...contextBindings, ...modalBindings] : [...contextBindings, ...globalBindings];
1523
- return /* @__PURE__ */ jsx11(Box11, { flexShrink: 0, paddingX: 1, gap: 2, children: allBindings.map((binding) => /* @__PURE__ */ jsxs11(Box11, { gap: 1, children: [
1524
- /* @__PURE__ */ jsx11(Text11, { bold: true, color: binding.color ?? "yellow", children: binding.key }),
1525
- /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: binding.label })
2027
+ return /* @__PURE__ */ jsx15(Box15, { flexShrink: 0, paddingX: 1, gap: 2, children: allBindings.map((binding) => /* @__PURE__ */ jsxs15(Box15, { gap: 1, children: [
2028
+ /* @__PURE__ */ jsx15(Text14, { bold: true, color: binding.color ?? "yellow", children: binding.key }),
2029
+ /* @__PURE__ */ jsx15(Text14, { dimColor: true, children: binding.label })
1526
2030
  ] }, binding.key)) });
1527
2031
  }
1528
2032
 
1529
2033
  // src/app.tsx
1530
- import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
2034
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
1531
2035
  function App() {
1532
2036
  const { exit } = useApp();
1533
- const [focusedView, setFocusedView] = useState8("github");
1534
- const [modalOpen, setModalOpen] = useState8(false);
1535
- const [contextBindings, setContextBindings] = useState8([]);
1536
- useInput10(
2037
+ const [focusedView, setFocusedView] = useState9("github");
2038
+ const [modalOpen, setModalOpen] = useState9(false);
2039
+ const [contextBindings, setContextBindings] = useState9([]);
2040
+ const [logRefreshKey, setLogRefreshKey] = useState9(0);
2041
+ const handleLogUpdated = useCallback4(() => {
2042
+ setLogRefreshKey((prev) => prev + 1);
2043
+ }, []);
2044
+ useInput13(
1537
2045
  (input, key) => {
1538
2046
  if (key.ctrl && input === "c") {
1539
2047
  exit();
@@ -1544,26 +2052,43 @@ function App() {
1544
2052
  if (input === "4") {
1545
2053
  setFocusedView("jira");
1546
2054
  }
2055
+ if (input === "5" || input === "6") {
2056
+ setFocusedView("logs");
2057
+ }
1547
2058
  },
1548
2059
  { isActive: !modalOpen }
1549
2060
  );
1550
- return /* @__PURE__ */ jsxs12(Box12, { flexGrow: 1, flexDirection: "column", overflow: "hidden", children: [
1551
- /* @__PURE__ */ jsx12(
1552
- GitHubView,
1553
- {
1554
- isFocused: focusedView === "github",
1555
- onKeybindingsChange: focusedView === "github" ? setContextBindings : void 0
1556
- }
1557
- ),
1558
- /* @__PURE__ */ jsx12(
1559
- JiraView,
1560
- {
1561
- isFocused: focusedView === "jira",
1562
- onModalChange: setModalOpen,
1563
- onKeybindingsChange: focusedView === "jira" ? setContextBindings : void 0
1564
- }
1565
- ),
1566
- /* @__PURE__ */ jsx12(KeybindingsBar, { contextBindings, modalOpen })
2061
+ return /* @__PURE__ */ jsxs16(Box16, { flexGrow: 1, flexDirection: "column", overflow: "hidden", children: [
2062
+ /* @__PURE__ */ jsxs16(Box16, { flexGrow: 1, flexDirection: "row", columnGap: 1, children: [
2063
+ /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", flexGrow: 1, flexBasis: 0, children: [
2064
+ /* @__PURE__ */ jsx16(
2065
+ GitHubView,
2066
+ {
2067
+ isFocused: focusedView === "github",
2068
+ onKeybindingsChange: focusedView === "github" ? setContextBindings : void 0,
2069
+ onLogUpdated: handleLogUpdated
2070
+ }
2071
+ ),
2072
+ /* @__PURE__ */ jsx16(
2073
+ JiraView,
2074
+ {
2075
+ isFocused: focusedView === "jira",
2076
+ onModalChange: setModalOpen,
2077
+ onKeybindingsChange: focusedView === "jira" ? setContextBindings : void 0,
2078
+ onLogUpdated: handleLogUpdated
2079
+ }
2080
+ )
2081
+ ] }),
2082
+ /* @__PURE__ */ jsx16(Box16, { flexDirection: "column", flexGrow: 1, flexBasis: 0, children: /* @__PURE__ */ jsx16(
2083
+ LogsView,
2084
+ {
2085
+ isFocused: focusedView === "logs",
2086
+ onKeybindingsChange: focusedView === "logs" ? setContextBindings : void 0,
2087
+ refreshKey: logRefreshKey
2088
+ }
2089
+ ) })
2090
+ ] }),
2091
+ /* @__PURE__ */ jsx16(KeybindingsBar, { contextBindings, modalOpen })
1567
2092
  ] });
1568
2093
  }
1569
2094
 
@@ -1571,34 +2096,34 @@ function App() {
1571
2096
  import { render as inkRender } from "ink";
1572
2097
 
1573
2098
  // src/lib/Screen.tsx
1574
- import { Box as Box13, useStdout } from "ink";
1575
- import { useCallback as useCallback3, useEffect as useEffect6, useState as useState9 } from "react";
1576
- import { jsx as jsx13 } from "react/jsx-runtime";
2099
+ import { Box as Box17, useStdout } from "ink";
2100
+ import { useCallback as useCallback5, useEffect as useEffect7, useState as useState10 } from "react";
2101
+ import { jsx as jsx17 } from "react/jsx-runtime";
1577
2102
  function Screen({ children }) {
1578
2103
  const { stdout } = useStdout();
1579
- const getSize = useCallback3(
2104
+ const getSize = useCallback5(
1580
2105
  () => ({ height: stdout.rows, width: stdout.columns }),
1581
2106
  [stdout]
1582
2107
  );
1583
- const [size, setSize] = useState9(getSize);
1584
- useEffect6(() => {
2108
+ const [size, setSize] = useState10(getSize);
2109
+ useEffect7(() => {
1585
2110
  const onResize = () => setSize(getSize());
1586
2111
  stdout.on("resize", onResize);
1587
2112
  return () => {
1588
2113
  stdout.off("resize", onResize);
1589
2114
  };
1590
2115
  }, [stdout, getSize]);
1591
- return /* @__PURE__ */ jsx13(Box13, { height: size.height, width: size.width, children });
2116
+ return /* @__PURE__ */ jsx17(Box17, { height: size.height, width: size.width, children });
1592
2117
  }
1593
2118
 
1594
2119
  // src/lib/render.tsx
1595
- import { jsx as jsx14 } from "react/jsx-runtime";
2120
+ import { jsx as jsx18 } from "react/jsx-runtime";
1596
2121
  var ENTER_ALT_BUFFER = "\x1B[?1049h";
1597
2122
  var EXIT_ALT_BUFFER = "\x1B[?1049l";
1598
2123
  var CLEAR_SCREEN = "\x1B[2J\x1B[H";
1599
2124
  function render(node, options) {
1600
2125
  process.stdout.write(ENTER_ALT_BUFFER + CLEAR_SCREEN);
1601
- const element = /* @__PURE__ */ jsx14(Screen, { children: node });
2126
+ const element = /* @__PURE__ */ jsx18(Screen, { children: node });
1602
2127
  const instance = inkRender(element, options);
1603
2128
  setImmediate(() => instance.rerender(element));
1604
2129
  const cleanup = () => process.stdout.write(EXIT_ALT_BUFFER);
@@ -1619,7 +2144,7 @@ function render(node, options) {
1619
2144
  }
1620
2145
 
1621
2146
  // src/cli.tsx
1622
- import { jsx as jsx15 } from "react/jsx-runtime";
2147
+ import { jsx as jsx19 } from "react/jsx-runtime";
1623
2148
  meow(
1624
2149
  `
1625
2150
  Usage
@@ -1641,4 +2166,4 @@ meow(
1641
2166
  }
1642
2167
  }
1643
2168
  );
1644
- render(/* @__PURE__ */ jsx15(App, {}));
2169
+ render(/* @__PURE__ */ jsx19(App, {}));