@kaban-board/cli 0.3.0 → 0.3.2

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 +926 -69
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  // src/index.ts
5
5
  import { createRequire as createRequire2 } from "module";
6
- import { Command as Command15 } from "commander";
6
+ import { Command as Command23 } from "commander";
7
7
 
8
8
  // src/commands/add.ts
9
9
  import { KabanError } from "@kaban-board/core";
@@ -21,11 +21,11 @@ async function getContext() {
21
21
  console.error("Error: No board found. Run 'kaban init' first");
22
22
  process.exit(1);
23
23
  }
24
- const db = await createDb(dbPath);
24
+ const db2 = await createDb(dbPath);
25
25
  const config = JSON.parse(readFileSync(configPath, "utf-8"));
26
- const boardService = new BoardService(db);
27
- const taskService = new TaskService(db, boardService);
28
- return { db, config, boardService, taskService };
26
+ const boardService = new BoardService(db2);
27
+ const taskService = new TaskService(db2, boardService);
28
+ return { db: db2, config, boardService, taskService };
29
29
  }
30
30
  function getKabanPaths() {
31
31
  const kabanDir = join(process.cwd(), ".kaban");
@@ -275,10 +275,200 @@ var resetCommand = new Command2("reset").description("Delete ALL tasks (destruct
275
275
  }
276
276
  });
277
277
 
278
- // src/commands/assign.ts
279
- import { KabanError as KabanError3 } from "@kaban-board/core";
278
+ // src/commands/audit.ts
279
+ import { AuditService, KabanError as KabanError3 } from "@kaban-board/core";
280
280
  import { Command as Command3 } from "commander";
