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.
- package/dist/cli.js +1266 -741
- 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
|
|
8
|
-
import { Box as
|
|
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
|
|
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/
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
if (
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (
|
|
269
|
-
|
|
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
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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/
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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/
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
const
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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
|
|
532
|
-
if (
|
|
533
|
-
|
|
534
|
-
|
|
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
|
-
|
|
544
|
-
|
|
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
|
|
554
|
-
|
|
555
|
-
|
|
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
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
451
|
+
if (result.status === 404) {
|
|
452
|
+
return {
|
|
453
|
+
success: false,
|
|
454
|
+
error: `Ticket ${ticketKey} not found`,
|
|
455
|
+
errorType: "invalid_ticket"
|
|
456
|
+
};
|
|
628
457
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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/
|
|
702
|
-
import {
|
|
703
|
-
import
|
|
704
|
-
import {
|
|
705
|
-
import {
|
|
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
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
542
|
+
`;
|
|
543
|
+
writeFileSync2(filePath, header);
|
|
544
|
+
}
|
|
545
|
+
appendFileSync(filePath, entry);
|
|
711
546
|
}
|
|
712
|
-
function
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
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
|
|
719
|
-
|
|
720
|
-
|
|
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
|
-
|
|
573
|
+
entry += "\n";
|
|
574
|
+
appendToLog(today, entry);
|
|
723
575
|
}
|
|
724
|
-
function
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
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
|
|
741
|
-
|
|
742
|
-
return
|
|
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
|
|
745
|
-
|
|
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
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
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
|
-
|
|
755
|
-
|
|
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
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
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
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
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
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
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
|
-
|
|
823
|
-
|
|
1007
|
+
const rootResult = getRepoRoot();
|
|
1008
|
+
if (rootResult.success) {
|
|
1009
|
+
setRepoPath(rootResult.data);
|
|
824
1010
|
}
|
|
825
|
-
const
|
|
826
|
-
|
|
827
|
-
|
|
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
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
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
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
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
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
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
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
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
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
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
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
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
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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__ */
|
|
995
|
-
/* @__PURE__ */
|
|
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__ */
|
|
1000
|
-
error && /* @__PURE__ */
|
|
1001
|
-
!loading && !error && transitions.length === 0 && /* @__PURE__ */
|
|
1002
|
-
!loading && !error && transitions.length > 0 && !applying && /* @__PURE__ */
|
|
1003
|
-
applying && /* @__PURE__ */
|
|
1004
|
-
/* @__PURE__ */
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
1020
|
-
const tempFile =
|
|
1292
|
+
const tempDir = mkdtempSync(join3(tmpdir(), "clairo-"));
|
|
1293
|
+
const tempFile = join3(tempDir, filename);
|
|
1021
1294
|
try {
|
|
1022
|
-
|
|
1023
|
-
const result =
|
|
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
|
|
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
|
|
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__ */
|
|
1106
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
1114
|
-
/* @__PURE__ */
|
|
1115
|
-
/* @__PURE__ */
|
|
1116
|
-
/* @__PURE__ */
|
|
1117
|
-
error && /* @__PURE__ */
|
|
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__ */
|
|
1392
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1 }),
|
|
1120
1393
|
renderItem("email", "Email", email),
|
|
1121
|
-
/* @__PURE__ */
|
|
1394
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1 }),
|
|
1122
1395
|
renderItem("apiToken", "API Token", apiToken, true),
|
|
1123
|
-
/* @__PURE__ */
|
|
1124
|
-
/* @__PURE__ */
|
|
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__ */
|
|
1129
|
-
/* @__PURE__ */
|
|
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
|
|
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
|
|
1139
|
-
import { jsx as
|
|
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__ */
|
|
1161
|
-
showPlaceholder ? /* @__PURE__ */
|
|
1162
|
-
isActive && /* @__PURE__ */
|
|
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
|
|
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__ */
|
|
1185
|
-
/* @__PURE__ */
|
|
1186
|
-
/* @__PURE__ */
|
|
1187
|
-
/* @__PURE__ */
|
|
1188
|
-
error && /* @__PURE__ */
|
|
1189
|
-
/* @__PURE__ */
|
|
1190
|
-
/* @__PURE__ */
|
|
1191
|
-
/* @__PURE__ */
|
|
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__ */
|
|
1202
|
-
/* @__PURE__ */
|
|
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
|
|
1208
|
-
import { jsx as
|
|
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__ */
|
|
1485
|
+
return /* @__PURE__ */ jsx10(Box10, { children: /* @__PURE__ */ jsxs10(Text10, { color: textColor, children: [
|
|
1213
1486
|
prefix,
|
|
1214
|
-
/* @__PURE__ */
|
|
1487
|
+
/* @__PURE__ */ jsx10(Text10, { bold: true, color: "blue", children: ticketKey }),
|
|
1215
1488
|
" ",
|
|
1216
1489
|
summary,
|
|
1217
|
-
status && /* @__PURE__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
1495
|
-
jiraState === "not_configured" && /* @__PURE__ */
|
|
1496
|
-
jiraState === "no_tickets" && /* @__PURE__ */
|
|
1497
|
-
jiraState === "has_tickets" && tickets.map((ticket, idx) => /* @__PURE__ */
|
|
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
|
|
1512
|
-
import { jsx as
|
|
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__ */
|
|
1524
|
-
/* @__PURE__ */
|
|
1525
|
-
/* @__PURE__ */
|
|
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
|
|
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] =
|
|
1534
|
-
const [modalOpen, setModalOpen] =
|
|
1535
|
-
const [contextBindings, setContextBindings] =
|
|
1536
|
-
|
|
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__ */
|
|
1551
|
-
/* @__PURE__ */
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
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
|
|
1575
|
-
import { useCallback as
|
|
1576
|
-
import { jsx as
|
|
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 =
|
|
2104
|
+
const getSize = useCallback5(
|
|
1580
2105
|
() => ({ height: stdout.rows, width: stdout.columns }),
|
|
1581
2106
|
[stdout]
|
|
1582
2107
|
);
|
|
1583
|
-
const [size, setSize] =
|
|
1584
|
-
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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
|
|
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__ */
|
|
2169
|
+
render(/* @__PURE__ */ jsx19(App, {}));
|