@kaban-board/cli 0.2.4 → 0.2.6

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.
Files changed (2) hide show
  1. package/dist/index.js +138 -115
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { createRequire } from "node:module";
4
+ import { createRequire as createRequire2 } from "node:module";
5
5
  import { Command as Command12 } from "commander";
6
6
 
7
7
  // src/commands/add.ts
@@ -802,6 +802,25 @@ import {
802
802
  ReadResourceRequestSchema
803
803
  } from "@modelcontextprotocol/sdk/types.js";
804
804
  import { Command as Command6 } from "commander";
805
+ var mcpHelpers = {
806
+ getParam(args, primary, alias) {
807
+ if (!args)
808
+ return;
809
+ const primaryVal = args[primary];
810
+ if (typeof primaryVal === "string" && primaryVal.trim())
811
+ return primaryVal;
812
+ const aliasVal = args[alias];
813
+ if (typeof aliasVal === "string" && aliasVal.trim())
814
+ return aliasVal;
815
+ return;
816
+ },
817
+ errorResponse(message) {
818
+ return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
819
+ },
820
+ jsonResponse(data) {
821
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
822
+ }
823
+ };
805
824
  function getKabanPaths2(basePath) {
806
825
  const base = basePath ?? process.cwd();
807
826
  const kabanDir = join4(base, ".kaban");
@@ -867,8 +886,10 @@ async function startMcpServer(workingDirectory) {
867
886
  description: "Get a task by its ID",
868
887
  inputSchema: {
869
888
  type: "object",
870
- properties: { id: { type: "string", description: "Task ID (ULID)" } },
871
- required: ["id"]
889
+ properties: {
890
+ id: { type: "string", description: "Task ID (ULID)" },
891
+ taskId: { type: "string", description: "Task ID (ULID) - alias for 'id'" }
892
+ }
872
893
  }
873
894
  },
874
895
  {
@@ -890,10 +911,11 @@ async function startMcpServer(workingDirectory) {
890
911
  type: "object",
891
912
  properties: {
892
913
  id: { type: "string", description: "Task ID (ULID)" },
914
+ taskId: { type: "string", description: "Task ID (ULID) - alias for 'id'" },
893
915
  columnId: { type: "string", description: "Target column ID" },
916
+ column: { type: "string", description: "Target column ID - alias for 'columnId'" },
894
917
  force: { type: "boolean", description: "Force move even if WIP limit exceeded" }
895
- },
896
- required: ["id", "columnId"]
918
+ }
897
919
  }
898
920
  },
899
921
  {
@@ -903,6 +925,7 @@ async function startMcpServer(workingDirectory) {
903
925
  type: "object",
904
926
  properties: {
905
927
  id: { type: "string", description: "Task ID (ULID)" },
928
+ taskId: { type: "string", description: "Task ID (ULID) - alias for 'id'" },
906
929
  title: { type: "string", description: "New task title" },
907
930
  description: { type: ["string", "null"], description: "New task description" },
908
931
  assignedTo: { type: ["string", "null"], description: "Assigned agent name" },
@@ -916,8 +939,7 @@ async function startMcpServer(workingDirectory) {
916
939
  type: "number",
917
940
  description: "Expected version for optimistic locking"
918
941
  }
919
- },
920
- required: ["id"]
942
+ }
921
943
  }
922
944
  },
923
945
  {
@@ -925,8 +947,10 @@ async function startMcpServer(workingDirectory) {
925
947
  description: "Delete a task",
926
948
  inputSchema: {
927
949
  type: "object",
928
- properties: { id: { type: "string", description: "Task ID (ULID)" } },
929
- required: ["id"]
950
+ properties: {
951
+ id: { type: "string", description: "Task ID (ULID)" },
952
+ taskId: { type: "string", description: "Task ID (ULID) - alias for 'id'" }
953
+ }
930
954
  }
931
955
  },
932
956
  {
@@ -934,8 +958,13 @@ async function startMcpServer(workingDirectory) {
934
958
  description: "Mark a task as completed (move to terminal column)",
935
959
  inputSchema: {
936
960
  type: "object",
937
- properties: { id: { type: "string", description: "Task ID (ULID) or partial ID" } },
938
- required: ["id"]
961
+ properties: {
962
+ id: { type: "string", description: "Task ID (ULID) or partial ID" },
963
+ taskId: {
964
+ type: "string",
965
+ description: "Task ID (ULID) or partial ID - alias for 'id'"
966
+ }
967
+ }
939
968
  }
940
969
  },
941
970
  {
@@ -945,6 +974,7 @@ async function startMcpServer(workingDirectory) {
945
974
  }
946
975
  ]
947
976
  }));
