@andypai/agent-kanban 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.
- package/README.md +34 -5
- package/package.json +1 -1
- package/src/__tests__/activity.test.ts +2 -2
- package/src/__tests__/api.test.ts +3 -3
- package/src/__tests__/commands/board.test.ts +3 -3
- package/src/__tests__/commands/bulk.test.ts +3 -3
- package/src/__tests__/commands/column.test.ts +4 -4
- package/src/__tests__/conflict.test.ts +3 -3
- package/src/__tests__/db.test.ts +2 -2
- package/src/__tests__/id.test.ts +1 -1
- package/src/__tests__/index.test.ts +3 -3
- package/src/__tests__/jira-adf.test.ts +270 -4
- package/src/__tests__/jira-cache.test.ts +1 -1
- package/src/__tests__/jira-client.test.ts +2 -2
- package/src/__tests__/jira-provider-comment.test.ts +3 -3
- package/src/__tests__/jira-provider-mutations.test.ts +4 -4
- package/src/__tests__/jira-provider-read.test.ts +52 -6
- package/src/__tests__/jira-wiring.test.ts +3 -3
- package/src/__tests__/linear-cache-description-activity.test.ts +1 -1
- package/src/__tests__/linear-provider-comment.test.ts +2 -2
- package/src/__tests__/linear-provider-sync.test.ts +4 -9
- package/src/__tests__/local-provider-comment.test.ts +2 -2
- package/src/__tests__/mcp-core.test.ts +4 -4
- package/src/__tests__/mcp-server.test.ts +3 -3
- package/src/__tests__/metrics.test.ts +2 -2
- package/src/__tests__/output.test.ts +1 -1
- package/src/__tests__/provider-capabilities.test.ts +40 -0
- package/src/__tests__/server.test.ts +3 -10
- package/src/__tests__/webhooks.test.ts +6 -6
- package/src/activity.ts +2 -2
- package/src/api.ts +3 -3
- package/src/commands/board.ts +4 -4
- package/src/commands/bulk.ts +4 -4
- package/src/commands/column.ts +4 -4
- package/src/commands/mcp.ts +3 -3
- package/src/config.ts +1 -1
- package/src/db.ts +4 -4
- package/src/index.ts +13 -19
- package/src/mcp/core.ts +4 -4
- package/src/mcp/errors.ts +1 -1
- package/src/mcp/index.ts +6 -6
- package/src/mcp/server.ts +3 -3
- package/src/mcp/types.ts +2 -2
- package/src/metrics.ts +1 -1
- package/src/output.ts +1 -1
- package/src/providers/capabilities.ts +21 -31
- package/src/providers/errors.ts +1 -1
- package/src/providers/index.ts +6 -6
- package/src/providers/jira-adf.ts +116 -12
- package/src/providers/jira-cache.ts +1 -1
- package/src/providers/jira-client.ts +2 -2
- package/src/providers/jira.ts +9 -14
- package/src/providers/linear-cache.ts +1 -1
- package/src/providers/linear-client.ts +3 -6
- package/src/providers/linear.ts +8 -13
- package/src/providers/local.ts +8 -8
- package/src/providers/types.ts +2 -2
- package/src/server.ts +2 -2
- package/src/types.ts +1 -0
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
//
|
|
3
3
|
// This module is intentionally dependency-free: no imports from the rest of the
|
|
4
4
|
// provider stack, no bun:sqlite, no fetch. It models only the subset of ADF
|
|
5
|
-
// that agent-kanban
|
|
5
|
+
// that agent-kanban writes, plus a few Jira nodes it must preserve on read:
|
|
6
6
|
//
|
|
7
|
-
// doc > { paragraph | bulletList | orderedList | codeBlock | heading }
|
|
8
|
-
// paragraph / heading / codeBlock > text(inline)
|
|
7
|
+
// doc > { paragraph | bulletList | orderedList | codeBlock | heading | card }
|
|
8
|
+
// paragraph / heading / codeBlock > text(inline) | inlineCard | hardBreak
|
|
9
9
|
// bulletList / orderedList > listItem > paragraph > text
|
|
10
10
|
//
|
|
11
|
-
//
|
|
12
|
-
// never emitted on the write path.
|
|
11
|
+
// Other unknown node types are tolerated on the read path (skipped silently)
|
|
12
|
+
// and never emitted on the write path.
|
|
13
13
|
|
|
14
14
|
export interface AdfDocument {
|
|
15
15
|
version: 1
|
|
@@ -17,10 +17,15 @@ export interface AdfDocument {
|
|
|
17
17
|
content: AdfBlockNode[]
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export interface AdfMark {
|
|
21
|
+
type: string
|
|
22
|
+
attrs?: Record<string, unknown>
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
export interface AdfTextNode {
|
|
21
26
|
type: 'text'
|
|
22
27
|
text: string
|
|
23
|
-
marks?:
|
|
28
|
+
marks?: AdfMark[]
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
export interface AdfUnknownInlineNode {
|
|
@@ -92,7 +97,7 @@ function paragraphFromText(text: string): AdfParagraphNode {
|
|
|
92
97
|
}
|
|
93
98
|
return {
|
|
94
99
|
type: 'paragraph',
|
|
95
|
-
content:
|
|
100
|
+
content: tokenizeInline(text),
|
|
96
101
|
}
|
|
97
102
|
}
|
|
98
103
|
|
|
@@ -103,6 +108,39 @@ function listItemFromText(text: string): AdfListItemNode {
|
|
|
103
108
|
}
|
|
104
109
|
}
|
|
105
110
|
|
|
111
|
+
const INLINE_MARK = /\*\*([^*\n]+)\*\*|\[([^\]\n]+)\]\((https?:\/\/[^)\s]+)\)/g
|
|
112
|
+
|
|
113
|
+
function tokenizeInline(text: string): AdfTextNode[] {
|
|
114
|
+
const out: AdfTextNode[] = []
|
|
115
|
+
INLINE_MARK.lastIndex = 0
|
|
116
|
+
let cursor = 0
|
|
117
|
+
for (const match of text.matchAll(INLINE_MARK)) {
|
|
118
|
+
const start = match.index ?? 0
|
|
119
|
+
if (start > cursor) {
|
|
120
|
+
out.push({ type: 'text', text: text.slice(cursor, start) })
|
|
121
|
+
}
|
|
122
|
+
const boldText = match[1]
|
|
123
|
+
if (boldText !== undefined) {
|
|
124
|
+
out.push({
|
|
125
|
+
type: 'text',
|
|
126
|
+
text: boldText,
|
|
127
|
+
marks: [{ type: 'strong' }],
|
|
128
|
+
})
|
|
129
|
+
} else {
|
|
130
|
+
out.push({
|
|
131
|
+
type: 'text',
|
|
132
|
+
text: match[2]!,
|
|
133
|
+
marks: [{ type: 'link', attrs: { href: match[3]! } }],
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
cursor = start + match[0].length
|
|
137
|
+
}
|
|
138
|
+
if (cursor < text.length) {
|
|
139
|
+
out.push({ type: 'text', text: text.slice(cursor) })
|
|
140
|
+
}
|
|
141
|
+
return out
|
|
142
|
+
}
|
|
143
|
+
|
|
106
144
|
export function plainTextToAdf(text: string): AdfDocument {
|
|
107
145
|
if (text.length === 0) {
|
|
108
146
|
return { version: 1, type: 'doc', content: [] }
|
|
@@ -211,18 +249,79 @@ export function plainTextToAdf(text: string): AdfDocument {
|
|
|
211
249
|
return { version: 1, type: 'doc', content: blocks }
|
|
212
250
|
}
|
|
213
251
|
|
|
214
|
-
function inlineText(
|
|
252
|
+
function inlineText(
|
|
253
|
+
nodes: AdfInlineNode[] | undefined,
|
|
254
|
+
opts: { renderMarks?: boolean } = {},
|
|
255
|
+
): string {
|
|
215
256
|
if (!nodes) return ''
|
|
257
|
+
const renderMarks = opts.renderMarks ?? true
|
|
216
258
|
let out = ''
|
|
217
|
-
for (
|
|
259
|
+
for (let i = 0; i < nodes.length; i += 1) {
|
|
260
|
+
const node = nodes[i]!
|
|
218
261
|
if (node.type === 'text') {
|
|
219
|
-
|
|
262
|
+
const textNode = node as AdfTextNode
|
|
263
|
+
if (!renderMarks) {
|
|
264
|
+
out += textNode.text
|
|
265
|
+
continue
|
|
266
|
+
}
|
|
267
|
+
const nextNode = nodes[i + 1]
|
|
268
|
+
const labelColon = readPlainTextLeadingColon(nextNode)
|
|
269
|
+
if (labelColon && hasMark(textNode, 'strong')) {
|
|
270
|
+
out += renderTextNode({ ...textNode, text: `${textNode.text}:` })
|
|
271
|
+
out += labelColon.remainder
|
|
272
|
+
i += 1
|
|
273
|
+
continue
|
|
274
|
+
}
|
|
275
|
+
out += renderTextNode(textNode)
|
|
276
|
+
continue
|
|
220
277
|
}
|
|
221
|
-
|
|
278
|
+
if (node.type === 'inlineCard') {
|
|
279
|
+
const url = readCardUrl(node)
|
|
280
|
+
if (url) out += url
|
|
281
|
+
continue
|
|
282
|
+
}
|
|
283
|
+
if (node.type === 'hardBreak') {
|
|
284
|
+
out += '\n'
|
|
285
|
+
continue
|
|
286
|
+
}
|
|
287
|
+
// Other unknown inline nodes (mentions, emoji, etc.) are skipped.
|
|
288
|
+
}
|
|
289
|
+
return out
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function hasMark(node: AdfTextNode, type: string): boolean {
|
|
293
|
+
return node.marks?.some((m) => m.type === type) ?? false
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function readPlainTextLeadingColon(node: AdfInlineNode | undefined): { remainder: string } | null {
|
|
297
|
+
if (!node || node.type !== 'text') return null
|
|
298
|
+
const textNode = node as AdfTextNode
|
|
299
|
+
if (textNode.marks && textNode.marks.length > 0) return null
|
|
300
|
+
if (!textNode.text.startsWith(':')) return null
|
|
301
|
+
return { remainder: textNode.text.slice(1) }
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function renderTextNode(node: AdfTextNode): string {
|
|
305
|
+
if (!node.marks || node.marks.length === 0) return node.text
|
|
306
|
+
const link = node.marks.find((m) => m.type === 'link')
|
|
307
|
+
let out = node.text
|
|
308
|
+
if (link) {
|
|
309
|
+
const href = link.attrs?.['href']
|
|
310
|
+
if (typeof href === 'string' && href.length > 0) out = `[${out}](${href})`
|
|
311
|
+
}
|
|
312
|
+
if (hasMark(node, 'strong')) {
|
|
313
|
+
out = `**${out}**`
|
|
222
314
|
}
|
|
223
315
|
return out
|
|
224
316
|
}
|
|
225
317
|
|
|
318
|
+
function readCardUrl(node: AdfInlineNode | AdfBlockNode): string | null {
|
|
319
|
+
const attrs = (node as { attrs?: Record<string, unknown> }).attrs
|
|
320
|
+
if (!attrs) return null
|
|
321
|
+
const url = attrs['url']
|
|
322
|
+
return typeof url === 'string' && url.length > 0 ? url : null
|
|
323
|
+
}
|
|
324
|
+
|
|
226
325
|
function listItemInnerText(item: AdfListItemNode): string {
|
|
227
326
|
// Each list item wraps a paragraph (or nested blocks). We flatten to the
|
|
228
327
|
// first paragraph's inline text, which is all the write path produces.
|
|
@@ -252,12 +351,17 @@ function renderBlock(node: AdfBlockNode): string | null {
|
|
|
252
351
|
case 'codeBlock': {
|
|
253
352
|
const code = node as AdfCodeBlockNode
|
|
254
353
|
const language = code.attrs?.language ?? ''
|
|
255
|
-
const body = inlineText(code.content)
|
|
354
|
+
const body = inlineText(code.content, { renderMarks: false })
|
|
256
355
|
const fence = language.length > 0 ? `\`\`\`${language}` : '```'
|
|
257
356
|
return `${fence}\n${body}\n\`\`\``
|
|
258
357
|
}
|
|
259
358
|
case 'heading':
|
|
260
359
|
return inlineText((node as AdfHeadingNode).content)
|
|
360
|
+
case 'blockCard':
|
|
361
|
+
case 'embedCard': {
|
|
362
|
+
const url = readCardUrl(node)
|
|
363
|
+
return url ?? null
|
|
364
|
+
}
|
|
261
365
|
default:
|
|
262
366
|
// Unknown block node — skip entirely, never throw.
|
|
263
367
|
return null
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Database } from 'bun:sqlite'
|
|
2
|
-
import type { BoardView, ProviderTeamInfo, Task } from '../types
|
|
2
|
+
import type { BoardView, ProviderTeamInfo, Task } from '../types'
|
|
3
3
|
|
|
4
4
|
// Column ids are prefixed to avoid collisions across sources:
|
|
5
5
|
// - board-sourced columns: 'board:<boardId>:<columnName>'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer'
|
|
2
|
-
import { ErrorCode } from '../errors
|
|
3
|
-
import { providerUpstreamError } from './errors
|
|
2
|
+
import { ErrorCode } from '../errors'
|
|
3
|
+
import { providerUpstreamError } from './errors'
|
|
4
4
|
|
|
5
5
|
export interface JiraProject {
|
|
6
6
|
id: string
|
package/src/providers/jira.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Database } from 'bun:sqlite'
|
|
2
|
-
import { ErrorCode, KanbanError } from '../errors
|
|
2
|
+
import { ErrorCode, KanbanError } from '../errors'
|
|
3
3
|
import type {
|
|
4
4
|
ActivityEntry,
|
|
5
5
|
BoardBootstrap,
|
|
@@ -10,17 +10,12 @@ import type {
|
|
|
10
10
|
Priority,
|
|
11
11
|
TaskComment,
|
|
12
12
|
Task,
|
|
13
|
-
} from '../types
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} from '../webhooks.ts'
|
|
20
|
-
import { adfToPlainText, plainTextToAdf, type AdfDocument } from './jira-adf.ts'
|
|
21
|
-
import { JIRA_CAPABILITIES } from './capabilities.ts'
|
|
22
|
-
import { providerUpstreamError, unsupportedOperation } from './errors.ts'
|
|
23
|
-
import { JiraClient, type JiraComment, type JiraIssue } from './jira-client.ts'
|
|
13
|
+
} from '../types'
|
|
14
|
+
import { headerLower, verifyHmacSha256, type WebhookRequest, type WebhookResult } from '../webhooks'
|
|
15
|
+
import { adfToPlainText, plainTextToAdf, type AdfDocument } from './jira-adf'
|
|
16
|
+
import { JIRA_CAPABILITIES } from './capabilities'
|
|
17
|
+
import { providerUpstreamError, unsupportedOperation } from './errors'
|
|
18
|
+
import { JiraClient, type JiraComment, type JiraIssue } from './jira-client'
|
|
24
19
|
import {
|
|
25
20
|
adjustJiraIssueCommentCount,
|
|
26
21
|
decodeColumnStatusIds,
|
|
@@ -45,7 +40,7 @@ import {
|
|
|
45
40
|
upsertJiraUsers,
|
|
46
41
|
type JiraActivityRow,
|
|
47
42
|
type JiraSyncMeta,
|
|
48
|
-
} from './jira-cache
|
|
43
|
+
} from './jira-cache'
|
|
49
44
|
import type {
|
|
50
45
|
CreateTaskInput,
|
|
51
46
|
KanbanProvider,
|
|
@@ -53,7 +48,7 @@ import type {
|
|
|
53
48
|
ProviderSyncStatus,
|
|
54
49
|
TaskListFilters,
|
|
55
50
|
UpdateTaskInput,
|
|
56
|
-
} from './types
|
|
51
|
+
} from './types'
|
|
57
52
|
|
|
58
53
|
const SYNC_INTERVAL_MS = 30_000
|
|
59
54
|
const FULL_RECONCILE_INTERVAL_MS = 5 * 60_000
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ErrorCode } from '../errors
|
|
2
|
-
import { providerUpstreamError } from './errors
|
|
1
|
+
import { ErrorCode } from '../errors'
|
|
2
|
+
import { providerUpstreamError } from './errors'
|
|
3
3
|
|
|
4
4
|
interface GraphQLResponse<T> {
|
|
5
5
|
data?: T
|
|
@@ -57,7 +57,6 @@ interface LinearIssueNode {
|
|
|
57
57
|
state: { id: string; name: string; position: number }
|
|
58
58
|
labels?: { nodes: Array<{ id: string; name: string }> }
|
|
59
59
|
comments?: {
|
|
60
|
-
totalCount?: number
|
|
61
60
|
nodes: Array<{ id: string }>
|
|
62
61
|
pageInfo?: { hasNextPage: boolean; endCursor: string | null }
|
|
63
62
|
} | null
|
|
@@ -81,7 +80,7 @@ function toLinearIssue(node: LinearIssueNode): LinearIssue {
|
|
|
81
80
|
}
|
|
82
81
|
: null,
|
|
83
82
|
labels: node.labels?.nodes.map((label) => label.name) ?? [],
|
|
84
|
-
commentCount: node.comments?.
|
|
83
|
+
commentCount: node.comments?.nodes?.length ?? undefined,
|
|
85
84
|
}
|
|
86
85
|
}
|
|
87
86
|
|
|
@@ -284,7 +283,6 @@ export class LinearClient {
|
|
|
284
283
|
nodes { id name }
|
|
285
284
|
}
|
|
286
285
|
comments(first: 250) {
|
|
287
|
-
totalCount
|
|
288
286
|
nodes { id }
|
|
289
287
|
pageInfo { hasNextPage endCursor }
|
|
290
288
|
}
|
|
@@ -335,7 +333,6 @@ export class LinearClient {
|
|
|
335
333
|
state { id name position }
|
|
336
334
|
labels { nodes { id name } }
|
|
337
335
|
comments(first: 250) {
|
|
338
|
-
totalCount
|
|
339
336
|
nodes { id }
|
|
340
337
|
pageInfo { hasNextPage endCursor }
|
|
341
338
|
}
|
package/src/providers/linear.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Database } from 'bun:sqlite'
|
|
2
|
-
import { ErrorCode, KanbanError } from '../errors
|
|
2
|
+
import { ErrorCode, KanbanError } from '../errors'
|
|
3
3
|
import type {
|
|
4
4
|
ActivityEntry,
|
|
5
5
|
BoardBootstrap,
|
|
@@ -8,14 +8,9 @@ import type {
|
|
|
8
8
|
Column,
|
|
9
9
|
TaskComment,
|
|
10
10
|
Task,
|
|
11
|
-
} from '../types
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
verifyHmacSha256,
|
|
15
|
-
type WebhookRequest,
|
|
16
|
-
type WebhookResult,
|
|
17
|
-
} from '../webhooks.ts'
|
|
18
|
-
import { LINEAR_CAPABILITIES } from './capabilities.ts'
|
|
11
|
+
} from '../types'
|
|
12
|
+
import { headerLower, verifyHmacSha256, type WebhookRequest, type WebhookResult } from '../webhooks'
|
|
13
|
+
import { LINEAR_CAPABILITIES } from './capabilities'
|
|
19
14
|
import {
|
|
20
15
|
adjustLinearIssueCommentCount,
|
|
21
16
|
deleteLinearIssue,
|
|
@@ -35,9 +30,9 @@ import {
|
|
|
35
30
|
upsertProjects,
|
|
36
31
|
upsertUsers,
|
|
37
32
|
type LinearActivityRow,
|
|
38
|
-
} from './linear-cache
|
|
39
|
-
import { LinearClient, type LinearComment } from './linear-client
|
|
40
|
-
import { unsupportedOperation } from './errors
|
|
33
|
+
} from './linear-cache'
|
|
34
|
+
import { LinearClient, type LinearComment } from './linear-client'
|
|
35
|
+
import { unsupportedOperation } from './errors'
|
|
41
36
|
import type {
|
|
42
37
|
CreateTaskInput,
|
|
43
38
|
KanbanProvider,
|
|
@@ -45,7 +40,7 @@ import type {
|
|
|
45
40
|
ProviderSyncStatus,
|
|
46
41
|
TaskListFilters,
|
|
47
42
|
UpdateTaskInput,
|
|
48
|
-
} from './types
|
|
43
|
+
} from './types'
|
|
49
44
|
|
|
50
45
|
const SYNC_INTERVAL_MS = 30_000
|
|
51
46
|
const FULL_RECONCILIATION_INTERVAL_MS = 5 * 60_000
|
package/src/providers/local.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Database } from 'bun:sqlite'
|
|
2
|
-
import { listActivity } from '../activity
|
|
3
|
-
import { getConfigPath, loadConfig, saveConfig } from '../config
|
|
2
|
+
import { listActivity } from '../activity'
|
|
3
|
+
import { getConfigPath, loadConfig, saveConfig } from '../config'
|
|
4
4
|
import {
|
|
5
5
|
addComment,
|
|
6
6
|
countComments,
|
|
@@ -17,11 +17,11 @@ import {
|
|
|
17
17
|
moveTask,
|
|
18
18
|
updateComment as updateTaskComment,
|
|
19
19
|
updateTask,
|
|
20
|
-
} from '../db
|
|
21
|
-
import { getBoardMetrics, getDiscoveredAssignees, getDiscoveredProjects } from '../metrics
|
|
22
|
-
import type { BoardBootstrap, BoardConfig, Task, TaskComment } from '../types
|
|
23
|
-
import { ErrorCode, KanbanError } from '../errors
|
|
24
|
-
import { LOCAL_CAPABILITIES } from './capabilities
|
|
20
|
+
} from '../db'
|
|
21
|
+
import { getBoardMetrics, getDiscoveredAssignees, getDiscoveredProjects } from '../metrics'
|
|
22
|
+
import type { BoardBootstrap, BoardConfig, Task, TaskComment } from '../types'
|
|
23
|
+
import { ErrorCode, KanbanError } from '../errors'
|
|
24
|
+
import { LOCAL_CAPABILITIES } from './capabilities'
|
|
25
25
|
import type {
|
|
26
26
|
CreateTaskInput,
|
|
27
27
|
KanbanProvider,
|
|
@@ -29,7 +29,7 @@ import type {
|
|
|
29
29
|
ProviderSyncStatus,
|
|
30
30
|
TaskListFilters,
|
|
31
31
|
UpdateTaskInput,
|
|
32
|
-
} from './types
|
|
32
|
+
} from './types'
|
|
33
33
|
|
|
34
34
|
function buildLocalConfig(
|
|
35
35
|
db: Database,
|
package/src/providers/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WebhookRequest, WebhookResult } from '../webhooks
|
|
1
|
+
import type { WebhookRequest, WebhookResult } from '../webhooks'
|
|
2
2
|
import type {
|
|
3
3
|
ActivityEntry,
|
|
4
4
|
BoardBootstrap,
|
|
@@ -11,7 +11,7 @@ import type {
|
|
|
11
11
|
ProviderTeamInfo,
|
|
12
12
|
TaskComment,
|
|
13
13
|
Task,
|
|
14
|
-
} from '../types
|
|
14
|
+
} from '../types'
|
|
15
15
|
|
|
16
16
|
export interface TaskListFilters {
|
|
17
17
|
column?: string
|
package/src/server.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
|
-
import { handleRequest } from './api
|
|
3
|
+
import { handleRequest } from './api'
|
|
4
4
|
import type { ServerWebSocket } from 'bun'
|
|
5
|
-
import type { KanbanProvider } from './providers/types
|
|
5
|
+
import type { KanbanProvider } from './providers/types'
|
|
6
6
|
|
|
7
7
|
const wsClients = new Set<ServerWebSocket<unknown>>()
|
|
8
8
|
const CORS_HEADERS = {
|
package/src/types.ts
CHANGED
|
@@ -115,6 +115,7 @@ export interface ProviderCapabilities {
|
|
|
115
115
|
taskMove: boolean
|
|
116
116
|
taskDelete: boolean
|
|
117
117
|
comment: boolean
|
|
118
|
+
/** True when provider-backed bootstrap/dashboard activity is exposed, not merely cached internally. */
|
|
118
119
|
activity: boolean
|
|
119
120
|
metrics: boolean
|
|
120
121
|
columnCrud: boolean
|