kanban-lite 1.0.9 → 1.0.13

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 (38) hide show
  1. package/dist/cli.js +1015 -407
  2. package/dist/extension.js +897 -347
  3. package/dist/mcp-server.js +550 -189
  4. package/dist/sdk/index.cjs +1223 -0
  5. package/dist/sdk/index.mjs +1168 -0
  6. package/dist/sdk/sdk/KanbanSDK.d.ts +39 -21
  7. package/dist/sdk/sdk/index.d.ts +4 -3
  8. package/dist/sdk/sdk/migration.d.ts +6 -0
  9. package/dist/sdk/sdk/types.d.ts +3 -5
  10. package/dist/sdk/shared/config.d.ts +23 -10
  11. package/dist/sdk/shared/types.d.ts +18 -4
  12. package/dist/standalone-webview/index.js +27 -27
  13. package/dist/standalone-webview/index.js.map +1 -1
  14. package/dist/standalone-webview/style.css +1 -1
  15. package/dist/standalone.js +831 -328
  16. package/dist/webview/index.js +45 -45
  17. package/dist/webview/index.js.map +1 -1
  18. package/dist/webview/style.css +1 -1
  19. package/package.json +4 -3
  20. package/src/cli/index.ts +217 -95
  21. package/src/extension/KanbanPanel.ts +49 -22
  22. package/src/mcp-server/index.ts +138 -62
  23. package/src/sdk/KanbanSDK.ts +283 -77
  24. package/src/sdk/__tests__/KanbanSDK.test.ts +5 -5
  25. package/src/sdk/__tests__/migration.test.ts +269 -0
  26. package/src/sdk/__tests__/multi-board.test.ts +449 -0
  27. package/src/sdk/index.ts +4 -3
  28. package/src/sdk/migration.ts +52 -0
  29. package/src/sdk/types.ts +3 -6
  30. package/src/shared/config.ts +144 -22
  31. package/src/shared/types.ts +14 -5
  32. package/src/standalone/__tests__/server.integration.test.ts +38 -37
  33. package/src/standalone/server.ts +239 -21
  34. package/src/webview/App.tsx +17 -7
  35. package/src/webview/components/Toolbar.tsx +99 -3
  36. package/src/webview/store/index.ts +11 -3
  37. package/.kanban/backlog/1-test1.md +0 -30
  38. package/.kanban.json +0 -42
package/src/cli/index.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  import * as path from 'path'
2
2
  import * as fs from 'fs/promises'
3
3
  import { KanbanSDK } from '../sdk/KanbanSDK'
4
- import type { Feature, FeatureStatus, Priority } from '../shared/types'
4
+ import type { Feature, Priority } from '../shared/types'
5
5
  import { loadWebhooks, createWebhook, deleteWebhook } from '../standalone/webhooks'
6
6
  import { readConfig, writeConfig, configToSettings, settingsToConfig } from '../shared/config'
7
7
  import type { CardDisplaySettings } from '../shared/types'
8
8
 
9
- const VALID_STATUSES: FeatureStatus[] = ['backlog', 'todo', 'in-progress', 'review', 'done']
10
9
  const VALID_PRIORITIES: Priority[] = ['critical', 'high', 'medium', 'low']
11
10
 
12
11
  // --- Arg parsing ---
@@ -36,6 +35,19 @@ function parseArgs(argv: string[]): { command: string; positional: string[]; fla
36
35
  return { command, positional, flags }
37
36
  }
38
37
 
38
+ // --- Board ID helper ---
39
+
40
+ function getBoardId(flags: Record<string, string | true>): string | undefined {
41
+ return typeof flags.board === 'string' ? flags.board : undefined
42
+ }
43
+
44
+ // --- Dynamic status validation ---
45
+
46
+ async function getValidStatuses(sdk: KanbanSDK, boardId?: string): Promise<string[]> {
47
+ const columns = await sdk.listColumns(boardId)
48
+ return columns.map(c => c.id)
49
+ }
50
+
39
51
  // --- Resolve features directory ---