977
+ const { getParam, errorResponse, jsonResponse } = mcpHelpers;
948
978
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
949
979
  const { name, arguments: args } = request.params;
950
980
  try {
@@ -953,15 +983,7 @@ async function startMcpServer(workingDirectory) {
953
983
  const targetPath = basePath ?? workingDirectory;
954
984
  const { kabanDir, dbPath, configPath } = getKabanPaths2(targetPath);
955
985
  if (existsSync5(dbPath)) {
956
- return {
957
- content: [
958
- {
959
- type: "text",
960
- text: JSON.stringify({ error: "Board already exists in this directory" })
961
- }
962
- ],
963
- isError: true
964
- };
986
+ return errorResponse("Board already exists in this directory");
965
987
  }
966
988
  mkdirSync2(kabanDir, { recursive: true });
967
989
  const config = {
@@ -973,70 +995,77 @@ async function startMcpServer(workingDirectory) {
973
995
  await initializeSchema2(db);
974
996
  const boardService2 = new BoardService3(db);
975
997
  await boardService2.initializeBoard(config);
976
- return {
977
- content: [
978
- {
979
- type: "text",
980
- text: JSON.stringify({
981
- success: true,
982
- board: boardName,
983
- paths: { database: dbPath, config: configPath }
984
- }, null, 2)
985
- }
986
- ]
987
- };
998
+ return jsonResponse({
999
+ success: true,
1000
+ board: boardName,
1001
+ paths: { database: dbPath, config: configPath }
1002
+ });
988
1003
  }
989
1004
  const { taskService, boardService } = await createContext(workingDirectory);
1005
+ const taskArgs = args;
1006
+ const taskId = getParam(taskArgs, "id", "taskId");
990
1007
  switch (name) {
991
1008
  case "kaban_add_task": {
1009
+ const addArgs = args;
1010
+ const title = addArgs?.title;
1011
+ if (typeof title !== "string" || !title.trim()) {
1012
+ return errorResponse("Title required (non-empty string)");
1013
+ }
992
1014
  const task = await taskService.addTask(args);
993
- return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }] };
1015
+ return jsonResponse(task);
994
1016
  }
995
1017
  case "kaban_get_task": {
996
- const task = await taskService.getTask(args.id);
1018
+ if (!taskId)
1019
+ return errorResponse("Task ID required (use 'id' or 'taskId')");
1020
+ const task = await taskService.getTask(taskId);
997
1021
  if (!task)
998
- return {
999
- content: [{ type: "text", text: JSON.stringify({ error: "Task not found" }) }]
1000
- };
1001
- return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }] };
1022
+ return errorResponse("Task not found");
1023
+ return jsonResponse(task);
1002
1024
  }
1003
1025
  case "kaban_list_tasks": {
1004
1026
  const tasks = await taskService.listTasks(args);
1005
- return { content: [{ type: "text", text: JSON.stringify(tasks, null, 2) }] };
1027
+ return jsonResponse(tasks);
1006
1028
  }
1007
1029
  case "kaban_move_task": {
1008
- const { id, columnId, force } = args;
1009
- const task = await taskService.moveTask(id, columnId, { force });
1010
- return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }] };
1030
+ if (!taskId)
1031
+ return errorResponse("Task ID required (use 'id' or 'taskId')");
1032
+ const targetColumn = getParam(taskArgs, "columnId", "column");
1033
+ if (!targetColumn)
1034
+ return errorResponse("Column ID required (use 'columnId' or 'column')");
1035
+ const { force } = args ?? {};
1036
+ const task = await taskService.moveTask(taskId, targetColumn, { force });
1037
+ return jsonResponse(task);
1011
1038
  }
1012
1039
  case "kaban_update_task": {
1013
- const { id, expectedVersion, ...updates } = args;
1014
- const task = await taskService.updateTask(id, updates, expectedVersion);
1015
- return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }] };
1040
+ if (!taskId)
1041
+ return errorResponse("Task ID required (use 'id' or 'taskId')");
1042
+ const {
1043
+ taskId: _t,
1044
+ id: _i,
1045
+ expectedVersion,
1046
+ ...updates
1047
+ } = args ?? {};
1048
+ const task = await taskService.updateTask(taskId, updates, expectedVersion);
1049
+ return jsonResponse(task);
1016
1050
  }
