@andypai/agent-kanban 0.3.0 → 0.3.1
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 +13 -1
- 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 +5 -5
- 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-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
package/README.md
CHANGED
|
@@ -2,11 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/abpai/agent-kanban/actions/workflows/ci.yml)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`agent-kanban` exists because browser-first project tools are a bad control
|
|
6
|
+
plane for agents, shell scripts, and CI jobs. If automation has to click
|
|
7
|
+
through a web app, scrape HTML, or learn a different integration for every
|
|
8
|
+
tracker, the setup gets brittle fast.
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
This repo gives you one small contract across three modes: a local SQLite board,
|
|
11
|
+
Linear, and Jira Cloud. The CLI stays the same. The JSON envelope stays the
|
|
12
|
+
same. Humans still get an optional dashboard when they want a visual pass.
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
That buys you a few things that are easy to miss at first:
|
|
15
|
+
|
|
16
|
+
- You can prototype an agent against a local board, then point the same workflow at Linear or Jira later.
|
|
17
|
+
- Remote modes use webhooks plus polling fallback, so missed events are less painful than with one-shot scripts or browser automation.
|
|
18
|
+
- Local mode needs no external database or service, which makes scratch boards, demos, and CI setups much easier to spin up.
|
|
19
|
+
- The repo also includes a reusable MCP layer, so sibling tools can reuse the same tracker semantics instead of growing their own tracker adapter.
|
|
10
20
|
|
|
11
21
|
## Documentation
|
|
12
22
|
|
|
@@ -107,6 +117,13 @@ kanban board view
|
|
|
107
117
|
| webhooks | no | yes | yes |
|
|
108
118
|
|
|
109
119
|
Linear tasks carry an `externalRef` (e.g. `TEAM-123`) and a `url`. Commands accept either the internal ID or the external ref.
|
|
120
|
+
Jira tasks can also be addressed by issue key (for example `ENG-123`).
|
|
121
|
+
|
|
122
|
+
Local mode is still the only mode with built-in metrics, config mutation, bulk
|
|
123
|
+
cleanup, and the dashboard/bootstrap activity feed. Linear and Jira do keep
|
|
124
|
+
remote issue history and comment counts in their cache tables for sync and
|
|
125
|
+
provider-backed flows, but those modes do not expose the same local analytics
|
|
126
|
+
surface.
|
|
110
127
|
|
|
111
128
|
Unsupported operations return error code `UNSUPPORTED_OPERATION` with exit code 1.
|
|
112
129
|
|
|
@@ -223,6 +240,16 @@ kanban serve --port 8080
|
|
|
223
240
|
kanban serve --tunnel # optional public URL for webhook testing
|
|
224
241
|
```
|
|
225
242
|
|
|
243
|
+
### mcp
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
kanban mcp
|
|
247
|
+
kanban mcp --db /path/to/board.db
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Runs the bundled MCP server over stdio for local MCP clients such as Claude
|
|
251
|
+
Desktop. See [`docs/mcp.md`](docs/mcp.md) for the tool surface and caveats.
|
|
252
|
+
|
|
226
253
|
## Global flags
|
|
227
254
|
|
|
228
255
|
| Flag | Description |
|
|
@@ -278,8 +305,10 @@ Comment routes:
|
|
|
278
305
|
## Reusable MCP core
|
|
279
306
|
|
|
280
307
|
The repo also includes a reusable tracker MCP implementation under `src/mcp/`.
|
|
281
|
-
|
|
282
|
-
|
|
308
|
+
There are two ways to use it today:
|
|
309
|
+
|
|
310
|
+
- run `kanban mcp` for a bundled stdio MCP server
|
|
311
|
+
- import the helpers in `src/mcp/` from a sibling workspace or in-repo consumer
|
|
283
312
|
|
|
284
313
|
See [`docs/mcp.md`](docs/mcp.md) for the current default tool set, the auth and
|
|
285
314
|
policy model, and the caveats around source-level imports and `kanban serve`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andypai/agent-kanban",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Agent-friendly kanban board CLI. Manage tasks via bash commands, parse structured JSON output.",
|
|
5
5
|
"homepage": "https://github.com/abpai/agent-kanban#readme",
|
|
6
6
|
"repository": {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, test } from 'bun:test'
|
|
2
2
|
import { Database } from 'bun:sqlite'
|
|
3
|
-
import { initSchema, seedDefaultColumns, addTask } from '../db
|
|
4
|
-
import { handleRequest } from '../api
|
|
5
|
-
import { createProvider } from '../providers/index
|
|
3
|
+
import { initSchema, seedDefaultColumns, addTask } from '../db'
|
|
4
|
+
import { handleRequest } from '../api'
|
|
5
|
+
import { createProvider } from '../providers/index'
|
|
6
6
|
|
|
7
7
|
let db: Database
|
|
8
8
|
let provider: ReturnType<typeof createProvider>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, expect, test, beforeEach } from 'bun:test'
|
|
2
2
|
import { Database } from 'bun:sqlite'
|
|
3
|
-
import { getBoardView, initSchema, seedDefaultColumns } from '../../db
|
|
4
|
-
import { boardInit, boardReset } from '../../commands/board
|
|
5
|
-
import { KanbanError } from '../../errors
|
|
3
|
+
import { getBoardView, initSchema, seedDefaultColumns } from '../../db'
|
|
4
|
+
import { boardInit, boardReset } from '../../commands/board'
|
|
5
|
+
import { KanbanError } from '../../errors'
|
|
6
6
|
|
|
7
7
|
let db: Database
|
|
8
8
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, expect, test, beforeEach } from 'bun:test'
|
|
2
2
|
import { Database } from 'bun:sqlite'
|
|
3
|
-
import { initSchema, seedDefaultColumns, addTask, listTasks } from '../../db
|
|
4
|
-
import { bulkMoveAllCmd, bulkClearDoneCmd } from '../../commands/bulk
|
|
5
|
-
import { KanbanError } from '../../errors
|
|
3
|
+
import { initSchema, seedDefaultColumns, addTask, listTasks } from '../../db'
|
|
4
|
+
import { bulkMoveAllCmd, bulkClearDoneCmd } from '../../commands/bulk'
|
|
5
|
+
import { KanbanError } from '../../errors'
|
|
6
6
|
|
|
7
7
|
let db: Database
|
|
8
8
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { describe, expect, test, beforeEach } from 'bun:test'
|
|
2
2
|
import { Database } from 'bun:sqlite'
|
|
3
|
-
import { initSchema, seedDefaultColumns, addTask } from '../../db
|
|
3
|
+
import { initSchema, seedDefaultColumns, addTask } from '../../db'
|
|
4
4
|
import {
|
|
5
5
|
columnAdd,
|
|
6
6
|
columnList,
|
|
7
7
|
columnRename,
|
|
8
8
|
columnReorder,
|
|
9
9
|
columnDelete,
|
|
10
|
-
} from '../../commands/column
|
|
11
|
-
import { KanbanError } from '../../errors
|
|
12
|
-
import type { Column } from '../../types
|
|
10
|
+
} from '../../commands/column'
|
|
11
|
+
import { KanbanError } from '../../errors'
|
|
12
|
+
import type { Column } from '../../types'
|
|
13
13
|
|
|
14
14
|
let db: Database
|
|
15
15
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, test } from 'bun:test'
|
|
2
2
|
import { Database } from 'bun:sqlite'
|
|
3
|
-
import { initSchema, seedDefaultColumns } from '../db
|
|
4
|
-
import { LocalProvider } from '../providers/local
|
|
5
|
-
import { ErrorCode, KanbanError } from '../errors
|
|
3
|
+
import { initSchema, seedDefaultColumns } from '../db'
|
|
4
|
+
import { LocalProvider } from '../providers/local'
|
|
5
|
+
import { ErrorCode, KanbanError } from '../errors'
|
|
6
6
|
|
|
7
7
|
let db: Database
|
|
8
8
|
let provider: LocalProvider
|
package/src/__tests__/db.test.ts
CHANGED
package/src/__tests__/id.test.ts
CHANGED
|
@@ -3,9 +3,9 @@ import { Database } from 'bun:sqlite'
|
|
|
3
3
|
import { mkdtempSync, rmSync } from 'node:fs'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
import { tmpdir } from 'node:os'
|
|
6
|
-
import { ErrorCode, KanbanError, type ErrorCodeValue } from '../errors
|
|
7
|
-
import type { Task } from '../types
|
|
8
|
-
import { parseServeArgs, run } from '../index
|
|
6
|
+
import { ErrorCode, KanbanError, type ErrorCodeValue } from '../errors'
|
|
7
|
+
import type { Task } from '../types'
|
|
8
|
+
import { parseServeArgs, run } from '../index'
|
|
9
9
|
|
|
10
10
|
async function withTempDb(runTest: (dbPath: string) => Promise<void>): Promise<void> {
|
|
11
11
|
const dir = mkdtempSync(join(tmpdir(), 'kanban-run-'))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
import { adfToPlainText, plainTextToAdf, type AdfDocument } from '../providers/jira-adf
|
|
2
|
+
import { adfToPlainText, plainTextToAdf, type AdfDocument } from '../providers/jira-adf'
|
|
3
3
|
|
|
4
4
|
describe('plainTextToAdf / adfToPlainText', () => {
|
|
5
5
|
test('empty doc round-trip', () => {
|
|
@@ -91,6 +91,18 @@ describe('plainTextToAdf / adfToPlainText', () => {
|
|
|
91
91
|
expect(adfToPlainText(doc)).toBe(input)
|
|
92
92
|
})
|
|
93
93
|
|
|
94
|
+
test('garage-baton fenced comment round-trips byte-for-byte', () => {
|
|
95
|
+
const input =
|
|
96
|
+
'garage-triage: ✅ Accepted — abpai/garage-band\n\nIncrement SMOKE_TEST_TASK.md from current_count=1 to 2.\n\n```garage-baton\n{"v":1,"accepted":true,"repo":{"owner":"abpai","name":"garage-band"},"questions":[],"summary":"Increment smoke counter."}\n```'
|
|
97
|
+
const doc = plainTextToAdf(input)
|
|
98
|
+
const code = doc.content.find((node) => node.type === 'codeBlock') as
|
|
99
|
+
| { type: string; attrs?: { language?: string } }
|
|
100
|
+
| undefined
|
|
101
|
+
|
|
102
|
+
expect(code?.attrs?.language).toBe('garage-baton')
|
|
103
|
+
expect(adfToPlainText(doc)).toBe(input)
|
|
104
|
+
})
|
|
105
|
+
|
|
94
106
|
test('mixed content (paragraph + list + code block) round-trip', () => {
|
|
95
107
|
const input = 'intro paragraph\n\n- a\n- b\n\n```js\nconsole.log(1)\n```\n\nouttro'
|
|
96
108
|
const doc = plainTextToAdf(input)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test } from 'bun:test'
|
|
2
2
|
import { Buffer } from 'node:buffer'
|
|
3
|
-
import { ErrorCode, KanbanError } from '../errors
|
|
4
|
-
import { JiraClient } from '../providers/jira-client
|
|
3
|
+
import { ErrorCode, KanbanError } from '../errors'
|
|
4
|
+
import { JiraClient } from '../providers/jira-client'
|
|
5
5
|
|
|
6
6
|
const origFetch = globalThis.fetch
|
|
7
7
|
let lastRequest: { url: string; init?: RequestInit } | null = null
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
2
|
import { Database } from 'bun:sqlite'
|
|
3
|
-
import { JiraClient } from '../providers/jira-client
|
|
4
|
-
import { JiraProvider, type JiraProviderConfig } from '../providers/jira
|
|
3
|
+
import { JiraClient } from '../providers/jira-client'
|
|
4
|
+
import { JiraProvider, type JiraProviderConfig } from '../providers/jira'
|
|
5
5
|
import {
|
|
6
6
|
initJiraCacheSchema,
|
|
7
7
|
saveJiraSyncMeta,
|
|
8
8
|
saveTeamInfo,
|
|
9
9
|
upsertJiraIssues,
|
|
10
|
-
} from '../providers/jira-cache
|
|
10
|
+
} from '../providers/jira-cache'
|
|
11
11
|
|
|
12
12
|
const baseConfig: JiraProviderConfig = {
|
|
13
13
|
baseUrl: 'https://example.atlassian.net',
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
2
|
import { Database } from 'bun:sqlite'
|
|
3
|
-
import { ErrorCode, KanbanError } from '../errors
|
|
4
|
-
import { JiraClient } from '../providers/jira-client
|
|
5
|
-
import { JiraProvider, type JiraProviderConfig } from '../providers/jira
|
|
3
|
+
import { ErrorCode, KanbanError } from '../errors'
|
|
4
|
+
import { JiraClient } from '../providers/jira-client'
|
|
5
|
+
import { JiraProvider, type JiraProviderConfig } from '../providers/jira'
|
|
6
6
|
import {
|
|
7
7
|
initJiraCacheSchema,
|
|
8
8
|
replaceJiraColumns,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
saveTeamInfo,
|
|
13
13
|
upsertJiraIssues,
|
|
14
14
|
upsertJiraUsers,
|
|
15
|
-
} from '../providers/jira-cache
|
|
15
|
+
} from '../providers/jira-cache'
|
|
16
16
|
|
|
17
17
|
type FetchInit = RequestInit | undefined
|
|
18
18
|
type StubCall = { url: string; method: string; body: string | null }
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
2
|
import { Database } from 'bun:sqlite'
|
|
3
|
-
import { ErrorCode, KanbanError } from '../errors
|
|
4
|
-
import { JiraClient } from '../providers/jira-client
|
|
5
|
-
import { JiraProvider, type JiraProviderConfig } from '../providers/jira
|
|
3
|
+
import { ErrorCode, KanbanError } from '../errors'
|
|
4
|
+
import { JiraClient } from '../providers/jira-client'
|
|
5
|
+
import { JiraProvider, type JiraProviderConfig } from '../providers/jira'
|
|
6
6
|
import {
|
|
7
7
|
getCachedColumns,
|
|
8
8
|
getCachedTasks,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
saveJiraSyncMeta,
|
|
12
12
|
saveTeamInfo,
|
|
13
13
|
initJiraCacheSchema,
|
|
14
|
-
} from '../providers/jira-cache
|
|
14
|
+
} from '../providers/jira-cache'
|
|
15
15
|
|
|
16
16
|
type FetchInit = RequestInit | undefined
|
|
17
17
|
type StubCall = { url: string; init?: FetchInit }
|
|
@@ -353,7 +353,7 @@ describe('JiraProvider read path', () => {
|
|
|
353
353
|
// Sync first (populates statuses-based columns)
|
|
354
354
|
await provider.getBoard()
|
|
355
355
|
// Overwrite with the test-scenario columns + issues directly.
|
|
356
|
-
const { replaceJiraColumns, upsertJiraIssues } = await import('../providers/jira-cache
|
|
356
|
+
const { replaceJiraColumns, upsertJiraIssues } = await import('../providers/jira-cache')
|
|
357
357
|
replaceJiraColumns(db, [
|
|
358
358
|
{
|
|
359
359
|
id: 'board:3:Done',
|
|
@@ -3,9 +3,9 @@ import { Database } from 'bun:sqlite'
|
|
|
3
3
|
import { mkdtempSync, rmSync } from 'node:fs'
|
|
4
4
|
import { tmpdir } from 'node:os'
|
|
5
5
|
import { join } from 'node:path'
|
|
6
|
-
import { ErrorCode, KanbanError } from '../errors
|
|
7
|
-
import { run } from '../index
|
|
8
|
-
import { createProvider } from '../providers/index
|
|
6
|
+
import { ErrorCode, KanbanError } from '../errors'
|
|
7
|
+
import { run } from '../index'
|
|
8
|
+
import { createProvider } from '../providers/index'
|
|
9
9
|
|
|
10
10
|
const ENV_KEYS = [
|
|
11
11
|
'KANBAN_PROVIDER',
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
2
|
import { Database } from 'bun:sqlite'
|
|
3
|
-
import { LinearProvider } from '../providers/linear
|
|
3
|
+
import { LinearProvider } from '../providers/linear'
|
|
4
4
|
import {
|
|
5
5
|
initLinearCacheSchema,
|
|
6
6
|
replaceStates,
|
|
7
7
|
saveSyncMeta,
|
|
8
8
|
upsertIssues,
|
|
9
|
-
} from '../providers/linear-cache
|
|
9
|
+
} from '../providers/linear-cache'
|
|
10
10
|
|
|
11
11
|
let db: Database
|
|
12
12
|
let originalFetch: typeof fetch
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
2
|
import { Database } from 'bun:sqlite'
|
|
3
|
-
import { LinearProvider } from '../providers/linear
|
|
3
|
+
import { LinearProvider } from '../providers/linear'
|
|
4
4
|
import {
|
|
5
5
|
getCachedTasks,
|
|
6
6
|
initLinearCacheSchema,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
replaceStates,
|
|
9
9
|
saveSyncMeta,
|
|
10
10
|
upsertIssues,
|
|
11
|
-
} from '../providers/linear-cache
|
|
11
|
+
} from '../providers/linear-cache'
|
|
12
12
|
|
|
13
13
|
let db: Database
|
|
14
14
|
let originalFetch: typeof fetch
|
|
@@ -38,7 +38,6 @@ function linearIssue(
|
|
|
38
38
|
state: { id: string; name: string; position: number }
|
|
39
39
|
labels: { nodes: Array<{ id: string; name: string }> }
|
|
40
40
|
comments: {
|
|
41
|
-
totalCount?: number
|
|
42
41
|
nodes: Array<{ id: string }>
|
|
43
42
|
pageInfo?: { hasNextPage: boolean; endCursor: string | null }
|
|
44
43
|
}
|
|
@@ -57,7 +56,7 @@ function linearIssue(
|
|
|
57
56
|
project: null,
|
|
58
57
|
state: { id: 'state-1', name: 'Todo', position: 0 },
|
|
59
58
|
labels: { nodes: [] },
|
|
60
|
-
comments: {
|
|
59
|
+
comments: { nodes: [], pageInfo: { hasNextPage: false, endCursor: null } },
|
|
61
60
|
...overrides,
|
|
62
61
|
}
|
|
63
62
|
}
|
|
@@ -162,7 +161,6 @@ describe('LinearProvider sync', () => {
|
|
|
162
161
|
state: { id: 'state-1', name: 'Todo', position: 0 },
|
|
163
162
|
labels: { nodes: [] },
|
|
164
163
|
comments: {
|
|
165
|
-
totalCount: 0,
|
|
166
164
|
nodes: [],
|
|
167
165
|
pageInfo: { hasNextPage: false, endCursor: null },
|
|
168
166
|
},
|
|
@@ -258,9 +256,7 @@ describe('LinearProvider sync', () => {
|
|
|
258
256
|
JSON.stringify({
|
|
259
257
|
data: {
|
|
260
258
|
issues: {
|
|
261
|
-
nodes: [
|
|
262
|
-
linearIssue({ comments: { totalCount: 2, nodes: [{ id: 'c1' }, { id: 'c2' }] } }),
|
|
263
|
-
],
|
|
259
|
+
nodes: [linearIssue({ comments: { nodes: [{ id: 'c1' }, { id: 'c2' }] } })],
|
|
264
260
|
pageInfo: { hasNextPage: false, endCursor: null },
|
|
265
261
|
},
|
|
266
262
|
},
|
|
@@ -368,7 +364,6 @@ describe('LinearProvider sync', () => {
|
|
|
368
364
|
nodes: [
|
|
369
365
|
linearIssue({
|
|
370
366
|
comments: {
|
|
371
|
-
totalCount: 7,
|
|
372
367
|
nodes: Array.from({ length: 7 }, (_, index) => ({ id: `c${index}` })),
|
|
373
368
|
},
|
|
374
369
|
}),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, test } from 'bun:test'
|
|
2
2
|
import { Database } from 'bun:sqlite'
|
|
3
|
-
import { initSchema, seedDefaultColumns, addTask } from '../db
|
|
4
|
-
import { LocalProvider } from '../providers/local
|
|
3
|
+
import { initSchema, seedDefaultColumns, addTask } from '../db'
|
|
4
|
+
import { LocalProvider } from '../providers/local'
|
|
5
5
|
|
|
6
6
|
let db: Database
|
|
7
7
|
let provider: LocalProvider
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, test } from 'bun:test'
|
|
2
2
|
import { Database } from 'bun:sqlite'
|
|
3
|
-
import { addTask, initSchema, seedDefaultColumns } from '../db
|
|
4
|
-
import { createTrackerCore } from '../mcp/core
|
|
5
|
-
import { TrackerMcpError } from '../mcp/errors
|
|
6
|
-
import { LocalProvider } from '../providers/local
|
|
3
|
+
import { addTask, initSchema, seedDefaultColumns } from '../db'
|
|
4
|
+
import { createTrackerCore } from '../mcp/core'
|
|
5
|
+
import { TrackerMcpError } from '../mcp/errors'
|
|
6
|
+
import { LocalProvider } from '../providers/local'
|
|
7
7
|
|
|
8
8
|
interface TestScope {
|
|
9
9
|
actor: string
|
|
@@ -2,9 +2,9 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
|
2
2
|
import { Database } from 'bun:sqlite'
|
|
3
3
|
import { Client } from '@modelcontextprotocol/sdk/client'
|
|
4
4
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
|
|
5
|
-
import { addTask, initSchema, seedDefaultColumns } from '../db
|
|
6
|
-
import { createTrackerCore, createTrackerMcpServer, TrackerMcpError } from '../mcp/index
|
|
7
|
-
import { LocalProvider } from '../providers/local
|
|
5
|
+
import { addTask, initSchema, seedDefaultColumns } from '../db'
|
|
6
|
+
import { createTrackerCore, createTrackerMcpServer, TrackerMcpError } from '../mcp/index'
|
|
7
|
+
import { LocalProvider } from '../providers/local'
|
|
8
8
|
|
|
9
9
|
interface TestScope {
|
|
10
10
|
actor: string
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test, beforeEach } from 'bun:test'
|
|
2
2
|
import { Database } from 'bun:sqlite'
|
|
3
|
-
import { initSchema, seedDefaultColumns, addTask } from '../db
|
|
4
|
-
import { getBoardMetrics } from '../metrics
|
|
3
|
+
import { initSchema, seedDefaultColumns, addTask } from '../db'
|
|
4
|
+
import { getBoardMetrics } from '../metrics'
|
|
5
5
|
|
|
6
6
|
let db: Database
|
|
7
7
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
JIRA_CAPABILITIES,
|
|
5
|
+
LINEAR_CAPABILITIES,
|
|
6
|
+
LOCAL_CAPABILITIES,
|
|
7
|
+
} from '../providers/capabilities'
|
|
8
|
+
|
|
9
|
+
describe('provider capabilities', () => {
|
|
10
|
+
test('remote providers share the same read/write baseline', () => {
|
|
11
|
+
expect(LINEAR_CAPABILITIES).toEqual(JIRA_CAPABILITIES)
|
|
12
|
+
expect(LINEAR_CAPABILITIES).toEqual({
|
|
13
|
+
taskCreate: true,
|
|
14
|
+
taskUpdate: true,
|
|
15
|
+
taskMove: true,
|
|
16
|
+
taskDelete: false,
|
|
17
|
+
comment: true,
|
|
18
|
+
activity: false,
|
|
19
|
+
metrics: false,
|
|
20
|
+
columnCrud: false,
|
|
21
|
+
bulk: false,
|
|
22
|
+
configEdit: false,
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('local provider exposes the full local board surface', () => {
|
|
27
|
+
expect(LOCAL_CAPABILITIES).toEqual({
|
|
28
|
+
taskCreate: true,
|
|
29
|
+
taskUpdate: true,
|
|
30
|
+
taskMove: true,
|
|
31
|
+
taskDelete: true,
|
|
32
|
+
comment: true,
|
|
33
|
+
activity: true,
|
|
34
|
+
metrics: true,
|
|
35
|
+
columnCrud: true,
|
|
36
|
+
bulk: true,
|
|
37
|
+
configEdit: true,
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
})
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test } from 'bun:test'
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
BoardConfig,
|
|
5
|
-
BoardMetrics,
|
|
6
|
-
BoardView,
|
|
7
|
-
Column,
|
|
8
|
-
Task,
|
|
9
|
-
} from '../types.ts'
|
|
10
|
-
import { startServer, type StartedServer } from '../server.ts'
|
|
2
|
+
import type { BoardBootstrap, BoardConfig, BoardMetrics, BoardView, Column, Task } from '../types'
|
|
3
|
+
import { startServer, type StartedServer } from '../server'
|
|
11
4
|
import type {
|
|
12
5
|
CreateTaskInput,
|
|
13
6
|
KanbanProvider,
|
|
@@ -15,7 +8,7 @@ import type {
|
|
|
15
8
|
ProviderSyncStatus,
|
|
16
9
|
TaskListFilters,
|
|
17
10
|
UpdateTaskInput,
|
|
18
|
-
} from '../providers/types
|
|
11
|
+
} from '../providers/types'
|
|
19
12
|
|
|
20
13
|
const emptyBoard: BoardView = { columns: [] }
|
|
21
14
|
const emptyConfig: BoardConfig = { members: [], projects: [], provider: 'local' }
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { beforeEach, afterEach, describe, expect, test } from 'bun:test'
|
|
2
2
|
import { Database } from 'bun:sqlite'
|
|
3
3
|
import { createHmac } from 'node:crypto'
|
|
4
|
-
import { verifyHmacSha256 } from '../webhooks
|
|
5
|
-
import { JiraProvider, type JiraProviderConfig } from '../providers/jira
|
|
6
|
-
import { JiraClient } from '../providers/jira-client
|
|
7
|
-
import { LinearProvider } from '../providers/linear
|
|
4
|
+
import { verifyHmacSha256 } from '../webhooks'
|
|
5
|
+
import { JiraProvider, type JiraProviderConfig } from '../providers/jira'
|
|
6
|
+
import { JiraClient } from '../providers/jira-client'
|
|
7
|
+
import { LinearProvider } from '../providers/linear'
|
|
8
8
|
import {
|
|
9
9
|
getCachedActivity,
|
|
10
10
|
getCachedTasks as getCachedJiraTasks,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
saveTeamInfo,
|
|
14
14
|
replaceJiraColumns,
|
|
15
15
|
upsertJiraIssues,
|
|
16
|
-
} from '../providers/jira-cache
|
|
16
|
+
} from '../providers/jira-cache'
|
|
17
17
|
import {
|
|
18
18
|
getCachedTasks as getCachedLinearTasks,
|
|
19
19
|
initLinearCacheSchema,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
replaceStates,
|
|
22
22
|
saveSyncMeta,
|
|
23
23
|
upsertIssues,
|
|
24
|
-
} from '../providers/linear-cache
|
|
24
|
+
} from '../providers/linear-cache'
|
|
25
25
|
|
|
26
26
|
function hmac(secret: string, body: string): string {
|
|
27
27
|
return createHmac('sha256', secret).update(body).digest('hex')
|
package/src/activity.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Database } from 'bun:sqlite'
|
|
2
|
-
import { generateId } from './id
|
|
3
|
-
import type { ActivityEntry, ActivityAction } from './types
|
|
2
|
+
import { generateId } from './id'
|
|
3
|
+
import type { ActivityEntry, ActivityAction } from './types'
|
|
4
4
|
|
|
5
5
|
export function logActivity(
|
|
6
6
|
db: Database,
|
package/src/api.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { KanbanError, ErrorCode } from './errors
|
|
2
|
-
import type { BoardConfig, CliOutput, Task } from './types
|
|
3
|
-
import type { CreateTaskInput, UpdateTaskInput, KanbanProvider } from './providers/types
|
|
1
|
+
import { KanbanError, ErrorCode } from './errors'
|
|
2
|
+
import type { BoardConfig, CliOutput, Task } from './types'
|
|
3
|
+
import type { CreateTaskInput, UpdateTaskInput, KanbanProvider } from './providers/types'
|
|
4
4
|
|
|
5
5
|
export type WsEvent =
|
|
6
6
|
| { type: 'task:upsert'; task: Task; columnName: string }
|
package/src/commands/board.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Database } from 'bun:sqlite'
|
|
2
|
-
import { initSchema, seedDefaultColumns, isInitialized, resetBoard } from '../db
|
|
3
|
-
import { ErrorCode, KanbanError } from '../errors
|
|
4
|
-
import { success } from '../output
|
|
5
|
-
import type { CliOutput } from '../types
|
|
2
|
+
import { initSchema, seedDefaultColumns, isInitialized, resetBoard } from '../db'
|
|
3
|
+
import { ErrorCode, KanbanError } from '../errors'
|
|
4
|
+
import { success } from '../output'
|
|
5
|
+
import type { CliOutput } from '../types'
|
|
6
6
|
|
|
7
7
|
export function boardInit(db: Database): CliOutput {
|
|
8
8
|
if (isInitialized(db)) {
|
package/src/commands/bulk.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Database } from 'bun:sqlite'
|
|
2
|
-
import { bulkMoveAll, bulkClearDone } from '../db
|
|
3
|
-
import { ErrorCode, KanbanError } from '../errors
|
|
4
|
-
import { success } from '../output
|
|
5
|
-
import type { CliOutput } from '../types
|
|
2
|
+
import { bulkMoveAll, bulkClearDone } from '../db'
|
|
3
|
+
import { ErrorCode, KanbanError } from '../errors'
|
|
4
|
+
import { success } from '../output'
|
|
5
|
+
import type { CliOutput } from '../types'
|
|
6
6
|
|
|
7
7
|
export function bulkMoveAllCmd(db: Database, args: { from?: string; to?: string }): CliOutput {
|
|
8
8
|
if (!args.from || !args.to) {
|
package/src/commands/column.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Database } from 'bun:sqlite'
|
|
2
|
-
import { addColumn, listColumns, renameColumn, reorderColumn, deleteColumn } from '../db
|
|
3
|
-
import { ErrorCode, KanbanError } from '../errors
|
|
4
|
-
import { success } from '../output
|
|
5
|
-
import type { CliOutput } from '../types
|
|
2
|
+
import { addColumn, listColumns, renameColumn, reorderColumn, deleteColumn } from '../db'
|
|
3
|
+
import { ErrorCode, KanbanError } from '../errors'
|
|
4
|
+
import { success } from '../output'
|
|
5
|
+
import type { CliOutput } from '../types'
|
|
6
6
|
|
|
7
7
|
export function columnAdd(
|
|
8
8
|
db: Database,
|
package/src/commands/mcp.ts
CHANGED
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
type CallToolResult,
|
|
7
7
|
} from '@modelcontextprotocol/sdk/types.js'
|
|
8
8
|
import { AjvJsonSchemaValidator } from '@modelcontextprotocol/sdk/validation/ajv'
|
|
9
|
-
import { createTrackerCore, defaultTools, TrackerMcpError } from '../mcp/index
|
|
10
|
-
import type { TrackerMcpPolicy, TrackerMcpTool } from '../mcp/index
|
|
11
|
-
import type { KanbanProvider } from '../providers/types
|
|
9
|
+
import { createTrackerCore, defaultTools, TrackerMcpError } from '../mcp/index'
|
|
10
|
+
import type { TrackerMcpPolicy, TrackerMcpTool } from '../mcp/index'
|
|
11
|
+
import type { KanbanProvider } from '../providers/types'
|
|
12
12
|
|
|
13
13
|
type LocalScope = Record<string, never>
|
|
14
14
|
|
package/src/config.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, renameSync } from 'node:fs'
|
|
2
2
|
import { dirname, join } from 'node:path'
|
|
3
|
-
import type { BoardConfig } from './types
|
|
3
|
+
import type { BoardConfig } from './types'
|
|
4
4
|
|
|
5
5
|
const DEFAULT_CONFIG: BoardConfig = { members: [], projects: [] }
|
|
6
6
|
|
package/src/db.ts
CHANGED
|
@@ -2,10 +2,10 @@ import { Database } from 'bun:sqlite'
|
|
|
2
2
|
import { existsSync, mkdirSync } from 'node:fs'
|
|
3
3
|
import { homedir } from 'node:os'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
|
-
import { generateId } from './id
|
|
6
|
-
import { ErrorCode, KanbanError } from './errors
|
|
7
|
-
import type { BoardView, Column, Priority, Task, TaskComment, TaskWithColumn } from './types
|
|
8
|
-
import { logActivity, enterColumn, exitColumn } from './activity
|
|
5
|
+
import { generateId } from './id'
|
|
6
|
+
import { ErrorCode, KanbanError } from './errors'
|
|
7
|
+
import type { BoardView, Column, Priority, Task, TaskComment, TaskWithColumn } from './types'
|
|
8
|
+
import { logActivity, enterColumn, exitColumn } from './activity'
|
|
9
9
|
|
|
10
10
|
const DEFAULT_COLUMNS = [
|
|
11
11
|
{ name: 'recurring', position: 0 },
|
package/src/index.ts
CHANGED
|
@@ -2,22 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
import { parseArgs } from 'node:util'
|
|
4
4
|
import { Database } from 'bun:sqlite'
|
|
5
|
-
import { KanbanError, ErrorCode } from './errors
|
|
6
|
-
import { formatOutput, error, success } from './output
|
|
7
|
-
import { openDb, getDbPath, initSchema, migrateSchema, seedDefaultColumns } from './db
|
|
8
|
-
import { boardInit, boardReset } from './commands/board
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from './commands/column.ts'
|
|
16
|
-
import { bulkClearDoneCmd, bulkMoveAllCmd } from './commands/bulk.ts'
|
|
17
|
-
import { getConfigPath, loadConfig, saveConfig } from './config.ts'
|
|
18
|
-
import type { CliOutput, Priority } from './types.ts'
|
|
19
|
-
import { createProvider } from './providers/index.ts'
|
|
20
|
-
import { unsupportedOperation } from './providers/errors.ts'
|
|
5
|
+
import { KanbanError, ErrorCode } from './errors'
|
|
6
|
+
import { formatOutput, error, success } from './output'
|
|
7
|
+
import { openDb, getDbPath, initSchema, migrateSchema, seedDefaultColumns } from './db'
|
|
8
|
+
import { boardInit, boardReset } from './commands/board'
|
|
9
|
+
import { columnAdd, columnDelete, columnList, columnRename, columnReorder } from './commands/column'
|
|
10
|
+
import { bulkClearDoneCmd, bulkMoveAllCmd } from './commands/bulk'
|
|
11
|
+
import { getConfigPath, loadConfig, saveConfig } from './config'
|
|
12
|
+
import type { CliOutput, Priority } from './types'
|
|
13
|
+
import { createProvider } from './providers/index'
|
|
14
|
+
import { unsupportedOperation } from './providers/errors'
|
|
21
15
|
|
|
22
16
|
interface ParsedArgs {
|
|
23
17
|
values: Record<string, unknown>
|
|
@@ -415,7 +409,7 @@ if (import.meta.main) {
|
|
|
415
409
|
const db = openDb(dbPath)
|
|
416
410
|
migrateSchema(db)
|
|
417
411
|
const provider = createProvider(db, dbPath)
|
|
418
|
-
const { startStdioMcpServer } = await import('./commands/mcp
|
|
412
|
+
const { startStdioMcpServer } = await import('./commands/mcp')
|
|
419
413
|
await startStdioMcpServer(provider)
|
|
420
414
|
} else if (argv[0] === 'serve') {
|
|
421
415
|
const opts = parseServeArgs(argv)
|
|
@@ -424,11 +418,11 @@ if (import.meta.main) {
|
|
|
424
418
|
const db = openDb(dbPath)
|
|
425
419
|
migrateSchema(db)
|
|
426
420
|
const provider = createProvider(db, dbPath)
|
|
427
|
-
const { startServer } = await import('./server
|
|
421
|
+
const { startServer } = await import('./server')
|
|
428
422
|
startServer(provider, opts.port)
|
|
429
423
|
|
|
430
424
|
if (opts.tunnel) {
|
|
431
|
-
const { startCloudflareTunnel } = await import('./tunnel
|
|
425
|
+
const { startCloudflareTunnel } = await import('./tunnel')
|
|
432
426
|
try {
|
|
433
427
|
const handle = startCloudflareTunnel(opts.port)
|
|
434
428
|
const shutdown = (): void => {
|
package/src/mcp/core.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { KanbanProvider } from '../providers/types
|
|
2
|
-
import type { Task, TaskComment } from '../types
|
|
3
|
-
import { TrackerMcpError, toTrackerMcpError } from './errors
|
|
4
|
-
import type { TrackerMcpHooks, TrackerMcpPolicy } from './types
|
|
1
|
+
import type { KanbanProvider } from '../providers/types'
|
|
2
|
+
import type { Task, TaskComment } from '../types'
|
|
3
|
+
import { TrackerMcpError, toTrackerMcpError } from './errors'
|
|
4
|
+
import type { TrackerMcpHooks, TrackerMcpPolicy } from './types'
|
|
5
5
|
|
|
6
6
|
export interface TrackerCore<TScope> {
|
|
7
7
|
notifyAuthFailure(input: {
|
package/src/mcp/errors.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { McpError, ErrorCode as JsonRpcErrorCode } from '@modelcontextprotocol/sdk/types.js'
|
|
2
|
-
import { ErrorCode, type ErrorCodeValue, KanbanError } from '../errors
|
|
2
|
+
import { ErrorCode, type ErrorCodeValue, KanbanError } from '../errors'
|
|
3
3
|
|
|
4
4
|
export type TrackerMcpErrorCode =
|
|
5
5
|
| 'auth_failed'
|
package/src/mcp/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { createTrackerCore } from './core
|
|
2
|
-
export { createTrackerMcpServer } from './server
|
|
3
|
-
export { TrackerMcpError, type TrackerMcpErrorCode } from './errors
|
|
4
|
-
export type { TrackerCore } from './core
|
|
1
|
+
export { createTrackerCore } from './core'
|
|
2
|
+
export { createTrackerMcpServer } from './server'
|
|
3
|
+
export { TrackerMcpError, type TrackerMcpErrorCode } from './errors'
|
|
4
|
+
export type { TrackerCore } from './core'
|
|
5
5
|
export type {
|
|
6
6
|
TrackerMcpAuthResolver,
|
|
7
7
|
TrackerMcpHooks,
|
|
@@ -9,5 +9,5 @@ export type {
|
|
|
9
9
|
TrackerMcpServer,
|
|
10
10
|
TrackerMcpTool,
|
|
11
11
|
TrackerMcpToolHandlerContext,
|
|
12
|
-
} from './types
|
|
13
|
-
export { defaultTools } from './server
|
|
12
|
+
} from './types'
|
|
13
|
+
export { defaultTools } from './server'
|
package/src/mcp/server.ts
CHANGED
|
@@ -13,9 +13,9 @@ import type {
|
|
|
13
13
|
JsonSchemaValidatorResult,
|
|
14
14
|
JsonSchemaType,
|
|
15
15
|
} from '@modelcontextprotocol/sdk/validation'
|
|
16
|
-
import type { TrackerCore } from './core
|
|
17
|
-
import { TrackerMcpError, toMcpError, toTrackerMcpError, trackerMcpJsonRpcCode } from './errors
|
|
18
|
-
import type { TrackerMcpAuthResolver, TrackerMcpServer, TrackerMcpTool } from './types
|
|
16
|
+
import type { TrackerCore } from './core'
|
|
17
|
+
import { TrackerMcpError, toMcpError, toTrackerMcpError, trackerMcpJsonRpcCode } from './errors'
|
|
18
|
+
import type { TrackerMcpAuthResolver, TrackerMcpServer, TrackerMcpTool } from './types'
|
|
19
19
|
|
|
20
20
|
const EMPTY_OBJECT_SCHEMA = {
|
|
21
21
|
type: 'object',
|
package/src/mcp/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js'
|
|
2
2
|
import type { JsonSchemaType } from '@modelcontextprotocol/sdk/validation'
|
|
3
|
-
import type { TaskComment } from '../types
|
|
4
|
-
import type { TrackerMcpError, TrackerMcpErrorCode } from './errors
|
|
3
|
+
import type { TaskComment } from '../types'
|
|
4
|
+
import type { TrackerMcpError, TrackerMcpErrorCode } from './errors'
|
|
5
5
|
|
|
6
6
|
export type TrackerMcpAuthResolver<TScope> = (ctx: {
|
|
7
7
|
request: Request
|
package/src/metrics.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Database } from 'bun:sqlite'
|
|
2
|
-
import type { ActivityEntry, BoardMetrics } from './types
|
|
2
|
+
import type { ActivityEntry, BoardMetrics } from './types'
|
|
3
3
|
|
|
4
4
|
function getDistinctTaskFieldValues(db: Database, field: 'assignee' | 'project'): string[] {
|
|
5
5
|
return (
|
package/src/output.ts
CHANGED
|
@@ -1,40 +1,30 @@
|
|
|
1
|
-
import type { ProviderCapabilities } from '../types
|
|
1
|
+
import type { ProviderCapabilities } from '../types'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
function capabilities(overrides: Partial<ProviderCapabilities> = {}): ProviderCapabilities {
|
|
4
|
+
return {
|
|
5
|
+
taskCreate: true,
|
|
6
|
+
taskUpdate: true,
|
|
7
|
+
taskMove: true,
|
|
8
|
+
taskDelete: false,
|
|
9
|
+
comment: true,
|
|
10
|
+
activity: false,
|
|
11
|
+
metrics: false,
|
|
12
|
+
columnCrud: false,
|
|
13
|
+
bulk: false,
|
|
14
|
+
configEdit: false,
|
|
15
|
+
...overrides,
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const LOCAL_CAPABILITIES: ProviderCapabilities = capabilities({
|
|
7
20
|
taskDelete: true,
|
|
8
|
-
comment: true,
|
|
9
21
|
activity: true,
|
|
10
22
|
metrics: true,
|
|
11
23
|
columnCrud: true,
|
|
12
24
|
bulk: true,
|
|
13
25
|
configEdit: true,
|
|
14
|
-
}
|
|
26
|
+
})
|
|
15
27
|
|
|
16
|
-
export const LINEAR_CAPABILITIES: ProviderCapabilities =
|
|
17
|
-
taskCreate: true,
|
|
18
|
-
taskUpdate: true,
|
|
19
|
-
taskMove: true,
|
|
20
|
-
taskDelete: false,
|
|
21
|
-
comment: true,
|
|
22
|
-
activity: false,
|
|
23
|
-
metrics: false,
|
|
24
|
-
columnCrud: false,
|
|
25
|
-
bulk: false,
|
|
26
|
-
configEdit: false,
|
|
27
|
-
}
|
|
28
|
+
export const LINEAR_CAPABILITIES: ProviderCapabilities = capabilities()
|
|
28
29
|
|
|
29
|
-
export const JIRA_CAPABILITIES: ProviderCapabilities =
|
|
30
|
-
taskCreate: true,
|
|
31
|
-
taskUpdate: true,
|
|
32
|
-
taskMove: true,
|
|
33
|
-
taskDelete: false,
|
|
34
|
-
comment: true,
|
|
35
|
-
activity: false,
|
|
36
|
-
metrics: false,
|
|
37
|
-
columnCrud: false,
|
|
38
|
-
bulk: false,
|
|
39
|
-
configEdit: false,
|
|
40
|
-
}
|
|
30
|
+
export const JIRA_CAPABILITIES: ProviderCapabilities = capabilities()
|
package/src/providers/errors.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ErrorCode, type ErrorCodeValue, KanbanError } from '../errors
|
|
1
|
+
import { ErrorCode, type ErrorCodeValue, KanbanError } from '../errors'
|
|
2
2
|
|
|
3
3
|
export function unsupportedOperation(message: string): never {
|
|
4
4
|
throw new KanbanError(ErrorCode.UNSUPPORTED_OPERATION, message)
|
package/src/providers/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { Database } from 'bun:sqlite'
|
|
2
|
-
import { getDbPath, initSchema, seedDefaultColumns } from '../db
|
|
3
|
-
import { providerNotConfigured } from './errors
|
|
4
|
-
import { JiraProvider } from './jira
|
|
5
|
-
import { LinearProvider } from './linear
|
|
6
|
-
import { LocalProvider } from './local
|
|
7
|
-
import type { KanbanProvider } from './types
|
|
2
|
+
import { getDbPath, initSchema, seedDefaultColumns } from '../db'
|
|
3
|
+
import { providerNotConfigured } from './errors'
|
|
4
|
+
import { JiraProvider } from './jira'
|
|
5
|
+
import { LinearProvider } from './linear'
|
|
6
|
+
import { LocalProvider } from './local'
|
|
7
|
+
import type { KanbanProvider } from './types'
|
|
8
8
|
|
|
9
9
|
export function createProvider(db: Database, dbPath = getDbPath()): KanbanProvider {
|
|
10
10
|
const providerType = (process.env['KANBAN_PROVIDER'] ?? 'local') as 'local' | 'linear' | 'jira'
|
|
@@ -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
|