kandown 0.7.5 → 0.9.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/README.md +15 -1
- package/bin/kandown.js +301 -42
- package/bin/tui.js +248 -41
- package/dist/index.html +375 -1904
- package/package.json +1 -3
package/bin/tui.js
CHANGED
|
@@ -54438,7 +54438,7 @@ function buildColumnsFromTasks(tasks, configuredColumns = DEFAULT_COLUMNS) {
|
|
|
54438
54438
|
const configured = columnNames.map((name) => ({ name, tasks: [] }));
|
|
54439
54439
|
for (const column of configured) columnsByName.set(column.name.toLowerCase(), column);
|
|
54440
54440
|
const unknownColumns = [];
|
|
54441
|
-
const sortedTasks = [...tasks].filter((task) => Boolean(task.frontmatter.id)).sort((a, b) => {
|
|
54441
|
+
const sortedTasks = [...tasks].filter((task) => Boolean(task.frontmatter.id)).filter((task) => !isArchived(task)).sort((a, b) => {
|
|
54442
54442
|
const byOrder = taskOrder(a) - taskOrder(b);
|
|
54443
54443
|
if (byOrder !== 0) return byOrder;
|
|
54444
54444
|
return a.frontmatter.id.localeCompare(b.frontmatter.id, void 0, { numeric: true });
|
|
@@ -54455,6 +54455,9 @@ function buildColumnsFromTasks(tasks, configuredColumns = DEFAULT_COLUMNS) {
|
|
|
54455
54455
|
}
|
|
54456
54456
|
return [...unknownColumns, ...configured];
|
|
54457
54457
|
}
|
|
54458
|
+
function isArchived(task) {
|
|
54459
|
+
return String(task.frontmatter.archived) === "true";
|
|
54460
|
+
}
|
|
54458
54461
|
function extractSubtasks(body) {
|
|
54459
54462
|
const subtasks = [];
|
|
54460
54463
|
if (!body || typeof body !== "string") return { subtasks, bodyWithoutSubtasks: body ?? "" };
|
|
@@ -54488,6 +54491,16 @@ function extractSubtasks(body) {
|
|
|
54488
54491
|
subtasks[subtasks.length - 1].report = reportMatch[1];
|
|
54489
54492
|
continue;
|
|
54490
54493
|
}
|
|
54494
|
+
const legacyDescMatch = line.match(/^\s+description:\s*(.+)$/);
|
|
54495
|
+
if (legacyDescMatch && inSubtaskSection && subtasks.length > 0) {
|
|
54496
|
+
subtasks[subtasks.length - 1].description = legacyDescMatch[1].trim();
|
|
54497
|
+
continue;
|
|
54498
|
+
}
|
|
54499
|
+
const legacyReportMatch = line.match(/^\s+report:\s*(.+)$/);
|
|
54500
|
+
if (legacyReportMatch && inSubtaskSection && subtasks.length > 0) {
|
|
54501
|
+
subtasks[subtasks.length - 1].report = legacyReportMatch[1].trim();
|
|
54502
|
+
continue;
|
|
54503
|
+
}
|
|
54491
54504
|
kept.push(line);
|
|
54492
54505
|
}
|
|
54493
54506
|
return { subtasks, bodyWithoutSubtasks: kept.join("\n") };
|
|
@@ -54504,7 +54517,7 @@ function serializeTaskFile(frontmatter, body) {
|
|
|
54504
54517
|
lines.push(`${k}: [${v.join(", ")}]`);
|
|
54505
54518
|
} else if (typeof v === "string" && v.includes("\n")) {
|
|
54506
54519
|
lines.push(`${k}: |`);
|
|
54507
|
-
lines.push(...v.split("\n").map((line) => ` ${line}`));
|
|
54520
|
+
lines.push(...v.split("\n").map((line) => line === "" ? "" : ` ${line}`));
|
|
54508
54521
|
} else if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
54509
54522
|
lines.push(`${k}: ${v}`);
|
|
54510
54523
|
}
|
|
@@ -54589,10 +54602,137 @@ function moveTaskToColumn(kandownDir, taskId, targetColumn) {
|
|
|
54589
54602
|
return true;
|
|
54590
54603
|
}
|
|
54591
54604
|
|
|
54605
|
+
// src/cli/lib/daemon.ts
|
|
54606
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, unlinkSync } from "fs";
|
|
54607
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
54608
|
+
import { spawn } from "child_process";
|
|
54609
|
+
function metadataPath(kandownDir) {
|
|
54610
|
+
return join3(kandownDir, "daemon.json");
|
|
54611
|
+
}
|
|
54612
|
+
function isRecord(value) {
|
|
54613
|
+
return typeof value === "object" && value !== null;
|
|
54614
|
+
}
|
|
54615
|
+
function parseMetadata(value) {
|
|
54616
|
+
if (!isRecord(value)) return null;
|
|
54617
|
+
const { pid, port, url, kandownDir, startedAt, version } = value;
|
|
54618
|
+
if (typeof pid !== "number" || !Number.isInteger(pid)) return null;
|
|
54619
|
+
if (typeof port !== "number" || !Number.isInteger(port)) return null;
|
|
54620
|
+
if (typeof url !== "string" || typeof kandownDir !== "string") return null;
|
|
54621
|
+
if (typeof startedAt !== "string") return null;
|
|
54622
|
+
if (version !== null && typeof version !== "string" && version !== void 0) return null;
|
|
54623
|
+
return { pid, port, url, kandownDir, startedAt, version: version ?? null };
|
|
54624
|
+
}
|
|
54625
|
+
function readDaemonMetadata(kandownDir) {
|
|
54626
|
+
const path = metadataPath(kandownDir);
|
|
54627
|
+
if (!existsSync4(path)) return null;
|
|
54628
|
+
try {
|
|
54629
|
+
return parseMetadata(JSON.parse(readFileSync4(path, "utf8")));
|
|
54630
|
+
} catch {
|
|
54631
|
+
return null;
|
|
54632
|
+
}
|
|
54633
|
+
}
|
|
54634
|
+
function removeDaemonMetadata(kandownDir) {
|
|
54635
|
+
try {
|
|
54636
|
+
const path = metadataPath(kandownDir);
|
|
54637
|
+
if (existsSync4(path)) unlinkSync(path);
|
|
54638
|
+
} catch {
|
|
54639
|
+
}
|
|
54640
|
+
}
|
|
54641
|
+
function isProcessAlive(pid) {
|
|
54642
|
+
try {
|
|
54643
|
+
process.kill(pid, 0);
|
|
54644
|
+
return true;
|
|
54645
|
+
} catch {
|
|
54646
|
+
return false;
|
|
54647
|
+
}
|
|
54648
|
+
}
|
|
54649
|
+
function parseRemoteDaemonInfo(value) {
|
|
54650
|
+
if (!isRecord(value)) return null;
|
|
54651
|
+
const { ok, pid, kandownDir } = value;
|
|
54652
|
+
if (ok !== true || typeof pid !== "number" || !Number.isInteger(pid) || typeof kandownDir !== "string") return null;
|
|
54653
|
+
return { ok, pid, kandownDir };
|
|
54654
|
+
}
|
|
54655
|
+
async function fetchDaemonInfo(port) {
|
|
54656
|
+
try {
|
|
54657
|
+
const response = await fetch(`http://127.0.0.1:${port}/api/daemon`, {
|
|
54658
|
+
signal: AbortSignal.timeout(700)
|
|
54659
|
+
});
|
|
54660
|
+
if (!response.ok) return null;
|
|
54661
|
+
return parseRemoteDaemonInfo(await response.json());
|
|
54662
|
+
} catch {
|
|
54663
|
+
return null;
|
|
54664
|
+
}
|
|
54665
|
+
}
|
|
54666
|
+
async function getDaemonStatus(kandownDir) {
|
|
54667
|
+
const metadata = readDaemonMetadata(kandownDir);
|
|
54668
|
+
if (!metadata) return { running: false, metadata: null };
|
|
54669
|
+
if (!isProcessAlive(metadata.pid)) {
|
|
54670
|
+
removeDaemonMetadata(kandownDir);
|
|
54671
|
+
return { running: false, metadata: null };
|
|
54672
|
+
}
|
|
54673
|
+
const remote = await fetchDaemonInfo(metadata.port);
|
|
54674
|
+
if (!remote || remote.pid !== metadata.pid || remote.kandownDir !== kandownDir) {
|
|
54675
|
+
removeDaemonMetadata(kandownDir);
|
|
54676
|
+
return { running: false, metadata: null };
|
|
54677
|
+
}
|
|
54678
|
+
return { running: true, metadata };
|
|
54679
|
+
}
|
|
54680
|
+
async function waitForDaemon(kandownDir, timeoutMs = 5e3) {
|
|
54681
|
+
const started = Date.now();
|
|
54682
|
+
while (Date.now() - started < timeoutMs) {
|
|
54683
|
+
const status = await getDaemonStatus(kandownDir);
|
|
54684
|
+
if (status.running) return status;
|
|
54685
|
+
await new Promise((resolve3) => setTimeout(resolve3, 150));
|
|
54686
|
+
}
|
|
54687
|
+
return { running: false, metadata: null };
|
|
54688
|
+
}
|
|
54689
|
+
async function startProjectDaemon(kandownDir, preferredPort) {
|
|
54690
|
+
const current = await getDaemonStatus(kandownDir);
|
|
54691
|
+
if (current.running) return current;
|
|
54692
|
+
const cliPath = process.argv[1];
|
|
54693
|
+
if (!cliPath) throw new Error("Cannot locate kandown CLI entrypoint");
|
|
54694
|
+
const args = [
|
|
54695
|
+
cliPath,
|
|
54696
|
+
"--no-update-check",
|
|
54697
|
+
"daemon",
|
|
54698
|
+
"run",
|
|
54699
|
+
"--path",
|
|
54700
|
+
kandownDir
|
|
54701
|
+
];
|
|
54702
|
+
if (typeof preferredPort === "number" && Number.isInteger(preferredPort)) {
|
|
54703
|
+
args.push("--port", String(preferredPort));
|
|
54704
|
+
}
|
|
54705
|
+
const child = spawn(process.execPath, args, {
|
|
54706
|
+
cwd: dirname2(kandownDir),
|
|
54707
|
+
detached: true,
|
|
54708
|
+
stdio: "ignore",
|
|
54709
|
+
env: { ...process.env, KANDOWN_DAEMON: "1" }
|
|
54710
|
+
});
|
|
54711
|
+
child.unref();
|
|
54712
|
+
return waitForDaemon(kandownDir);
|
|
54713
|
+
}
|
|
54714
|
+
async function stopProjectDaemon(kandownDir) {
|
|
54715
|
+
const status = await getDaemonStatus(kandownDir);
|
|
54716
|
+
if (!status.running || !status.metadata) {
|
|
54717
|
+
removeDaemonMetadata(kandownDir);
|
|
54718
|
+
return false;
|
|
54719
|
+
}
|
|
54720
|
+
try {
|
|
54721
|
+
process.kill(status.metadata.pid, "SIGTERM");
|
|
54722
|
+
} catch {
|
|
54723
|
+
}
|
|
54724
|
+
const started = Date.now();
|
|
54725
|
+
while (Date.now() - started < 2500 && isProcessAlive(status.metadata.pid)) {
|
|
54726
|
+
await new Promise((resolve3) => setTimeout(resolve3, 100));
|
|
54727
|
+
}
|
|
54728
|
+
removeDaemonMetadata(kandownDir);
|
|
54729
|
+
return true;
|
|
54730
|
+
}
|
|
54731
|
+
|
|
54592
54732
|
// src/cli/lib/file-watcher.ts
|
|
54593
54733
|
import { createReadStream, statSync } from "fs";
|
|
54594
54734
|
import { createHash } from "crypto";
|
|
54595
|
-
import { join as
|
|
54735
|
+
import { join as join6 } from "path";
|
|
54596
54736
|
|
|
54597
54737
|
// node_modules/.pnpm/chokidar@4.0.3/node_modules/chokidar/esm/index.js
|
|
54598
54738
|
import { stat as statcb } from "fs";
|
|
@@ -55323,9 +55463,9 @@ var NodeFsHandler = class {
|
|
|
55323
55463
|
if (this.fsw.closed) {
|
|
55324
55464
|
return;
|
|
55325
55465
|
}
|
|
55326
|
-
const
|
|
55466
|
+
const dirname5 = sysPath.dirname(file);
|
|
55327
55467
|
const basename3 = sysPath.basename(file);
|
|
55328
|
-
const parent = this.fsw._getWatchedDir(
|
|
55468
|
+
const parent = this.fsw._getWatchedDir(dirname5);
|
|
55329
55469
|
let prevStats = stats;
|
|
55330
55470
|
if (parent.has(basename3))
|
|
55331
55471
|
return;
|
|
@@ -55352,7 +55492,7 @@ var NodeFsHandler = class {
|
|
|
55352
55492
|
prevStats = newStats2;
|
|
55353
55493
|
}
|
|
55354
55494
|
} catch (error) {
|
|
55355
|
-
this.fsw._remove(
|
|
55495
|
+
this.fsw._remove(dirname5, basename3);
|
|
55356
55496
|
}
|
|
55357
55497
|
} else if (parent.has(basename3)) {
|
|
55358
55498
|
const at = newStats.atimeMs;
|
|
@@ -56314,18 +56454,18 @@ var FileWatcher = class {
|
|
|
56314
56454
|
* poll to catch any races or network-mounted file changes.
|
|
56315
56455
|
*/
|
|
56316
56456
|
start(kandownDir) {
|
|
56317
|
-
const tasksDir =
|
|
56318
|
-
const configPath =
|
|
56457
|
+
const tasksDir = join6(kandownDir, "tasks");
|
|
56458
|
+
const configPath = join6(kandownDir, "kandown.json");
|
|
56319
56459
|
const existingIds = listTaskIds(kandownDir);
|
|
56320
56460
|
for (const id of existingIds) {
|
|
56321
56461
|
this.knownTaskIds.add(id);
|
|
56322
56462
|
try {
|
|
56323
|
-
const filePath =
|
|
56463
|
+
const filePath = join6(tasksDir, `${id}.md`);
|
|
56324
56464
|
this.taskHashes.set(id, hashFileSync(filePath));
|
|
56325
56465
|
} catch {
|
|
56326
56466
|
}
|
|
56327
56467
|
}
|
|
56328
|
-
this.watcher = watch([
|
|
56468
|
+
this.watcher = watch([join6(tasksDir, "*.md"), configPath], {
|
|
56329
56469
|
ignoreInitial: true,
|
|
56330
56470
|
awaitWriteFinish: { stabilityThreshold: 25, pollInterval: 25 },
|
|
56331
56471
|
alwaysStat: true
|
|
@@ -56373,8 +56513,8 @@ var FileWatcher = class {
|
|
|
56373
56513
|
// ─── Private ───────────────────────────────────────────────────────────────
|
|
56374
56514
|
handleFsEvent(event, filePath, kandownDir) {
|
|
56375
56515
|
if (this.stopped) return;
|
|
56376
|
-
const tasksDir =
|
|
56377
|
-
const configPath =
|
|
56516
|
+
const tasksDir = join6(kandownDir, "tasks");
|
|
56517
|
+
const configPath = join6(kandownDir, "kandown.json");
|
|
56378
56518
|
if (filePath === configPath) {
|
|
56379
56519
|
const key = `config:${event}`;
|
|
56380
56520
|
const existing = this.debounceTimers.get(key);
|
|
@@ -56426,14 +56566,14 @@ var FileWatcher = class {
|
|
|
56426
56566
|
/** 📖 Fallback poll — catches changes that chokidar missed (network mounts, exotic FS). */
|
|
56427
56567
|
async pollHashes(kandownDir) {
|
|
56428
56568
|
if (this.stopped) return;
|
|
56429
|
-
const tasksDir =
|
|
56430
|
-
const configPath =
|
|
56569
|
+
const tasksDir = join6(kandownDir, "tasks");
|
|
56570
|
+
const configPath = join6(kandownDir, "kandown.json");
|
|
56431
56571
|
try {
|
|
56432
56572
|
const newHash = hashFileSync(configPath);
|
|
56433
56573
|
} catch {
|
|
56434
56574
|
}
|
|
56435
56575
|
for (const taskId of this.knownTaskIds) {
|
|
56436
|
-
const filePath =
|
|
56576
|
+
const filePath = join6(tasksDir, `${taskId}.md`);
|
|
56437
56577
|
try {
|
|
56438
56578
|
statSync(filePath);
|
|
56439
56579
|
const newHash = await hashFile(filePath);
|
|
@@ -56450,7 +56590,7 @@ var FileWatcher = class {
|
|
|
56450
56590
|
const currentIds = listTaskIds(kandownDir);
|
|
56451
56591
|
for (const id of currentIds) {
|
|
56452
56592
|
if (!this.knownTaskIds.has(id)) {
|
|
56453
|
-
const filePath =
|
|
56593
|
+
const filePath = join6(tasksDir, `${id}.md`);
|
|
56454
56594
|
try {
|
|
56455
56595
|
const newHash = await hashFile(filePath);
|
|
56456
56596
|
this.knownTaskIds.add(id);
|
|
@@ -56620,9 +56760,9 @@ function buildPrompt(agentDoc, taskContent, taskId, kandownDir) {
|
|
|
56620
56760
|
}
|
|
56621
56761
|
|
|
56622
56762
|
// src/cli/lib/launcher.ts
|
|
56623
|
-
import { execSync, spawn } from "child_process";
|
|
56763
|
+
import { execSync, spawn as spawn2 } from "child_process";
|
|
56624
56764
|
import { writeFileSync as writeFileSync3 } from "fs";
|
|
56625
|
-
import { join as
|
|
56765
|
+
import { join as join7 } from "path";
|
|
56626
56766
|
import { tmpdir } from "os";
|
|
56627
56767
|
function isInTmux() {
|
|
56628
56768
|
return !!process.env.TMUX;
|
|
@@ -56646,7 +56786,7 @@ function launchAgent(opts) {
|
|
|
56646
56786
|
].join("\n");
|
|
56647
56787
|
const { systemPrompt, taskPrompt } = buildPrompt(agentDoc, taskFileContent, taskId, kandownDir);
|
|
56648
56788
|
moveTaskToColumn(kandownDir, taskId, "In Progress");
|
|
56649
|
-
const contextFile =
|
|
56789
|
+
const contextFile = join7(tmpdir(), `kandown-${taskId}-context.md`);
|
|
56650
56790
|
writeFileSync3(contextFile, `${systemPrompt}
|
|
56651
56791
|
|
|
56652
56792
|
---
|
|
@@ -56664,7 +56804,7 @@ ${taskPrompt}`, "utf8");
|
|
|
56664
56804
|
});
|
|
56665
56805
|
} else {
|
|
56666
56806
|
onBeforeExec?.();
|
|
56667
|
-
const child =
|
|
56807
|
+
const child = spawn2(binary, args, {
|
|
56668
56808
|
stdio: "inherit",
|
|
56669
56809
|
env: {
|
|
56670
56810
|
...process.env,
|
|
@@ -56864,6 +57004,15 @@ var RE_HEADER = /^#{1,3}\s/;
|
|
|
56864
57004
|
var RE_SUBTASK = /^\s*-\s+\[([ xX])\]/;
|
|
56865
57005
|
var RE_DONE = /^\s*-\s+\[x\]/i;
|
|
56866
57006
|
var RE_BRACKET_TAG = /^\[([^\]]+)\]\s*/;
|
|
57007
|
+
function columnAccentColor(name) {
|
|
57008
|
+
const normalized = name.toLowerCase();
|
|
57009
|
+
if (/backlog/.test(normalized)) return "magenta";
|
|
57010
|
+
if (/todo/.test(normalized)) return "cyan";
|
|
57011
|
+
if (/progress|doing/.test(normalized)) return "yellow";
|
|
57012
|
+
if (/review/.test(normalized)) return "blue";
|
|
57013
|
+
if (/done|archive|closed|complete/.test(normalized)) return "green";
|
|
57014
|
+
return "cyan";
|
|
57015
|
+
}
|
|
56867
57016
|
function TaskRow({ task, focused, dragging, colWidth }) {
|
|
56868
57017
|
const cursor = dragging ? "\u2195" : focused ? "\u25B8" : " ";
|
|
56869
57018
|
const check2 = task.checked ? "\u2713" : "\u25CB";
|
|
@@ -56920,8 +57069,9 @@ function KanbanColumn({
|
|
|
56920
57069
|
isMoveFocused,
|
|
56921
57070
|
draggedTaskId
|
|
56922
57071
|
}) {
|
|
56923
|
-
const
|
|
56924
|
-
const
|
|
57072
|
+
const accent = columnAccentColor(name);
|
|
57073
|
+
const headerBg = isFocused ? accent : void 0;
|
|
57074
|
+
const headerColor = isFocused ? "black" : accent;
|
|
56925
57075
|
const countStr = tasks.length > 0 ? ` (${tasks.length})` : "";
|
|
56926
57076
|
const rows = [];
|
|
56927
57077
|
tasks.forEach((task, idx) => {
|
|
@@ -56960,31 +57110,45 @@ function KanbanColumn({
|
|
|
56960
57110
|
showMoveTarget && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MovePlaceholder, { name, focused: !!isMoveFocused, colWidth })
|
|
56961
57111
|
] });
|
|
56962
57112
|
}
|
|
56963
|
-
function BoardHeader({ title, inTmux, modeHint, version }) {
|
|
57113
|
+
function BoardHeader({ title, inTmux, modeHint, version, daemonStatus, daemonBusy }) {
|
|
56964
57114
|
const tmuxHint = inTmux ? " tmux" : "";
|
|
56965
57115
|
const versionTag = version ? ` v${version}` : "";
|
|
56966
|
-
const
|
|
57116
|
+
const daemonLabel = daemonBusy ? "\u25CC daemon\u2026" : daemonStatus.running ? `\u25CF web ${daemonStatus.metadata?.port ?? ""}` : "\u25CB web off";
|
|
57117
|
+
const hint = modeHint || "h/l cols \xB7 j/k tasks \xB7 drag tasks \xB7 d daemon \xB7 r reload \xB7 q quit";
|
|
56967
57118
|
const width = termWidth();
|
|
56968
|
-
const leftWidth = Math.min(Math.max(
|
|
56969
|
-
const
|
|
56970
|
-
const
|
|
57119
|
+
const leftWidth = Math.min(Math.max(34, Math.floor(width * 0.46)), width);
|
|
57120
|
+
const daemonWidth = Math.min(16, Math.max(0, width - leftWidth));
|
|
57121
|
+
const rightWidth = Math.max(0, width - leftWidth - daemonWidth);
|
|
57122
|
+
const left = pad(` \u25C6 KANDOWN${tmuxHint}${versionTag} ${title}`, leftWidth);
|
|
57123
|
+
const daemon = pad(daemonLabel, daemonWidth);
|
|
56971
57124
|
const right = truncate(hint, rightWidth).padStart(rightWidth, " ");
|
|
56972
57125
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Box_default, { marginBottom: 1, children: [
|
|
56973
57126
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { bold: true, color: "cyan", children: left }),
|
|
57127
|
+
daemonWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: daemonStatus.running ? "green" : "yellow", bold: true, children: daemon }),
|
|
56974
57128
|
rightWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "gray", dimColor: true, children: right })
|
|
56975
57129
|
] });
|
|
56976
57130
|
}
|
|
56977
|
-
function StatusBar({ message, task }) {
|
|
56978
|
-
|
|
56979
|
-
|
|
56980
|
-
|
|
56981
|
-
|
|
56982
|
-
|
|
56983
|
-
task.
|
|
56984
|
-
|
|
56985
|
-
|
|
56986
|
-
|
|
56987
|
-
|
|
57131
|
+
function StatusBar({ message, task, daemonStatus }) {
|
|
57132
|
+
const daemonText = daemonStatus.running && daemonStatus.metadata ? `web daemon ON \xB7 ${daemonStatus.metadata.url}` : "web daemon OFF \xB7 press d to start";
|
|
57133
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Box_default, { marginTop: 1, flexDirection: "column", children: [
|
|
57134
|
+
message ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Text, { color: "yellow", bold: true, children: [
|
|
57135
|
+
" \u2726 ",
|
|
57136
|
+
message
|
|
57137
|
+
] }) : task ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Text, { color: "gray", children: [
|
|
57138
|
+
" ",
|
|
57139
|
+
"\u25C6 ",
|
|
57140
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "yellow", bold: true, children: task.id }),
|
|
57141
|
+
task.progress ? ` checklist ${task.progress.done}/${task.progress.total}` : "",
|
|
57142
|
+
" ",
|
|
57143
|
+
task.checked ? "\u2713 done" : "\u25CB open"
|
|
57144
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "gray", children: " " }),
|
|
57145
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Text, { color: daemonStatus.running ? "green" : "yellow", dimColor: !daemonStatus.running, children: [
|
|
57146
|
+
" ",
|
|
57147
|
+
daemonStatus.running ? "\u25CF" : "\u25CB",
|
|
57148
|
+
" ",
|
|
57149
|
+
daemonText
|
|
57150
|
+
] })
|
|
57151
|
+
] });
|
|
56988
57152
|
}
|
|
56989
57153
|
function TaskDetail({ task, taskId, scrollOffset }) {
|
|
56990
57154
|
const fm = task.frontmatter;
|
|
@@ -57049,6 +57213,9 @@ function Board({ kandownDir, version }) {
|
|
|
57049
57213
|
const [moveTargetCol, setMoveTargetCol] = (0, import_react37.useState)(0);
|
|
57050
57214
|
const [mousePress, setMousePress] = (0, import_react37.useState)(null);
|
|
57051
57215
|
const [taskDrag, setTaskDrag] = (0, import_react37.useState)(null);
|
|
57216
|
+
const [daemonStatus, setDaemonStatus] = (0, import_react37.useState)({ running: false, metadata: null });
|
|
57217
|
+
const [daemonBusy, setDaemonBusy] = (0, import_react37.useState)(false);
|
|
57218
|
+
const [preferredDaemonPort, setPreferredDaemonPort] = (0, import_react37.useState)(null);
|
|
57052
57219
|
const [installedAgents, setInstalledAgents] = (0, import_react37.useState)([]);
|
|
57053
57220
|
const inTmux = isInTmux();
|
|
57054
57221
|
const layoutRef = (0, import_react37.useRef)({
|
|
@@ -57122,6 +57289,42 @@ function Board({ kandownDir, version }) {
|
|
|
57122
57289
|
setStatusMsg("Board reloaded");
|
|
57123
57290
|
setTimeout(() => setStatusMsg(""), 1500);
|
|
57124
57291
|
}, [kandownDir, updateLayout]);
|
|
57292
|
+
const refreshDaemonStatus = (0, import_react37.useCallback)(async () => {
|
|
57293
|
+
const next = await getDaemonStatus(kandownDir);
|
|
57294
|
+
setDaemonStatus(next);
|
|
57295
|
+
if (next.running && next.metadata) setPreferredDaemonPort(next.metadata.port);
|
|
57296
|
+
}, [kandownDir]);
|
|
57297
|
+
(0, import_react37.useEffect)(() => {
|
|
57298
|
+
void refreshDaemonStatus();
|
|
57299
|
+
const timer = setInterval(() => {
|
|
57300
|
+
void refreshDaemonStatus();
|
|
57301
|
+
}, 2e3);
|
|
57302
|
+
return () => clearInterval(timer);
|
|
57303
|
+
}, [refreshDaemonStatus]);
|
|
57304
|
+
const toggleDaemon = (0, import_react37.useCallback)(async () => {
|
|
57305
|
+
if (daemonBusy) return;
|
|
57306
|
+
setDaemonBusy(true);
|
|
57307
|
+
try {
|
|
57308
|
+
const current = await getDaemonStatus(kandownDir);
|
|
57309
|
+
if (current.running) {
|
|
57310
|
+
if (current.metadata) setPreferredDaemonPort(current.metadata.port);
|
|
57311
|
+
await stopProjectDaemon(kandownDir);
|
|
57312
|
+
const next = await getDaemonStatus(kandownDir);
|
|
57313
|
+
setDaemonStatus(next);
|
|
57314
|
+
setStatusMsg("Web daemon stopped");
|
|
57315
|
+
} else {
|
|
57316
|
+
const next = await startProjectDaemon(kandownDir, preferredDaemonPort);
|
|
57317
|
+
setDaemonStatus(next);
|
|
57318
|
+
if (next.running && next.metadata) setPreferredDaemonPort(next.metadata.port);
|
|
57319
|
+
setStatusMsg(next.running ? "Web daemon started" : "Web daemon failed to start");
|
|
57320
|
+
}
|
|
57321
|
+
} catch (error) {
|
|
57322
|
+
setStatusMsg(`Daemon error: ${error instanceof Error ? error.message : String(error)}`);
|
|
57323
|
+
} finally {
|
|
57324
|
+
setDaemonBusy(false);
|
|
57325
|
+
setTimeout(() => setStatusMsg(""), 2500);
|
|
57326
|
+
}
|
|
57327
|
+
}, [daemonBusy, kandownDir, preferredDaemonPort]);
|
|
57125
57328
|
const getFocusedTask = (0, import_react37.useCallback)(() => {
|
|
57126
57329
|
if (!board) return null;
|
|
57127
57330
|
const col = board.columns[colIndex];
|
|
@@ -57370,6 +57573,10 @@ function Board({ kandownDir, version }) {
|
|
|
57370
57573
|
reloadBoard();
|
|
57371
57574
|
return;
|
|
57372
57575
|
}
|
|
57576
|
+
if (input === "d") {
|
|
57577
|
+
void toggleDaemon();
|
|
57578
|
+
return;
|
|
57579
|
+
}
|
|
57373
57580
|
if (input === "l" || key.rightArrow) {
|
|
57374
57581
|
const maxCol = (board?.columns.length ?? 1) - 1;
|
|
57375
57582
|
setColIndex((c) => Math.min(c + 1, maxCol));
|
|
@@ -57548,7 +57755,7 @@ function Board({ kandownDir, version }) {
|
|
|
57548
57755
|
if (mode === "agent-picker") {
|
|
57549
57756
|
const taskId = detailTaskId || focusedTask?.id || "";
|
|
57550
57757
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Box_default, { flexDirection: "column", children: [
|
|
57551
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BoardHeader, { title: board.title, inTmux, version }),
|
|
57758
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BoardHeader, { title: board.title, inTmux, version, daemonStatus, daemonBusy }),
|
|
57552
57759
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
57553
57760
|
AgentPicker,
|
|
57554
57761
|
{
|
|
@@ -57574,7 +57781,7 @@ function Board({ kandownDir, version }) {
|
|
|
57574
57781
|
] });
|
|
57575
57782
|
}
|
|
57576
57783
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Box_default, { flexDirection: "column", children: [
|
|
57577
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BoardHeader, { title: board.title, inTmux, modeHint, version }),
|
|
57784
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BoardHeader, { title: board.title, inTmux, modeHint, version, daemonStatus, daemonBusy }),
|
|
57578
57785
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Box_default, { flexDirection: "row", children: board.columns.map((col, cIdx) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
57579
57786
|
KanbanColumn,
|
|
57580
57787
|
{
|
|
@@ -57598,7 +57805,7 @@ function Board({ kandownDir, version }) {
|
|
|
57598
57805
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "yellow", bold: true, children: mode === "dragging" ? "release over a column" : "click \u2193" }),
|
|
57599
57806
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "gray", children: mode === "dragging" ? " \xB7 Esc cancel" : " or \u2190/\u2192 + Enter" })
|
|
57600
57807
|
] }) }),
|
|
57601
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(StatusBar, { message: statusMsg, task: focusedTask })
|
|
57808
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(StatusBar, { message: statusMsg, task: focusedTask, daemonStatus })
|
|
57602
57809
|
] });
|
|
57603
57810
|
}
|
|
57604
57811
|
|