1017
1051
  case "kaban_delete_task": {
1018
- await taskService.deleteTask(args.id);
1019
- return { content: [{ type: "text", text: JSON.stringify({ success: true }) }] };
1052
+ if (!taskId)
1053
+ return errorResponse("Task ID required (use 'id' or 'taskId')");
1054
+ await taskService.deleteTask(taskId);
1055
+ return jsonResponse({ success: true });
1020
1056
  }
1021
1057
  case "kaban_complete_task": {
1022
- const { id } = args;
1058
+ if (!taskId)
1059
+ return errorResponse("Task ID required (use 'id' or 'taskId')");
1023
1060
  const tasks = await taskService.listTasks();
1024
- const task = tasks.find((t) => t.id.startsWith(id));
1061
+ const task = tasks.find((t) => t.id.startsWith(taskId));
1025
1062
  if (!task)
1026
- return {
1027
- content: [
1028
- { type: "text", text: JSON.stringify({ error: `Task '${id}' not found` }) }
1029
- ]
1030
- };
1063
+ return errorResponse(`Task '${taskId}' not found`);
1031
1064
  const terminal = await boardService.getTerminalColumn();
1032
1065
  if (!terminal)
1033
- return {
1034
- content: [
1035
- { type: "text", text: JSON.stringify({ error: "No terminal column configured" }) }
1036
- ]
1037
- };
1066
+ return errorResponse("No terminal column configured");
1038
1067
  const completed = await taskService.moveTask(task.id, terminal.id);
1039
- return { content: [{ type: "text", text: JSON.stringify(completed, null, 2) }] };
1068
+ return jsonResponse(completed);
1040
1069
  }
1041
1070
  case "kaban_status": {
1042
1071
  const board = await boardService.getBoard();
@@ -1049,25 +1078,15 @@ async function startMcpServer(workingDirectory) {
1049
1078
  wipLimit: column.wipLimit,
1050
1079
  isTerminal: column.isTerminal
1051
1080
  }));
1052
- return {
1053
- content: [
1054
- {
1055
- type: "text",
1056
- text: JSON.stringify({
1057
- board: { name: board?.name ?? "Kaban Board" },
1058
- columns: columnStats,
1059
- blockedCount: tasks.filter((t) => t.blockedReason).length,
1060
- totalTasks: tasks.length
1061
- }, null, 2)
1062
- }
1063
- ]
1064
- };
1081
+ return jsonResponse({
1082
+ board: { name: board?.name ?? "Kaban Board" },
1083
+ columns: columnStats,
1084
+ blockedCount: tasks.filter((t) => t.blockedReason).length,
1085
+ totalTasks: tasks.length
1086
+ });
1065
1087
  }
1066
1088
  default:
1067
- return {
1068
- content: [{ type: "text", text: JSON.stringify({ error: `Unknown tool: ${name}` }) }],
1069
- isError: true
1070
- };
1089
+ return errorResponse(`Unknown tool: ${name}`);
1071
1090
  }
