kandown 0.12.0 → 0.13.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/bin/tui.js CHANGED
@@ -54431,6 +54431,7 @@ function taskToBoardTask(task) {
54431
54431
  priority: normalizePriority(frontmatter.priority),
54432
54432
  ownerType: normalizeOwnerType(frontmatter.ownerType),
54433
54433
  progress: total > 0 ? { done, total } : null,
54434
+ dependsOn: Array.isArray(frontmatter.depends_on) ? frontmatter.depends_on.filter((d) => typeof d === "string" && d.trim().length > 0) : [],
54434
54435
  frontmatter: metadata
54435
54436
  };
54436
54437
  }
@@ -56684,7 +56685,7 @@ ${taskPrompt}`;
56684
56685
  ---
56685
56686
 
56686
56687
  ${taskPrompt}`;
56687
- return ["gemini", "-p", combined];
56688
+ return ["gemini", "--prompt-interactive", combined];
56688
56689
  }
56689
56690
  },
56690
56691
  {
@@ -56723,8 +56724,13 @@ ${taskPrompt}`;
56723
56724
  bin: "opencode",
56724
56725
  description: "SST AI coding TUI",
56725
56726
  interactive: true,
56726
- buildCommand: ({ taskPrompt }) => {
56727
- return ["opencode"];
56727
+ buildCommand: ({ systemPrompt, taskPrompt }) => {
56728
+ const combined = `${systemPrompt}
56729
+
56730
+ ---
56731
+
56732
+ ${taskPrompt}`;
56733
+ return ["opencode", "--prompt", combined];
56728
56734
  }
56729
56735
  }
56730
56736
  ];
@@ -56807,7 +56813,12 @@ ${taskPrompt}`, "utf8");
56807
56813
  }
56808
56814
  if (isInTmux()) {
56809
56815
  const shellCmd = buildShellCmd(binary, args);
56810
- execSync(`tmux split-window -h -p 50 ${shellescape(shellCmd)}`, {
56816
+ const envPrefix = [
56817
+ `KANDOWN_CONTEXT_FILE=${shellescape(contextFile)}`,
56818
+ `KANDOWN_TASK_ID=${shellescape(taskId)}`,
56819
+ `KANDOWN_DIR=${shellescape(kandownDir)}`
56820
+ ].join(" ");
56821
+ execSync(`tmux split-window -h -p 50 ${shellescape(`env ${envPrefix} ${shellCmd}`)}`, {
56811
56822
  stdio: "inherit"
56812
56823
  });
56813
56824
  } else {
@@ -57041,6 +57052,14 @@ function SingleTaskRow({ task, focused, dragging, colWidth }) {
57041
57052
  " "
57042
57053
  ] }),
57043
57054
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: dragging ? "yellow" : focused ? "cyan" : "yellow", bold: focused || dragging, children: idStr }),
57055
+ task.dependsOn && task.dependsOn.length > 0 && // 📖 TUI equivalent of the web card's `↩N` chip. Surfaces blocked
57056
+ // work inline so the user doesn't try to move a task they can't
57057
+ // unblock without a different upstream task first.
57058
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Text, { color: "yellow", children: [
57059
+ " ",
57060
+ "\u21AA",
57061
+ task.dependsOn.length
57062
+ ] }),
57044
57063
  tag && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Text, { color: focused ? "white" : "magenta", bold: true, children: [
57045
57064
  " ",
57046
57065
  tag
@@ -57155,7 +57174,7 @@ function BoardHeader({ title, inTmux, modeHint, version, daemonStatus, daemonBus
57155
57174
  const tmuxHint = inTmux ? " tmux" : "";
57156
57175
  const versionTag = version ? ` v${version}` : "";
57157
57176
  const daemonLabel = daemonBusy ? "\u25CC daemon\u2026" : daemonStatus.running ? `\u25CF web ${daemonStatus.metadata?.port ?? ""}` : "\u25CB web off";
57158
- const hint = modeHint || "h/l cols \xB7 j/k tasks \xB7 drag tasks \xB7 d daemon \xB7 r reload \xB7 q quit";
57177
+ const hint = modeHint || "h/l cols \xB7 j/k tasks \xB7 drag tasks \xB7 a agent \xB7 g send-hook \xB7 d daemon \xB7 r reload \xB7 q quit";
57159
57178
  const width = termWidth();
57160
57179
  const leftWidth = Math.min(Math.max(34, Math.floor(width * 0.46)), width);
57161
57180
  const daemonWidth = Math.min(16, Math.max(0, width - leftWidth));
@@ -57211,6 +57230,14 @@ function TaskDetail({ task, taskId, scrollOffset }) {
57211
57230
  fm.assignee ? ` assignee: ${fm.assignee}` : "",
57212
57231
  fm.due ? ` due: ${fm.due}` : ""
57213
57232
  ] }) }),
