@tarcisiopgs/lisa 1.6.0 → 1.7.1

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