prjct-cli 0.40.0 → 0.42.0
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/CHANGELOG.md +37 -0
- package/bin/prjct.ts +26 -0
- package/core/cli/linear.ts +381 -0
- package/core/integrations/jira/cache.ts +57 -0
- package/core/integrations/jira/index.ts +16 -11
- package/core/integrations/jira/service.ts +244 -0
- package/core/integrations/linear/cache.ts +68 -0
- package/core/integrations/linear/index.ts +15 -1
- package/core/integrations/linear/service.ts +260 -0
- package/core/utils/project-credentials.ts +148 -0
- package/dist/bin/prjct.mjs +35 -6
- package/package.json +1 -1
- package/templates/commands/done.md +24 -1
- package/templates/commands/enrich.md +152 -18
- package/templates/commands/jira.md +91 -139
- package/templates/commands/linear.md +172 -187
- package/templates/commands/task.md +66 -2
- package/templates/mcp-config.json +4 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.42.0] - 2026-01-29
|
|
4
|
+
|
|
5
|
+
### Feature: Linear SDK Integration with Per-Project Credentials
|
|
6
|
+
|
|
7
|
+
Linear integration now uses the native `@linear/sdk` with per-project credential storage, enabling different projects to use different Linear workspaces.
|
|
8
|
+
|
|
9
|
+
**New: Per-Project Credentials**
|
|
10
|
+
- Credentials stored at `~/.prjct-cli/projects/{projectId}/config/credentials.json`
|
|
11
|
+
- Fallback chain: project credentials → macOS keychain → environment variable
|
|
12
|
+
- Each project can connect to a different Linear workspace
|
|
13
|
+
|
|
14
|
+
**New: CLI Bridge (`core/cli/linear.ts`)**
|
|
15
|
+
- Direct access to Linear SDK from templates
|
|
16
|
+
- Commands: `setup`, `list`, `get`, `create`, `update`, `start`, `done`, `comment`, `teams`, `projects`, `status`
|
|
17
|
+
- JSON output for easy parsing by Claude
|
|
18
|
+
|
|
19
|
+
**New: `prjct linear <cmd>` Subcommand**
|
|
20
|
+
- Direct CLI access: `prjct linear status`, `prjct linear list`, etc.
|
|
21
|
+
- Auto-resolves project ID from current directory
|
|
22
|
+
|
|
23
|
+
**Updated Templates**
|
|
24
|
+
- `linear.md` — Natural language interpretation guide for Claude
|
|
25
|
+
- `enrich.md` — Uses CLI instead of MCP for ticket enrichment
|
|
26
|
+
|
|
27
|
+
**Breaking: Removed MCP-only Trackers**
|
|
28
|
+
- Removed JIRA, GitHub Issues, Monday.com support (no native SDK)
|
|
29
|
+
- Only Linear is supported (has native SDK)
|
|
30
|
+
|
|
31
|
+
**New Files:**
|
|
32
|
+
- `core/cli/linear.ts` — CLI bridge for Linear SDK
|
|
33
|
+
- `core/utils/project-credentials.ts` — Per-project credential storage
|
|
34
|
+
|
|
35
|
+
**Modified:**
|
|
36
|
+
- `bin/prjct.ts` — Added `prjct linear` subcommand
|
|
37
|
+
- `templates/commands/linear.md` — Updated with CLI execution pattern
|
|
38
|
+
- `templates/commands/enrich.md` — Linear-only, uses CLI
|
|
39
|
+
|
|
3
40
|
## [0.40.0] - 2026-01-28
|
|
4
41
|
|
|
5
42
|
### Feature: Enhanced Skill System
|
package/bin/prjct.ts
CHANGED
|
@@ -85,6 +85,32 @@ if (args[0] === 'start' || args[0] === 'setup') {
|
|
|
85
85
|
console.error('Server error:', (error as Error).message)
|
|
86
86
|
process.exitCode = 1
|
|
87
87
|
}
|
|
88
|
+
} else if (args[0] === 'linear') {
|
|
89
|
+
// Linear CLI subcommand - direct access to Linear SDK
|
|
90
|
+
const { spawn } = await import('child_process')
|
|
91
|
+
const projectPath = process.cwd()
|
|
92
|
+
const projectId = await configManager.getProjectId(projectPath)
|
|
93
|
+
|
|
94
|
+
if (!projectId) {
|
|
95
|
+
console.error('No prjct project found. Run "prjct init" first.')
|
|
96
|
+
process.exitCode = 1
|
|
97
|
+
} else {
|
|
98
|
+
// Get the path to the linear CLI
|
|
99
|
+
const linearCliPath = path.join(__dirname, '..', 'core', 'cli', 'linear.ts')
|
|
100
|
+
|
|
101
|
+
// Forward args to linear CLI, adding --project flag
|
|
102
|
+
const linearArgs = ['--project', projectId, ...args.slice(1)]
|
|
103
|
+
|
|
104
|
+
// Use bun to run the CLI
|
|
105
|
+
const child = spawn('bun', [linearCliPath, ...linearArgs], {
|
|
106
|
+
stdio: 'inherit',
|
|
107
|
+
cwd: projectPath,
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
child.on('close', (code) => {
|
|
111
|
+
process.exitCode = code || 0
|
|
112
|
+
})
|
|
113
|
+
}
|
|
88
114
|
} else if (args[0] === 'version' || args[0] === '-v' || args[0] === '--version') {
|
|
89
115
|
// Show version with provider status
|
|
90
116
|
const detection = detectAllProviders()
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Linear CLI - Bridge between templates and SDK
|
|
4
|
+
*
|
|
5
|
+
* Usage: bun core/cli/linear.ts --project <projectId> <command> [args...]
|
|
6
|
+
*
|
|
7
|
+
* Commands:
|
|
8
|
+
* setup <apiKey> [teamId] - Store API key in project credentials
|
|
9
|
+
* list - List my assigned issues
|
|
10
|
+
* list-team <teamId> - List issues from a team
|
|
11
|
+
* get <id> - Get issue by ID or identifier (PRJ-123)
|
|
12
|
+
* create <json> - Create issue from JSON input
|
|
13
|
+
* update <id> <json> - Update issue
|
|
14
|
+
* start <id> - Mark issue as in progress
|
|
15
|
+
* done <id> - Mark issue as done
|
|
16
|
+
* comment <id> <text> - Add comment to issue
|
|
17
|
+
* teams - List available teams
|
|
18
|
+
* projects - List available projects
|
|
19
|
+
* status - Check connection status
|
|
20
|
+
*
|
|
21
|
+
* All output is JSON for easy parsing by Claude.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { linearService } from '../integrations/linear'
|
|
25
|
+
import {
|
|
26
|
+
getLinearApiKey,
|
|
27
|
+
setLinearCredentials,
|
|
28
|
+
getProjectCredentials,
|
|
29
|
+
getCredentialSource,
|
|
30
|
+
} from '../utils/project-credentials'
|
|
31
|
+
|
|
32
|
+
// Parse arguments
|
|
33
|
+
const args = process.argv.slice(2)
|
|
34
|
+
|
|
35
|
+
// Extract --project flag
|
|
36
|
+
const projectIdx = args.indexOf('--project')
|
|
37
|
+
let projectId: string | null = null
|
|
38
|
+
if (projectIdx !== -1 && args[projectIdx + 1]) {
|
|
39
|
+
projectId = args[projectIdx + 1]
|
|
40
|
+
args.splice(projectIdx, 2)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const [command, ...commandArgs] = args
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Output result as JSON
|
|
47
|
+
*/
|
|
48
|
+
function output(data: unknown): void {
|
|
49
|
+
console.log(JSON.stringify(data, null, 2))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Output error as JSON and exit
|
|
54
|
+
*/
|
|
55
|
+
function error(message: string, code = 1): never {
|
|
56
|
+
console.error(JSON.stringify({ error: message }))
|
|
57
|
+
process.exit(code)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Initialize Linear service from project credentials
|
|
62
|
+
*/
|
|
63
|
+
async function initFromProject(): Promise<void> {
|
|
64
|
+
if (!projectId) {
|
|
65
|
+
error('No --project specified. Usage: linear.ts --project <projectId> <command>')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const apiKey = await getLinearApiKey(projectId)
|
|
69
|
+
if (!apiKey) {
|
|
70
|
+
error('Linear not configured. Run: p. linear setup')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const creds = await getProjectCredentials(projectId)
|
|
74
|
+
await linearService.initializeFromApiKey(apiKey, creds.linear?.teamId)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Main CLI handler
|
|
79
|
+
*/
|
|
80
|
+
async function main(): Promise<void> {
|
|
81
|
+
try {
|
|
82
|
+
switch (command) {
|
|
83
|
+
case 'setup': {
|
|
84
|
+
if (!projectId) {
|
|
85
|
+
error('--project required for setup')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const apiKey = commandArgs[0]
|
|
89
|
+
const teamId = commandArgs[1] // optional
|
|
90
|
+
|
|
91
|
+
if (!apiKey) {
|
|
92
|
+
error('API key required. Usage: setup <apiKey> [teamId]')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Test connection first
|
|
96
|
+
await linearService.initializeFromApiKey(apiKey, teamId)
|
|
97
|
+
const teams = await linearService.getTeams()
|
|
98
|
+
|
|
99
|
+
if (teams.length === 0) {
|
|
100
|
+
error('No teams found. Check your API key permissions.')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Determine default team
|
|
104
|
+
let selectedTeamId = teamId
|
|
105
|
+
let selectedTeamKey: string | undefined
|
|
106
|
+
|
|
107
|
+
if (!selectedTeamId && teams.length === 1) {
|
|
108
|
+
selectedTeamId = teams[0].id
|
|
109
|
+
selectedTeamKey = teams[0].key
|
|
110
|
+
} else if (selectedTeamId) {
|
|
111
|
+
const team = teams.find((t) => t.id === selectedTeamId || t.key === selectedTeamId)
|
|
112
|
+
if (team) {
|
|
113
|
+
selectedTeamId = team.id
|
|
114
|
+
selectedTeamKey = team.key
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Store in project credentials
|
|
119
|
+
await setLinearCredentials(projectId, {
|
|
120
|
+
apiKey,
|
|
121
|
+
teamId: selectedTeamId,
|
|
122
|
+
teamKey: selectedTeamKey,
|
|
123
|
+
setupAt: new Date().toISOString(),
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
output({
|
|
127
|
+
success: true,
|
|
128
|
+
teams,
|
|
129
|
+
defaultTeam: selectedTeamId
|
|
130
|
+
? { id: selectedTeamId, key: selectedTeamKey }
|
|
131
|
+
: null,
|
|
132
|
+
})
|
|
133
|
+
break
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
case 'list': {
|
|
137
|
+
await initFromProject()
|
|
138
|
+
const limit = commandArgs[0] ? parseInt(commandArgs[0], 10) : 20
|
|
139
|
+
const issues = await linearService.fetchAssignedIssues({ limit })
|
|
140
|
+
output({
|
|
141
|
+
count: issues.length,
|
|
142
|
+
issues: issues.map((issue) => ({
|
|
143
|
+
id: issue.id,
|
|
144
|
+
identifier: issue.externalId,
|
|
145
|
+
title: issue.title,
|
|
146
|
+
status: issue.status,
|
|
147
|
+
priority: issue.priority,
|
|
148
|
+
url: issue.url,
|
|
149
|
+
})),
|
|
150
|
+
})
|
|
151
|
+
break
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
case 'list-team': {
|
|
155
|
+
await initFromProject()
|
|
156
|
+
const teamId = commandArgs[0]
|
|
157
|
+
const limit = commandArgs[1] ? parseInt(commandArgs[1], 10) : 20
|
|
158
|
+
|
|
159
|
+
if (!teamId) {
|
|
160
|
+
error('Team ID required. Usage: list-team <teamId> [limit]')
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const issues = await linearService.fetchTeamIssues(teamId, { limit })
|
|
164
|
+
output({
|
|
165
|
+
count: issues.length,
|
|
166
|
+
issues: issues.map((issue) => ({
|
|
167
|
+
id: issue.id,
|
|
168
|
+
identifier: issue.externalId,
|
|
169
|
+
title: issue.title,
|
|
170
|
+
status: issue.status,
|
|
171
|
+
priority: issue.priority,
|
|
172
|
+
url: issue.url,
|
|
173
|
+
})),
|
|
174
|
+
})
|
|
175
|
+
break
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
case 'get': {
|
|
179
|
+
await initFromProject()
|
|
180
|
+
const id = commandArgs[0]
|
|
181
|
+
|
|
182
|
+
if (!id) {
|
|
183
|
+
error('Issue ID required. Usage: get <id>')
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const issue = await linearService.fetchIssue(id)
|
|
187
|
+
if (!issue) {
|
|
188
|
+
error(`Issue not found: ${id}`)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
output(issue)
|
|
192
|
+
break
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
case 'create': {
|
|
196
|
+
await initFromProject()
|
|
197
|
+
const inputJson = commandArgs[0]
|
|
198
|
+
|
|
199
|
+
if (!inputJson) {
|
|
200
|
+
error('JSON input required. Usage: create \'{"title":"...", "teamId":"..."}\'')
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let input
|
|
204
|
+
try {
|
|
205
|
+
input = JSON.parse(inputJson)
|
|
206
|
+
} catch {
|
|
207
|
+
error(`Invalid JSON: ${inputJson}`)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!input.title) {
|
|
211
|
+
error('title is required')
|
|
212
|
+
}
|
|
213
|
+
if (!input.teamId) {
|
|
214
|
+
// Try to use default team from credentials
|
|
215
|
+
const creds = await getProjectCredentials(projectId!)
|
|
216
|
+
if (creds.linear?.teamId) {
|
|
217
|
+
input.teamId = creds.linear.teamId
|
|
218
|
+
} else {
|
|
219
|
+
error('teamId is required (no default team configured)')
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const issue = await linearService.createIssue(input)
|
|
224
|
+
output(issue)
|
|
225
|
+
break
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
case 'update': {
|
|
229
|
+
await initFromProject()
|
|
230
|
+
const id = commandArgs[0]
|
|
231
|
+
const inputJson = commandArgs[1]
|
|
232
|
+
|
|
233
|
+
if (!id) {
|
|
234
|
+
error('Issue ID required. Usage: update <id> \'{"description":"..."}\'')
|
|
235
|
+
}
|
|
236
|
+
if (!inputJson) {
|
|
237
|
+
error('JSON input required. Usage: update <id> \'{"description":"..."}\'')
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let input
|
|
241
|
+
try {
|
|
242
|
+
input = JSON.parse(inputJson)
|
|
243
|
+
} catch {
|
|
244
|
+
error(`Invalid JSON: ${inputJson}`)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const issue = await linearService.updateIssue(id, input)
|
|
248
|
+
output(issue)
|
|
249
|
+
break
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
case 'start': {
|
|
253
|
+
await initFromProject()
|
|
254
|
+
const id = commandArgs[0]
|
|
255
|
+
|
|
256
|
+
if (!id) {
|
|
257
|
+
error('Issue ID required. Usage: start <id>')
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
await linearService.markInProgress(id)
|
|
261
|
+
output({ success: true, id, status: 'in_progress' })
|
|
262
|
+
break
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
case 'done': {
|
|
266
|
+
await initFromProject()
|
|
267
|
+
const id = commandArgs[0]
|
|
268
|
+
|
|
269
|
+
if (!id) {
|
|
270
|
+
error('Issue ID required. Usage: done <id>')
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
await linearService.markDone(id)
|
|
274
|
+
output({ success: true, id, status: 'done' })
|
|
275
|
+
break
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
case 'comment': {
|
|
279
|
+
await initFromProject()
|
|
280
|
+
const id = commandArgs[0]
|
|
281
|
+
const body = commandArgs.slice(1).join(' ')
|
|
282
|
+
|
|
283
|
+
if (!id) {
|
|
284
|
+
error('Issue ID required. Usage: comment <id> <text>')
|
|
285
|
+
}
|
|
286
|
+
if (!body) {
|
|
287
|
+
error('Comment text required. Usage: comment <id> <text>')
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
await linearService.addComment(id, body)
|
|
291
|
+
output({ success: true, id })
|
|
292
|
+
break
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
case 'teams': {
|
|
296
|
+
await initFromProject()
|
|
297
|
+
const teams = await linearService.getTeams()
|
|
298
|
+
output({ count: teams.length, teams })
|
|
299
|
+
break
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
case 'projects': {
|
|
303
|
+
await initFromProject()
|
|
304
|
+
const projects = await linearService.getProjects()
|
|
305
|
+
output({ count: projects.length, projects })
|
|
306
|
+
break
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
case 'status': {
|
|
310
|
+
if (!projectId) {
|
|
311
|
+
error('--project required for status')
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const source = await getCredentialSource(projectId)
|
|
315
|
+
const apiKey = await getLinearApiKey(projectId)
|
|
316
|
+
const creds = await getProjectCredentials(projectId)
|
|
317
|
+
|
|
318
|
+
if (!apiKey) {
|
|
319
|
+
output({
|
|
320
|
+
configured: false,
|
|
321
|
+
source: 'none',
|
|
322
|
+
message: 'Linear not configured. Run: p. linear setup',
|
|
323
|
+
})
|
|
324
|
+
break
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Test connection
|
|
328
|
+
try {
|
|
329
|
+
await linearService.initializeFromApiKey(apiKey, creds.linear?.teamId)
|
|
330
|
+
const teams = await linearService.getTeams()
|
|
331
|
+
|
|
332
|
+
output({
|
|
333
|
+
configured: true,
|
|
334
|
+
source,
|
|
335
|
+
teamId: creds.linear?.teamId,
|
|
336
|
+
teamKey: creds.linear?.teamKey,
|
|
337
|
+
teamsAvailable: teams.length,
|
|
338
|
+
})
|
|
339
|
+
} catch (err) {
|
|
340
|
+
output({
|
|
341
|
+
configured: true,
|
|
342
|
+
source,
|
|
343
|
+
connectionError: (err as Error).message,
|
|
344
|
+
})
|
|
345
|
+
}
|
|
346
|
+
break
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
case 'help':
|
|
350
|
+
case '--help':
|
|
351
|
+
case '-h':
|
|
352
|
+
case undefined: {
|
|
353
|
+
output({
|
|
354
|
+
usage: 'linear.ts --project <projectId> <command> [args...]',
|
|
355
|
+
commands: {
|
|
356
|
+
setup: 'setup <apiKey> [teamId] - Store API key',
|
|
357
|
+
list: 'list [limit] - List my assigned issues',
|
|
358
|
+
'list-team': 'list-team <teamId> [limit] - List team issues',
|
|
359
|
+
get: 'get <id> - Get issue by ID or identifier',
|
|
360
|
+
create: 'create <json> - Create issue',
|
|
361
|
+
update: 'update <id> <json> - Update issue',
|
|
362
|
+
start: 'start <id> - Mark as in progress',
|
|
363
|
+
done: 'done <id> - Mark as done',
|
|
364
|
+
comment: 'comment <id> <text> - Add comment',
|
|
365
|
+
teams: 'teams - List available teams',
|
|
366
|
+
projects: 'projects - List available projects',
|
|
367
|
+
status: 'status - Check connection',
|
|
368
|
+
},
|
|
369
|
+
})
|
|
370
|
+
break
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
default:
|
|
374
|
+
error(`Unknown command: ${command}. Use --help to see available commands.`)
|
|
375
|
+
}
|
|
376
|
+
} catch (err) {
|
|
377
|
+
error((err as Error).message)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
main()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JIRA Cache Module
|
|
3
|
+
* 5-minute TTL cache for JIRA API responses to reduce API calls
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { TTLCache } from '../../utils/cache'
|
|
7
|
+
import type { Issue } from '../issue-tracker/types'
|
|
8
|
+
|
|
9
|
+
// 5-minute TTL for JIRA API responses
|
|
10
|
+
const JIRA_CACHE_TTL = 5 * 60 * 1000 // 300000ms
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Cache for individual issues (by key like "ENG-123" or ID)
|
|
14
|
+
* Key format: "issue:{key}" or "issue:{id}"
|
|
15
|
+
*/
|
|
16
|
+
export const issueCache = new TTLCache<Issue>({
|
|
17
|
+
ttl: JIRA_CACHE_TTL,
|
|
18
|
+
maxSize: 100,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Cache for assigned issues list
|
|
23
|
+
* Key format: "assigned:{userId}" or "assigned:me"
|
|
24
|
+
*/
|
|
25
|
+
export const assignedIssuesCache = new TTLCache<Issue[]>({
|
|
26
|
+
ttl: JIRA_CACHE_TTL,
|
|
27
|
+
maxSize: 10,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Cache for projects list
|
|
32
|
+
* Key format: "projects"
|
|
33
|
+
*/
|
|
34
|
+
export const projectsCache = new TTLCache<Array<{ id: string; name: string; key?: string }>>({
|
|
35
|
+
ttl: JIRA_CACHE_TTL,
|
|
36
|
+
maxSize: 5,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Clear all JIRA caches
|
|
41
|
+
*/
|
|
42
|
+
export function clearJiraCache(): void {
|
|
43
|
+
issueCache.clear()
|
|
44
|
+
assignedIssuesCache.clear()
|
|
45
|
+
projectsCache.clear()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get cache statistics for debugging
|
|
50
|
+
*/
|
|
51
|
+
export function getJiraCacheStats() {
|
|
52
|
+
return {
|
|
53
|
+
issues: issueCache.stats(),
|
|
54
|
+
assignedIssues: assignedIssuesCache.stats(),
|
|
55
|
+
projects: projectsCache.stats(),
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* JIRA Integration
|
|
3
3
|
*
|
|
4
|
-
* Provides JIRA issue tracking integration for prjct-cli.
|
|
4
|
+
* Provides JIRA issue tracking integration for prjct-cli using REST API.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* 1. API Token Mode (direct REST API):
|
|
6
|
+
* Authentication (API Token Mode):
|
|
9
7
|
* - JIRA_BASE_URL: Your JIRA instance URL (e.g., https://company.atlassian.net)
|
|
10
8
|
* - JIRA_EMAIL: Your Atlassian account email
|
|
11
9
|
* - JIRA_API_TOKEN: API token from https://id.atlassian.com/manage-profile/security/api-tokens
|
|
12
|
-
*
|
|
13
|
-
* 2. MCP Mode (for corporate SSO):
|
|
14
|
-
* - No environment variables needed
|
|
15
|
-
* - Requires Atlassian MCP server configured in ~/.claude/mcp.json
|
|
16
|
-
* - Authenticates via browser (OAuth 2.1, SSO compatible)
|
|
17
10
|
*/
|
|
18
11
|
|
|
19
|
-
// REST API client
|
|
12
|
+
// REST API client
|
|
20
13
|
export { JiraProvider, jiraProvider, type JiraAuthMode } from './client'
|
|
21
14
|
|
|
22
|
-
//
|
|
15
|
+
// Service layer with caching (preferred API)
|
|
16
|
+
export { JiraService, jiraService } from './service'
|
|
17
|
+
|
|
18
|
+
// Cache utilities
|
|
19
|
+
export {
|
|
20
|
+
issueCache,
|
|
21
|
+
assignedIssuesCache,
|
|
22
|
+
projectsCache,
|
|
23
|
+
clearJiraCache,
|
|
24
|
+
getJiraCacheStats,
|
|
25
|
+
} from './cache'
|
|
26
|
+
|
|
27
|
+
// MCP adapter (deprecated - will be removed)
|
|
23
28
|
export {
|
|
24
29
|
JiraMCPAdapter,
|
|
25
30
|
jiraMCPAdapter,
|