kanban-lite 1.2.2 → 1.2.4

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 (39) hide show
  1. package/dist/cli.js +257 -90
  2. package/dist/extension.js +245 -78
  3. package/dist/mcp-server.js +117 -33
  4. package/dist/sdk/index.cjs +117 -32
  5. package/dist/sdk/index.mjs +117 -32
  6. package/dist/sdk/sdk/KanbanSDK.d.ts +27 -10
  7. package/dist/sdk/sdk/modules/cards.d.ts +11 -2
  8. package/dist/sdk/sdk/plugins/index.d.ts +7 -0
  9. package/dist/sdk/sdk/types.d.ts +12 -27
  10. package/dist/sdk/shared/config.d.ts +17 -1
  11. package/dist/standalone-webview/index.js +38 -38
  12. package/dist/standalone-webview/index.js.map +1 -1
  13. package/dist/standalone.js +307 -125
  14. package/package.json +1 -1
  15. package/src/cli/index.test.ts +157 -0
  16. package/src/cli/index.ts +1 -1
  17. package/src/mcp-server/index.test.ts +76 -0
  18. package/src/mcp-server/index.ts +1 -1
  19. package/src/sdk/KanbanSDK.d.ts +26 -10
  20. package/src/sdk/KanbanSDK.ts +37 -11
  21. package/src/sdk/__tests__/KanbanSDK.test.ts +79 -24
  22. package/src/sdk/integrationCatalog.ts +1 -0
  23. package/src/sdk/modules/cards.ts +13 -24
  24. package/src/sdk/plugins/index.d.ts +7 -0
  25. package/src/sdk/plugins/index.ts +17 -2
  26. package/src/sdk/types.d.ts +10 -26
  27. package/src/sdk/types.ts +11 -24
  28. package/src/sdk/webhooks.ts +19 -2
  29. package/src/shared/config.ts +130 -2
  30. package/src/standalone/__tests__/server.integration.test.ts +81 -2
  31. package/src/standalone/internal/runtime.ts +11 -6
  32. package/src/standalone/internal/websocket.ts +13 -3
  33. package/src/standalone/server.ts +67 -9
  34. package/src/standalone/watcherSetup.ts +9 -0
  35. package/src/webview/standalone-shim.ts +2 -1
  36. package/tmp/screenshots-workspace/.kanban/.active-card.json +5 -0
  37. package/tmp/screenshots-workspace/.kanban/boards/default/deleted/1-dddd.md +17 -0
  38. package/tmp/screenshots-workspace/.kanban/boards/default/deleted/attachments/1.log +1 -0
  39. package/tmp/screenshots-workspace/.kanban.json +59 -0
@@ -9,11 +9,11 @@ import type { StandaloneHttpHandler, StandaloneHttpPlugin } from '../sdk'
9
9
  import { createRouteMatcher, type StandaloneRequestContext, type StandaloneRouteHandler } from './internal/common'
10
10
  import { KANBAN_OPENAPI_SPEC } from './internal/openapi-spec'
11
11
  import { handleCardFileRoute, setupStandaloneLifecycle } from './internal/lifecycle'
12
- import { createStandaloneRuntime, indexHtml } from './internal/runtime'
12
+ import { createStandaloneRuntime, getIndexHtml } from './internal/runtime'
13
13
  import { handleBoardRoutes } from './internal/routes/boards'
14
14
  import { handleSystemRoutes } from './internal/routes/system'
15
15
  import { handleTaskRoutes } from './internal/routes/tasks'
16
- import { getRequestAuthContext, mergeRequestAuthContext, setRequestAuthContext } from './authUtils'
16
+ import { extractAuthContext, getRequestAuthContext, mergeRequestAuthContext, setRequestAuthContext } from './authUtils'
17
17
  import { attachWebSocketHandlers } from './internal/websocket'
18
18
  import { matchRoute, type IncomingMessageWithRawBody } from './httpUtils'
19
19
 