57233
+ Array.isArray(fm.depends_on) && fm.depends_on.length > 0 && // 📖 TUI equivalent of the web Drawer's dependency chips. Lists raw
57234
+ // ids — the user can `cat tasks/<id>.md` to inspect any one. We don't
57235
+ // resolve live status here (read-only context) but the move gate in
57236
+ // the TUI blocks the user from moving into Done until they're clear.
57237
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Box_default, { marginBottom: 1, children: [
57238
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "gray", children: "depends on: " }),
57239
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "yellow", children: fm.depends_on.join(", ") })
57240
+ ] }),
57214
57241
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "gray", children: "\u2500".repeat(termWidth() - 4) }),
57215
57242
  visibleLines.map((line, idx) => {
57216
57243
  const isH = RE_HEADER.test(line);
@@ -57366,6 +57393,61 @@ function Board({ kandownDir, version }) {
57366
57393
  setTimeout(() => setStatusMsg(""), 2500);
57367
57394
  }
57368
57395
  }, [daemonBusy, kandownDir, preferredDaemonPort]);
57396
+ const tryMoveWithGate = (0, import_react37.useCallback)((taskId, targetCol) => {
57397
+ if (!board) return false;
57398
+ const cfg = loadConfig(kandownDir);
57399
+ const cols = cfg.board.columns;
57400
+ const terminalLower = (cols[cols.length - 1] || "Done").toLowerCase();
57401
+ const isTerminal = targetCol.toLowerCase() === terminalLower;
57402
+ if (!isTerminal) return true;
57403
+ const resolved = /* @__PURE__ */ new Map();
57404
+ for (const col of board.columns) {
57405
+ for (const t of col.tasks) {
57406
+ const isArch = t.frontmatter && (t.frontmatter.archived === true || t.frontmatter.archived === "true");
57407
+ resolved.set(t.id, isArch || col.name.toLowerCase() === terminalLower);
57408
+ }
57409
+ }
57410
+ const movingTask = board.columns.flatMap((c) => c.tasks).find((t) => t.id === taskId);
57411
+ if (!movingTask) return true;
57412
+ const deps = Array.isArray(movingTask.dependsOn) ? movingTask.dependsOn : [];
57413
+ const blocked = [];
57414
+ for (const dep of deps) {
57415
+ if (typeof dep !== "string" || !dep.trim() || dep === taskId) continue;
57416
+ const r = resolved.get(dep);
57417
+ if (!r) blocked.push(dep);
57418
+ }
57419
+ if (blocked.length > 0) {
57420
+ const list = blocked.length === 1 ? blocked[0] : `${blocked.slice(0, -1).join(", ")} and ${blocked[blocked.length - 1]}`;
57421
+ setStatusMsg(`Blocked: ${taskId} \u2190 ${list}`);
57422
+ setTimeout(() => setStatusMsg(""), 3500);
57423
+ return false;
57424
+ }
57425
+ return true;
57426
+ }, [board, kandownDir]);
57427
+ const sendTaskToAgentHook = (0, import_react37.useCallback)(async (taskId) => {
57428
+ const status = await getDaemonStatus(kandownDir);
57429
+ if (!status.running || !status.metadata) {
57430
+ setStatusMsg("Web daemon not running (press d to start)");
57431
+ setTimeout(() => setStatusMsg(""), 2500);
57432
+ return;
57433
+ }
57434
+ try {
57435
+ const res = await fetch(`http://127.0.0.1:${status.metadata.port}/api/tasks/${encodeURIComponent(taskId)}/agent`, {
57436
+ method: "POST",
57437
+ signal: AbortSignal.timeout(8e3)
57438
+ });
57439
+ if (res.ok) {
57440
+ setStatusMsg(`Sent ${taskId} to agent hook`);
57441
+ } else {
57442
+ const body = await res.text().catch(() => "");
57443
+ setStatusMsg(`Agent hook: ${res.status}${body ? " \u2014 " + body.slice(0, 60) : ""}`);
57444
+ }
57445
+ } catch (error) {
57446
+ setStatusMsg(`Agent hook failed: ${error instanceof Error ? error.message : String(error)}`);
57447
+ } finally {
57448
+ setTimeout(() => setStatusMsg(""), 3e3);
57449
+ }
57450
+ }, [kandownDir]);
57369
57451
  const getFocusedTask = (0, import_react37.useCallback)(() => {
57370
57452
  if (!board) return null;
57371
57453
  const col = board.columns[colIndex];
@@ -57506,6 +57588,11 @@ function Board({ kandownDir, version }) {
57506
57588
  if (y === placeholderY) {
57507
57589
  const targetColName = col.name;
57508
57590
  if (moveTaskId) {
57591
+ if (!tryMoveWithGate(moveTaskId, targetColName)) {
57592
+ setMoveTaskId(null);
57593
+ setMode("browse");
57594
+ return;
57595
+ }
57509
57596
  moveTaskToColumn(kandownDir, moveTaskId, targetColName);
57510
57597
  const loaded = readBoard(kandownDir);
57511
57598
  setBoard(loaded);
@@ -57576,6 +57663,12 @@ function Board({ kandownDir, version }) {
57576
57663
  if (targetCol >= 0 && targetCol !== taskDrag.sourceCol) {
57577
57664
  const targetColName = board.columns[targetCol]?.name;
57578
57665
  if (targetColName) {
57666
+ if (!tryMoveWithGate(taskDrag.taskId, targetColName)) {
57667
+ setTaskDrag(null);
57668
+ setMousePress(null);
57669
+ setMode("browse");
57670
+ return;
57671
+ }
57579
57672
  moveTaskToColumn(kandownDir, taskDrag.taskId, targetColName);
57580
57673
  const loaded = readBoard(kandownDir);
57581
57674
  setBoard(loaded);
@@ -57664,6 +57757,12 @@ function Board({ kandownDir, version }) {
57664
57757
  setMode("agent-picker");
57665
57758
  return;
57666
57759
  }
57760
+ if (input === "g") {
57761
+ const task = getFocusedTask();
57762
+ if (!task) return;
57763
+ void sendTaskToAgentHook(task.id);
57764
+ return;
57765
+ }
57667
57766
  }
57668
57767
  if (mode === "context-menu") {
57669
57768
  if (key.escape || input === "q") {
@@ -57731,6 +57830,11 @@ function Board({ kandownDir, version }) {
57731
57830
  if (!board || !moveTaskId) return;
57732
57831
  const name = board.columns[moveTargetCol]?.name;
57733
57832
  if (name) {
57833
+ if (!tryMoveWithGate(moveTaskId, name)) {
57834
+ setMoveTaskId(null);
57835
+ setMode("browse");
57836
+ return;
57837
+ }
57734
57838
  moveTaskToColumn(kandownDir, moveTaskId, name);
57735
57839
  const loaded = readBoard(kandownDir);
57736
57840
  setBoard(loaded);
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/dist/favicon.svg CHANGED
@@ -1,7 +1,25 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 150">
2
- <!-- SVG created with Arrow, by QuiverAI (https://quiver.ai) -->
3
- <style type="text/css">.cls-0 {fill:#161616;}</style>
4
- <path class="cls-0" d="m57.6 64.6 0.1-0.1v-31.3c-0.1-3.5-2.7-5.6-5.7-5.6h-16.3v59.6c0.2-1.3 0.9-2.6 1.7-3.2l20.2-19.4z"/>
5
- <path class="cls-0" d="m87.6 43.8c-3.4 0.1-7.1 1.3-9.9 3.7l-38.5 38.7c-2.1 2.1-3.4 4.8-3.5 7.5v26.7l77.5-76.4v-0.2h-25.6z"/>
6
- <path class="cls-0" d="m108.1 96.4-6 5-22.4-20.8-14.6 14.2 21.1 21.4-5 4.1c-0.6 0.5-0.3 0.9 0.2 0.9h27.6c0.4 0 0.7-0.4 0.7-0.6v-24.8c0-0.7-1.1-0.3-1.6 0.3v0.3z"/>
7
- </svg>
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!--
3
+ @file public/favicon.svg
4
+ @description Kandown brand mark — vector favicon.
5
+ 📖 Hand-traced from kandownlogo.png. Optimised for 16–256px rendering.
6
+ Colors: dark surface #0a0a0a, K strokes #ffffff, lime accent #cef867.
7
+ The lime slash is a parallelogram that slices from top-right to bottom-left,
8
+ crossing the K. Z-order is: surface → K strokes → lime slash.
9
+ -->
10
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" role="img" aria-label="Kandown">
11
+ <title>Kandown</title>
12
+ <rect width="256" height="256" rx="48" ry="48" fill="#0a0a0a"/>
13
+
14
+ <!-- K vertical bar (left stem) -->
15
+ <rect x="50" y="36" width="34" height="184" rx="3" ry="3" fill="#ffffff"/>
16
+
17
+ <!-- K upper diagonal: middle-left → top-right -->
18
+ <path d="M 84 128 L 200 36 L 222 36 L 84 152 Z" fill="#ffffff"/>
19
+
20
+ <!-- K lower diagonal: middle-left → bottom-right (with the small flag/notch on the right) -->
21
+ <path d="M 84 128 L 200 220 L 222 220 L 188 186 L 200 174 L 84 132 Z" fill="#ffffff"/>
22
+
23
+ <!-- Lime diagonal slash: thick parallelogram from top-right to bottom-left -->
24
+ <path d="M 232 22 L 174 22 L 24 172 L 24 230 Z" fill="#cef867"/>
25
+ </svg>