1072
1091
  } catch (error2) {
1073
1092
  const message = error2 instanceof Error ? error2.message : String(error2);
@@ -1704,63 +1723,67 @@ var syncCommand = new Command10("sync").description("Sync TodoWrite input to Kab
1704
1723
  import { spawn as spawn3, spawnSync } from "node:child_process";
1705
1724
  import { existsSync as existsSync7 } from "node:fs";
1706
1725
  import { dirname as dirname3, join as join5 } from "node:path";
1726
+ import { fileURLToPath } from "node:url";
1727
+ import { createRequire } from "node:module";
1707
1728
  import { Command as Command11 } from "commander";
1729
+ var __dirname2 = dirname3(fileURLToPath(import.meta.url));
1730
+ var require2 = createRequire(import.meta.url);
1731
+ function hasBun() {
1732
+ try {
1733
+ const result = spawnSync("bun", ["--version"], { stdio: "ignore" });
1734
+ return result.status === 0;
1735
+ } catch {
1736
+ return false;
1737
+ }
1738
+ }
1708
1739
  function findInPath(name) {
1709
1740
  const result = spawnSync("which", [name], { encoding: "utf-8" });
1710
1741
  return result.status === 0 ? result.stdout.trim() : null;
1711
1742
  }
1743
+ function resolveTuiPackage() {
1744
+ try {
1745
+ return require2.resolve("@kaban-board/tui");
1746
+ } catch {
1747
+ return null;
1748
+ }
1749
+ }
1712
1750
  function runBinary(path, args) {
1713
1751
  const child = spawn3(path, args, { stdio: "inherit", cwd: process.cwd() });
1714
1752
  child.on("exit", (code) => process.exit(code ?? 0));
1715
1753
  }
1716
- function runBunx(bunPath, args) {
1717
- let started = false;
1718
- const child = spawn3(bunPath, ["x", "@kaban-board/tui", ...args], {
1719
- stdio: "inherit",
1720
- cwd: process.cwd()
1721
- });
1722
- child.on("spawn", () => {
1723
- started = true;
1724
- });
1725
- child.on("error", () => {
1726
- if (!started)
1727
- showInstallError();
1728
- });
1729
- child.on("exit", (code) => process.exit(code ?? 0));
1730
- return true;
1731
- }
1732
- function showInstallError() {
1733
- console.error(`
1734
- Error: kaban-tui not found
1735
-
1736
- The TUI requires Bun runtime. Install with one of:
1737
-
1738
- # Homebrew (recommended)
1739
- brew install beshkenadze/tap/kaban-tui
1740
-
1741
- # Or install Bun, then run via bunx
1742
- curl -fsSL https://bun.sh/install | bash
1743
- bunx @kaban-board/tui
1744
- `);
1745
- process.exit(1);
1746
- }
1747
- var tuiCommand = new Command11("tui").description("Start interactive Terminal UI (requires Bun)").action(async () => {
1754
+ var tuiCommand = new Command11("tui").description("Start interactive Terminal UI").action(async () => {
1755
+ const cwd = process.cwd();
1748
1756
  const args = process.argv.slice(3);
1757
+ const useBun = hasBun();
1749
1758
  const siblingBinary = join5(dirname3(process.execPath), "kaban-tui");
1750
1759
  if (existsSync7(siblingBinary))
1751
1760
  return runBinary(siblingBinary, args);
1752
1761
  const pathBinary = findInPath("kaban-tui");
1753
1762
  if (pathBinary)
1754
1763
  return runBinary(pathBinary, args);
1755
- const bunPath = findInPath("bun");
1756
- if (bunPath)
1757
- return runBunx(bunPath, args);
1758
- showInstallError();
1764
+ const tuiDevEntry = join5(__dirname2, "../../../tui/src/index.ts");
1765
+ if (existsSync7(tuiDevEntry) && useBun) {
1766
+ const child2 = spawn3("bun", ["run", tuiDevEntry], { stdio: "inherit", cwd });
1767
+ child2.on("exit", (code) => process.exit(code ?? 0));
1768
+ return;
1769
+ }
1770
+ if (!useBun) {
1771
+ console.error("TUI requires Bun. Install: curl -fsSL https://bun.sh/install | bash");
1772
+ process.exit(1);
1773
+ }
1774
+ const tuiEntry = resolveTuiPackage();
1775
+ if (tuiEntry) {
1776
+ const child2 = spawn3("bun", [tuiEntry, ...args], { stdio: "inherit", cwd });
1777
+ child2.on("exit", (code) => process.exit(code ?? 0));
1778
+ return;
1779
+ }
1780
+ const child = spawn3("bun", ["x", "@kaban-board/tui", ...args], { stdio: "inherit", cwd });
1781
+ child.on("exit", (code) => process.exit(code ?? 0));
1759
1782
  });
1760
1783
 
1761
1784
  // src/index.ts
1762
- var require2 = createRequire(import.meta.url);
1763
- var pkg = require2("../package.json");
1785
+ var require3 = createRequire2(import.meta.url);
1786
+ var pkg = require3("../package.json");
1764
1787
  var program = new Command12;
1765
1788
  program.name("kaban").description("Terminal Kanban for AI Code Agents").version(pkg.version);
1766
1789
  program.addCommand(initCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaban-board/cli",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Terminal Kanban for AI Code Agents - CLI and MCP server",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,7 +18,8 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@clack/prompts": "^0.11.0",
21
- "@kaban-board/core": "0.1.3",
21
+ "@kaban-board/core": "0.2.4",
22
+ "@kaban-board/tui": "0.2.4",
22
23
  "@modelcontextprotocol/sdk": "^1.25.2",
23
24
  "chalk": "^5.6.2",
24
25
  "commander": "^12.0.0",