@tarcisiopgs/lisa 1.6.0 → 1.7.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/{chunk-KAME5MG7.js → chunk-MUBWKMRZ.js} +22 -3
- package/dist/index.js +37 -2
- package/dist/kanban-UW2MC2PB.js +481 -0
- package/package.json +61 -59
- package/dist/kanban-KIPKQ2IL.js +0 -127
|
@@ -8,11 +8,15 @@ var KanbanEmitter = class extends EventEmitter {
|
|
|
8
8
|
var kanbanEmitter = new KanbanEmitter();
|
|
9
9
|
function useKanbanState() {
|
|
10
10
|
const [cards, setCards] = useState([]);
|
|
11
|
+
const [isEmpty, setIsEmpty] = useState(false);
|
|
12
|
+
const [workComplete, setWorkComplete] = useState(
|
|
13
|
+
null
|
|
14
|
+
);
|
|
11
15
|
useEffect(() => {
|
|
12
16
|
const onQueued = (issue) => {
|
|
13
17
|
setCards((prev) => {
|
|
14
18
|
if (prev.some((c) => c.id === issue.id)) return prev;
|
|
15
|
-
return [...prev, { id: issue.id, title: issue.title, column: "backlog" }];
|
|
19
|
+
return [...prev, { id: issue.id, title: issue.title, column: "backlog", outputLog: "" }];
|
|
16
20
|
});
|
|
17
21
|
};
|
|
18
22
|
const onStarted = (issueId) => {
|
|
@@ -24,7 +28,9 @@ function useKanbanState() {
|
|
|
24
28
|
};
|
|
25
29
|
const onDone = (issueId, prUrl) => {
|
|
26
30
|
setCards(
|
|
27
|
-
(prev) => prev.map(
|
|
31
|
+
(prev) => prev.map(
|
|
32
|
+
(c) => c.id === issueId ? { ...c, column: "done", prUrl, finishedAt: Date.now() } : c
|
|
33
|
+
)
|
|
28
34
|
);
|
|
29
35
|
};
|
|
30
36
|
const onReverted = (issueId) => {
|
|
@@ -34,18 +40,31 @@ function useKanbanState() {
|
|
|
34
40
|
)
|
|
35
41
|
);
|
|
36
42
|
};
|
|
43
|
+
const onOutput = (issueId, text) => {
|
|
44
|
+
setCards(
|
|
45
|
+
(prev) => prev.map((c) => c.id === issueId ? { ...c, outputLog: c.outputLog + text } : c)
|
|
46
|
+
);
|
|
47
|
+
};
|
|
37
48
|
kanbanEmitter.on("issue:queued", onQueued);
|
|
38
49
|
kanbanEmitter.on("issue:started", onStarted);
|
|
39
50
|
kanbanEmitter.on("issue:done", onDone);
|
|
40
51
|
kanbanEmitter.on("issue:reverted", onReverted);
|
|
52
|
+
kanbanEmitter.on("issue:output", onOutput);
|
|
53
|
+
const onEmpty = () => setIsEmpty(true);
|
|
54
|
+
const onComplete = (data) => setWorkComplete(data);
|
|
55
|
+
kanbanEmitter.on("work:empty", onEmpty);
|
|
56
|
+
kanbanEmitter.on("work:complete", onComplete);
|
|
41
57
|
return () => {
|
|
42
58
|
kanbanEmitter.off("issue:queued", onQueued);
|
|
43
59
|
kanbanEmitter.off("issue:started", onStarted);
|
|
44
60
|
kanbanEmitter.off("issue:done", onDone);
|
|
45
61
|
kanbanEmitter.off("issue:reverted", onReverted);
|
|
62
|
+
kanbanEmitter.off("issue:output", onOutput);
|
|
63
|
+
kanbanEmitter.off("work:empty", onEmpty);
|
|
64
|
+
kanbanEmitter.off("work:complete", onComplete);
|
|
46
65
|
};
|
|
47
66
|
}, []);
|
|
48
|
-
return { cards };
|
|
67
|
+
return { cards, isEmpty, workComplete };
|
|
49
68
|
}
|
|
50
69
|
|
|
51
70
|
export {
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
kanbanEmitter
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-MUBWKMRZ.js";
|
|
5
5
|
|
|
6
6
|
// src/cli.ts
|
|
7
7
|
import { execSync as execSync8 } from "child_process";
|
|
@@ -388,6 +388,7 @@ function setTitle(title) {
|
|
|
388
388
|
}
|
|
389
389
|
function startSpinner(message) {
|
|
390
390
|
if (!isTTY()) return;
|
|
391
|
+
if (getOutputMode() === "tui") return;
|
|
391
392
|
stopSpinner();
|
|
392
393
|
spinnerFrame = 0;
|
|
393
394
|
writeOSC(`${SPINNER_FRAMES[0]} Lisa \u2014 ${message}`);
|
|
@@ -402,6 +403,7 @@ function stopSpinner(message) {
|
|
|
402
403
|
spinnerTimer = null;
|
|
403
404
|
}
|
|
404
405
|
if (!isTTY()) return;
|
|
406
|
+
if (getOutputMode() === "tui") return;
|
|
405
407
|
if (message) {
|
|
406
408
|
writeOSC(message);
|
|
407
409
|
}
|
|
@@ -1036,6 +1038,9 @@ var AiderProvider = class {
|
|
|
1036
1038
|
proc.stdout.on("data", (chunk) => {
|
|
1037
1039
|
const text2 = chunk.toString();
|
|
1038
1040
|
if (getOutputMode() !== "tui") process.stdout.write(text2);
|
|
1041
|
+
if (opts.issueId) {
|
|
1042
|
+
kanbanEmitter.emit("issue:output", opts.issueId, text2);
|
|
1043
|
+
}
|
|
1039
1044
|
chunks.push(text2);
|
|
1040
1045
|
try {
|
|
1041
1046
|
appendFileSync3(opts.logFile, text2);
|
|
@@ -1117,6 +1122,9 @@ var ClaudeProvider = class {
|
|
|
1117
1122
|
proc.stdout.on("data", (chunk) => {
|
|
1118
1123
|
const text2 = chunk.toString();
|
|
1119
1124
|
if (getOutputMode() !== "tui") process.stdout.write(text2);
|
|
1125
|
+
if (opts.issueId) {
|
|
1126
|
+
kanbanEmitter.emit("issue:output", opts.issueId, text2);
|
|
1127
|
+
}
|
|
1120
1128
|
chunks.push(text2);
|
|
1121
1129
|
try {
|
|
1122
1130
|
appendFileSync4(opts.logFile, text2);
|
|
@@ -1191,6 +1199,9 @@ var CopilotProvider = class {
|
|
|
1191
1199
|
proc.stdout.on("data", (chunk) => {
|
|
1192
1200
|
const text2 = chunk.toString();
|
|
1193
1201
|
if (getOutputMode() !== "tui") process.stdout.write(text2);
|
|
1202
|
+
if (opts.issueId) {
|
|
1203
|
+
kanbanEmitter.emit("issue:output", opts.issueId, text2);
|
|
1204
|
+
}
|
|
1194
1205
|
chunks.push(text2);
|
|
1195
1206
|
try {
|
|
1196
1207
|
appendFileSync5(opts.logFile, text2);
|
|
@@ -1283,6 +1294,9 @@ var CursorProvider = class {
|
|
|
1283
1294
|
proc.stdout.on("data", (chunk) => {
|
|
1284
1295
|
const text2 = chunk.toString();
|
|
1285
1296
|
if (getOutputMode() !== "tui") process.stdout.write(text2);
|
|
1297
|
+
if (opts.issueId) {
|
|
1298
|
+
kanbanEmitter.emit("issue:output", opts.issueId, text2);
|
|
1299
|
+
}
|
|
1286
1300
|
chunks.push(text2);
|
|
1287
1301
|
try {
|
|
1288
1302
|
appendFileSync6(opts.logFile, text2);
|
|
@@ -1358,6 +1372,9 @@ var GeminiProvider = class {
|
|
|
1358
1372
|
proc.stdout.on("data", (chunk) => {
|
|
1359
1373
|
const text2 = chunk.toString();
|
|
1360
1374
|
if (getOutputMode() !== "tui") process.stdout.write(text2);
|
|
1375
|
+
if (opts.issueId) {
|
|
1376
|
+
kanbanEmitter.emit("issue:output", opts.issueId, text2);
|
|
1377
|
+
}
|
|
1361
1378
|
chunks.push(text2);
|
|
1362
1379
|
try {
|
|
1363
1380
|
appendFileSync7(opts.logFile, text2);
|
|
@@ -1433,6 +1450,9 @@ var GooseProvider = class {
|
|
|
1433
1450
|
proc.stdout.on("data", (chunk) => {
|
|
1434
1451
|
const text2 = chunk.toString();
|
|
1435
1452
|
if (getOutputMode() !== "tui") process.stdout.write(text2);
|
|
1453
|
+
if (opts.issueId) {
|
|
1454
|
+
kanbanEmitter.emit("issue:output", opts.issueId, text2);
|
|
1455
|
+
}
|
|
1436
1456
|
chunks.push(text2);
|
|
1437
1457
|
try {
|
|
1438
1458
|
appendFileSync8(opts.logFile, text2);
|
|
@@ -1507,6 +1527,9 @@ var OpenCodeProvider = class {
|
|
|
1507
1527
|
proc.stdout.on("data", (chunk) => {
|
|
1508
1528
|
const text2 = chunk.toString();
|
|
1509
1529
|
if (getOutputMode() !== "tui") process.stdout.write(text2);
|
|
1530
|
+
if (opts.issueId) {
|
|
1531
|
+
kanbanEmitter.emit("issue:output", opts.issueId, text2);
|
|
1532
|
+
}
|
|
1510
1533
|
chunks.push(text2);
|
|
1511
1534
|
try {
|
|
1512
1535
|
appendFileSync9(opts.logFile, text2);
|
|
@@ -3322,6 +3345,8 @@ async function runLoop(config2, opts) {
|
|
|
3322
3345
|
}
|
|
3323
3346
|
}
|
|
3324
3347
|
let session = 0;
|
|
3348
|
+
const loopStart = Date.now();
|
|
3349
|
+
let completedCount = 0;
|
|
3325
3350
|
while (true) {
|
|
3326
3351
|
session++;
|
|
3327
3352
|
if (opts.limit > 0 && session > opts.limit) {
|
|
@@ -3370,6 +3395,9 @@ async function runLoop(config2, opts) {
|
|
|
3370
3395
|
error(`Issue '${opts.issueId}' not found.`);
|
|
3371
3396
|
} else {
|
|
3372
3397
|
ok(`No more issues with label '${config2.source_config.label}'. Done.`);
|
|
3398
|
+
if (session === 1) {
|
|
3399
|
+
kanbanEmitter.emit("work:empty");
|
|
3400
|
+
}
|
|
3373
3401
|
}
|
|
3374
3402
|
break;
|
|
3375
3403
|
}
|
|
@@ -3479,6 +3507,7 @@ async function runLoop(config2, opts) {
|
|
|
3479
3507
|
for (const prUrl of sessionResult.prUrls) {
|
|
3480
3508
|
kanbanEmitter.emit("issue:done", issue2.id, prUrl);
|
|
3481
3509
|
}
|
|
3510
|
+
completedCount++;
|
|
3482
3511
|
if (labelToRemove) {
|
|
3483
3512
|
ok(`Removed label "${labelToRemove}" from ${issue2.id}`);
|
|
3484
3513
|
}
|
|
@@ -3496,6 +3525,12 @@ async function runLoop(config2, opts) {
|
|
|
3496
3525
|
setTitle("Lisa \u2014 cooling down...");
|
|
3497
3526
|
await sleep(config2.loop.cooldown * 1e3);
|
|
3498
3527
|
}
|
|
3528
|
+
if (completedCount > 0) {
|
|
3529
|
+
kanbanEmitter.emit("work:complete", {
|
|
3530
|
+
total: completedCount,
|
|
3531
|
+
duration: Date.now() - loopStart
|
|
3532
|
+
});
|
|
3533
|
+
}
|
|
3499
3534
|
resetTitle();
|
|
3500
3535
|
ok(`lisa finished. ${session} session(s) run.`);
|
|
3501
3536
|
}
|
|
@@ -4079,7 +4114,7 @@ Add them to your ${shell} and run: source ${shell}`));
|
|
|
4079
4114
|
if (isTUI) {
|
|
4080
4115
|
const { render } = await import("ink");
|
|
4081
4116
|
const { createElement } = await import("react");
|
|
4082
|
-
const { KanbanApp } = await import("./kanban-
|
|
4117
|
+
const { KanbanApp } = await import("./kanban-UW2MC2PB.js");
|
|
4083
4118
|
render(createElement(KanbanApp, { config: merged }), { exitOnCtrlC: false });
|
|
4084
4119
|
}
|
|
4085
4120
|
await runLoop(merged, {
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
useKanbanState
|
|
4
|
+
} from "./chunk-MUBWKMRZ.js";
|
|
5
|
+
|
|
6
|
+
// src/ui/kanban.tsx
|
|
7
|
+
import { Box as Box6, useApp, useInput as useInput2 } from "ink";
|
|
8
|
+
import { useState as useState3 } from "react";
|
|
9
|
+
|
|
10
|
+
// src/ui/board.tsx
|
|
11
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
12
|
+
|
|
13
|
+
// src/ui/column.tsx
|
|
14
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
15
|
+
|
|
16
|
+
// src/ui/card.tsx
|
|
17
|
+
import { Box, Text } from "ink";
|
|
18
|
+
import Spinner from "ink-spinner";
|
|
19
|
+
import { useEffect, useState } from "react";
|
|
20
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
21
|
+
function formatElapsed(ms) {
|
|
22
|
+
const seconds = Math.floor(ms / 1e3);
|
|
23
|
+
const minutes = Math.floor(seconds / 60);
|
|
24
|
+
const remainingSeconds = seconds % 60;
|
|
25
|
+
if (minutes > 0) return `${minutes}m ${remainingSeconds}s`;
|
|
26
|
+
return `${seconds}s`;
|
|
27
|
+
}
|
|
28
|
+
function Card({ card, isSelected = false }) {
|
|
29
|
+
const [now, setNow] = useState(Date.now());
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (card.column !== "in_progress") return;
|
|
32
|
+
const interval = setInterval(() => setNow(Date.now()), 1e3);
|
|
33
|
+
return () => clearInterval(interval);
|
|
34
|
+
}, [card.column]);
|
|
35
|
+
let statusGlyph;
|
|
36
|
+
let statusColor;
|
|
37
|
+
if (card.hasError) {
|
|
38
|
+
statusGlyph = "\u2716";
|
|
39
|
+
statusColor = "red";
|
|
40
|
+
} else if (card.column === "in_progress") {
|
|
41
|
+
statusGlyph = "\u25C9";
|
|
42
|
+
statusColor = "yellow";
|
|
43
|
+
} else if (card.column === "done") {
|
|
44
|
+
statusGlyph = "\u2714";
|
|
45
|
+
statusColor = "green";
|
|
46
|
+
} else {
|
|
47
|
+
statusGlyph = "\u25CB";
|
|
48
|
+
statusColor = "white";
|
|
49
|
+
}
|
|
50
|
+
const selectionBar = isSelected ? "\u2590" : " ";
|
|
51
|
+
const selectionColor = isSelected ? "yellow" : "white";
|
|
52
|
+
const truncated = card.title.length > 30 ? `${card.title.slice(0, 27)}\u2026` : card.title;
|
|
53
|
+
return /* @__PURE__ */ jsxs(
|
|
54
|
+
Box,
|
|
55
|
+
{
|
|
56
|
+
flexDirection: "row",
|
|
57
|
+
paddingX: 0,
|
|
58
|
+
marginBottom: 0,
|
|
59
|
+
borderStyle: "single",
|
|
60
|
+
borderColor: card.hasError ? "red" : isSelected ? "yellow" : "gray",
|
|
61
|
+
children: [
|
|
62
|
+
/* @__PURE__ */ jsx(Text, { color: selectionColor, children: selectionBar }),
|
|
63
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
|
|
64
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [
|
|
65
|
+
/* @__PURE__ */ jsx(Text, { color: "yellow", bold: isSelected, children: card.id }),
|
|
66
|
+
/* @__PURE__ */ jsx(Text, { color: statusColor, children: statusGlyph })
|
|
67
|
+
] }),
|
|
68
|
+
/* @__PURE__ */ jsx(Text, { bold: isSelected, dimColor: !isSelected, children: truncated }),
|
|
69
|
+
card.column === "in_progress" && card.startedAt !== void 0 && /* @__PURE__ */ jsxs(Box, { flexDirection: "row", marginTop: 0, children: [
|
|
70
|
+
/* @__PURE__ */ jsx(Text, { color: "yellow", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
|
|
71
|
+
/* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
|
|
72
|
+
" ",
|
|
73
|
+
formatElapsed(now - card.startedAt)
|
|
74
|
+
] })
|
|
75
|
+
] }),
|
|
76
|
+
card.column === "done" && card.startedAt !== void 0 && card.finishedAt !== void 0 && /* @__PURE__ */ jsxs(Text, { color: "green", children: [
|
|
77
|
+
"\u2714 ",
|
|
78
|
+
formatElapsed(card.finishedAt - card.startedAt)
|
|
79
|
+
] }),
|
|
80
|
+
card.hasError && /* @__PURE__ */ jsx(Text, { color: "red", children: "FAILED" })
|
|
81
|
+
] })
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/ui/column.tsx
|
|
88
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
89
|
+
var CARD_HEIGHT = 5;
|
|
90
|
+
var HEADER_ROWS = 4;
|
|
91
|
+
function Column({ label, cards, isFocused = false, activeCardIndex = 0 }) {
|
|
92
|
+
const terminalRows = process.stdout.rows ?? 24;
|
|
93
|
+
const visibleCount = Math.max(1, Math.floor((terminalRows - HEADER_ROWS) / CARD_HEIGHT));
|
|
94
|
+
let scrollOffset = 0;
|
|
95
|
+
if (activeCardIndex >= visibleCount) {
|
|
96
|
+
scrollOffset = activeCardIndex - visibleCount + 1;
|
|
97
|
+
}
|
|
98
|
+
scrollOffset = Math.max(0, Math.min(scrollOffset, Math.max(0, cards.length - visibleCount)));
|
|
99
|
+
const visibleCards = cards.slice(scrollOffset, scrollOffset + visibleCount);
|
|
100
|
+
const hiddenAbove = scrollOffset;
|
|
101
|
+
const hiddenBelow = Math.max(0, cards.length - scrollOffset - visibleCount);
|
|
102
|
+
const borderColor = isFocused ? "yellow" : "gray";
|
|
103
|
+
const headerColor = isFocused ? "yellow" : "white";
|
|
104
|
+
const runningCount = cards.filter((c) => c.column === "in_progress").length;
|
|
105
|
+
const errorCount = cards.filter((c) => c.hasError).length;
|
|
106
|
+
return /* @__PURE__ */ jsxs2(
|
|
107
|
+
Box2,
|
|
108
|
+
{
|
|
109
|
+
flexDirection: "column",
|
|
110
|
+
flexGrow: 1,
|
|
111
|
+
borderStyle: "single",
|
|
112
|
+
borderColor,
|
|
113
|
+
paddingX: 1,
|
|
114
|
+
paddingY: 0,
|
|
115
|
+
children: [
|
|
116
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", justifyContent: "space-between", marginBottom: 1, children: [
|
|
117
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", children: [
|
|
118
|
+
isFocused && /* @__PURE__ */ jsx2(Text2, { color: "yellow", bold: true, children: "\u25B6 " }),
|
|
119
|
+
!isFocused && /* @__PURE__ */ jsx2(Text2, { color: "gray", children: " " }),
|
|
120
|
+
/* @__PURE__ */ jsx2(Text2, { color: headerColor, bold: true, children: label.toUpperCase() })
|
|
121
|
+
] }),
|
|
122
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", children: [
|
|
123
|
+
errorCount > 0 && /* @__PURE__ */ jsx2(Text2, { color: "red", bold: true, children: `!${errorCount} ` }),
|
|
124
|
+
runningCount > 0 && /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: `~${runningCount} ` }),
|
|
125
|
+
/* @__PURE__ */ jsx2(Text2, { color: headerColor, children: `[${cards.length}]` })
|
|
126
|
+
] })
|
|
127
|
+
] }),
|
|
128
|
+
hiddenAbove > 0 && /* @__PURE__ */ jsx2(Box2, { justifyContent: "center", children: /* @__PURE__ */ jsx2(Text2, { color: "yellow", dimColor: true, children: `\u2191 ${hiddenAbove} more` }) }),
|
|
129
|
+
visibleCards.map((card, idx) => {
|
|
130
|
+
const absoluteIdx = scrollOffset + idx;
|
|
131
|
+
const isSelected = isFocused && absoluteIdx === activeCardIndex;
|
|
132
|
+
return /* @__PURE__ */ jsx2(Card, { card, isSelected }, card.id);
|
|
133
|
+
}),
|
|
134
|
+
cards.length === 0 && /* @__PURE__ */ jsx2(Box2, { justifyContent: "center", paddingY: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "gray", dimColor: true, children: "\u2014 empty \u2014" }) }),
|
|
135
|
+
hiddenBelow > 0 && /* @__PURE__ */ jsx2(Box2, { justifyContent: "center", children: /* @__PURE__ */ jsx2(Text2, { color: "yellow", dimColor: true, children: `\u2193 ${hiddenBelow} more` }) })
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/ui/board.tsx
|
|
142
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
143
|
+
function formatDuration(ms) {
|
|
144
|
+
const totalSeconds = Math.floor(ms / 1e3);
|
|
145
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
146
|
+
const seconds = totalSeconds % 60;
|
|
147
|
+
if (minutes > 0) return `${minutes}m ${seconds}s`;
|
|
148
|
+
return `${seconds}s`;
|
|
149
|
+
}
|
|
150
|
+
function Board({
|
|
151
|
+
cards,
|
|
152
|
+
labels,
|
|
153
|
+
isEmpty,
|
|
154
|
+
workComplete,
|
|
155
|
+
activeColIndex = 0,
|
|
156
|
+
activeCardIndex = 0
|
|
157
|
+
}) {
|
|
158
|
+
const backlog = cards.filter((c) => c.column === "backlog");
|
|
159
|
+
const inProgress = cards.filter((c) => c.column === "in_progress");
|
|
160
|
+
const done = cards.filter((c) => c.column === "done");
|
|
161
|
+
if (isEmpty) {
|
|
162
|
+
return /* @__PURE__ */ jsx3(Box3, { flexGrow: 1, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsxs3(
|
|
163
|
+
Box3,
|
|
164
|
+
{
|
|
165
|
+
flexDirection: "column",
|
|
166
|
+
borderStyle: "single",
|
|
167
|
+
borderColor: "yellow",
|
|
168
|
+
paddingX: 3,
|
|
169
|
+
paddingY: 1,
|
|
170
|
+
children: [
|
|
171
|
+
/* @__PURE__ */ jsx3(Text3, { color: "yellow", bold: true, children: "\u25C8 NO ISSUES FOUND" }),
|
|
172
|
+
/* @__PURE__ */ jsx3(Box3, { height: 1 }),
|
|
173
|
+
/* @__PURE__ */ jsx3(Text3, { color: "white", dimColor: true, children: "No issues match the current label and status filters." }),
|
|
174
|
+
/* @__PURE__ */ jsx3(Box3, { height: 1 }),
|
|
175
|
+
/* @__PURE__ */ jsx3(Text3, { color: "gray", dimColor: true, children: "Check your source configuration and labels." })
|
|
176
|
+
]
|
|
177
|
+
}
|
|
178
|
+
) });
|
|
179
|
+
}
|
|
180
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexGrow: 1, flexDirection: "column", children: [
|
|
181
|
+
workComplete && /* @__PURE__ */ jsxs3(Box3, { borderStyle: "single", borderColor: "green", paddingX: 2, paddingY: 0, marginBottom: 0, children: [
|
|
182
|
+
/* @__PURE__ */ jsx3(Text3, { color: "green", bold: true, children: "\u2714 ALL SESSIONS COMPLETE" }),
|
|
183
|
+
/* @__PURE__ */ jsx3(Text3, { color: "white", children: " \u2014 " }),
|
|
184
|
+
/* @__PURE__ */ jsx3(Text3, { color: "white", bold: true, children: workComplete.total }),
|
|
185
|
+
/* @__PURE__ */ jsx3(Text3, { color: "white", children: ` issue${workComplete.total !== 1 ? "s" : ""} resolved in ` }),
|
|
186
|
+
/* @__PURE__ */ jsx3(Text3, { color: "green", bold: true, children: formatDuration(workComplete.duration) })
|
|
187
|
+
] }),
|
|
188
|
+
/* @__PURE__ */ jsxs3(Box3, { flexGrow: 1, flexDirection: "row", children: [
|
|
189
|
+
/* @__PURE__ */ jsx3(
|
|
190
|
+
Column,
|
|
191
|
+
{
|
|
192
|
+
label: labels.backlog,
|
|
193
|
+
cards: backlog,
|
|
194
|
+
isFocused: activeColIndex === 0,
|
|
195
|
+
activeCardIndex: activeColIndex === 0 ? activeCardIndex : 0
|
|
196
|
+
}
|
|
197
|
+
),
|
|
198
|
+
/* @__PURE__ */ jsx3(
|
|
199
|
+
Column,
|
|
200
|
+
{
|
|
201
|
+
label: labels.inProgress,
|
|
202
|
+
cards: inProgress,
|
|
203
|
+
isFocused: activeColIndex === 1,
|
|
204
|
+
activeCardIndex: activeColIndex === 1 ? activeCardIndex : 0
|
|
205
|
+
}
|
|
206
|
+
),
|
|
207
|
+
/* @__PURE__ */ jsx3(
|
|
208
|
+
Column,
|
|
209
|
+
{
|
|
210
|
+
label: labels.done,
|
|
211
|
+
cards: done,
|
|
212
|
+
isFocused: activeColIndex === 2,
|
|
213
|
+
activeCardIndex: activeColIndex === 2 ? activeCardIndex : 0
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
] })
|
|
217
|
+
] });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/ui/detail.tsx
|
|
221
|
+
import { Box as Box4, Text as Text4, useInput } from "ink";
|
|
222
|
+
import Spinner2 from "ink-spinner";
|
|
223
|
+
import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
|
|
224
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
225
|
+
function formatElapsed2(ms) {
|
|
226
|
+
const seconds = Math.floor(ms / 1e3);
|
|
227
|
+
const minutes = Math.floor(seconds / 60);
|
|
228
|
+
const remainingSeconds = seconds % 60;
|
|
229
|
+
if (minutes > 0) return `${minutes}m ${remainingSeconds}s`;
|
|
230
|
+
return `${seconds}s`;
|
|
231
|
+
}
|
|
232
|
+
function statusLabel(column, hasError) {
|
|
233
|
+
if (hasError) return { text: "FAILED", color: "red" };
|
|
234
|
+
if (column === "in_progress") return { text: "IN PROGRESS", color: "yellow" };
|
|
235
|
+
if (column === "done") return { text: "DONE", color: "green" };
|
|
236
|
+
return { text: "QUEUED", color: "white" };
|
|
237
|
+
}
|
|
238
|
+
function IssueDetail({ card, onBack }) {
|
|
239
|
+
const [now, setNow] = useState2(Date.now());
|
|
240
|
+
const [logScrollOffset, setLogScrollOffset] = useState2(0);
|
|
241
|
+
const [userScrolled, setUserScrolled] = useState2(false);
|
|
242
|
+
const prevOutputLen = useRef(card.outputLog.length);
|
|
243
|
+
useEffect2(() => {
|
|
244
|
+
if (card.column !== "in_progress") return;
|
|
245
|
+
const interval = setInterval(() => setNow(Date.now()), 1e3);
|
|
246
|
+
return () => clearInterval(interval);
|
|
247
|
+
}, [card.column]);
|
|
248
|
+
useEffect2(() => {
|
|
249
|
+
if (!userScrolled && card.outputLog.length !== prevOutputLen.current) {
|
|
250
|
+
prevOutputLen.current = card.outputLog.length;
|
|
251
|
+
setLogScrollOffset(0);
|
|
252
|
+
}
|
|
253
|
+
}, [card.outputLog, userScrolled]);
|
|
254
|
+
useInput((_input, key) => {
|
|
255
|
+
if (key.escape) {
|
|
256
|
+
onBack();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (key.upArrow) {
|
|
260
|
+
setUserScrolled(true);
|
|
261
|
+
setLogScrollOffset((prev) => prev + 1);
|
|
262
|
+
}
|
|
263
|
+
if (key.downArrow) {
|
|
264
|
+
setLogScrollOffset((prev) => {
|
|
265
|
+
const next = Math.max(0, prev - 1);
|
|
266
|
+
if (next === 0) setUserScrolled(false);
|
|
267
|
+
return next;
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
const terminalCols = process.stdout.columns ?? 80;
|
|
272
|
+
const terminalRows = process.stdout.rows ?? 24;
|
|
273
|
+
const bodyRows = Math.max(1, terminalRows - 10);
|
|
274
|
+
const lines = card.outputLog.split("\n");
|
|
275
|
+
const startLine = Math.max(0, lines.length - bodyRows - logScrollOffset);
|
|
276
|
+
const visibleLines = lines.slice(startLine, startLine + bodyRows);
|
|
277
|
+
const status = statusLabel(card.column, card.hasError);
|
|
278
|
+
let elapsedDisplay = null;
|
|
279
|
+
let isRunning = false;
|
|
280
|
+
if (card.column === "in_progress" && card.startedAt !== void 0) {
|
|
281
|
+
elapsedDisplay = formatElapsed2(now - card.startedAt);
|
|
282
|
+
isRunning = true;
|
|
283
|
+
} else if (card.column === "done" && card.startedAt !== void 0 && card.finishedAt !== void 0) {
|
|
284
|
+
elapsedDisplay = formatElapsed2(card.finishedAt - card.startedAt);
|
|
285
|
+
}
|
|
286
|
+
const innerWidth = Math.max(0, terminalCols - 6);
|
|
287
|
+
const separator = `\u2560${"\u2550".repeat(innerWidth)}\u2563`;
|
|
288
|
+
const totalLines = lines.length;
|
|
289
|
+
const scrollPct = totalLines <= bodyRows ? "100%" : `${Math.round((startLine + bodyRows) / totalLines * 100)}%`;
|
|
290
|
+
return /* @__PURE__ */ jsxs4(
|
|
291
|
+
Box4,
|
|
292
|
+
{
|
|
293
|
+
flexGrow: 1,
|
|
294
|
+
flexDirection: "column",
|
|
295
|
+
borderStyle: "single",
|
|
296
|
+
borderColor: "yellow",
|
|
297
|
+
paddingX: 1,
|
|
298
|
+
paddingY: 0,
|
|
299
|
+
children: [
|
|
300
|
+
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", justifyContent: "space-between", marginTop: 0, children: [
|
|
301
|
+
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", children: [
|
|
302
|
+
/* @__PURE__ */ jsx4(Text4, { color: "yellow", bold: true, children: card.id }),
|
|
303
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: " \u2502 " }),
|
|
304
|
+
/* @__PURE__ */ jsx4(Text4, { color: status.color, bold: true, children: status.text })
|
|
305
|
+
] }),
|
|
306
|
+
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", children: [
|
|
307
|
+
isRunning && elapsedDisplay && /* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", marginRight: 2, children: [
|
|
308
|
+
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: /* @__PURE__ */ jsx4(Spinner2, { type: "dots" }) }),
|
|
309
|
+
/* @__PURE__ */ jsx4(Text4, { color: "yellow", bold: true, children: ` ${elapsedDisplay}` })
|
|
310
|
+
] }),
|
|
311
|
+
!isRunning && elapsedDisplay && /* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", marginRight: 2, children: [
|
|
312
|
+
/* @__PURE__ */ jsx4(Text4, { color: "green", children: "\u2714 " }),
|
|
313
|
+
/* @__PURE__ */ jsx4(Text4, { color: "green", bold: true, children: elapsedDisplay })
|
|
314
|
+
] })
|
|
315
|
+
] })
|
|
316
|
+
] }),
|
|
317
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 0, children: /* @__PURE__ */ jsx4(Text4, { color: "white", bold: true, children: card.title }) }),
|
|
318
|
+
card.prUrl !== void 0 && card.prUrl.length > 0 && /* @__PURE__ */ jsxs4(Box4, { marginTop: 0, children: [
|
|
319
|
+
/* @__PURE__ */ jsx4(Text4, { color: "yellow", dimColor: true, children: "PR: " }),
|
|
320
|
+
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: card.prUrl })
|
|
321
|
+
] }),
|
|
322
|
+
/* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { color: "yellow", dimColor: true, children: separator }) }),
|
|
323
|
+
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", justifyContent: "space-between", children: [
|
|
324
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: "PROVIDER OUTPUT" }),
|
|
325
|
+
userScrolled && /* @__PURE__ */ jsx4(Text4, { color: "yellow", dimColor: true, children: `scroll ${scrollPct}` }),
|
|
326
|
+
!userScrolled && totalLines > bodyRows && /* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: "auto-scroll" })
|
|
327
|
+
] }),
|
|
328
|
+
/* @__PURE__ */ jsx4(Box4, { flexGrow: 1, flexDirection: "column", overflow: "hidden", children: card.outputLog.length === 0 ? /* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", marginTop: 1, children: [
|
|
329
|
+
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: /* @__PURE__ */ jsx4(Spinner2, { type: "dots" }) }),
|
|
330
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: " Waiting for provider output..." })
|
|
331
|
+
] }) : visibleLines.map((line, i) => (
|
|
332
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: log lines have no stable key
|
|
333
|
+
/* @__PURE__ */ jsx4(Text4, { color: "white", dimColor: true, children: line }, i)
|
|
334
|
+
)) }),
|
|
335
|
+
/* @__PURE__ */ jsxs4(Box4, { justifyContent: "space-between", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
|
|
336
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: "[\u2191\u2193] scroll" }),
|
|
337
|
+
/* @__PURE__ */ jsx4(Text4, { color: "yellow", dimColor: true, children: "[Esc] back to board" })
|
|
338
|
+
] })
|
|
339
|
+
]
|
|
340
|
+
}
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// src/ui/sidebar.tsx
|
|
345
|
+
import { basename } from "path";
|
|
346
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
347
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
348
|
+
function Sidebar({ provider, source, cwd }) {
|
|
349
|
+
const dir = basename(cwd);
|
|
350
|
+
return /* @__PURE__ */ jsxs5(
|
|
351
|
+
Box5,
|
|
352
|
+
{
|
|
353
|
+
flexDirection: "column",
|
|
354
|
+
width: 28,
|
|
355
|
+
borderStyle: "single",
|
|
356
|
+
borderColor: "yellow",
|
|
357
|
+
paddingX: 1,
|
|
358
|
+
paddingY: 0,
|
|
359
|
+
children: [
|
|
360
|
+
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
|
|
361
|
+
/* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557" }),
|
|
362
|
+
/* @__PURE__ */ jsxs5(Text5, { color: "yellow", children: [
|
|
363
|
+
"\u2551",
|
|
364
|
+
/* @__PURE__ */ jsx5(Text5, { color: "white", bold: true, children: " L I S A v 2 " }),
|
|
365
|
+
/* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "\u2551" })
|
|
366
|
+
] }),
|
|
367
|
+
/* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D" })
|
|
368
|
+
] }),
|
|
369
|
+
/* @__PURE__ */ jsxs5(Box5, { marginBottom: 1, children: [
|
|
370
|
+
/* @__PURE__ */ jsx5(Text5, { color: "green", children: "\u25B6 " }),
|
|
371
|
+
/* @__PURE__ */ jsx5(Text5, { color: "green", bold: true, children: "RUNNING" })
|
|
372
|
+
] }),
|
|
373
|
+
/* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
|
|
374
|
+
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
|
|
375
|
+
/* @__PURE__ */ jsx5(Text5, { color: "white", dimColor: true, children: "PROVIDER" }),
|
|
376
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
377
|
+
/* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "\u25B8 " }),
|
|
378
|
+
/* @__PURE__ */ jsx5(Text5, { color: "white", bold: true, children: provider.toUpperCase() })
|
|
379
|
+
] })
|
|
380
|
+
] }),
|
|
381
|
+
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
|
|
382
|
+
/* @__PURE__ */ jsx5(Text5, { color: "white", dimColor: true, children: "SOURCE" }),
|
|
383
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
384
|
+
/* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "\u25B8 " }),
|
|
385
|
+
/* @__PURE__ */ jsx5(Text5, { color: "white", bold: true, children: source.toUpperCase() })
|
|
386
|
+
] })
|
|
387
|
+
] }),
|
|
388
|
+
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
|
|
389
|
+
/* @__PURE__ */ jsx5(Text5, { color: "white", dimColor: true, children: "WORKSPACE" }),
|
|
390
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
391
|
+
/* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "\u25B8 " }),
|
|
392
|
+
/* @__PURE__ */ jsx5(Text5, { color: "white", bold: true, children: dir.length > 18 ? `${dir.slice(0, 15)}\u2026` : dir })
|
|
393
|
+
] })
|
|
394
|
+
] }),
|
|
395
|
+
/* @__PURE__ */ jsx5(Box5, { flexGrow: 1 }),
|
|
396
|
+
/* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
|
|
397
|
+
/* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
398
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[Tab] next column" }),
|
|
399
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[\u2191\u2193] navigate " }),
|
|
400
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[\u21B5] view detail " }),
|
|
401
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[q] quit " })
|
|
402
|
+
] })
|
|
403
|
+
]
|
|
404
|
+
}
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/ui/kanban.tsx
|
|
409
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
410
|
+
function KanbanApp({ config }) {
|
|
411
|
+
const { exit } = useApp();
|
|
412
|
+
const { cards, isEmpty, workComplete } = useKanbanState();
|
|
413
|
+
const [activeView, setActiveView] = useState3("board");
|
|
414
|
+
const [activeColIndex, setActiveColIndex] = useState3(0);
|
|
415
|
+
const [activeCardIndex, setActiveCardIndex] = useState3(0);
|
|
416
|
+
const backlog = cards.filter((c) => c.column === "backlog");
|
|
417
|
+
const inProgress = cards.filter((c) => c.column === "in_progress");
|
|
418
|
+
const done = cards.filter((c) => c.column === "done");
|
|
419
|
+
const columnCards = [backlog, inProgress, done];
|
|
420
|
+
useInput2((input, key) => {
|
|
421
|
+
if (input === "q") {
|
|
422
|
+
process.emit("SIGINT");
|
|
423
|
+
exit();
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
if (activeView === "detail") {
|
|
427
|
+
if (key.escape) setActiveView("board");
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (key.tab && !key.shift) {
|
|
431
|
+
const nextCol = (activeColIndex + 1) % 3;
|
|
432
|
+
setActiveColIndex(nextCol);
|
|
433
|
+
const colLen = columnCards[nextCol]?.length ?? 0;
|
|
434
|
+
setActiveCardIndex(Math.min(activeCardIndex, Math.max(0, colLen - 1)));
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
if (key.tab && key.shift) {
|
|
438
|
+
const prevCol = (activeColIndex + 2) % 3;
|
|
439
|
+
setActiveColIndex(prevCol);
|
|
440
|
+
const colLen = columnCards[prevCol]?.length ?? 0;
|
|
441
|
+
setActiveCardIndex(Math.min(activeCardIndex, Math.max(0, colLen - 1)));
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
if (key.downArrow) {
|
|
445
|
+
const colLen = columnCards[activeColIndex]?.length ?? 0;
|
|
446
|
+
setActiveCardIndex((prev) => Math.min(prev + 1, Math.max(0, colLen - 1)));
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (key.upArrow) {
|
|
450
|
+
setActiveCardIndex((prev) => Math.max(0, prev - 1));
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (key.return) {
|
|
454
|
+
const colLen = columnCards[activeColIndex]?.length ?? 0;
|
|
455
|
+
if (colLen > 0) setActiveView("detail");
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
const labels = {
|
|
459
|
+
backlog: config.source_config.pick_from,
|
|
460
|
+
inProgress: config.source_config.in_progress,
|
|
461
|
+
done: config.source_config.done
|
|
462
|
+
};
|
|
463
|
+
const selectedCard = activeView === "detail" ? columnCards[activeColIndex]?.[activeCardIndex] ?? null : null;
|
|
464
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "row", height: process.stdout.rows, children: [
|
|
465
|
+
/* @__PURE__ */ jsx6(Sidebar, { provider: config.provider, source: config.source, cwd: process.cwd() }),
|
|
466
|
+
activeView === "board" || !selectedCard ? /* @__PURE__ */ jsx6(
|
|
467
|
+
Board,
|
|
468
|
+
{
|
|
469
|
+
cards,
|
|
470
|
+
labels,
|
|
471
|
+
isEmpty,
|
|
472
|
+
workComplete,
|
|
473
|
+
activeColIndex,
|
|
474
|
+
activeCardIndex
|
|
475
|
+
}
|
|
476
|
+
) : /* @__PURE__ */ jsx6(IssueDetail, { card: selectedCard, onBack: () => setActiveView("board") })
|
|
477
|
+
] });
|
|
478
|
+
}
|
|
479
|
+
export {
|
|
480
|
+
KanbanApp
|
|
481
|
+
};
|
package/package.json
CHANGED
|
@@ -1,60 +1,62 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
2
|
+
"name": "@tarcisiopgs/lisa",
|
|
3
|
+
"version": "1.7.0",
|
|
4
|
+
"description": "Deterministic autonomous issue resolver — structured AI agent loop for Linear/Trello",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"lisa": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsup",
|
|
12
|
+
"dev": "tsx src/index.ts",
|
|
13
|
+
"check": "biome check src/",
|
|
14
|
+
"format": "biome format --write src/",
|
|
15
|
+
"lint": "biome lint src/",
|
|
16
|
+
"typecheck": "tsc --noEmit",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest",
|
|
19
|
+
"test:coverage": "vitest run --coverage",
|
|
20
|
+
"ci": "concurrently -n lint,typecheck,test \"pnpm lint\" \"pnpm typecheck\" \"pnpm test\"",
|
|
21
|
+
"prepare": "husky"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@clack/prompts": "^1.0.1",
|
|
25
|
+
"citty": "^0.2.1",
|
|
26
|
+
"execa": "^9.6.1",
|
|
27
|
+
"ink": "^6.8.0",
|
|
28
|
+
"ink-spinner": "^5.0.0",
|
|
29
|
+
"picocolors": "^1.1.1",
|
|
30
|
+
"react": "^19.2.4",
|
|
31
|
+
"yaml": "^2.8.2"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@biomejs/biome": "^2.4.3",
|
|
35
|
+
"@types/node": "^22.13.4",
|
|
36
|
+
"@types/react": "^19.2.14",
|
|
37
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
38
|
+
"concurrently": "^9.2.1",
|
|
39
|
+
"husky": "^9.1.7",
|
|
40
|
+
"lint-staged": "^16.2.7",
|
|
41
|
+
"tsup": "^8.4.0",
|
|
42
|
+
"tsx": "^4.19.3",
|
|
43
|
+
"typescript": "^5.9.3",
|
|
44
|
+
"vitest": "^4.0.18"
|
|
45
|
+
},
|
|
46
|
+
"packageManager": "pnpm@10.29.3",
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "https://github.com/tarcisiopgs/lisa.git"
|
|
53
|
+
},
|
|
54
|
+
"files": [
|
|
55
|
+
"dist"
|
|
56
|
+
],
|
|
57
|
+
"lint-staged": {
|
|
58
|
+
"*.{ts,tsx}": [
|
|
59
|
+
"biome check --write"
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
}
|
package/dist/kanban-KIPKQ2IL.js
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
useKanbanState
|
|
4
|
-
} from "./chunk-KAME5MG7.js";
|
|
5
|
-
|
|
6
|
-
// src/ui/kanban.tsx
|
|
7
|
-
import { Box as Box5, useApp, useInput } from "ink";
|
|
8
|
-
|
|
9
|
-
// src/ui/board.tsx
|
|
10
|
-
import { Box as Box3 } from "ink";
|
|
11
|
-
|
|
12
|
-
// src/ui/column.tsx
|
|
13
|
-
import { Box as Box2, Text as Text2 } from "ink";
|
|
14
|
-
|
|
15
|
-
// src/ui/card.tsx
|
|
16
|
-
import { Box, Text } from "ink";
|
|
17
|
-
import Spinner from "ink-spinner";
|
|
18
|
-
import { useEffect, useState } from "react";
|
|
19
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
20
|
-
function formatElapsed(ms) {
|
|
21
|
-
const seconds = Math.floor(ms / 1e3);
|
|
22
|
-
const minutes = Math.floor(seconds / 60);
|
|
23
|
-
const remainingSeconds = seconds % 60;
|
|
24
|
-
if (minutes > 0) return `${minutes}m ${remainingSeconds}s`;
|
|
25
|
-
return `${seconds}s`;
|
|
26
|
-
}
|
|
27
|
-
function Card({ card }) {
|
|
28
|
-
const [now, setNow] = useState(Date.now());
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
if (card.column !== "in_progress") return;
|
|
31
|
-
const interval = setInterval(() => setNow(Date.now()), 1e3);
|
|
32
|
-
return () => clearInterval(interval);
|
|
33
|
-
}, [card.column]);
|
|
34
|
-
const truncated = card.title.length > 22 ? `${card.title.slice(0, 19)}...` : card.title;
|
|
35
|
-
const borderColor = card.hasError ? "red" : card.column === "done" ? "green" : "white";
|
|
36
|
-
return /* @__PURE__ */ jsxs(
|
|
37
|
-
Box,
|
|
38
|
-
{
|
|
39
|
-
borderStyle: "round",
|
|
40
|
-
borderColor,
|
|
41
|
-
flexDirection: "column",
|
|
42
|
-
paddingX: 1,
|
|
43
|
-
marginBottom: 1,
|
|
44
|
-
children: [
|
|
45
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: card.id }),
|
|
46
|
-
/* @__PURE__ */ jsx(Text, { children: truncated }),
|
|
47
|
-
card.column === "in_progress" && card.startedAt !== void 0 && /* @__PURE__ */ jsxs(Box, { children: [
|
|
48
|
-
/* @__PURE__ */ jsx(Text, { color: "yellow", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
|
|
49
|
-
/* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
|
|
50
|
-
" ",
|
|
51
|
-
formatElapsed(now - card.startedAt)
|
|
52
|
-
] })
|
|
53
|
-
] })
|
|
54
|
-
]
|
|
55
|
-
}
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// src/ui/column.tsx
|
|
60
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
61
|
-
function Column({ label, cards }) {
|
|
62
|
-
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", flexGrow: 1, borderStyle: "single", paddingX: 1, children: [
|
|
63
|
-
/* @__PURE__ */ jsxs2(Text2, { bold: true, color: "cyan", children: [
|
|
64
|
-
label,
|
|
65
|
-
" (",
|
|
66
|
-
cards.length,
|
|
67
|
-
")"
|
|
68
|
-
] }),
|
|
69
|
-
/* @__PURE__ */ jsx2(Box2, { height: 1 }),
|
|
70
|
-
cards.map((card) => /* @__PURE__ */ jsx2(Card, { card }, card.id))
|
|
71
|
-
] });
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// src/ui/board.tsx
|
|
75
|
-
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
76
|
-
function Board({ cards, labels }) {
|
|
77
|
-
const backlog = cards.filter((c) => c.column === "backlog");
|
|
78
|
-
const inProgress = cards.filter((c) => c.column === "in_progress");
|
|
79
|
-
const done = cards.filter((c) => c.column === "done");
|
|
80
|
-
return /* @__PURE__ */ jsxs3(Box3, { flexGrow: 1, flexDirection: "row", children: [
|
|
81
|
-
/* @__PURE__ */ jsx3(Column, { label: labels.backlog, cards: backlog }),
|
|
82
|
-
/* @__PURE__ */ jsx3(Column, { label: labels.inProgress, cards: inProgress }),
|
|
83
|
-
/* @__PURE__ */ jsx3(Column, { label: labels.done, cards: done })
|
|
84
|
-
] });
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// src/ui/sidebar.tsx
|
|
88
|
-
import { Box as Box4, Text as Text3 } from "ink";
|
|
89
|
-
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
90
|
-
function Sidebar({ provider, source }) {
|
|
91
|
-
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", width: 18, borderStyle: "single", paddingX: 1, children: [
|
|
92
|
-
/* @__PURE__ */ jsx4(Text3, { bold: true, color: "yellow", children: "\u25C6 LISA" }),
|
|
93
|
-
/* @__PURE__ */ jsx4(Box4, { height: 1 }),
|
|
94
|
-
/* @__PURE__ */ jsx4(Text3, { dimColor: true, children: "Provider" }),
|
|
95
|
-
/* @__PURE__ */ jsx4(Text3, { color: "cyan", children: provider }),
|
|
96
|
-
/* @__PURE__ */ jsx4(Box4, { height: 1 }),
|
|
97
|
-
/* @__PURE__ */ jsx4(Text3, { dimColor: true, children: "Source" }),
|
|
98
|
-
/* @__PURE__ */ jsx4(Text3, { color: "cyan", children: source }),
|
|
99
|
-
/* @__PURE__ */ jsx4(Box4, { flexGrow: 1 }),
|
|
100
|
-
/* @__PURE__ */ jsx4(Text3, { dimColor: true, children: "[q] Quit" })
|
|
101
|
-
] });
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// src/ui/kanban.tsx
|
|
105
|
-
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
106
|
-
function KanbanApp({ config }) {
|
|
107
|
-
const { exit } = useApp();
|
|
108
|
-
const { cards } = useKanbanState();
|
|
109
|
-
useInput((input) => {
|
|
110
|
-
if (input === "q") {
|
|
111
|
-
process.emit("SIGINT");
|
|
112
|
-
exit();
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
const labels = {
|
|
116
|
-
backlog: config.source_config.pick_from,
|
|
117
|
-
inProgress: config.source_config.in_progress,
|
|
118
|
-
done: config.source_config.done
|
|
119
|
-
};
|
|
120
|
-
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", height: process.stdout.rows, children: [
|
|
121
|
-
/* @__PURE__ */ jsx5(Sidebar, { provider: config.provider, source: config.source }),
|
|
122
|
-
/* @__PURE__ */ jsx5(Board, { cards, labels })
|
|
123
|
-
] });
|
|
124
|
-
}
|
|
125
|
-
export {
|
|
126
|
-
KanbanApp
|
|
127
|
-
};
|