40
52
 
41
53
  async function findWorkspaceRoot(startDir: string): Promise<string> {
@@ -129,6 +141,9 @@ function formatCardDetail(c: Feature): string {
129
141
  ` Modified: ${c.modified}`,
130
142
  ` File: ${c.filePath}`,
131
143
  ]
144
+ if (c.boardId) {
145
+ lines.push(` Board: ${c.boardId}`)
146
+ }
132
147
  if (c.completedAt) {
133
148
  lines.push(` Completed: ${c.completedAt}`)
134
149
  }
@@ -143,7 +158,8 @@ function formatCardDetail(c: Feature): string {
143
158
  // --- Card Commands ---
144
159
 
145
160
  async function cmdList(sdk: KanbanSDK, flags: Record<string, string | true>): Promise<void> {
146
- let cards = await sdk.listCards()
161
+ const boardId = getBoardId(flags)
162
+ let cards = await sdk.listCards(undefined, boardId)
147
163
 
148
164
  if (typeof flags.status === 'string') {
149
165
  cards = cards.filter(c => c.status === flags.status)
@@ -183,10 +199,11 @@ async function cmdShow(sdk: KanbanSDK, positional: string[], flags: Record<strin
183
199
  process.exit(1)
184
200
  }
185
201
 
186
- const card = await sdk.getCard(cardId)
202
+ const boardId = getBoardId(flags)
203
+ const card = await sdk.getCard(cardId, boardId)
187
204
  if (!card) {
188
205
  // Try partial match
189
- const all = await sdk.listCards()
206
+ const all = await sdk.listCards(undefined, boardId)
190
207
  const matches = all.filter(c => c.id.includes(cardId))
191
208
  if (matches.length === 1) {
192
209
  if (flags.json) {
@@ -218,10 +235,14 @@ async function cmdAdd(sdk: KanbanSDK, flags: Record<string, string | true>): Pro
218
235
  process.exit(1)
219
236
  }
220
237
 
221
- const status = (typeof flags.status === 'string' ? flags.status : 'backlog') as FeatureStatus
222
- if (!VALID_STATUSES.includes(status)) {
223
- console.error(red(`Invalid status: ${status}. Must be one of: ${VALID_STATUSES.join(', ')}`))
224
- process.exit(1)
238
+ const boardId = getBoardId(flags)
239
+ const status = typeof flags.status === 'string' ? flags.status : undefined
240
+ if (status) {
241
+ const validStatuses = await getValidStatuses(sdk, boardId)
242
+ if (!validStatuses.includes(status)) {
243
+ console.error(red(`Invalid status: ${status}. Must be one of: ${validStatuses.join(', ')}`))
244
+ process.exit(1)
245
+ }
225
246
  }
226
247
 
227
248
  const priority = (typeof flags.priority === 'string' ? flags.priority : 'medium') as Priority
@@ -237,7 +258,7 @@ async function cmdAdd(sdk: KanbanSDK, flags: Record<string, string | true>): Pro
237
258
 
238
259
  const content = `# ${title}${body ? '\n\n' + body : ''}`
239
260
 
240
- const card = await sdk.createCard({ content, status, priority, assignee, dueDate, labels })
261
+ const card = await sdk.createCard({ content, status, priority, assignee, dueDate, labels, boardId })
241
262
 
242
263
  if (flags.json) {
243
264
  console.log(JSON.stringify(card, null, 2))
@@ -250,37 +271,25 @@ async function cmdAdd(sdk: KanbanSDK, flags: Record<string, string | true>): Pro
250
271
 
251
272
  async function cmdMove(sdk: KanbanSDK, positional: string[], flags: Record<string, string | true>): Promise<void> {
252
273
  const cardId = positional[0]
253
- const newStatus = positional[1] as FeatureStatus
274
+ const newStatus = positional[1]
254
275
 
255
276
  if (!cardId || !newStatus) {
256
277
  console.error(red('Usage: kl move <id> <status> [--position <n>]'))
257
278
  process.exit(1)
258
279
  }
259
- if (!VALID_STATUSES.includes(newStatus)) {
260
- console.error(red(`Invalid status: ${newStatus}. Must be one of: ${VALID_STATUSES.join(', ')}`))
280
+
281
+ const boardId = getBoardId(flags)
282
+ const validStatuses = await getValidStatuses(sdk, boardId)
283
+ if (!validStatuses.includes(newStatus)) {
284
+ console.error(red(`Invalid status: ${newStatus}. Must be one of: ${validStatuses.join(', ')}`))
261
285
  process.exit(1)
262
286
  }
263
287
 
264
288
  // Support partial ID match
265
- let resolvedId = cardId
266
- const card = await sdk.getCard(cardId)
267
- if (!card) {
268
- const all = await sdk.listCards()
269
- const matches = all.filter(c => c.id.includes(cardId))
270
- if (matches.length === 1) {
271
- resolvedId = matches[0].id
272
- } else if (matches.length > 1) {
273
- console.error(red(`Multiple cards match "${cardId}":`))
274
- for (const m of matches) console.error(` ${m.id}`)
275
- process.exit(1)
276
- } else {
277
- console.error(red(`Card not found: ${cardId}`))
278
- process.exit(1)
279
- }
280
- }
289
+ const resolvedId = await resolveCardId(sdk, cardId, boardId)
281
290
 
282
291
  const position = typeof flags.position === 'string' ? parseInt(flags.position, 10) : undefined
283
- const updated = await sdk.moveCard(resolvedId, newStatus, position)
292
+ const updated = await sdk.moveCard(resolvedId, newStatus, position, boardId)
284
293
  console.log(green(`Moved ${updated.id} → ${colorStatus(newStatus)}`))
285
294
  }
286
295
 
@@ -291,31 +300,19 @@ async function cmdEdit(sdk: KanbanSDK, positional: string[], flags: Record<strin
291
300
  process.exit(1)
292
301
  }
293
302
 
303
+ const boardId = getBoardId(flags)
304
+
294
305
  // Support partial ID match
295
- let resolvedId = cardId
296
- const card = await sdk.getCard(cardId)
297
- if (!card) {
298
- const all = await sdk.listCards()
299
- const matches = all.filter(c => c.id.includes(cardId))
300
- if (matches.length === 1) {
301
- resolvedId = matches[0].id
302
- } else if (matches.length > 1) {
303
- console.error(red(`Multiple cards match "${cardId}":`))
304
- for (const m of matches) console.error(` ${m.id}`)
305
- process.exit(1)
306
- } else {
307
- console.error(red(`Card not found: ${cardId}`))
308
- process.exit(1)
309
- }
310
- }
306
+ const resolvedId = await resolveCardId(sdk, cardId, boardId)
311
307
 
312
308
  const updates: Partial<Feature> = {}
313
309
  if (typeof flags.status === 'string') {
314
- if (!VALID_STATUSES.includes(flags.status as FeatureStatus)) {
315
- console.error(red(`Invalid status: ${flags.status}`))
310
+ const validStatuses = await getValidStatuses(sdk, boardId)
311
+ if (!validStatuses.includes(flags.status)) {
312
+ console.error(red(`Invalid status: ${flags.status}. Must be one of: ${validStatuses.join(', ')}`))
316
313
  process.exit(1)
317
314
  }
318
- updates.status = flags.status as FeatureStatus
315
+ updates.status = flags.status
319
316
  }
320
317
  if (typeof flags.priority === 'string') {
321
318
  if (!VALID_PRIORITIES.includes(flags.priority as Priority)) {
@@ -333,36 +330,23 @@ async function cmdEdit(sdk: KanbanSDK, positional: string[], flags: Record<strin
333
330
  process.exit(1)
334
331
  }
335
332
 
336
- const updated = await sdk.updateCard(resolvedId, updates)
333
+ const updated = await sdk.updateCard(resolvedId, updates, boardId)
337
334
  console.log(green(`Updated: ${updated.id}`))
338
335
  }
339
336
 
340
- async function cmdDelete(sdk: KanbanSDK, positional: string[]): Promise<void> {
337
+ async function cmdDelete(sdk: KanbanSDK, positional: string[], flags: Record<string, string | true>): Promise<void> {
341
338
  const cardId = positional[0]
342
339
  if (!cardId) {
343
340
  console.error(red('Usage: kl delete <id>'))
344
341
  process.exit(1)
345
342
  }
346
343
 
344
+ const boardId = getBoardId(flags)
345
+
347
346
  // Support partial ID match
348
- let resolvedId = cardId
349
- const card = await sdk.getCard(cardId)
350
- if (!card) {
351
- const all = await sdk.listCards()
352
- const matches = all.filter(c => c.id.includes(cardId))
353
- if (matches.length === 1) {
354
- resolvedId = matches[0].id
355
- } else if (matches.length > 1) {
356
- console.error(red(`Multiple cards match "${cardId}":`))
357
- for (const m of matches) console.error(` ${m.id}`)
358
- process.exit(1)
359
- } else {
360
- console.error(red(`Card not found: ${cardId}`))
361
- process.exit(1)
362
- }
363
- }
347
+ const resolvedId = await resolveCardId(sdk, cardId, boardId)
364
348
 
365
- await sdk.deleteCard(resolvedId)
349
+ await sdk.deleteCard(resolvedId, boardId)
366
350
  console.log(green(`Deleted: ${resolvedId}`))
367
351
  }
368
352
 
@@ -371,16 +355,131 @@ async function cmdInit(sdk: KanbanSDK): Promise<void> {
371
355
  console.log(green(`Initialized: ${sdk.featuresDir}`))
372
356
  }
373
357
 
358
+ // --- Board Commands ---
359
+
360
+ async function cmdBoards(sdk: KanbanSDK, positional: string[], flags: Record<string, string | true>, workspaceRoot: string): Promise<void> {
361
+ const subcommand = positional[0] || 'list'
362
+
363
+ switch (subcommand) {
364
+ case 'list': {
365
+ const boards = sdk.listBoards()
366
+ if (flags.json) {
367
+ console.log(JSON.stringify(boards, null, 2))
368
+ } else if (boards.length === 0) {
369
+ console.log(dim(' No boards found.'))
370
+ } else {
371
+ console.log(` ${dim('ID'.padEnd(20))} ${dim('NAME'.padEnd(20))} ${dim('DESCRIPTION')}`)
372
+ console.log(dim(' ' + '-'.repeat(60)))
373
+ for (const b of boards) {
374
+ console.log(` ${bold(b.id.padEnd(20))} ${b.name.padEnd(20)} ${b.description || '-'}`)
375
+ }
376
+ }
377
+ break
378
+ }
379
+ case 'add': {
380
+ const id = typeof flags.id === 'string' ? flags.id : ''
381
+ const name = typeof flags.name === 'string' ? flags.name : ''
382
+ if (!id || !name) {
383
+ console.error(red('Usage: kl boards add --id <id> --name <name> [--description <desc>]'))
384
+ process.exit(1)
385
+ }
386
+ const description = typeof flags.description === 'string' ? flags.description : undefined
387
+ const board = await sdk.createBoard(id, name, { description })
388
+ if (flags.json) {
389
+ console.log(JSON.stringify(board, null, 2))
390
+ } else {
391
+ console.log(green(`Created board: ${board.id} (${board.name})`))
392
+ }
393
+ break
394
+ }
395
+ case 'show': {
396
+ const boardId = positional[1]
397
+ if (!boardId) {
398
+ console.error(red('Usage: kl boards show <id>'))
399
+ process.exit(1)
400
+ }
401
+ const board = sdk.getBoard(boardId)
402
+ if (flags.json) {
403
+ console.log(JSON.stringify(board, null, 2))
404
+ } else {
405
+ console.log(`${bold(board.name)}`)
406
+ console.log(` ID: ${boardId}`)
407
+ if (board.description) console.log(` Description: ${board.description}`)
408
+ console.log(` Columns: ${board.columns.map(c => c.name).join(', ')}`)
409
+ console.log(` Next Card: ${board.nextCardId}`)
410
+ console.log(` Default: status=${board.defaultStatus}, priority=${board.defaultPriority}`)
411
+ }
412
+ break
413
+ }
414
+ case 'remove':
415
+ case 'rm': {
416
+ const boardId = positional[1]
417
+ if (!boardId) {
418
+ console.error(red('Usage: kl boards remove <id>'))
419
+ process.exit(1)
420
+ }
421
+ await sdk.deleteBoard(boardId)
422
+ console.log(green(`Removed board: ${boardId}`))
423
+ break
424
+ }
425
+ case 'default': {
426
+ const boardId = positional[1]
427
+ if (!boardId) {
428
+ const config = readConfig(workspaceRoot)
429
+ console.log(config.defaultBoard)
430
+ break
431
+ }
432
+ const config = readConfig(workspaceRoot)
433
+ if (!config.boards[boardId]) {
434
+ console.error(red(`Board not found: ${boardId}`))
435
+ process.exit(1)
436
+ }
437
+ config.defaultBoard = boardId
438
+ writeConfig(workspaceRoot, config)
439
+ console.log(green(`Default board set to: ${boardId}`))
440
+ break
441
+ }
442
+ default:
443
+ console.error(red(`Unknown boards subcommand: ${subcommand}`))
444
+ console.error('Available: list, add, show, remove, default')
445
+ process.exit(1)
446
+ }
447
+ }
448
+
449
+ // --- Transfer Command ---
450
+
451
+ async function cmdTransfer(sdk: KanbanSDK, positional: string[], flags: Record<string, string | true>): Promise<void> {
452
+ const cardId = positional[0]
453
+ if (!cardId) {
454
+ console.error(red('Usage: kl transfer <card-id> --from <board> --to <board> [--status <status>]'))
455
+ process.exit(1)
456
+ }
457
+
458
+ const fromBoard = typeof flags.from === 'string' ? flags.from : undefined
459
+ const toBoard = typeof flags.to === 'string' ? flags.to : undefined
460
+
461
+ if (!fromBoard || !toBoard) {
462
+ console.error(red('Both --from and --to are required'))
463
+ process.exit(1)
464
+ }
465
+
466
+ const targetStatus = typeof flags.status === 'string' ? flags.status : undefined
467
+ const resolvedId = await resolveCardId(sdk, cardId, fromBoard)
468
+ const card = await sdk.transferCard(resolvedId, fromBoard, toBoard, targetStatus)
469
+ console.log(green(`Transferred ${card.id} from ${fromBoard} → ${toBoard} (${colorStatus(card.status)})`))
470
+ }
471
+
374
472
  // --- Attachment Commands ---
375
473
 
376
474
  async function cmdAttach(sdk: KanbanSDK, positional: string[], flags: Record<string, string | true>): Promise<void> {
377
475
  const subcommand = positional[0] || 'list'
378
476
  const cardId = positional[1]
477
+ const boardId = getBoardId(flags)
379
478
 
380
479
  if (subcommand !== 'list' && subcommand !== 'add' && subcommand !== 'rm' && subcommand !== 'remove') {
381
480
  // If first positional looks like a card ID, treat it as "list <cardId>"
382
- const resolvedId = await resolveCardId(sdk, subcommand)
383
- const attachments = await sdk.listAttachments(resolvedId)
481
+ const resolvedId = await resolveCardId(sdk, subcommand, boardId)
482
+ const attachments = await sdk.listAttachments(resolvedId, boardId)
384
483
  if (flags.json) {
385
484
  console.log(JSON.stringify(attachments, null, 2))
386
485
  } else if (attachments.length === 0) {
@@ -397,8 +496,8 @@ async function cmdAttach(sdk: KanbanSDK, positional: string[], flags: Record<str
397
496
  console.error(red('Usage: kl attach list <card-id>'))
398
497
  process.exit(1)
399
498
  }
400
- const resolvedId = await resolveCardId(sdk, cardId)
401
- const attachments = await sdk.listAttachments(resolvedId)
499
+ const resolvedId = await resolveCardId(sdk, cardId, boardId)
500
+ const attachments = await sdk.listAttachments(resolvedId, boardId)
402
501
  if (flags.json) {
403
502
  console.log(JSON.stringify(attachments, null, 2))
404
503
  } else if (attachments.length === 0) {
@@ -418,8 +517,8 @@ async function cmdAttach(sdk: KanbanSDK, positional: string[], flags: Record<str
418
517
  console.error(red('Usage: kl attach add <card-id> <file-path>'))
419
518
  process.exit(1)
420
519
  }
421
- const resolvedId = await resolveCardId(sdk, cardId)
422
- const updated = await sdk.addAttachment(resolvedId, filePath)
520
+ const resolvedId = await resolveCardId(sdk, cardId, boardId)
521
+ const updated = await sdk.addAttachment(resolvedId, filePath, boardId)
423
522
  console.log(green(`Attached to ${updated.id}: ${path.basename(filePath)}`))
424
523
  break
425
524
  }
@@ -434,19 +533,19 @@ async function cmdAttach(sdk: KanbanSDK, positional: string[], flags: Record<str
434
533
  console.error(red('Usage: kl attach remove <card-id> <filename>'))
435
534
  process.exit(1)
436
535
  }
437
- const resolvedId = await resolveCardId(sdk, cardId)
438
- const updated = await sdk.removeAttachment(resolvedId, filename)
536
+ const resolvedId = await resolveCardId(sdk, cardId, boardId)
537
+ const updated = await sdk.removeAttachment(resolvedId, filename, boardId)
439
538
  console.log(green(`Removed from ${updated.id}: ${filename}`))
440
539
  break
441
540
  }
442
541
  }
443
542
  }
444
543
 
445
- async function resolveCardId(sdk: KanbanSDK, cardId: string): Promise<string> {
446
- const card = await sdk.getCard(cardId)
544
+ async function resolveCardId(sdk: KanbanSDK, cardId: string, boardId?: string): Promise<string> {
545
+ const card = await sdk.getCard(cardId, boardId)
447
546
  if (card) return cardId
448
547
 
449
- const all = await sdk.listCards()
548
+ const all = await sdk.listCards(undefined, boardId)
450
549
  const matches = all.filter(c => c.id.includes(cardId))
451
550
  if (matches.length === 1) return matches[0].id
452
551
  if (matches.length > 1) {
@@ -462,11 +561,12 @@ async function resolveCardId(sdk: KanbanSDK, cardId: string): Promise<string> {
462
561
 
463
562
  async function cmdComment(sdk: KanbanSDK, positional: string[], flags: Record<string, string | true>): Promise<void> {
464
563
  const subcommand = positional[0] || 'list'
564
+ const boardId = getBoardId(flags)
465
565
 
466
566
  if (subcommand !== 'list' && subcommand !== 'add' && subcommand !== 'edit' && subcommand !== 'remove' && subcommand !== 'rm') {
467
567
  // If first positional looks like a card ID, treat it as "list <cardId>"
468
- const resolvedId = await resolveCardId(sdk, subcommand)
469
- const comments = await sdk.listComments(resolvedId)
568
+ const resolvedId = await resolveCardId(sdk, subcommand, boardId)
569
+ const comments = await sdk.listComments(resolvedId, boardId)
470
570
  if (flags.json) {
471
571
  console.log(JSON.stringify(comments, null, 2))
472
572
  } else if (comments.length === 0) {
@@ -489,8 +589,8 @@ async function cmdComment(sdk: KanbanSDK, positional: string[], flags: Record<st
489
589
  console.error(red('Usage: kl comment list <card-id>'))
490
590
  process.exit(1)
491
591
  }
492
- const resolvedId = await resolveCardId(sdk, cardId)
493
- const comments = await sdk.listComments(resolvedId)
592
+ const resolvedId = await resolveCardId(sdk, cardId, boardId)
593
+ const comments = await sdk.listComments(resolvedId, boardId)
494
594
  if (flags.json) {
495
595
  console.log(JSON.stringify(comments, null, 2))
496
596
  } else if (comments.length === 0) {
@@ -519,8 +619,8 @@ async function cmdComment(sdk: KanbanSDK, positional: string[], flags: Record<st
519
619
  console.error(red('Error: --body is required'))
520
620
  process.exit(1)
521
621
  }
522
- const resolvedId = await resolveCardId(sdk, cardId)
523
- const card = await sdk.addComment(resolvedId, author, body)
622
+ const resolvedId = await resolveCardId(sdk, cardId, boardId)
623
+ const card = await sdk.addComment(resolvedId, author, body, boardId)
524
624
  const added = card.comments[card.comments.length - 1]
525
625
  if (flags.json) {
526
626
  console.log(JSON.stringify(added, null, 2))
@@ -544,10 +644,10 @@ async function cmdComment(sdk: KanbanSDK, positional: string[], flags: Record<st
544
644
  console.error(red('Error: --body is required'))
545
645
  process.exit(1)
546
646
  }
547
- const resolvedId = await resolveCardId(sdk, cardId)
548
- await sdk.updateComment(resolvedId, commentId, body)
647
+ const resolvedId = await resolveCardId(sdk, cardId, boardId)
648
+ await sdk.updateComment(resolvedId, commentId, body, boardId)
549
649
  if (flags.json) {
550
- const comments = await sdk.listComments(resolvedId)
650
+ const comments = await sdk.listComments(resolvedId, boardId)
551
651
  const updated = comments.find(c => c.id === commentId)
552
652
  console.log(JSON.stringify(updated, null, 2))
553
653
  } else {
@@ -566,8 +666,8 @@ async function cmdComment(sdk: KanbanSDK, positional: string[], flags: Record<st
566
666
  console.error(red('Usage: kl comment remove <card-id> <comment-id>'))
567
667
  process.exit(1)
568
668
  }
569
- const resolvedId = await resolveCardId(sdk, cardId)
570
- await sdk.deleteComment(resolvedId, commentId)
669
+ const resolvedId = await resolveCardId(sdk, cardId, boardId)
670
+ await sdk.deleteComment(resolvedId, commentId, boardId)
571
671
  console.log(green(`Deleted comment ${commentId}`))
572
672
  break
573
673
  }
@@ -578,10 +678,11 @@ async function cmdComment(sdk: KanbanSDK, positional: string[], flags: Record<st
578
678
 
579
679
  async function cmdColumns(sdk: KanbanSDK, positional: string[], flags: Record<string, string | true>): Promise<void> {
580
680
  const subcommand = positional[0] || 'list'
681
+ const boardId = getBoardId(flags)
581
682
 
582
683
  switch (subcommand) {
583
684
  case 'list': {
584
- const columns = await sdk.listColumns()
685
+ const columns = await sdk.listColumns(boardId)
585
686
  if (flags.json) {
586
687
  console.log(JSON.stringify(columns, null, 2))
587
688
  } else {
@@ -601,7 +702,7 @@ async function cmdColumns(sdk: KanbanSDK, positional: string[], flags: Record<st
601
702
  console.error(red('Usage: kl columns add --id <id> --name <name> [--color <hex>]'))
602
703
  process.exit(1)
603
704
  }
604
- const columns = await sdk.addColumn({ id, name, color })
705
+ const columns = await sdk.addColumn({ id, name, color }, boardId)
605
706
  console.log(green(`Added column: ${id} (${name})`))
606
707
  if (flags.json) console.log(JSON.stringify(columns, null, 2))
607
708
  break
@@ -619,7 +720,7 @@ async function cmdColumns(sdk: KanbanSDK, positional: string[], flags: Record<st
619
720
  console.error(red('No updates specified. Use --name or --color'))
620
721
  process.exit(1)
621
722
  }
622
- const columns = await sdk.updateColumn(columnId, updates)
723
+ const columns = await sdk.updateColumn(columnId, updates, boardId)
623
724
  console.log(green(`Updated column: ${columnId}`))
624
725
  if (flags.json) console.log(JSON.stringify(columns, null, 2))
625
726
  break
@@ -631,7 +732,7 @@ async function cmdColumns(sdk: KanbanSDK, positional: string[], flags: Record<st
631
732
  console.error(red('Usage: kl columns remove <id>'))
632
733
  process.exit(1)
633
734
  }
634
- const columns = await sdk.removeColumn(columnId)
735
+ const columns = await sdk.removeColumn(columnId, boardId)
635
736
  console.log(green(`Removed column: ${columnId}`))
636
737
  if (flags.json) console.log(JSON.stringify(columns, null, 2))
637
738
  break
@@ -821,6 +922,14 @@ ${bold('Card Commands:')}
821
922
  edit <id> [--field value] Update card fields
822
923
  delete <id> Delete a card
823
924
 
925
+ ${bold('Board Commands:')}
926
+ boards List boards
927
+ boards add Create a board (--id, --name, --description)
928
+ boards show <id> Show board details
929
+ boards remove <id> Remove a board
930
+ boards default [id] Get or set the default board
931
+ transfer <id> Transfer a card (--from, --to, --status)
932
+
824
933
  ${bold('Attachment Commands:')}
825
934
  attach <id> List attachments on a card
826
935
  attach add <id> <path> Attach a file to a card
@@ -856,6 +965,7 @@ ${bold('Other:')}
856
965
 
857
966
  ${bold('Global Options:')}
858
967
  --dir <path> Features directory (default: .kanban)
968
+ --board <id> Target board (default: default board)
859
969
  --json Output as JSON
860
970
 
861
971
  ${bold('List Filters:')}
@@ -873,6 +983,11 @@ ${bold('Add/Edit Options:')}
873
983
  --due <date> Due date
874
984
  --label <l1,l2> Labels (comma-separated)
875
985
 
986
+ ${bold('Transfer Options:')}
987
+ --from <board> Source board (required)
988
+ --to <board> Destination board (required)
989
+ --status <status> Target status in destination board
990
+
876
991
  ${bold('Webhook Options:')}
877
992
  --url <url> Webhook target URL (required for add)
878
993
  --events <e1,e2> Events to subscribe to (default: *)
@@ -928,7 +1043,14 @@ async function main(): Promise<void> {
928
1043
  break
929
1044
  case 'delete':
930
1045
  case 'rm':
931
- await cmdDelete(sdk, positional)
1046
+ await cmdDelete(sdk, positional, flags)
1047
+ break
1048
+ case 'boards':
1049
+ case 'board':
1050
+ await cmdBoards(sdk, positional, flags, workspaceRoot)
1051
+ break
1052
+ case 'transfer':
1053
+ await cmdTransfer(sdk, positional, flags)
932
1054
  break
933
1055
  case 'attach':
934
1056
  await cmdAttach(sdk, positional, flags)