@andypai/agent-kanban 0.8.0 → 0.8.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 +10 -5
- package/package.json +8 -1
- package/src/api.ts +1 -1
- package/src/mcp/index.ts +2 -10
- package/src/mcp/types.ts +1 -1
- package/src/metrics-spec.ts +1 -1
- package/src/providers/jira-adf.ts +8 -11
- package/src/providers/jira-client.ts +2 -2
- package/src/providers/jira-core.ts +3 -3
- package/src/providers/linear-cache.ts +4 -4
- package/src/providers/linear-client.ts +1 -1
- package/src/providers/postgres-linear-cache.ts +1 -1
- package/src/storage-config.ts +2 -4
- package/src/tracker-config.ts +2 -2
package/README.md
CHANGED
|
@@ -20,7 +20,8 @@ That buys you a few things that are easy to miss at first:
|
|
|
20
20
|
|
|
21
21
|
## Documentation
|
|
22
22
|
|
|
23
|
-
- [`docs/
|
|
23
|
+
- [`docs/INDEX.md`](https://github.com/abpai/agent-kanban/blob/main/docs/INDEX.md) for the documentation index
|
|
24
|
+
- [`docs/SPEC_CONTRACT.md`](https://github.com/abpai/agent-kanban/blob/main/docs/SPEC_CONTRACT.md) for the repo proof menu and spec contract
|
|
24
25
|
- [`docs/workflow.md`](https://github.com/abpai/agent-kanban/blob/main/docs/workflow.md) for a common day-to-day workflow
|
|
25
26
|
- [`docs/mcp.md`](https://github.com/abpai/agent-kanban/blob/main/docs/mcp.md) for the reusable tracker MCP module
|
|
26
27
|
- [`docs/providers/linear.md`](https://github.com/abpai/agent-kanban/blob/main/docs/providers/linear.md) for Linear provider details
|
|
@@ -271,14 +272,18 @@ compatibility. To require authentication, set a token via `--token` or the
|
|
|
271
272
|
accepts `?token=<token>` instead.
|
|
272
273
|
- `/api/health` stays public (liveness probe).
|
|
273
274
|
- `/api/webhooks/*` are **not** covered by this token — they authenticate with
|
|
274
|
-
the provider webhook secret
|
|
275
|
+
the provider webhook secret when one is configured. Without that secret,
|
|
276
|
+
Linear/Jira webhooks run in local open dev mode and accept unsigned payloads.
|
|
275
277
|
- The bundled UI picks the token up once from `?token=` / `#token=` and stores
|
|
276
278
|
it in `localStorage`, so you can open `https://<host>/?token=<token>`.
|
|
277
279
|
|
|
278
280
|
`--tunnel` exposes the dashboard publicly, so it **refuses to start without a
|
|
279
|
-
token**.
|
|
280
|
-
|
|
281
|
-
|
|
281
|
+
token**. In Linear or Jira mode it also refuses to start unless the matching
|
|
282
|
+
webhook secret is set, so auth-exempt webhook routes cannot accept unsigned
|
|
283
|
+
public writes. CORS is off by default (same-origin); set
|
|
284
|
+
`KANBAN_ALLOWED_ORIGIN` (or `--allowed-origin`) to allow a specific cross-origin
|
|
285
|
+
browser client. CORS is origin hygiene, not an auth control — the token is the
|
|
286
|
+
security boundary.
|
|
282
287
|
|
|
283
288
|
### mcp
|
|
284
289
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andypai/agent-kanban",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.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": {
|
|
@@ -45,6 +45,8 @@
|
|
|
45
45
|
"access": "public"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
|
+
"bootstrap": "bun install && cd ui && bun install",
|
|
49
|
+
"smoke": "bun src/index.ts --help",
|
|
48
50
|
"dev": "bun --watch src/index.ts",
|
|
49
51
|
"start": "bun src/index.ts",
|
|
50
52
|
"build": "bun build ./src/index.ts --target bun --outdir ./dist",
|
|
@@ -56,6 +58,10 @@
|
|
|
56
58
|
"check": "bun run lint && bun run typecheck && bun run ui:typecheck",
|
|
57
59
|
"test": "bun test",
|
|
58
60
|
"test:watch": "bun test --watch",
|
|
61
|
+
"knip": "knip",
|
|
62
|
+
"test:pg": "DATABASE_URL=${DATABASE_URL:-postgres://postgres:postgres@localhost:${KANBAN_PG_PORT:-5432}/kanban_test} bun test",
|
|
63
|
+
"pg:up": "docker compose -f docker-compose.postgres.yml up -d --wait",
|
|
64
|
+
"pg:down": "docker compose -f docker-compose.postgres.yml down -v",
|
|
59
65
|
"seed:test": "bun --env-file=.env.test scripts/seed-test-db.ts",
|
|
60
66
|
"serve": "bun src/index.ts serve",
|
|
61
67
|
"ui:dev": "cd ui && bun run dev",
|
|
@@ -83,6 +89,7 @@
|
|
|
83
89
|
"eslint-config-prettier": "^10.1.8",
|
|
84
90
|
"eslint-plugin-prettier": "^5.5.4",
|
|
85
91
|
"husky": "^8.0.3",
|
|
92
|
+
"knip": "^6.24.0",
|
|
86
93
|
"lint-staged": "^15.2.0",
|
|
87
94
|
"prettier": "^3.7.4",
|
|
88
95
|
"typescript": "^5.7.2"
|
package/src/api.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { parsePositiveInt } from './transport-input'
|
|
|
5
5
|
import { success } from './output'
|
|
6
6
|
import { normalizeCreateTaskInput } from './use-cases'
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
type WsEvent =
|
|
9
9
|
| { type: 'task:upsert'; task: Task; columnId: string }
|
|
10
10
|
| { type: 'task:delete'; id: string }
|
|
11
11
|
// Fallback when a mutation has no precise event; the UI does a full refresh.
|
package/src/mcp/index.ts
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
export { createTrackerCore } from './core'
|
|
2
2
|
export { createTrackerMcpServer } from './server'
|
|
3
|
-
export { TrackerMcpError
|
|
4
|
-
export type {
|
|
5
|
-
export type {
|
|
6
|
-
TrackerMcpAuthResolver,
|
|
7
|
-
TrackerMcpHooks,
|
|
8
|
-
TrackerMcpPolicy,
|
|
9
|
-
TrackerMcpServer,
|
|
10
|
-
TrackerMcpTool,
|
|
11
|
-
TrackerMcpToolHandlerContext,
|
|
12
|
-
} from './types'
|
|
3
|
+
export { TrackerMcpError } from './errors'
|
|
4
|
+
export type { TrackerMcpPolicy, TrackerMcpTool } from './types'
|
|
13
5
|
export { defaultTools } from './server'
|
package/src/mcp/types.ts
CHANGED
|
@@ -61,7 +61,7 @@ export interface TrackerMcpHooks<TScope> {
|
|
|
61
61
|
}): Promise<void> | void
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
interface TrackerMcpToolHandlerContext<TScope, TArgs = Record<string, unknown>> {
|
|
65
65
|
scope: TScope
|
|
66
66
|
args: TArgs
|
|
67
67
|
request?: Request
|
package/src/metrics-spec.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
const PRIORITY_RANK: Record<string, number> = { urgent: 0, high: 1, medium: 2, low: 3 }
|
|
15
15
|
|
|
16
16
|
/** A column plus how many tasks currently sit in it. */
|
|
17
|
-
|
|
17
|
+
interface MetricsColumnCount extends ClassifiableColumn {
|
|
18
18
|
count: number
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -17,18 +17,18 @@ export interface AdfDocument {
|
|
|
17
17
|
content: AdfBlockNode[]
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
interface AdfMark {
|
|
21
21
|
type: string
|
|
22
22
|
attrs?: Record<string, unknown>
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
interface AdfTextNode {
|
|
26
26
|
type: 'text'
|
|
27
27
|
text: string
|
|
28
28
|
marks?: AdfMark[]
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
interface AdfUnknownInlineNode {
|
|
32
32
|
type: string
|
|
33
33
|
[key: string]: unknown
|
|
34
34
|
}
|
|
@@ -40,7 +40,7 @@ export interface AdfParagraphNode {
|
|
|
40
40
|
content?: AdfInlineNode[]
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
interface AdfListItemNode {
|
|
44
44
|
type: 'listItem'
|
|
45
45
|
content: AdfBlockNode[]
|
|
46
46
|
}
|
|
@@ -50,7 +50,7 @@ export interface AdfBulletListNode {
|
|
|
50
50
|
content: AdfListItemNode[]
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
interface AdfOrderedListNode {
|
|
54
54
|
type: 'orderedList'
|
|
55
55
|
content: AdfListItemNode[]
|
|
56
56
|
attrs?: { order?: number }
|
|
@@ -62,7 +62,7 @@ export interface AdfCodeBlockNode {
|
|
|
62
62
|
content?: AdfInlineNode[]
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
interface AdfHeadingNode {
|
|
66
66
|
type: 'heading'
|
|
67
67
|
attrs: { level: number }
|
|
68
68
|
content?: AdfInlineNode[]
|
|
@@ -74,12 +74,12 @@ export interface AdfExpandNode {
|
|
|
74
74
|
content: AdfBlockNode[]
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
interface AdfUnknownBlockNode {
|
|
78
78
|
type: string
|
|
79
79
|
[key: string]: unknown
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
type AdfBlockNode =
|
|
83
83
|
| AdfParagraphNode
|
|
84
84
|
| AdfBulletListNode
|
|
85
85
|
| AdfOrderedListNode
|
|
@@ -88,9 +88,6 @@ export type AdfBlockNode =
|
|
|
88
88
|
| AdfExpandNode
|
|
89
89
|
| AdfUnknownBlockNode
|
|
90
90
|
|
|
91
|
-
// Public AdfNode union covers every node shape this module recognizes.
|
|
92
|
-
export type AdfNode = AdfDocument | AdfBlockNode | AdfListItemNode | AdfInlineNode
|
|
93
|
-
|
|
94
91
|
const BULLET_MARKER = /^[-*] (.*)$/
|
|
95
92
|
const ORDERED_MARKER = /^(\d+)\. (.*)$/
|
|
96
93
|
// Opening/closing fence: `` ``` `` optionally followed by a language tag with
|
|
@@ -168,7 +168,7 @@ export interface JiraIssueType {
|
|
|
168
168
|
name: string
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
interface JiraChangelogItem {
|
|
172
172
|
field: string
|
|
173
173
|
fieldtype?: string
|
|
174
174
|
fromString?: string | null
|
|
@@ -177,7 +177,7 @@ export interface JiraChangelogItem {
|
|
|
177
177
|
to?: string | null
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
interface JiraChangelogEntry {
|
|
181
181
|
id: string
|
|
182
182
|
author?: { accountId?: string; displayName?: string }
|
|
183
183
|
created: string
|
|
@@ -51,9 +51,9 @@ import { WEBHOOK_SECRET_ENV, webhookSecretFromEnv } from '../tracker-config'
|
|
|
51
51
|
import { warnOnce } from './warn-once'
|
|
52
52
|
import { applyTaskFilters, forEachWithConcurrency, SyncGate, syncStatusFromMeta } from './sync-core'
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
const FULL_RECONCILE_INTERVAL_MS = 5 * 60_000
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
function shouldRunFullReconcile(lastFullSyncAt: string | null, now: number): boolean {
|
|
57
57
|
if (!lastFullSyncAt) return true
|
|
58
58
|
const lastFullSyncAtMs = Date.parse(lastFullSyncAt)
|
|
59
59
|
if (!Number.isFinite(lastFullSyncAtMs)) return true
|
|
@@ -64,7 +64,7 @@ export function shouldRunFullReconcile(lastFullSyncAt: string | null, now: numbe
|
|
|
64
64
|
// priorities; the write path looks up the resolved name (case-insensitive)
|
|
65
65
|
// in the cached `jira_priorities` table, so renames that preserve the default
|
|
66
66
|
// casing still resolve.
|
|
67
|
-
|
|
67
|
+
const CANONICAL_TO_JIRA_DEFAULT: Record<Priority, string> = {
|
|
68
68
|
urgent: 'Highest',
|
|
69
69
|
high: 'High',
|
|
70
70
|
medium: 'Medium',
|
|
@@ -316,10 +316,10 @@ export function upsertProjects(
|
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
// Bound per-value storage so repeated edits to a long description can't balloon the cache.
|
|
319
|
-
//
|
|
320
|
-
// backends clamp identically and the truncation marker can't drift
|
|
321
|
-
|
|
322
|
-
|
|
319
|
+
// clampActivityValue is shared with the Postgres cache (postgres-linear-cache.ts)
|
|
320
|
+
// so both backends clamp identically and the truncation marker can't drift.
|
|
321
|
+
const ACTIVITY_VALUE_MAX_CHARS = 4096
|
|
322
|
+
const ACTIVITY_TRUNCATION_SUFFIX = '…[truncated]'
|
|
323
323
|
const ACTIVITY_VALUE_BUDGET = ACTIVITY_VALUE_MAX_CHARS - ACTIVITY_TRUNCATION_SUFFIX.length
|
|
324
324
|
|
|
325
325
|
export function clampActivityValue(value: string): string {
|
|
@@ -694,7 +694,7 @@ export async function resolveLabelIdsForCreate(
|
|
|
694
694
|
return resolveLinearLabelIds(inputLabels, await client.listIssueLabels())
|
|
695
695
|
}
|
|
696
696
|
|
|
697
|
-
|
|
697
|
+
function resolveLinearLabelIds(
|
|
698
698
|
inputLabels: string[] | undefined,
|
|
699
699
|
availableLabels: LinearIssueLabel[],
|
|
700
700
|
): string[] | undefined {
|
|
@@ -15,7 +15,7 @@ import { parseProviderTeamInfo } from './team-info'
|
|
|
15
15
|
|
|
16
16
|
export type { LinearActivityRow, LinearStateRow, LinearSyncMeta } from './linear-cache'
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
type LinearIssueRow = LinearTaskRow
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Postgres-backed cache/repository for the Linear provider. Mirrors the role of
|
package/src/storage-config.ts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { getDbPath } from './db'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export interface SqliteKanbanStorageConfig {
|
|
3
|
+
interface SqliteKanbanStorageConfig {
|
|
6
4
|
mode: 'sqlite'
|
|
7
5
|
sqlitePath: string
|
|
8
6
|
}
|
|
9
7
|
|
|
10
|
-
|
|
8
|
+
interface PostgresKanbanStorageConfig {
|
|
11
9
|
mode: 'postgres'
|
|
12
10
|
databaseUrl: string
|
|
13
11
|
}
|
package/src/tracker-config.ts
CHANGED
|
@@ -62,14 +62,14 @@ export interface LocalTrackerConfig {
|
|
|
62
62
|
syncIntervalMs?: number
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
interface LinearTrackerConfig {
|
|
66
66
|
provider: 'linear'
|
|
67
67
|
apiKey: string
|
|
68
68
|
teamId: string
|
|
69
69
|
syncIntervalMs?: number
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
interface JiraTrackerConfig {
|
|
73
73
|
provider: 'jira'
|
|
74
74
|
baseUrl: string
|
|
75
75
|
email: string
|