@andypai/agent-kanban 0.3.4 → 0.3.5
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 +24 -17
- package/package.json +3 -2
- package/src/__tests__/api.test.ts +3 -3
- package/src/__tests__/index.test.ts +22 -2
- package/src/__tests__/jira-wiring.test.ts +47 -17
- package/src/__tests__/postgres-jira-provider.test.ts +315 -0
- package/src/__tests__/postgres-linear-provider.test.ts +309 -0
- package/src/__tests__/postgres-local-provider.test.ts +129 -0
- package/src/__tests__/storage-config.test.ts +39 -0
- package/src/index.ts +65 -28
- package/src/provider-runtime.ts +110 -0
- package/src/providers/index.ts +16 -39
- package/src/providers/jira.ts +2 -2
- package/src/providers/linear.ts +2 -2
- package/src/providers/postgres-jira.ts +1188 -0
- package/src/providers/postgres-linear.ts +1088 -0
- package/src/providers/postgres-local.ts +611 -0
- package/src/server.ts +2 -2
- package/src/storage-config.ts +41 -0
- package/src/sync-config.ts +5 -2
- package/src/tracker-config.ts +104 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite'
|
|
2
|
+
import postgres from 'postgres'
|
|
3
|
+
|
|
4
|
+
import { getDbPath, migrateSchema, openDb } from './db'
|
|
5
|
+
import { unsupportedOperation } from './providers/errors'
|
|
6
|
+
import { createProvider } from './providers/index'
|
|
7
|
+
import { PostgresJiraProvider } from './providers/postgres-jira'
|
|
8
|
+
import { PostgresLinearProvider } from './providers/postgres-linear'
|
|
9
|
+
import { PostgresLocalProvider } from './providers/postgres-local'
|
|
10
|
+
import type { KanbanProvider } from './providers/types'
|
|
11
|
+
import { resolveKanbanStorageConfig } from './storage-config'
|
|
12
|
+
import type { KanbanStorageConfig } from './storage-config'
|
|
13
|
+
import { trackerConfigFromEnv, type TrackerConfig } from './tracker-config'
|
|
14
|
+
|
|
15
|
+
export interface KanbanRuntime {
|
|
16
|
+
provider: KanbanProvider
|
|
17
|
+
dbPath: string
|
|
18
|
+
trackerConfig: TrackerConfig
|
|
19
|
+
sqliteDb?: Database
|
|
20
|
+
syncIntervalMs?: number
|
|
21
|
+
close(): Promise<void>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function openKanbanRuntime(
|
|
25
|
+
opts: { dbPath?: string; storage?: KanbanStorageConfig; tracker?: TrackerConfig } = {},
|
|
26
|
+
): Promise<KanbanRuntime> {
|
|
27
|
+
const dbPath = opts.dbPath ?? getDbPath()
|
|
28
|
+
const storage =
|
|
29
|
+
opts.storage ?? resolveKanbanStorageConfig(process.env, { defaultSqlitePath: dbPath })
|
|
30
|
+
const trackerConfig = opts.tracker ?? trackerConfigFromEnv(process.env)
|
|
31
|
+
|
|
32
|
+
if (storage.mode === 'postgres') {
|
|
33
|
+
const sql = postgres(storage.databaseUrl, { max: 5, onnotice: () => {} })
|
|
34
|
+
if (trackerConfig.provider === 'local') {
|
|
35
|
+
const provider = new PostgresLocalProvider(sql, trackerConfig)
|
|
36
|
+
await provider.initialize()
|
|
37
|
+
return {
|
|
38
|
+
provider,
|
|
39
|
+
dbPath,
|
|
40
|
+
trackerConfig,
|
|
41
|
+
syncIntervalMs: trackerConfig.syncIntervalMs,
|
|
42
|
+
async close() {
|
|
43
|
+
await sql.end({ timeout: 1 })
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (trackerConfig.provider === 'jira') {
|
|
48
|
+
const provider = new PostgresJiraProvider(sql, {
|
|
49
|
+
baseUrl: trackerConfig.baseUrl,
|
|
50
|
+
email: trackerConfig.email,
|
|
51
|
+
apiToken: trackerConfig.apiToken,
|
|
52
|
+
projectKey: trackerConfig.projectKey,
|
|
53
|
+
...(trackerConfig.boardId !== undefined ? { boardId: trackerConfig.boardId } : {}),
|
|
54
|
+
defaultIssueType: trackerConfig.defaultIssueType ?? 'Task',
|
|
55
|
+
pollingSyncIntervalMs: trackerConfig.syncIntervalMs,
|
|
56
|
+
})
|
|
57
|
+
await provider.initialize()
|
|
58
|
+
return {
|
|
59
|
+
provider,
|
|
60
|
+
dbPath,
|
|
61
|
+
trackerConfig,
|
|
62
|
+
syncIntervalMs: trackerConfig.syncIntervalMs,
|
|
63
|
+
async close() {
|
|
64
|
+
await sql.end({ timeout: 1 })
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (trackerConfig.provider === 'linear') {
|
|
69
|
+
const provider = new PostgresLinearProvider(
|
|
70
|
+
sql,
|
|
71
|
+
trackerConfig.teamId,
|
|
72
|
+
trackerConfig.apiKey,
|
|
73
|
+
trackerConfig.syncIntervalMs,
|
|
74
|
+
)
|
|
75
|
+
await provider.initialize()
|
|
76
|
+
return {
|
|
77
|
+
provider,
|
|
78
|
+
dbPath,
|
|
79
|
+
trackerConfig,
|
|
80
|
+
syncIntervalMs: trackerConfig.syncIntervalMs,
|
|
81
|
+
async close() {
|
|
82
|
+
await sql.end({ timeout: 1 })
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const _exhaustive: never = trackerConfig
|
|
88
|
+
unsupportedOperation(
|
|
89
|
+
`KANBAN_STORAGE=postgres currently supports KANBAN_PROVIDER=local, linear, or jira in agent-kanban.`,
|
|
90
|
+
)
|
|
91
|
+
void _exhaustive
|
|
92
|
+
} catch (err) {
|
|
93
|
+
await sql.end({ timeout: 1 })
|
|
94
|
+
throw err
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const db = openDb(storage.sqlitePath)
|
|
99
|
+
migrateSchema(db)
|
|
100
|
+
return {
|
|
101
|
+
provider: createProvider(db, trackerConfig, storage.sqlitePath),
|
|
102
|
+
dbPath: storage.sqlitePath,
|
|
103
|
+
trackerConfig,
|
|
104
|
+
sqliteDb: db,
|
|
105
|
+
syncIntervalMs: trackerConfig.syncIntervalMs,
|
|
106
|
+
async close() {
|
|
107
|
+
db.close()
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
}
|
package/src/providers/index.ts
CHANGED
|
@@ -1,52 +1,29 @@
|
|
|
1
1
|
import type { Database } from 'bun:sqlite'
|
|
2
2
|
import { getDbPath, initSchema, seedDefaultColumns } from '../db'
|
|
3
|
-
import { providerNotConfigured } from './errors'
|
|
4
3
|
import { JiraProvider } from './jira'
|
|
5
4
|
import { LinearProvider } from './linear'
|
|
6
5
|
import { LocalProvider } from './local'
|
|
6
|
+
import type { TrackerConfig } from '../tracker-config'
|
|
7
7
|
import type { KanbanProvider } from './types'
|
|
8
|
-
import { resolvePollingSyncIntervalMs } from '../sync-config'
|
|
9
8
|
|
|
10
|
-
export function createProvider(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
providerNotConfigured(
|
|
18
|
-
'LINEAR_API_KEY and LINEAR_TEAM_ID are required when KANBAN_PROVIDER=linear',
|
|
19
|
-
)
|
|
20
|
-
}
|
|
21
|
-
return new LinearProvider(db, teamId!, apiKey!, resolvePollingSyncIntervalMs())
|
|
9
|
+
export function createProvider(
|
|
10
|
+
db: Database,
|
|
11
|
+
config: TrackerConfig,
|
|
12
|
+
dbPath = getDbPath(),
|
|
13
|
+
): KanbanProvider {
|
|
14
|
+
if (config.provider === 'linear') {
|
|
15
|
+
return new LinearProvider(db, config.teamId, config.apiKey, config.syncIntervalMs)
|
|
22
16
|
}
|
|
23
17
|
|
|
24
|
-
if (
|
|
25
|
-
const baseUrl = process.env['JIRA_BASE_URL']
|
|
26
|
-
const email = process.env['JIRA_EMAIL']
|
|
27
|
-
const apiToken = process.env['JIRA_API_TOKEN']
|
|
28
|
-
const projectKey = process.env['JIRA_PROJECT_KEY']
|
|
29
|
-
const missing: string[] = []
|
|
30
|
-
if (!baseUrl) missing.push('JIRA_BASE_URL')
|
|
31
|
-
if (!email) missing.push('JIRA_EMAIL')
|
|
32
|
-
if (!apiToken) missing.push('JIRA_API_TOKEN')
|
|
33
|
-
if (!projectKey) missing.push('JIRA_PROJECT_KEY')
|
|
34
|
-
if (missing.length > 0) {
|
|
35
|
-
providerNotConfigured(
|
|
36
|
-
`${missing.join(', ')} ${missing.length === 1 ? 'is' : 'are'} required when KANBAN_PROVIDER=jira`,
|
|
37
|
-
)
|
|
38
|
-
}
|
|
39
|
-
const boardIdRaw = process.env['JIRA_BOARD_ID']
|
|
40
|
-
const boardId = boardIdRaw ? Number.parseInt(boardIdRaw, 10) : undefined
|
|
41
|
-
const defaultIssueType = process.env['JIRA_ISSUE_TYPE'] ?? 'Task'
|
|
18
|
+
if (config.provider === 'jira') {
|
|
42
19
|
return new JiraProvider(db, {
|
|
43
|
-
baseUrl: baseUrl
|
|
44
|
-
email: email
|
|
45
|
-
apiToken: apiToken
|
|
46
|
-
projectKey: projectKey
|
|
47
|
-
boardId:
|
|
48
|
-
defaultIssueType,
|
|
49
|
-
pollingSyncIntervalMs:
|
|
20
|
+
baseUrl: config.baseUrl,
|
|
21
|
+
email: config.email,
|
|
22
|
+
apiToken: config.apiToken,
|
|
23
|
+
projectKey: config.projectKey,
|
|
24
|
+
...(config.boardId !== undefined ? { boardId: config.boardId } : {}),
|
|
25
|
+
defaultIssueType: config.defaultIssueType ?? 'Task',
|
|
26
|
+
pollingSyncIntervalMs: config.syncIntervalMs,
|
|
50
27
|
})
|
|
51
28
|
}
|
|
52
29
|
|
package/src/providers/jira.ts
CHANGED
|
@@ -49,7 +49,7 @@ import type {
|
|
|
49
49
|
TaskListFilters,
|
|
50
50
|
UpdateTaskInput,
|
|
51
51
|
} from './types'
|
|
52
|
-
import {
|
|
52
|
+
import { DEFAULT_POLLING_SYNC_INTERVAL_MS } from '../sync-config'
|
|
53
53
|
|
|
54
54
|
const FULL_RECONCILE_INTERVAL_MS = 5 * 60_000
|
|
55
55
|
|
|
@@ -92,7 +92,7 @@ export class JiraProvider implements KanbanProvider {
|
|
|
92
92
|
client?: JiraClient,
|
|
93
93
|
) {
|
|
94
94
|
initJiraCacheSchema(db)
|
|
95
|
-
this.pollingSyncIntervalMs = config.pollingSyncIntervalMs ??
|
|
95
|
+
this.pollingSyncIntervalMs = config.pollingSyncIntervalMs ?? DEFAULT_POLLING_SYNC_INTERVAL_MS
|
|
96
96
|
this.client =
|
|
97
97
|
client ??
|
|
98
98
|
new JiraClient({
|
package/src/providers/linear.ts
CHANGED
|
@@ -41,7 +41,7 @@ import type {
|
|
|
41
41
|
TaskListFilters,
|
|
42
42
|
UpdateTaskInput,
|
|
43
43
|
} from './types'
|
|
44
|
-
import {
|
|
44
|
+
import { DEFAULT_POLLING_SYNC_INTERVAL_MS } from '../sync-config'
|
|
45
45
|
|
|
46
46
|
const FULL_RECONCILIATION_INTERVAL_MS = 5 * 60_000
|
|
47
47
|
|
|
@@ -81,7 +81,7 @@ export class LinearProvider implements KanbanProvider {
|
|
|
81
81
|
private readonly db: Database,
|
|
82
82
|
private readonly teamId: string,
|
|
83
83
|
apiKey: string,
|
|
84
|
-
private readonly pollingSyncIntervalMs =
|
|
84
|
+
private readonly pollingSyncIntervalMs = DEFAULT_POLLING_SYNC_INTERVAL_MS,
|
|
85
85
|
) {
|
|
86
86
|
initLinearCacheSchema(db)
|
|
87
87
|
this.client = new LinearClient(apiKey)
|