281
- var assignCommand = new Command3("assign").description("Assign a task to an agent").argument("<id>", "Task ID (can be partial)").argument("[agent]", "Agent to assign (omit with --clear to unassign)").option("-c, --clear", "Unassign the task").option("-j, --json", "Output as JSON").action(async (id, agent, options) => {
281
+ function parseRelativeDate(input) {
282
+ const match = input.match(/^(\d+)([dwmh])$/);
283
+ if (!match) {
284
+ return new Date(input);
285
+ }
286
+ const [, numStr, unit] = match;
287
+ const num = parseInt(numStr, 10);
288
+ const now = new Date;
289
+ switch (unit) {
290
+ case "h":
291
+ return new Date(now.getTime() - num * 60 * 60 * 1000);
292
+ case "d":
293
+ return new Date(now.getTime() - num * 24 * 60 * 60 * 1000);
294
+ case "w":
295
+ return new Date(now.getTime() - num * 7 * 24 * 60 * 60 * 1000);
296
+ case "m":
297
+ return new Date(now.getTime() - num * 30 * 24 * 60 * 60 * 1000);
298
+ default:
299
+ return new Date(input);
300
+ }
301
+ }
302
+ function formatEntry(entry) {
303
+ const time = entry.timestamp.toISOString().slice(0, 19).replace("T", " ");
304
+ const actor = entry.actor ? `@${entry.actor}` : "";
305
+ const field = entry.fieldName ? `.${entry.fieldName}` : "";
306
+ let change = "";
307
+ if (entry.eventType === "UPDATE") {
308
+ change = `${entry.oldValue ?? "null"} -> ${entry.newValue ?? "null"}`;
309
+ } else if (entry.eventType === "CREATE") {
310
+ change = entry.newValue ?? "";
311
+ } else {
312
+ change = entry.oldValue ?? "";
313
+ }
314
+ return ` ${time} ${entry.eventType.padEnd(6)} ${entry.objectType}${field}
315
+ ${entry.objectId.slice(0, 8)} ${actor} ${change}`;
316
+ }
317
+ var auditCommand = new Command3("audit").description("View audit log history");
318
+ auditCommand.command("list").description("List recent audit entries").option("-l, --limit <n>", "Max entries", "50").option("-a, --actor <name>", "Filter by actor").option("-t, --type <type>", "Filter by object type (task|column|board)").option("-e, --event <type>", "Filter by event type (CREATE|UPDATE|DELETE)").option("--since <date>", "Filter from date (ISO 8601 or relative: 1d, 1w, 30d)").option("--until <date>", "Filter to date (ISO 8601 or relative: 1d, 1w, 30d)").option("-j, --json", "Output as JSON").action(async (options) => {
319
+ const json = options.json;
320
+ try {
321
+ const { db: db2 } = await getContext();
322
+ const auditService = new AuditService(db2);
323
+ const result = await auditService.getHistory({
324
+ limit: parseInt(options.limit, 10),
325
+ actor: options.actor,
326
+ objectType: options.type,
327
+ eventType: options.event,
328
+ since: options.since ? parseRelativeDate(options.since) : undefined,
329
+ until: options.until ? parseRelativeDate(options.until) : undefined
330
+ });
331
+ if (json) {
332
+ outputSuccess(result);
333
+ return;
334
+ }
335
+ console.log(`
336
+ Audit Log (${result.entries.length} of ${result.total})
337
+ `);
338
+ for (const entry of result.entries) {
339
+ console.log(formatEntry(entry));
340
+ }
341
+ if (result.hasMore)
342
+ console.log(`
343
+ ... more entries available`);
344
+ console.log();
345
+ } catch (error2) {
346
+ if (error2 instanceof KabanError3) {
347
+ if (json)
348
+ outputError(error2.code, error2.message);
349
+ console.error(`Error: ${error2.message}`);
350
+ process.exit(error2.code);
351
+ }
352
+ throw error2;
353
+ }
354
+ });
355
+ auditCommand.command("task <id>").description("View history for a specific task").option("-l, --limit <n>", "Max entries", "50").option("-j, --json", "Output as JSON").action(async (id, options) => {
356
+ const json = options.json;
357
+ try {
358
+ const { db: db2, taskService } = await getContext();
359
+ const task = await taskService.resolveTask(id);
360
+ if (!task) {
361
+ if (json)
362
+ outputError(2, `Task '${id}' not found`);
363
+ console.error(`Error: Task '${id}' not found`);
364
+ process.exit(2);
365
+ }
366
+ const auditService = new AuditService(db2);
367
+ const entries = await auditService.getTaskHistory(task.id, parseInt(options.limit, 10));
368
+ if (json) {
369
+ outputSuccess({ task: { id: task.id, title: task.title }, entries });
370
+ return;
371
+ }
372
+ console.log(`
373
+ History for [${task.id.slice(0, 8)}] "${task.title}"
374
+ `);
375
+ for (const entry of entries) {
376
+ const time = entry.timestamp.toISOString().slice(0, 19).replace("T", " ");
377
+ const actor = entry.actor ? `@${entry.actor}` : "";
378
+ if (entry.eventType === "CREATE") {
379
+ console.log(` ${time} CREATED ${actor}`);
380
+ } else if (entry.eventType === "DELETE") {
381
+ console.log(` ${time} DELETED ${actor}`);
382
+ } else {
383
+ const field = entry.fieldName ?? "?";
384
+ console.log(` ${time} ${field}: ${entry.oldValue ?? "null"} -> ${entry.newValue ?? "null"} ${actor}`);
385
+ }
386
+ }
387
+ console.log();
388
+ } catch (error2) {
389
+ if (error2 instanceof KabanError3) {
390
+ if (json)
391
+ outputError(error2.code, error2.message);
392
+ console.error(`Error: ${error2.message}`);
393
+ process.exit(error2.code);
394
+ }
395
+ throw error2;
396
+ }
397
+ });
398
+ auditCommand.command("stats").description("Show audit statistics").option("-j, --json", "Output as JSON").action(async (options) => {
399
+ const json = options.json;
400
+ try {
401
+ const { db: db2 } = await getContext();
402
+ const auditService = new AuditService(db2);
403
+ const stats = await auditService.getStats();
404
+ if (json) {
405
+ outputSuccess(stats);
406
+ return;
407
+ }
408
+ console.log(`
409
+ Audit Statistics
410
+ `);
411
+ console.log(` Total entries: ${stats.totalEntries}`);
412
+ console.log(`
413
+ By Event Type:`);
414
+ for (const [type, count] of Object.entries(stats.byEventType)) {
415
+ console.log(` ${type}: ${count}`);
416
+ }
417
+ console.log(`
418
+ By Object Type:`);
419
+ for (const [type, count] of Object.entries(stats.byObjectType)) {
420
+ console.log(` ${type}: ${count}`);
421
+ }
422
+ if (stats.recentActors.length > 0) {
423
+ console.log(`
424
+ Recent Actors:`);
425
+ for (const actor of stats.recentActors) {
426
+ console.log(` ${actor}`);
427
+ }
428
+ }
429
+ console.log();
430
+ } catch (error2) {
431
+ if (error2 instanceof KabanError3) {
432
+ if (json)
433
+ outputError(error2.code, error2.message);
434
+ console.error(`Error: ${error2.message}`);
435
+ process.exit(error2.code);
436
+ }
437
+ throw error2;
438
+ }
439
+ });
440
+ auditCommand.command("actor <name>").description("View changes by a specific actor").option("-l, --limit <n>", "Max entries", "50").option("-j, --json", "Output as JSON").action(async (name, options) => {
441
+ const json = options.json;
442
+ try {
443
+ const { db: db2 } = await getContext();
444
+ const auditService = new AuditService(db2);
445
+ const entries = await auditService.getChangesByActor(name, parseInt(options.limit, 10));
446
+ if (json) {
447
+ outputSuccess({ actor: name, entries, count: entries.length });
448
+ return;
449
+ }
450
+ console.log(`
451
+ Changes by @${name} (${entries.length} entries)
452
+ `);
453
+ for (const entry of entries) {
454
+ console.log(formatEntry(entry));
455
+ }
456
+ console.log();
457
+ } catch (error2) {
458
+ if (error2 instanceof KabanError3) {
459
+ if (json)
460
+ outputError(error2.code, error2.message);
461
+ console.error(`Error: ${error2.message}`);
462
+ process.exit(error2.code);
463
+ }
464
+ throw error2;
465
+ }
466
+ });
467
+
468
+ // src/commands/assign.ts
469
+ import { KabanError as KabanError4 } from "@kaban-board/core";
470
+ import { Command as Command4 } from "commander";
471
+ var assignCommand = new Command4("assign").description("Assign a task to an agent").argument("<id>", "Task ID (can be partial)").argument("[agent]", "Agent to assign (omit with --clear to unassign)").option("-c, --clear", "Unassign the task").option("-j, --json", "Output as JSON").action(async (id, agent, options) => {
282
472
  const json = options.json;
283
473
  try {
284
474
  const { taskService } = await getContext();
@@ -288,8 +478,7 @@ var assignCommand = new Command3("assign").description("Assign a task to an agen
288
478
  console.error("Error: Cannot use --clear with agent argument");
289
479
  process.exit(4);
290
480
  }
291
- const tasks = await taskService.listTasks();
292
- const task = tasks.find((t) => t.id.startsWith(id));
481
+ const task = await taskService.resolveTask(id);
293
482
  if (!task) {
294
483
  if (json)
295
484
  outputError(2, `Task '${id}' not found`);
@@ -320,7 +509,50 @@ var assignCommand = new Command3("assign").description("Assign a task to an agen
320
509
  const prevMsg = previousAssignee ? ` (was: ${previousAssignee})` : "";
321
510
  console.log(`Assigned [${updated.id.slice(0, 8)}] "${updated.title}" to ${updated.assignedTo}${prevMsg}`);
322
511
  } catch (error2) {
323
- if (error2 instanceof KabanError3) {
512
+ if (error2 instanceof KabanError4) {
513
+ if (json)
514
+ outputError(error2.code, error2.message);
515
+ console.error(`Error: ${error2.message}`);
516
+ process.exit(error2.code);
517
+ }
518
+ throw error2;
519
+ }
520
+ });
521
+
522
+ // src/commands/delete.ts
523
+ import { KabanError as KabanError5 } from "@kaban-board/core";
524
+ import { Command as Command5 } from "commander";
525
+ import { createInterface } from "readline";
526
+ var deleteCommand = new Command5("delete").description("Delete a task").argument("<id>", "Task ID").option("-f, --force", "Skip confirmation").option("-j, --json", "Output as JSON").action(async (id, options) => {
527
+ const json = options.json;
528
+ try {
529
+ const { taskService } = await getContext();
530
+ const task = await taskService.resolveTask(id);
531
+ if (!task) {
532
+ if (json)
533
+ outputError(2, `Task '${id}' not found`);
534
+ console.error(`Error: Task '${id}' not found`);
535
+ process.exit(2);
536
+ }
537
+ if (!options.force && !json) {
538
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
539
+ const answer = await new Promise((resolve) => {
540
+ rl.question(`Delete "${task.title}"? [y/N] `, resolve);
541
+ });
542
+ rl.close();
543
+ if (answer.toLowerCase() !== "y") {
544
+ console.log("Cancelled");
545
+ return;
546
+ }
547
+ }
548
+ await taskService.deleteTask(task.id);
549
+ if (json) {
550
+ outputSuccess({ deleted: true, id: task.id });
551
+ return;
552
+ }
553
+ console.log(`Deleted [${task.id.slice(0, 8)}] "${task.title}"`);
554
+ } catch (error2) {
555
+ if (error2 instanceof KabanError5) {
324
556
  if (json)
325
557
  outputError(error2.code, error2.message);
326
558
  console.error(`Error: ${error2.message}`);
@@ -331,14 +563,13 @@ var assignCommand = new Command3("assign").description("Assign a task to an agen
331
563
  });
332
564
 
333
565
  // src/commands/done.ts
334
- import { KabanError as KabanError4 } from "@kaban-board/core";
335
- import { Command as Command4 } from "commander";
336
- var doneCommand = new Command4("done").description("Mark a task as done").argument("<id>", "Task ID (can be partial)").option("-j, --json", "Output as JSON").action(async (id, options) => {
566
+ import { KabanError as KabanError6 } from "@kaban-board/core";
567
+ import { Command as Command6 } from "commander";
568
+ var doneCommand = new Command6("done").description("Mark a task as done").argument("<id>", "Task ID (can be partial)").option("-j, --json", "Output as JSON").action(async (id, options) => {
337
569
  const json = options.json;
338
570
  try {
339
571
  const { taskService, boardService } = await getContext();
340
- const tasks = await taskService.listTasks();
341
- const task = tasks.find((t) => t.id.startsWith(id));
572
+ const task = await taskService.resolveTask(id);
342
573
  if (!task) {
343
574
  if (json)
344
575
  outputError(2, `Task '${id}' not found`);
@@ -359,7 +590,145 @@ var doneCommand = new Command4("done").description("Mark a task as done").argume
359
590
  }
360
591
  console.log(`Completed [${moved.id.slice(0, 8)}] "${moved.title}"`);
361
592
  } catch (error2) {
362
- if (error2 instanceof KabanError4) {
593
+ if (error2 instanceof KabanError6) {
594
+ if (json)
595
+ outputError(error2.code, error2.message);
596
+ console.error(`Error: ${error2.message}`);
597
+ process.exit(error2.code);
598
+ }
599
+ throw error2;
600
+ }
601
+ });
602
+
603
+ // src/commands/edit.ts
604
+ import { KabanError as KabanError7 } from "@kaban-board/core";
605
+ import { Command as Command7 } from "commander";
606
+ var editCommand = new Command7("edit").description("Edit a task").argument("<id>", "Task ID").option("-t, --title <title>", "New title").option("-d, --description <desc>", "New description").option("--clear-description", "Clear description").option("-l, --labels <labels>", "Labels (comma-separated)").option("--due <date>", "Due date (natural language)").option("--clear-due", "Clear due date").option("-j, --json", "Output as JSON").action(async (id, options) => {
607
+ const json = options.json;
608
+ try {
609
+ const { taskService } = await getContext();
610
+ const task = await taskService.resolveTask(id);
611
+ if (!task) {
612
+ if (json)
613
+ outputError(2, `Task '${id}' not found`);
614
+ console.error(`Error: Task '${id}' not found`);
615
+ process.exit(2);
616
+ }
617
+ const updates = {};
618
+ if (options.title)
619
+ updates.title = options.title;
620
+ if (options.description)
621
+ updates.description = options.description;
622
+ if (options.clearDescription)
623
+ updates.description = null;
624
+ if (options.labels)
625
+ updates.labels = options.labels.split(",").map((l) => l.trim());
626
+ if (options.due)
627
+ updates.dueDate = options.due;
628
+ if (options.clearDue)
629
+ updates.dueDate = null;
630
+ if (Object.keys(updates).length === 0) {
631
+ if (json)
632
+ outputError(4, "No updates specified");
633
+ console.error("Error: No updates specified. Use --title, --description, etc.");
634
+ process.exit(4);
635
+ }
636
+ const updated = await taskService.updateTask(task.id, updates);
637
+ if (json) {
638
+ outputSuccess(updated);
639
+ return;
640
+ }
641
+ console.log(`Updated [${updated.id.slice(0, 8)}] "${updated.title}"`);
642
+ } catch (error2) {
643
+ if (error2 instanceof KabanError7) {
644
+ if (json)
645
+ outputError(error2.code, error2.message);
646
+ console.error(`Error: ${error2.message}`);
647
+ process.exit(error2.code);
648
+ }
649
+ throw error2;
650
+ }
651
+ });
652
+
653
+ // src/commands/export.ts
654
+ import { writeFileSync } from "fs";
655
+ import { KabanError as KabanError8, MarkdownService } from "@kaban-board/core";
656
+ import { Command as Command8 } from "commander";
657
+ var exportCommand = new Command8("export").description("Export board to markdown format").option("-o, --output <file>", "Output file path (default: stdout)").option("-a, --archived", "Include archived tasks").option("--no-metadata", "Exclude task metadata (IDs)").option("-j, --json", "Output as JSON").action(async (options) => {
658
+ const json = options.json;
659
+ try {
660
+ const { taskService, boardService } = await getContext();
661
+ const markdownService = new MarkdownService;
662
+ const board = await boardService.getBoard();
663
+ const columns = await boardService.getColumns();
664
+ const allTasks = await taskService.listTasks({ includeArchived: options.archived });
665
+ const tasksByColumn = new Map;
666
+ for (const task of allTasks) {
667
+ const existing = tasksByColumn.get(task.columnId) ?? [];
668
+ existing.push(task);
669
+ tasksByColumn.set(task.columnId, existing);
670
+ }
671
+ const markdown = markdownService.exportBoard({ name: board?.name ?? "Kaban Board" }, columns, tasksByColumn, { includeArchived: options.archived, includeMetadata: options.metadata !== false });
672
+ if (options.output) {
673
+ writeFileSync(options.output, markdown);
674
+ if (json) {
675
+ outputSuccess({ file: options.output, tasks: allTasks.length });
676
+ } else {
677
+ console.log(`Exported ${allTasks.length} tasks to ${options.output}`);
678
+ }
679
+ } else {
680
+ if (json) {
681
+ outputSuccess({ markdown, tasks: allTasks.length });
682
+ } else {
683
+ console.log(markdown);
684
+ }
685
+ }
686
+ } catch (error2) {
687
+ if (error2 instanceof KabanError8) {
688
+ if (json)
689
+ outputError(error2.code, error2.message);
690
+ console.error(`Error: ${error2.message}`);
691
+ process.exit(error2.code);
692
+ }
693
+ throw error2;
694
+ }
695
+ });
696
+
697
+ // src/commands/get.ts
698
+ import { KabanError as KabanError9 } from "@kaban-board/core";
699
+ import { Command as Command9 } from "commander";
700
+ var getCommand = new Command9("get").description("View a task by ID").argument("<id>", "Task ID (ULID, partial, or #number)").option("-j, --json", "Output as JSON").action(async (id, options) => {
701
+ const json = options.json;
702
+ try {
703
+ const { taskService } = await getContext();
704
+ const task = await taskService.resolveTask(id);
705
+ if (!task) {
706
+ if (json)
707
+ outputError(2, `Task '${id}' not found`);
708
+ console.error(`Error: Task '${id}' not found`);
709
+ process.exit(2);
710
+ }
711
+ if (json) {
712
+ outputSuccess(task);
713
+ return;
714
+ }
715
+ console.log(`
716
+ [${task.id.slice(0, 8)}] ${task.title}`);
717
+ console.log(` Column: ${task.columnId}`);
718
+ if (task.description)
719
+ console.log(` Description: ${task.description}`);
720
+ if (task.assignedTo)
721
+ console.log(` Assigned: ${task.assignedTo}`);
722
+ if (task.labels.length)
723
+ console.log(` Labels: ${task.labels.join(", ")}`);
724
+ if (task.dueDate)
725
+ console.log(` Due: ${task.dueDate.toISOString().split("T")[0]}`);
726
+ if (task.dependsOn.length)
727
+ console.log(` Depends on: ${task.dependsOn.length} task(s)`);
728
+ console.log(` Created: ${task.createdAt.toISOString()}`);
729
+ console.log();
730
+ } catch (error2) {
731
+ if (error2 instanceof KabanError9) {
363
732
  if (json)
364
733
  outputError(error2.code, error2.message);
365
734
  console.error(`Error: ${error2.message}`);
@@ -377,7 +746,7 @@ import { homedir as homedir2 } from "os";
377
746
  import { dirname, join as join3 } from "path";
378
747
  import * as p from "@clack/prompts";
379
748
  import chalk from "chalk";
380
- import { Command as Command5 } from "commander";
749
+ import { Command as Command10 } from "commander";
381
750
 
382
751
  // src/hook/settings-manager.ts
383
752
  import { existsSync as existsSync2 } from "fs";
@@ -726,7 +1095,7 @@ function formatSize(bytes) {
726
1095
  return `${bytes} B`;
727
1096
  return `${Math.round(bytes / 1024)} KB`;
728
1097
  }
729
- var installCommand = new Command5("install").description("Install TodoWrite sync hook for Claude Code").option("-y, --yes", "Skip confirmation").action(async (options) => {
1098
+ var installCommand = new Command10("install").description("Install TodoWrite sync hook for Claude Code").option("-y, --yes", "Skip confirmation").action(async (options) => {
730
1099
  p.intro(chalk.bgCyan.black(" kaban hook install "));
731
1100
  const s = p.spinner();
732
1101
  s.start("Checking dependencies...");
@@ -787,7 +1156,7 @@ var installCommand = new Command5("install").description("Install TodoWrite sync
787
1156
  process.exit(1);
788
1157
  }
789
1158
  });
790
- var uninstallCommand = new Command5("uninstall").description("Remove TodoWrite sync hook").option("-y, --yes", "Skip confirmation").option("--clean", "Also remove sync logs").action(async (options) => {
1159
+ var uninstallCommand = new Command10("uninstall").description("Remove TodoWrite sync hook").option("-y, --yes", "Skip confirmation").option("--clean", "Also remove sync logs").action(async (options) => {
791
1160
  p.intro(chalk.bgRed.white(" kaban hook uninstall "));
792
1161
  const binaryExists = existsSync3(join3(HOOKS_DIR, HOOK_BINARY_NAME));
793
1162
  const logExists = existsSync3(join3(HOOKS_DIR, LOG_FILE));
@@ -857,7 +1226,7 @@ var uninstallCommand = new Command5("uninstall").description("Remove TodoWrite s
857
1226
  process.exit(1);
858
1227
  }
859
1228
  });
860
- var statusCommand = new Command5("status").description("Check hook installation status").action(async () => {
1229
+ var statusCommand = new Command10("status").description("Check hook installation status").action(async () => {
861
1230
  p.intro(chalk.bgBlue.white(" kaban hook status "));
862
1231
  const s = p.spinner();
863
1232
  s.start("Checking status...");
@@ -923,18 +1292,95 @@ var statusCommand = new Command5("status").description("Check hook installation
923
1292
  process.exit(1);
924
1293
  }
925
1294
  });
926
- var hookCommand = new Command5("hook").description("Manage TodoWrite sync hook for Claude Code").addCommand(installCommand).addCommand(uninstallCommand).addCommand(statusCommand);
1295
+ var hookCommand = new Command10("hook").description("Manage TodoWrite sync hook for Claude Code").addCommand(installCommand).addCommand(uninstallCommand).addCommand(statusCommand);
1296
+
1297
+ // src/commands/import.ts
1298
+ import { readFileSync as readFileSync2 } from "fs";
1299
+ import { KabanError as KabanError10, MarkdownService as MarkdownService2 } from "@kaban-board/core";
1300
+ import { Command as Command11 } from "commander";
1301
+ var importCommand = new Command11("import").description("Import tasks from markdown file").argument("<file>", "Markdown file to import").option("-d, --dry-run", "Preview import without creating tasks").option("-j, --json", "Output as JSON").action(async (file, options) => {
1302
+ const json = options.json;
1303
+ try {
1304
+ const { taskService, boardService } = await getContext();
1305
+ const markdownService = new MarkdownService2;
1306
+ const markdown = readFileSync2(file, "utf-8");
1307
+ const parseResult = markdownService.parseMarkdown(markdown);
1308
+ if (parseResult.errors.length > 0) {
1309
+ if (json) {
1310
+ outputError(1, `Parse errors: ${parseResult.errors.join(", ")}`);
1311
+ } else {
1312
+ console.error("Parse errors:");
1313
+ for (const error2 of parseResult.errors) {
1314
+ console.error(` - ${error2}`);
1315
+ }
1316
+ }
1317
+ process.exit(1);
1318
+ }
1319
+ const columns = await boardService.getColumns();
1320
+ const columnMap = new Map(columns.map((c) => [c.name.toLowerCase(), c.id]));
1321
+ if (options.dryRun) {
1322
+ let taskCount = 0;
1323
+ for (const column of parseResult.columns) {
1324
+ taskCount += column.tasks.length;
1325
+ }
1326
+ if (json) {
1327
+ outputSuccess({
1328
+ dryRun: true,
1329
+ wouldCreate: taskCount,
1330
+ columns: parseResult.columns.map((c) => ({
1331
+ name: c.name,
1332
+ tasks: c.tasks.length
1333
+ }))
1334
+ });
1335
+ } else {
1336
+ console.log(`Dry run: would import ${taskCount} tasks`);
1337
+ for (const column of parseResult.columns) {
1338
+ console.log(` ${column.name}: ${column.tasks.length} tasks`);
1339
+ }
1340
+ }
1341
+ return;
1342
+ }
1343
+ const createdTasks = [];
1344
+ for (const column of parseResult.columns) {
1345
+ const columnId = columnMap.get(column.name.toLowerCase()) ?? "todo";
1346
+ for (const task of column.tasks) {
1347
+ const created = await taskService.addTask({
1348
+ title: task.title,
1349
+ description: task.description ?? undefined,
1350
+ columnId,
1351
+ labels: task.labels,
1352
+ assignedTo: task.assignedTo ?? undefined,
1353
+ dueDate: task.dueDate?.toISOString()
1354
+ });
1355
+ createdTasks.push(created);
1356
+ }
1357
+ }
1358
+ if (json) {
1359
+ outputSuccess({ imported: createdTasks.length, tasks: createdTasks });
1360
+ } else {
1361
+ console.log(`Imported ${createdTasks.length} tasks`);
1362
+ }
1363
+ } catch (error2) {
1364
+ if (error2 instanceof KabanError10) {
1365
+ if (json)
1366
+ outputError(error2.code, error2.message);
1367
+ console.error(`Error: ${error2.message}`);
1368
+ process.exit(error2.code);
1369
+ }
1370
+ throw error2;
1371
+ }
1372
+ });
927
1373
 
928
1374
  // src/commands/init.ts
929
- import { existsSync as existsSync4, mkdirSync, writeFileSync } from "fs";
1375
+ import { existsSync as existsSync4, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
930
1376
  import {
931
1377
  BoardService as BoardService2,
932
1378
  createDb as createDb2,
933
1379
  DEFAULT_CONFIG as DEFAULT_CONFIG2,
934
1380
  initializeSchema
935
1381
  } from "@kaban-board/core";
936
- import { Command as Command6 } from "commander";
937
- var initCommand = new Command6("init").description("Initialize a new Kaban board in the current directory").option("-n, --name <name>", "Board name", "Kaban Board").action(async (options) => {
1382
+ import { Command as Command12 } from "commander";
1383
+ var initCommand = new Command12("init").description("Initialize a new Kaban board in the current directory").option("-n, --name <name>", "Board name", "Kaban Board").action(async (options) => {
938
1384
  const { kabanDir, dbPath, configPath } = getKabanPaths();
939
1385
  if (existsSync4(dbPath)) {
940
1386
  console.error("Error: Board already exists in this directory");
@@ -945,10 +1391,10 @@ var initCommand = new Command6("init").description("Initialize a new Kaban board
945
1391
  ...DEFAULT_CONFIG2,
946
1392
  board: { name: options.name }
947
1393
  };
948
- writeFileSync(configPath, JSON.stringify(config, null, 2));
949
- const db = await createDb2(dbPath);
950
- await initializeSchema(db);
951
- const boardService = new BoardService2(db);
1394
+ writeFileSync2(configPath, JSON.stringify(config, null, 2));
1395
+ const db2 = await createDb2(dbPath);
1396
+ await initializeSchema(db2);
1397
+ const boardService = new BoardService2(db2);
952
1398
  await boardService.initializeBoard(config);
953
1399
  console.log(`Initialized Kaban board: ${options.name}`);
954
1400
  console.log(` Database: ${dbPath}`);
@@ -956,8 +1402,8 @@ var initCommand = new Command6("init").description("Initialize a new Kaban board
956
1402
  });
957
1403
 
958
1404
  // src/commands/list.ts
959
- import { KabanError as KabanError5 } from "@kaban-board/core";
960
- import { Command as Command7 } from "commander";
1405
+ import { KabanError as KabanError11 } from "@kaban-board/core";
1406
+ import { Command as Command13 } from "commander";
961
1407
  function sortTasks(tasks, sortBy, reverse) {
962
1408
  const sorted = [...tasks].sort((a, b) => {
963
1409
  switch (sortBy) {
@@ -973,7 +1419,7 @@ function sortTasks(tasks, sortBy, reverse) {
973
1419
  });
974
1420
  return reverse ? sorted.reverse() : sorted;
975
1421
  }
976
- var listCommand = new Command7("list").description("List tasks").option("-c, --column <column>", "Filter by column").option("-a, --agent <agent>", "Filter by creator agent").option("-u, --assignee <assignee>", "Filter by assigned agent").option("-b, --blocked", "Show only blocked tasks").option("-s, --sort <field>", "Sort by: name, date, updated").option("-r, --reverse", "Reverse sort order").option("-j, --json", "Output as JSON").action(async (options) => {
1422
+ var listCommand = new Command13("list").description("List tasks").option("-c, --column <column>", "Filter by column").option("-a, --agent <agent>", "Filter by creator agent").option("-u, --assignee <assignee>", "Filter by assigned agent").option("-b, --blocked", "Show only blocked tasks").option("-s, --sort <field>", "Sort by: name, date, updated").option("-r, --reverse", "Reverse sort order").option("-j, --json", "Output as JSON").action(async (options) => {
977
1423
  const json = options.json;
978
1424
  try {
979
1425
  const { taskService, boardService } = await getContext();
@@ -1014,7 +1460,7 @@ var listCommand = new Command7("list").description("List tasks").option("-c, --c
1014
1460
  }
1015
1461
  }
1016
1462
  } catch (error2) {
1017
- if (error2 instanceof KabanError5) {
1463
+ if (error2 instanceof KabanError11) {
1018
1464
  if (json)
1019
1465
  outputError(error2.code, error2.message);
1020
1466
  console.error(`Error: ${error2.message}`);
@@ -1025,14 +1471,22 @@ var listCommand = new Command7("list").description("List tasks").option("-c, --c
1025
1471
  });
1026
1472
 
1027
1473
  // src/commands/mcp.ts
1028
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1474
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
1029
1475
  import { join as join4 } from "path";
1030
1476
  import {
1477
+ AuditService as AuditService2,
1031
1478
  BoardService as BoardService3,
1032
1479
  createDb as createDb3,
1033
1480
  DEFAULT_CONFIG as DEFAULT_CONFIG3,
1034
1481
  initializeSchema as initializeSchema2,
1035
- TaskService as TaskService2
1482
+ TaskService as TaskService2,
1483
+ LinkService,
1484
+ MarkdownService as MarkdownService3,
1485
+ ScoringService,
1486
+ fifoScorer,
1487
+ priorityScorer,
1488
+ dueDateScorer,
1489
+ createBlockingScorer
1036
1490
  } from "@kaban-board/core";
1037
1491
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1038
1492
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -1042,7 +1496,7 @@ import {
1042
1496
  ListToolsRequestSchema,
1043
1497
  ReadResourceRequestSchema
1044
1498
  } from "@modelcontextprotocol/sdk/types.js";
1045
- import { Command as Command8 } from "commander";
1499
+ import { Command as Command14 } from "commander";
1046
1500
  var mcpHelpers = {
1047
1501
  getParam(args, primary, alias) {
1048
1502
  if (!args)
@@ -1076,11 +1530,21 @@ async function createContext(basePath) {
1076
1530
  if (!existsSync5(dbPath)) {
1077
1531
  throw new Error("No board found. Run 'kaban init' first");
1078
1532
  }
1079
- const db = await createDb3(dbPath);
1080
- const config = JSON.parse(readFileSync2(configPath, "utf-8"));
1081
- const boardService = new BoardService3(db);
1082
- const taskService = new TaskService2(db, boardService);
1083
- return { db, config, boardService, taskService };
1533
+ const db2 = await createDb3(dbPath);
1534
+ const config = JSON.parse(readFileSync3(configPath, "utf-8"));
1535
+ const boardService = new BoardService3(db2);
1536
+ const taskService = new TaskService2(db2, boardService);
1537
+ const linkService = new LinkService(db2);
1538
+ const markdownService = new MarkdownService3;
1539
+ const scoringService = new ScoringService;
1540
+ scoringService.addScorer(priorityScorer);
1541
+ scoringService.addScorer(dueDateScorer);
1542
+ scoringService.addScorer(createBlockingScorer(async (taskId) => {
1543
+ const blocking = await linkService.getBlocking(taskId);
1544
+ return blocking.length;
1545
+ }));
1546
+ scoringService.addScorer(fifoScorer);
1547
+ return { db: db2, config, boardService, taskService, linkService, markdownService, scoringService };
1084
1548
  }
1085
1549
  async function startMcpServer(workingDirectory) {
1086
1550
  const server = new Server({ name: "kaban-mcp", version: "0.1.0" }, { capabilities: { tools: {}, resources: {} } });
@@ -1184,6 +1648,18 @@ async function startMcpServer(workingDirectory) {
1184
1648
  }
1185
1649
  }
1186
1650
  },
1651
+ {
1652
+ name: "kaban_assign_task",
1653
+ description: "Assign or unassign a task to/from an agent (convenience wrapper for kaban_update_task)",
1654
+ inputSchema: {
1655
+ type: "object",
1656
+ properties: {
1657
+ id: { type: "string", description: "Task ID (ULID)" },
1658
+ taskId: { type: "string", description: "Task ID - alias for 'id'" },
1659
+ assignee: { type: ["string", "null"], description: "Agent name (null to unassign)" }
1660
+ }
1661
+ }
1662
+ },
1187
1663
  {
1188
1664
  name: "kaban_delete_task",
1189
1665
  description: "Delete a task",
@@ -1334,6 +1810,140 @@ async function startMcpServer(workingDirectory) {
1334
1810
  },
1335
1811
  required: ["title"]
1336
1812
  }
1813
+ },
1814
+ {
1815
+ name: "kaban_add_link",
1816
+ description: "Create a link between two tasks",
1817
+ inputSchema: {
1818
+ type: "object",
1819
+ properties: {
1820
+ sourceId: { type: "string", description: "Source task ID" },
1821
+ targetId: { type: "string", description: "Target task ID" },
1822
+ type: {
1823
+ type: "string",
1824
+ enum: ["relates_to", "blocks", "duplicates", "parent_of"],
1825
+ description: "Link type (default: relates_to)"
1826
+ },
1827
+ metadata: { type: "object", description: "Optional metadata for the link" }
1828
+ },
1829
+ required: ["sourceId", "targetId"]
1830
+ }
1831
+ },
1832
+ {
1833
+ name: "kaban_remove_link",
1834
+ description: "Remove a link between two tasks",
1835
+ inputSchema: {
1836
+ type: "object",
1837
+ properties: {
1838
+ sourceId: { type: "string", description: "Source task ID" },
1839
+ targetId: { type: "string", description: "Target task ID" },
1840
+ type: {
1841
+ type: "string",
1842
+ enum: ["relates_to", "blocks", "duplicates", "parent_of"],
1843
+ description: "Link type to remove (removes all types if not specified)"
1844
+ }
1845
+ },
1846
+ required: ["sourceId", "targetId"]
1847
+ }
1848
+ },
1849
+ {
1850
+ name: "kaban_get_links",
1851
+ description: "Get links for a task",
1852
+ inputSchema: {
1853
+ type: "object",
1854
+ properties: {
1855
+ taskId: { type: "string", description: "Task ID" },
1856
+ id: { type: "string", description: "Task ID - alias for taskId" },
1857
+ direction: {
1858
+ type: "string",
1859
+ enum: ["outgoing", "incoming", "both"],
1860
+ description: "Link direction to return (default: both)"
1861
+ },
1862
+ type: {
1863
+ type: "string",
1864
+ enum: ["relates_to", "blocks", "duplicates", "parent_of"],
1865
+ description: "Filter by link type"
1866
+ }
1867
+ }
1868
+ }
1869
+ },
1870
+ {
1871
+ name: "kaban_get_next_task",
1872
+ description: "Get the highest-priority task to work on next (considering dependencies, due dates, etc.)",
1873
+ inputSchema: {
1874
+ type: "object",
1875
+ properties: {
1876
+ columnId: { type: "string", description: "Filter by column (default: todo)" }
1877
+ }
1878
+ }
1879
+ },
1880
+ {
1881
+ name: "kaban_score_tasks",
1882
+ description: "Get all tasks with their priority scores",
1883
+ inputSchema: {
1884
+ type: "object",
1885
+ properties: {
1886
+ columnId: { type: "string", description: "Filter by column" },
1887
+ limit: { type: "number", description: "Max tasks to return (default: 10)" }
1888
+ }
1889
+ }
1890
+ },
1891
+ {
1892
+ name: "kaban_export_markdown",
1893
+ description: "Export the board to markdown format",
1894
+ inputSchema: {
1895
+ type: "object",
1896
+ properties: {
1897
+ includeArchived: { type: "boolean", description: "Include archived tasks (default: false)" },
1898
+ includeMetadata: { type: "boolean", description: "Include task metadata (default: true)" }
1899
+ }
1900
+ }
1901
+ },
1902
+ {
1903
+ name: "kaban_import_markdown",
1904
+ description: "Import tasks from markdown format (creates new tasks, does not modify existing)",
1905
+ inputSchema: {
1906
+ type: "object",
1907
+ properties: {
1908
+ markdown: { type: "string", description: "Markdown content to import" },
1909
+ dryRun: { type: "boolean", description: "Preview import without creating tasks" }
1910
+ },
1911
+ required: ["markdown"]
1912
+ }
1913
+ },
1914
+ {
1915
+ name: "kaban_get_audit_history",
1916
+ description: "Get audit log history with optional filters",
1917
+ inputSchema: {
1918
+ type: "object",
1919
+ properties: {
1920
+ objectType: {
1921
+ type: "string",
1922
+ enum: ["task", "column", "board"],
1923
+ description: "Filter by object type"
1924
+ },
1925
+ objectId: { type: "string", description: "Filter by object ID" },
1926
+ eventType: {
1927
+ type: "string",
1928
+ enum: ["CREATE", "UPDATE", "DELETE"],
1929
+ description: "Filter by event type"
1930
+ },
1931
+ actor: { type: "string", description: "Filter by actor name" },
1932
+ limit: { type: "number", description: "Max entries (default: 50, max: 1000)" }
1933
+ }
1934
+ }
1935
+ },
1936
+ {
1937
+ name: "kaban_get_task_history",
1938
+ description: "Get change history for a specific task",
1939
+ inputSchema: {
1940
+ type: "object",
1941
+ properties: {
1942
+ taskId: { type: "string", description: "Task ID (ULID or partial)" },
1943
+ id: { type: "string", description: "Task ID - alias for taskId" },
1944
+ limit: { type: "number", description: "Max entries (default: 50)" }
1945
+ }
1946
+ }
1337
1947
  }
1338
1948
  ]
1339
1949
  }));
@@ -1353,10 +1963,10 @@ async function startMcpServer(workingDirectory) {
1353
1963
  ...DEFAULT_CONFIG3,
1354
1964
  board: { name: boardName }
1355
1965
  };
1356
- writeFileSync2(configPath, JSON.stringify(config, null, 2));
1357
- const db = await createDb3(dbPath);
1358
- await initializeSchema2(db);
1359
- const boardService2 = new BoardService3(db);
1966
+ writeFileSync3(configPath, JSON.stringify(config, null, 2));
1967
+ const db2 = await createDb3(dbPath);
1968
+ await initializeSchema2(db2);
1969
+ const boardService2 = new BoardService3(db2);
1360
1970
  await boardService2.initializeBoard(config);
1361
1971
  return jsonResponse({
1362
1972
  success: true,
@@ -1364,7 +1974,7 @@ async function startMcpServer(workingDirectory) {
1364
1974
  paths: { database: dbPath, config: configPath }
1365
1975
  });
1366
1976
  }
1367
- const { taskService, boardService } = await createContext(workingDirectory);
1977
+ const { taskService, boardService, linkService, markdownService, scoringService } = await createContext(workingDirectory);
1368
1978
  const taskArgs = args;
1369
1979
  const taskId = getParam(taskArgs, "id", "taskId");
1370
1980
  switch (name) {
@@ -1411,6 +2021,14 @@ async function startMcpServer(workingDirectory) {
1411
2021
  const task = await taskService.updateTask(taskId, updates, expectedVersion);
1412
2022
  return jsonResponse(task);
1413
2023
  }
2024
+ case "kaban_assign_task": {
2025
+ const id = getParam(taskArgs, "id", "taskId");
2026
+ if (!id)
2027
+ return errorResponse("Task ID required (use 'id' or 'taskId')");
2028
+ const { assignee } = args ?? {};
2029
+ const task = await taskService.updateTask(id, { assignedTo: assignee ?? null });
2030
+ return jsonResponse(task);
2031
+ }
1414
2032
  case "kaban_delete_task": {
1415
2033
  if (!taskId)
1416
2034
  return errorResponse("Task ID required (use 'id' or 'taskId')");
@@ -1595,6 +2213,126 @@ async function startMcpServer(workingDirectory) {
1595
2213
  const result = await taskService.addTaskChecked(taskInput, { force });
1596
2214
  return jsonResponse(result);
1597
2215
  }
2216
+ case "kaban_add_link": {
2217
+ const { sourceId, targetId, type } = args ?? {};
2218
+ if (!sourceId)
2219
+ return errorResponse("sourceId required");
2220
+ if (!targetId)
2221
+ return errorResponse("targetId required");
2222
+ const linkType = type ?? "related";
2223
+ const link = await linkService.addLink(sourceId, targetId, linkType);
2224
+ return jsonResponse(link);
2225
+ }
2226
+ case "kaban_remove_link": {
2227
+ const { sourceId, targetId, type } = args ?? {};
2228
+ if (!sourceId)
2229
+ return errorResponse("sourceId required");
2230
+ if (!targetId)
2231
+ return errorResponse("targetId required");
2232
+ if (!type)
2233
+ return errorResponse("type required");
2234
+ await linkService.removeLink(sourceId, targetId, type);
2235
+ return jsonResponse({ success: true });
2236
+ }
2237
+ case "kaban_get_links": {
2238
+ const id = getParam(taskArgs, "taskId", "id");
2239
+ if (!id)
2240
+ return errorResponse("Task ID required");
2241
+ const { direction } = args ?? {};
2242
+ let links;
2243
+ if (direction === "outgoing") {
2244
+ links = await linkService.getLinksFrom(id);
2245
+ } else if (direction === "incoming") {
2246
+ links = await linkService.getLinksTo(id);
2247
+ } else {
2248
+ links = await linkService.getAllLinks(id);
2249
+ }
2250
+ return jsonResponse(links);
2251
+ }
2252
+ case "kaban_get_next_task": {
2253
+ const { columnId } = args ?? {};
2254
+ const allTasks = await taskService.listTasks({ columnId: columnId ?? "todo" });
2255
+ const unblockedTasks = allTasks.filter((t) => !t.blockedReason && t.dependsOn.length === 0);
2256
+ if (unblockedTasks.length === 0) {
2257
+ return jsonResponse({ message: "No actionable tasks found", task: null });
2258
+ }
2259
+ const ranked = await scoringService.rankTasks(unblockedTasks);
2260
+ return jsonResponse(ranked[0]);
2261
+ }
2262
+ case "kaban_score_tasks": {
2263
+ const { columnId, limit } = args ?? {};
2264
+ const allTasks = await taskService.listTasks(columnId ? { columnId } : undefined);
2265
+ const ranked = await scoringService.rankTasks(allTasks);
2266
+ return jsonResponse(ranked.slice(0, limit ?? 10));
2267
+ }
2268
+ case "kaban_export_markdown": {
2269
+ const { includeArchived, includeMetadata } = args ?? {};
2270
+ const board = await boardService.getBoard();
2271
+ const columns = await boardService.getColumns();
2272
+ const allTasks = await taskService.listTasks({ includeArchived });
2273
+ const tasksByColumn = new Map;
2274
+ for (const task of allTasks) {
2275
+ const existing = tasksByColumn.get(task.columnId) ?? [];
2276
+ existing.push(task);
2277
+ tasksByColumn.set(task.columnId, existing);
2278
+ }
2279
+ const markdown = markdownService.exportBoard({ name: board?.name ?? "Kaban Board" }, columns, tasksByColumn, { includeArchived, includeMetadata: includeMetadata ?? true });
2280
+ return jsonResponse({ markdown });
2281
+ }
2282
+ case "kaban_import_markdown": {
2283
+ const { markdown, dryRun } = args ?? {};
2284
+ if (!markdown)
2285
+ return errorResponse("markdown content required");
2286
+ const parseResult = markdownService.parseMarkdown(markdown);
2287
+ if (parseResult.errors.length > 0) {
2288
+ return jsonResponse({ success: false, errors: parseResult.errors, parsed: parseResult });
2289
+ }
2290
+ if (dryRun) {
2291
+ return jsonResponse({ dryRun: true, parsed: parseResult });
2292
+ }
2293
+ const createdTasks = [];
2294
+ for (const column of parseResult.columns) {
2295
+ for (const task of column.tasks) {
2296
+ const created = await taskService.addTask({
2297
+ title: task.title,
2298
+ description: task.description ?? undefined,
2299
+ columnId: column.name.toLowerCase().replace(/\s+/g, "_"),
2300
+ labels: task.labels,
2301
+ assignedTo: task.assignedTo ?? undefined,
2302
+ dueDate: task.dueDate?.toISOString()
2303
+ });
2304
+ createdTasks.push(created);
2305
+ }
2306
+ }
2307
+ return jsonResponse({ success: true, tasksCreated: createdTasks.length, tasks: createdTasks });
2308
+ }
2309
+ case "kaban_get_audit_history": {
2310
+ const { objectType, objectId, eventType, actor, limit } = args ?? {};
2311
+ const auditService = new AuditService2(db);
2312
+ const result = await auditService.getHistory({
2313
+ objectType,
2314
+ objectId,
2315
+ eventType,
2316
+ actor,
2317
+ limit: limit ?? 50
2318
+ });
2319
+ return jsonResponse(result);
2320
+ }
2321
+ case "kaban_get_task_history": {
2322
+ const taskIdArg = getParam(args, "taskId", "id");
2323
+ if (!taskIdArg)
2324
+ return errorResponse("Task ID required (use 'taskId' or 'id')");
2325
+ const task = await taskService.resolveTask(taskIdArg);
2326
+ if (!task)
2327
+ return errorResponse(`Task '${taskIdArg}' not found`);
2328
+ const { limit } = args ?? {};
2329
+ const auditService = new AuditService2(db);
2330
+ const entries = await auditService.getTaskHistory(task.id, limit ?? 50);
2331
+ return jsonResponse({
2332
+ task: { id: task.id, title: task.title },
2333
+ entries
2334
+ });
2335
+ }
1598
2336
  default:
1599
2337
  return errorResponse(`Unknown tool: ${name}`);
1600
2338
  }
@@ -1708,20 +2446,19 @@ async function startMcpServer(workingDirectory) {
1708
2446
  await server.connect(transport);
1709
2447
  console.error("Kaban MCP server running on stdio");
1710
2448
  }
1711
- var mcpCommand = new Command8("mcp").description("Start MCP server for AI agent integration").option("-p, --path <path>", "Working directory for Kaban board").action(async (options) => {
2449
+ var mcpCommand = new Command14("mcp").description("Start MCP server for AI agent integration").option("-p, --path <path>", "Working directory for Kaban board").action(async (options) => {
1712
2450
  const workingDirectory = options.path ?? process.env.KABAN_PATH ?? process.cwd();
1713
2451
  await startMcpServer(workingDirectory);
1714
2452
  });
1715
2453
 
1716
2454
  // src/commands/move.ts
1717
- import { KabanError as KabanError6 } from "@kaban-board/core";
1718
- import { Command as Command9 } from "commander";
1719
- var moveCommand = new Command9("move").description("Move a task to a different column").argument("<id>", "Task ID (can be partial)").argument("[column]", "Target column").option("-n, --next", "Move to next column").option("-f, --force", "Force move even if WIP limit exceeded").option("-A, --assign [agent]", "Assign task to agent (defaults to current agent)").option("-j, --json", "Output as JSON").action(async (id, column, options) => {
2455
+ import { KabanError as KabanError12 } from "@kaban-board/core";
2456
+ import { Command as Command15 } from "commander";
2457
+ var moveCommand = new Command15("move").description("Move a task to a different column").argument("<id>", "Task ID (can be partial)").argument("[column]", "Target column").option("-n, --next", "Move to next column").option("-f, --force", "Force move even if WIP limit exceeded").option("-A, --assign [agent]", "Assign task to agent (defaults to current agent)").option("-j, --json", "Output as JSON").action(async (id, column, options) => {
1720
2458
  const json = options.json;
1721
2459
  try {
1722
2460
  const { taskService, boardService } = await getContext();
1723
- const tasks = await taskService.listTasks();
1724
- const task = tasks.find((t) => t.id.startsWith(id));
2461
+ const task = await taskService.resolveTask(id);
1725
2462
  if (!task) {
1726
2463
  if (json)
1727
2464
  outputError(2, `Task '${id}' not found`);
@@ -1767,7 +2504,64 @@ var moveCommand = new Command9("move").description("Move a task to a different c
1767
2504
  }
1768
2505
  console.log(msg);
1769
2506
  } catch (error2) {
1770
- if (error2 instanceof KabanError6) {
2507
+ if (error2 instanceof KabanError12) {
2508
+ if (json)
2509
+ outputError(error2.code, error2.message);
2510
+ console.error(`Error: ${error2.message}`);
2511
+ process.exit(error2.code);
2512
+ }
2513
+ throw error2;
2514
+ }
2515
+ });
2516
+
2517
+ // src/commands/next.ts
2518
+ import {
2519
+ KabanError as KabanError13,
2520
+ ScoringService as ScoringService2,
2521
+ fifoScorer as fifoScorer2,
2522
+ priorityScorer as priorityScorer2,
2523
+ dueDateScorer as dueDateScorer2,
2524
+ createBlockingScorer as createBlockingScorer2,
2525
+ LinkService as LinkService2
2526
+ } from "@kaban-board/core";
2527
+ import { Command as Command16 } from "commander";
2528
+ var nextCommand = new Command16("next").description("Get the next highest-priority task").option("-c, --column <column>", "Column to pick from", "todo").option("-j, --json", "Output as JSON").action(async (options) => {
2529
+ const json = options.json;
2530
+ try {
2531
+ const { taskService, db: db2 } = await getContext();
2532
+ const linkService = new LinkService2(db2);
2533
+ const scoringService = new ScoringService2;
2534
+ scoringService.addScorer(priorityScorer2);
2535
+ scoringService.addScorer(dueDateScorer2);
2536
+ scoringService.addScorer(createBlockingScorer2(async (taskId) => {
2537
+ const blocking = await linkService.getBlocking(taskId);
2538
+ return blocking.length;
2539
+ }));
2540
+ scoringService.addScorer(fifoScorer2);
2541
+ const tasks = await taskService.listTasks({ columnId: options.column });
2542
+ const unblocked = tasks.filter((t) => !t.blockedReason && t.dependsOn.length === 0);
2543
+ if (unblocked.length === 0) {
2544
+ if (json) {
2545
+ outputSuccess({ task: null, message: "No actionable tasks" });
2546
+ return;
2547
+ }
2548
+ console.log("No actionable tasks in", options.column);
2549
+ return;
2550
+ }
2551
+ const ranked = await scoringService.rankTasks(unblocked);
2552
+ const next = ranked[0];
2553
+ if (json) {
2554
+ outputSuccess(next);
2555
+ return;
2556
+ }
2557
+ console.log(`
2558
+ Next: [${next.task.id.slice(0, 8)}] "${next.task.title}"`);
2559
+ console.log(` Score: ${next.score} (${Object.entries(next.breakdown).map(([k, v]) => `${k}:${v}`).join(", ")})`);
2560
+ if (next.task.dueDate)
2561
+ console.log(` Due: ${next.task.dueDate.toISOString().split("T")[0]}`);
2562
+ console.log();
2563
+ } catch (error2) {
2564
+ if (error2 instanceof KabanError13) {
1771
2565
  if (json)
1772
2566
  outputError(error2.code, error2.message);
1773
2567
  console.error(`Error: ${error2.message}`);
@@ -1779,9 +2573,9 @@ var moveCommand = new Command9("move").description("Move a task to a different c
1779
2573
 
1780
2574
  // src/commands/schema.ts
1781
2575
  import { jsonSchemas } from "@kaban-board/core";
1782
- import { Command as Command10 } from "commander";
2576
+ import { Command as Command17 } from "commander";
1783
2577
  var availableSchemas = Object.keys(jsonSchemas);
1784
- var schemaCommand = new Command10("schema").description("Output JSON schemas for AI agents").argument("[name]", "Schema name (omit to list available)").action((name) => {
2578
+ var schemaCommand = new Command17("schema").description("Output JSON schemas for AI agents").argument("[name]", "Schema name (omit to list available)").action((name) => {
1785
2579
  if (!name) {
1786
2580
  console.log("Available schemas:");
1787
2581
  for (const schemaName of availableSchemas) {
@@ -1800,10 +2594,65 @@ Usage: kaban schema <name>`);
1800
2594
  console.log(JSON.stringify(schema, null, 2));
1801
2595
  });
1802
2596
 
2597
+ // src/commands/stats.ts
2598
+ import { KabanError as KabanError14 } from "@kaban-board/core";
2599
+ import { Command as Command18 } from "commander";
2600
+ var statsCommand = new Command18("stats").description("Show board and archive statistics").option("-j, --json", "Output as JSON").action(async (options) => {
2601
+ const json = options.json;
2602
+ try {
2603
+ const { taskService, boardService } = await getContext();
2604
+ const tasks = await taskService.listTasks();
2605
+ const columns = await boardService.getColumns();
2606
+ const terminalColumns = columns.filter((c) => c.isTerminal);
2607
+ const archivedResult = await taskService.searchArchive("", { limit: 1 });
2608
+ const completedNotArchived = tasks.filter((t) => terminalColumns.some((c) => c.id === t.columnId)).length;
2609
+ const blocked = tasks.filter((t) => t.blockedReason).length;
2610
+ const withDeps = tasks.filter((t) => t.dependsOn.length > 0).length;
2611
+ const stats = {
2612
+ activeTasks: tasks.length,
2613
+ archivedTasks: archivedResult.total,
2614
+ completedNotArchived,
2615
+ blockedTasks: blocked,
2616
+ tasksWithDependencies: withDeps,
2617
+ byColumn: columns.map((c) => ({
2618
+ id: c.id,
2619
+ name: c.name,
2620
+ count: tasks.filter((t) => t.columnId === c.id).length
2621
+ }))
2622
+ };
2623
+ if (json) {
2624
+ outputSuccess(stats);
2625
+ return;
2626
+ }
2627
+ console.log(`
2628
+ Board Statistics
2629
+ `);
2630
+ console.log(` Active tasks: ${stats.activeTasks}`);
2631
+ console.log(` Archived tasks: ${stats.archivedTasks}`);
2632
+ console.log(` Completed (live): ${stats.completedNotArchived}`);
2633
+ console.log(` Blocked: ${stats.blockedTasks}`);
2634
+ console.log(` With dependencies: ${stats.tasksWithDependencies}`);
2635
+ console.log(`
2636
+ By Column:`);
2637
+ for (const col of stats.byColumn) {
2638
+ console.log(` ${col.name}: ${col.count}`);
2639
+ }
2640
+ console.log();
2641
+ } catch (error2) {
2642
+ if (error2 instanceof KabanError14) {
2643
+ if (json)
2644
+ outputError(error2.code, error2.message);
2645
+ console.error(`Error: ${error2.message}`);
2646
+ process.exit(error2.code);
2647
+ }
2648
+ throw error2;
2649
+ }
2650
+ });
2651
+
1803
2652
  // src/commands/search.ts
1804
- import { KabanError as KabanError7 } from "@kaban-board/core";
1805
- import { Command as Command11 } from "commander";
1806
- var searchCommand = new Command11("search").description("Search tasks").argument("<query>", "Search query").option("--archive", "Search in archive only").option("-l, --limit <n>", "Max results", parseInt, 50).option("-j, --json", "Output as JSON").action(async (query, options) => {
2653
+ import { KabanError as KabanError15 } from "@kaban-board/core";
2654
+ import { Command as Command19 } from "commander";
2655
+ var searchCommand = new Command19("search").description("Search tasks").argument("<query>", "Search query").option("--archive", "Search in archive only").option("-l, --limit <n>", "Max results", parseInt, 50).option("-j, --json", "Output as JSON").action(async (query, options) => {
1807
2656
  const json = options.json;
1808
2657
  try {
1809
2658
  const { taskService } = await getContext();
@@ -1832,7 +2681,7 @@ var searchCommand = new Command11("search").description("Search tasks").argument
1832
2681
  console.log(` Column: ${task.columnId} | By: ${task.createdBy}`);
1833
2682
  }
1834
2683
  } catch (error2) {
1835
- if (error2 instanceof KabanError7) {
2684
+ if (error2 instanceof KabanError15) {
1836
2685
  if (json)
1837
2686
  outputError(error2.code, error2.message);
1838
2687
  console.error(`Error: ${error2.message}`);
@@ -1843,9 +2692,9 @@ var searchCommand = new Command11("search").description("Search tasks").argument
1843
2692
  });
1844
2693
 
1845
2694
  // src/commands/status.ts
1846
- import { KabanError as KabanError8 } from "@kaban-board/core";
1847
- import { Command as Command12 } from "commander";
1848
- var statusCommand2 = new Command12("status").description("Show board status summary").option("-j, --json", "Output as JSON").action(async (options) => {
2695
+ import { KabanError as KabanError16 } from "@kaban-board/core";
2696
+ import { Command as Command20 } from "commander";
2697
+ var statusCommand2 = new Command20("status").description("Show board status summary").option("-j, --json", "Output as JSON").action(async (options) => {
1849
2698
  const json = options.json;
1850
2699
  try {
1851
2700
  const { taskService, boardService } = await getContext();
@@ -1888,7 +2737,7 @@ var statusCommand2 = new Command12("status").description("Show board status summ
1888
2737
  }
1889
2738
  console.log();
1890
2739
  } catch (error2) {
1891
- if (error2 instanceof KabanError8) {
2740
+ if (error2 instanceof KabanError16) {
1892
2741
  if (json)
1893
2742
  outputError(error2.code, error2.message);
1894
2743
  console.error(`Error: ${error2.message}`);
@@ -1899,7 +2748,7 @@ var statusCommand2 = new Command12("status").description("Show board status summ
1899
2748
  });
1900
2749
 
1901
2750
  // src/commands/sync.ts
1902
- import { Command as Command13 } from "commander";
2751
+ import { Command as Command21 } from "commander";
1903
2752
 
1904
2753
  // src/hook/constants.ts
1905
2754
  var STATUS_PRIORITY = {
@@ -2241,7 +3090,7 @@ class SyncLogger {
2241
3090
  }
2242
3091
 
2243
3092
  // src/commands/sync.ts
2244
- var syncCommand = new Command13("sync").description("Sync TodoWrite input to Kaban board (reads from stdin)").option("--no-log", "Disable sync logging").action(async (options) => {
3093
+ var syncCommand = new Command21("sync").description("Sync TodoWrite input to Kaban board (reads from stdin)").option("--no-log", "Disable sync logging").action(async (options) => {
2245
3094
  const startTime = performance.now();
2246
3095
  let input;
2247
3096
  try {
@@ -2293,11 +3142,11 @@ import { existsSync as existsSync7 } from "fs";
2293
3142
  import { createRequire } from "module";
2294
3143
  import { dirname as dirname3, join as join5 } from "path";
2295
3144
  import { fileURLToPath } from "url";
2296
- import { Command as Command14 } from "commander";
3145
+ import { Command as Command22 } from "commander";
2297
3146
  // package.json
2298
3147
  var package_default = {
2299
3148
  name: "@kaban-board/cli",
2300
- version: "0.3.0",
3149
+ version: "0.3.2",
2301
3150
  description: "Terminal Kanban for AI Code Agents - CLI and MCP server",
2302
3151
  type: "module",
2303
3152
  bin: {
@@ -2381,7 +3230,7 @@ function runBinary(path, args) {
2381
3230
  const child = spawn3(path, args, { stdio: "inherit", cwd: process.cwd() });
2382
3231
  child.on("exit", (code) => process.exit(code ?? 0));
2383
3232
  }
2384
- var tuiCommand = new Command14("tui").description("Start interactive Terminal UI").action(async () => {
3233
+ var tuiCommand = new Command22("tui").description("Start interactive Terminal UI").action(async () => {
2385
3234
  const cwd = process.cwd();
2386
3235
  const args = process.argv.slice(3);
2387
3236
  const useBun = hasBun();
@@ -2415,18 +3264,24 @@ var tuiCommand = new Command14("tui").description("Start interactive Terminal UI
2415
3264
  // src/index.ts
2416
3265
  var require3 = createRequire2(import.meta.url);
2417
3266
  var pkg = require3("../package.json");
2418
- var program = new Command15;
3267
+ var program = new Command23;
2419
3268
  program.name("kaban").description("Terminal Kanban for AI Code Agents").version(pkg.version);
2420
3269
  program.addCommand(initCommand);
2421
3270
  program.addCommand(addCommand);
3271
+ program.addCommand(auditCommand);
2422
3272
  program.addCommand(listCommand);
2423
3273
  program.addCommand(moveCommand);
2424
3274
  program.addCommand(assignCommand);
3275
+ program.addCommand(deleteCommand);
2425
3276
  program.addCommand(doneCommand);
3277
+ program.addCommand(editCommand);
3278
+ program.addCommand(getCommand);
2426
3279
  program.addCommand(statusCommand2);
2427
3280
  program.addCommand(schemaCommand);
2428
3281
  program.addCommand(searchCommand);
2429
3282
  program.addCommand(mcpCommand);
3283
+ program.addCommand(nextCommand);
3284
+ program.addCommand(statsCommand);
2430
3285
  program.addCommand(tuiCommand);
2431
3286
  program.addCommand(hookCommand);
2432
3287
  program.addCommand(syncCommand);
@@ -2434,4 +3289,6 @@ program.addCommand(archiveCommand);
2434
3289
  program.addCommand(restoreCommand);
2435
3290
  program.addCommand(purgeCommand);
2436
3291
  program.addCommand(resetCommand);
3292
+ program.addCommand(exportCommand);
3293
+ program.addCommand(importCommand);
2437
3294
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaban-board/cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Terminal Kanban for AI Code Agents - CLI and MCP server",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@clack/prompts": "^0.11.0",
21
- "@kaban-board/core": "0.3.0",
21
+ "@kaban-board/core": "0.3.1",
22
22
  "@modelcontextprotocol/sdk": "^1.25.3",
23
23
  "chalk": "^5.6.2",
24
24
  "commander": "^14.0.2",