@@ -176,6 +176,7 @@ function collectStandaloneHttpHandlers(
176
176
  ): StandaloneHttpHandler[] {
177
177
  const plugins = ctx.sdk.capabilities?.standaloneHttpPlugins ?? []
178
178
  const registrationOptions = {
179
+ sdk: ctx.sdk,
179
180
  workspaceRoot: ctx.workspaceRoot,
180
181
  kanbanDir: ctx.absoluteKanbanDir,
181
182
  capabilities: ctx.sdk.capabilities?.providers ?? {
@@ -249,11 +250,15 @@ export function startServer(kanbanDir: string, port: number, webviewDir?: string
249
250
  ? { type: 'image/svg+xml', content: fs.readFileSync(swaggerUiLogoPath) }
250
251
  : null
251
252
 
252
- const runtime = createStandaloneRuntime(kanbanDir, webviewDir, fastify.server)
253
+ const workspaceRoot = path.dirname(path.resolve(kanbanDir))
254
+ const config = readConfig(workspaceRoot)
255
+ const rawBase = config.basePath ?? ''
256
+ const basePath = rawBase ? (rawBase.startsWith('/') ? rawBase : '/' + rawBase).replace(/\/+$/, '') : ''
257
+
258
+ const runtime = createStandaloneRuntime(kanbanDir, webviewDir, fastify.server, basePath)
253
259
  const { ctx, resolvedWebviewDir } = runtime
254
260
 
255
- const config = readConfig(ctx.workspaceRoot)
256
- let resolvedIndexHtml = indexHtml
261
+ let resolvedIndexHtml = getIndexHtml(basePath)
257
262
  let customHead = config.customHeadHtml || ''
258
263
  if (config.customHeadHtmlFile) {
259
264
  try {
@@ -262,7 +267,7 @@ export function startServer(kanbanDir: string, port: number, webviewDir?: string
262
267
  } catch { /* file not found, fall back to customHeadHtml */ }
263
268
  }
264
269
  if (customHead) {
265
- resolvedIndexHtml = indexHtml.replace('</head>', `${customHead}\n</head>`)
270
+ resolvedIndexHtml = resolvedIndexHtml.replace('</head>', `${customHead}\n</head>`)
266
271
  }
267
272
  const standaloneHttpPlugins = ctx.sdk.capabilities?.standaloneHttpPlugins ?? []
268
273
  const standaloneOpenApiSpec = buildStandaloneOpenApiSpec(standaloneHttpPlugins)
@@ -270,7 +275,7 @@ export function startServer(kanbanDir: string, port: number, webviewDir?: string
270
275
  // OpenAPI spec and interactive docs (served before the catch-all so Fastify prefers these routes)
271
276
  fastify.register(swagger, { openapi: standaloneOpenApiSpec as any })
272
277
  fastify.register(swaggerUi, {
273
- routePrefix: '/api/docs',
278
+ routePrefix: `${basePath}/api/docs`,
274
279
  uiConfig: { docExpansion: 'list', deepLinking: false },
275
280
  logo: swaggerUiLogo,
276
281
  ...(swaggerUiStaticDir ? { baseDir: swaggerUiStaticDir } : {}),
@@ -313,6 +318,16 @@ export function startServer(kanbanDir: string, port: number, webviewDir?: string
313
318
  req._rawBody = request.body
314
319
  }
315
320
 
321
+ // Strip base path prefix so internal route handlers see root-relative paths
322
+ if (basePath) {
323
+ const rawUrl = req.url ?? '/'
324
+ if (rawUrl === basePath) {
325
+ req.url = '/'
326
+ } else if (rawUrl.startsWith(basePath + '/') || rawUrl.startsWith(basePath + '?')) {
327
+ req.url = rawUrl.slice(basePath.length)
328
+ }
329
+ }
330
+
316
331
  const requestContext = createRequestContext(ctx, req, reply.raw, resolvedWebviewDir, resolvedIndexHtml)
317
332
 
318
333
  await dispatchRequest(requestContext, middlewareHandlers)
@@ -324,7 +339,50 @@ export function startServer(kanbanDir: string, port: number, webviewDir?: string
324
339
  reply.hijack()
325
340
  })
326
341
 
327
- attachWebSocketHandlers(ctx)
342
+ // Resolve auth context for WebSocket upgrade requests by running the middleware
343
+ // pipeline so session cookies set by auth plugins (e.g. kl-auth-plugin) are honoured.
344
+ const resolveWsAuthContext = async (req: http.IncomingMessage) => {
345
+ const silentRes = (() => {
346
+ const r: Record<string, unknown> = {
347
+ writableEnded: false,
348
+ writeHead() { return r },
349
+ setHeader() { return r },
350
+ removeHeader() { /* no-op */ },
351
+ getHeader() { return undefined },
352
+ getHeaders() { return {} },
353
+ end(..._args: unknown[]) { (r as { writableEnded: boolean }).writableEnded = true; return r },
354
+ write() { return false },
355
+ }
356
+ return r as unknown as import('http').ServerResponse
357
+ })()
358
+ const reqWithBody = req as IncomingMessageWithRawBody
359
+ const wsUrl = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`)
360
+ const requestContext: StandaloneRequestContext = {
361
+ ctx,
362
+ sdk: ctx.sdk,
363
+ workspaceRoot: ctx.workspaceRoot,
364
+ kanbanDir: ctx.absoluteKanbanDir,
365
+ req: reqWithBody,
366
+ res: silentRes,
367
+ url: wsUrl,
368
+ pathname: wsUrl.pathname,
369
+ method: 'GET',
370
+ resolvedWebviewDir,
371
+ indexHtml: resolvedIndexHtml,
372
+ route: createRouteMatcher('GET', wsUrl.pathname, matchRoute),
373
+ isApiRequest: false,
374
+ isPageRequest: false,
375
+ getAuthContext: () => getRequestAuthContext(req),
376
+ setAuthContext: (auth) => setRequestAuthContext(req, auth),
377
+ mergeAuthContext: (auth) => mergeRequestAuthContext(req, auth),
378
+ }
379
+ for (const handler of middlewareHandlers) {
380
+ if (await handler(requestContext)) break
381
+ }
382
+ return extractAuthContext(req)
383
+ }
384
+
385
+ attachWebSocketHandlers(ctx, resolveWsAuthContext)
328
386
  setupStandaloneLifecycle(ctx, fastify.server)
329
387
 
330
388
  const effectiveConfigPath = resolvedConfigPath ?? configPath(path.dirname(ctx.absoluteKanbanDir))
@@ -334,7 +392,7 @@ export function startServer(kanbanDir: string, port: number, webviewDir?: string
334
392
  console.error('Failed to start server:', err)
335
393
  process.exit(1)
336
394
  }
337
- console.log(`Kanban board running at http://localhost:${port}`)
395
+ console.log(`Kanban board running at http://localhost:${port}${basePath}`)
338
396
  console.log(`API available at http://localhost:${port}/api`)
339
397
  console.log(`Kanban config: ${effectiveConfigPath}`)
340
398
  console.log(`Kanban directory: ${ctx.absoluteKanbanDir}`)
@@ -110,4 +110,13 @@ export function setupWatcher(ctx: StandaloneContext, server: http.Server): void
110
110
  ctx.wss.close()
111
111
  })
112
112
  }
113
+
114
+ // Watch .kanban.json for config changes and re-broadcast init on change
115
+ const configFilePath = path.join(ctx.workspaceRoot, '.kanban.json')
116
+ const configWatcher = chokidar.watch(configFilePath, {
117
+ ignoreInitial: true,
118
+ awaitWriteFinish: { stabilityThreshold: 100 }
119
+ })
120
+ configWatcher.on('change', () => handleFileChange(ctx, debounceRef))
121
+ server.on('close', () => configWatcher.close())
113
122
  }
@@ -6,7 +6,8 @@ import type { ConnectionStatusMessage, ExtensionMessage, WebviewMessage } from '
6
6
 
7
7
  if (!('acquireVsCodeApi' in window)) {
8
8
 
9
- const WS_URL = `ws://${window.location.host}/ws`
9
+ const kbBase = (window as unknown as { __KB_BASE__?: string }).__KB_BASE__ ?? ''
10
+ const WS_URL = `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}${kbBase}/ws`
10
11
  const RECONNECT_DELAYS_MS = [250, 500, 1000, 2000, 4000] as const
11
12
  const MAX_RETRIES = RECONNECT_DELAYS_MS.length
12
13
 
@@ -0,0 +1,5 @@
1
+ {
2
+ "cardId": "1",
3
+ "boardId": "default",
4
+ "updatedAt": "2026-03-26T00:54:58.370Z"
5
+ }
@@ -0,0 +1,17 @@
1
+ ---
2
+ version: 1
3
+ id: "1"
4
+ status: "deleted"
5
+ priority: "medium"
6
+ assignee: null
7
+ dueDate: null
8
+ created: "2026-03-26T00:54:50.866Z"
9
+ modified: "2026-03-26T00:55:04.504Z"
10
+ completedAt: null
11
+ labels: []
12
+ attachments:
13
+ - "1.log"
14
+ order: "a0"
15
+ ---
16
+
17
+ # dddd
@@ -0,0 +1 @@
1
+ 2026-03-26T00:55:04.503Z [system] Status changed: `in-progress` → `deleted` {"previousStatus":"in-progress","status":"deleted","activity":{"type":"card.status.changed","qualifiesForUnread":true}}
@@ -0,0 +1,59 @@
1
+ {
2
+ "version": 2,
3
+ "boards": {
4
+ "default": {
5
+ "name": "Default",
6
+ "columns": [
7
+ {
8
+ "id": "backlog",
9
+ "name": "Backlog",
10
+ "color": "#6b7280"
11
+ },
12
+ {
13
+ "id": "todo",
14
+ "name": "To Do",
15
+ "color": "#3b82f6"
16
+ },
17
+ {
18
+ "id": "in-progress",
19
+ "name": "In Progress",
20
+ "color": "#f59e0b"
21
+ },
22
+ {
23
+ "id": "review",
24
+ "name": "Review",
25
+ "color": "#8b5cf6"
26
+ },
27
+ {
28
+ "id": "done",
29
+ "name": "Done",
30
+ "color": "#22c55e"
31
+ }
32
+ ],
33
+ "nextCardId": 1,
34
+ "defaultStatus": "backlog",
35
+ "defaultPriority": "medium"
36
+ }
37
+ },
38
+ "defaultBoard": "default",
39
+ "kanbanDirectory": ".kanban",
40
+ "aiAgent": "claude",
41
+ "defaultPriority": "medium",
42
+ "defaultStatus": "backlog",
43
+ "nextCardId": 2,
44
+ "showPriorityBadges": true,
45
+ "showAssignee": true,
46
+ "showDueDate": true,
47
+ "showLabels": true,
48
+ "showBuildWithAI": true,
49
+ "showFileName": false,
50
+ "compactMode": false,
51
+ "markdownEditorMode": false,
52
+ "showDeletedColumn": false,
53
+ "boardZoom": 100,
54
+ "cardZoom": 100,
55
+ "boardBackgroundMode": "fancy",
56
+ "boardBackgroundPreset": "aurora",
57
+ "port": 2954,
58
+ "labels": {}
59
+ }