@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.
@@ -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";
@@ -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-KIPKQ2IL.js");
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
- "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.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
+ }
@@ -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
- };