@fink-andreas/pi-linear-tools 0.1.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 +16 -0
- package/FUNCTIONALITY.md +57 -0
- package/LICENSE +21 -0
- package/POST_RELEASE_CHECKLIST.md +30 -0
- package/README.md +157 -0
- package/RELEASE.md +50 -0
- package/bin/pi-linear-tools.js +8 -0
- package/extensions/pi-linear-tools.js +582 -0
- package/index.js +8 -0
- package/package.json +49 -0
- package/settings.json.example +12 -0
- package/src/cli.js +729 -0
- package/src/handlers.js +781 -0
- package/src/linear-client.js +43 -0
- package/src/linear.js +1433 -0
- package/src/logger.js +128 -0
- package/src/settings.js +173 -0
package/src/cli.js
ADDED
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
import { loadSettings, saveSettings } from './settings.js';
|
|
2
|
+
import { createLinearClient } from './linear-client.js';
|
|
3
|
+
import { resolveProjectRef } from './linear.js';
|
|
4
|
+
import {
|
|
5
|
+
executeIssueList,
|
|
6
|
+
executeIssueView,
|
|
7
|
+
executeIssueCreate,
|
|
8
|
+
executeIssueUpdate,
|
|
9
|
+
executeIssueComment,
|
|
10
|
+
executeIssueStart,
|
|
11
|
+
executeIssueDelete,
|
|
12
|
+
executeProjectList,
|
|
13
|
+
executeTeamList,
|
|
14
|
+
executeMilestoneList,
|
|
15
|
+
executeMilestoneView,
|
|
16
|
+
executeMilestoneCreate,
|
|
17
|
+
executeMilestoneUpdate,
|
|
18
|
+
executeMilestoneDelete,
|
|
19
|
+
} from './handlers.js';
|
|
20
|
+
|
|
21
|
+
// ===== ARGUMENT PARSING =====
|
|
22
|
+
|
|
23
|
+
function readFlag(args, flag) {
|
|
24
|
+
const idx = args.indexOf(flag);
|
|
25
|
+
if (idx >= 0 && idx + 1 < args.length) {
|
|
26
|
+
return args[idx + 1];
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readMultiFlag(args, flag) {
|
|
32
|
+
const values = [];
|
|
33
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
34
|
+
if (args[i] === flag && i + 1 < args.length) {
|
|
35
|
+
values.push(args[i + 1]);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return values;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function hasFlag(args, flag) {
|
|
42
|
+
return args.includes(flag);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseArrayValue(value) {
|
|
46
|
+
if (!value) return undefined;
|
|
47
|
+
// Support comma-separated values
|
|
48
|
+
return value.split(',').map((v) => v.trim()).filter(Boolean);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function parseNumber(value) {
|
|
52
|
+
if (value === undefined || value === null) return undefined;
|
|
53
|
+
const parsed = Number.parseInt(value, 10);
|
|
54
|
+
return Number.isNaN(parsed) ? undefined : parsed;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function parseBoolean(value) {
|
|
58
|
+
if (value === 'true' || value === '1') return true;
|
|
59
|
+
if (value === 'false' || value === '0') return false;
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ===== API KEY RESOLUTION =====
|
|
64
|
+
|
|
65
|
+
let cachedApiKey = null;
|
|
66
|
+
|
|
67
|
+
async function getLinearApiKey() {
|
|
68
|
+
const envKey = process.env.LINEAR_API_KEY;
|
|
69
|
+
if (envKey && envKey.trim()) {
|
|
70
|
+
return envKey.trim();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (cachedApiKey) {
|
|
74
|
+
return cachedApiKey;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const settings = await loadSettings();
|
|
79
|
+
if (settings.linearApiKey && settings.linearApiKey.trim()) {
|
|
80
|
+
cachedApiKey = settings.linearApiKey.trim();
|
|
81
|
+
return cachedApiKey;
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
// ignore, error below
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
throw new Error('LINEAR_API_KEY not set. Run: pi-linear-tools config --api-key <key>');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function resolveDefaultTeam(projectId) {
|
|
91
|
+
const settings = await loadSettings();
|
|
92
|
+
|
|
93
|
+
if (projectId && settings.projects?.[projectId]?.scope?.team) {
|
|
94
|
+
return settings.projects[projectId].scope.team;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return settings.defaultTeam || null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ===== HELP OUTPUT =====
|
|
101
|
+
|
|
102
|
+
function printHelp() {
|
|
103
|
+
console.log(`pi-linear-tools - Linear CLI
|
|
104
|
+
|
|
105
|
+
Usage:
|
|
106
|
+
pi-linear-tools <command> [options]
|
|
107
|
+
|
|
108
|
+
Commands:
|
|
109
|
+
help Show this help message
|
|
110
|
+
config Show current configuration
|
|
111
|
+
config --api-key <key> Set Linear API key
|
|
112
|
+
config --default-team <key> Set default team
|
|
113
|
+
issue <action> [options] Manage issues
|
|
114
|
+
project <action> [options] Manage projects
|
|
115
|
+
team <action> [options] Manage teams
|
|
116
|
+
milestone <action> [options] Manage milestones
|
|
117
|
+
|
|
118
|
+
Issue Actions:
|
|
119
|
+
list [--project X] [--states X,Y] [--assignee me|all] [--limit N]
|
|
120
|
+
view <issue> [--no-comments]
|
|
121
|
+
create --title X [--team X] [--project X] [--description X] [--priority 0-4] [--assignee me|ID]
|
|
122
|
+
update <issue> [--title X] [--description X] [--state X] [--priority 0-4]
|
|
123
|
+
[--assignee me|ID] [--milestone X] [--sub-issue-of X]
|
|
124
|
+
comment <issue> --body X
|
|
125
|
+
start <issue> [--branch X] [--from-ref X] [--on-branch-exists switch|suffix]
|
|
126
|
+
delete <issue>
|
|
127
|
+
|
|
128
|
+
Project Actions:
|
|
129
|
+
list
|
|
130
|
+
|
|
131
|
+
Team Actions:
|
|
132
|
+
list
|
|
133
|
+
|
|
134
|
+
Milestone Actions:
|
|
135
|
+
list [--project X]
|
|
136
|
+
view <milestone-id>
|
|
137
|
+
create --project X --name X [--description X] [--target-date YYYY-MM-DD] [--status X]
|
|
138
|
+
update <milestone-id> [--name X] [--description X] [--target-date X] [--status X]
|
|
139
|
+
delete <milestone-id>
|
|
140
|
+
|
|
141
|
+
Common Flags:
|
|
142
|
+
--project Project name or ID
|
|
143
|
+
--team Team key (e.g., ENG)
|
|
144
|
+
--assignee "me" or assignee ID
|
|
145
|
+
--priority Priority 0-4 (0=None, 1=Urgent, 2=High, 3=Medium, 4=Low)
|
|
146
|
+
--state State name or ID
|
|
147
|
+
--limit Max results (default: 50)
|
|
148
|
+
|
|
149
|
+
Examples:
|
|
150
|
+
pi-linear-tools issue list --project MyProject --states "In Progress,Backlog"
|
|
151
|
+
pi-linear-tools issue view ENG-123
|
|
152
|
+
pi-linear-tools issue create --title "Fix bug" --team ENG --priority 2
|
|
153
|
+
pi-linear-tools issue update ENG-123 --state "In Progress" --assignee me
|
|
154
|
+
pi-linear-tools issue start ENG-123
|
|
155
|
+
pi-linear-tools milestone list --project MyProject
|
|
156
|
+
pi-linear-tools config --api-key lin_xxx
|
|
157
|
+
`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function printIssueHelp() {
|
|
161
|
+
console.log(`pi-linear-tools issue - Manage Linear issues
|
|
162
|
+
|
|
163
|
+
Usage:
|
|
164
|
+
pi-linear-tools issue <action> [options]
|
|
165
|
+
|
|
166
|
+
Actions:
|
|
167
|
+
list List issues in a project
|
|
168
|
+
view View issue details
|
|
169
|
+
create Create a new issue
|
|
170
|
+
update Update an existing issue
|
|
171
|
+
comment Add a comment to an issue
|
|
172
|
+
start Start working on an issue (create branch, set In Progress)
|
|
173
|
+
delete Delete an issue
|
|
174
|
+
|
|
175
|
+
List Options:
|
|
176
|
+
--project X Project name or ID (default: current directory name)
|
|
177
|
+
--states X,Y Filter by state names (comma-separated)
|
|
178
|
+
--assignee X Filter by assignee: "me" or "all"
|
|
179
|
+
--limit N Max results (default: 50)
|
|
180
|
+
|
|
181
|
+
View Options:
|
|
182
|
+
<issue> Issue key (e.g., ENG-123) or ID
|
|
183
|
+
--no-comments Exclude comments from output
|
|
184
|
+
|
|
185
|
+
Create Options:
|
|
186
|
+
--title X Issue title (required)
|
|
187
|
+
--team X Team key, e.g., ENG (required if no default team)
|
|
188
|
+
--project X Project name or ID
|
|
189
|
+
--description X Issue description (markdown)
|
|
190
|
+
--priority N Priority 0-4
|
|
191
|
+
--assignee X "me" or assignee ID
|
|
192
|
+
--parent-id X Parent issue ID for sub-issues
|
|
193
|
+
|
|
194
|
+
Update Options:
|
|
195
|
+
<issue> Issue key or ID
|
|
196
|
+
--title X New title
|
|
197
|
+
--description X New description
|
|
198
|
+
--state X New state name or ID
|
|
199
|
+
--priority N New priority 0-4
|
|
200
|
+
--assignee X "me" or assignee ID
|
|
201
|
+
--milestone X Milestone name/ID, or "none" to clear
|
|
202
|
+
--sub-issue-of X Parent issue key/ID, or "none" to clear
|
|
203
|
+
|
|
204
|
+
Comment Options:
|
|
205
|
+
<issue> Issue key or ID
|
|
206
|
+
--body X Comment body (markdown)
|
|
207
|
+
|
|
208
|
+
Start Options:
|
|
209
|
+
<issue> Issue key or ID
|
|
210
|
+
--branch X Custom branch name (default: issue's branch name)
|
|
211
|
+
--from-ref X Git ref to branch from (default: HEAD)
|
|
212
|
+
--on-branch-exists X "switch" or "suffix" (default: switch)
|
|
213
|
+
|
|
214
|
+
Delete Options:
|
|
215
|
+
<issue> Issue key or ID
|
|
216
|
+
`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function printProjectHelp() {
|
|
220
|
+
console.log(`pi-linear-tools project - Manage Linear projects
|
|
221
|
+
|
|
222
|
+
Usage:
|
|
223
|
+
pi-linear-tools project <action>
|
|
224
|
+
|
|
225
|
+
Actions:
|
|
226
|
+
list List all accessible projects
|
|
227
|
+
`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function printTeamHelp() {
|
|
231
|
+
console.log(`pi-linear-tools team - Manage Linear teams
|
|
232
|
+
|
|
233
|
+
Usage:
|
|
234
|
+
pi-linear-tools team <action>
|
|
235
|
+
|
|
236
|
+
Actions:
|
|
237
|
+
list List all accessible teams
|
|
238
|
+
`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function printMilestoneHelp() {
|
|
242
|
+
console.log(`pi-linear-tools milestone - Manage Linear project milestones
|
|
243
|
+
|
|
244
|
+
Usage:
|
|
245
|
+
pi-linear-tools milestone <action> [options]
|
|
246
|
+
|
|
247
|
+
Actions:
|
|
248
|
+
list List milestones in a project
|
|
249
|
+
view View milestone details
|
|
250
|
+
create Create a new milestone
|
|
251
|
+
update Update an existing milestone
|
|
252
|
+
delete Delete a milestone
|
|
253
|
+
|
|
254
|
+
List Options:
|
|
255
|
+
--project X Project name or ID (default: current directory name)
|
|
256
|
+
|
|
257
|
+
View Options:
|
|
258
|
+
<milestone-id> Milestone ID
|
|
259
|
+
|
|
260
|
+
Create Options:
|
|
261
|
+
--project X Project name or ID (required)
|
|
262
|
+
--name X Milestone name (required)
|
|
263
|
+
--description X Milestone description
|
|
264
|
+
--target-date X Target date (YYYY-MM-DD)
|
|
265
|
+
--status X Status: backlogged, planned, inProgress, paused, completed, cancelled
|
|
266
|
+
|
|
267
|
+
Update Options:
|
|
268
|
+
<milestone-id> Milestone ID
|
|
269
|
+
--name X New name
|
|
270
|
+
--description X New description
|
|
271
|
+
--target-date X New target date
|
|
272
|
+
--status X New status
|
|
273
|
+
|
|
274
|
+
Delete Options:
|
|
275
|
+
<milestone-id> Milestone ID
|
|
276
|
+
`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ===== CONFIG HANDLER =====
|
|
280
|
+
|
|
281
|
+
async function tryResolveProjectId(projectRef, explicitApiKey = null) {
|
|
282
|
+
const envKey = process.env.LINEAR_API_KEY;
|
|
283
|
+
const apiKey = explicitApiKey || (envKey && envKey.trim() ? envKey.trim() : null);
|
|
284
|
+
|
|
285
|
+
if (!apiKey) {
|
|
286
|
+
return projectRef;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
const client = createLinearClient(apiKey);
|
|
291
|
+
const resolved = await resolveProjectRef(client, projectRef);
|
|
292
|
+
return resolved.id;
|
|
293
|
+
} catch {
|
|
294
|
+
return projectRef;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function handleConfig(args) {
|
|
299
|
+
const apiKey = readFlag(args, '--api-key');
|
|
300
|
+
const defaultTeam = readFlag(args, '--default-team');
|
|
301
|
+
const projectTeam = readFlag(args, '--team');
|
|
302
|
+
const projectName = readFlag(args, '--project');
|
|
303
|
+
|
|
304
|
+
if (apiKey) {
|
|
305
|
+
const settings = await loadSettings();
|
|
306
|
+
settings.linearApiKey = apiKey;
|
|
307
|
+
await saveSettings(settings);
|
|
308
|
+
cachedApiKey = null;
|
|
309
|
+
console.log('LINEAR_API_KEY saved to settings');
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (defaultTeam) {
|
|
314
|
+
const settings = await loadSettings();
|
|
315
|
+
settings.defaultTeam = defaultTeam;
|
|
316
|
+
await saveSettings(settings);
|
|
317
|
+
console.log(`Default team set to: ${defaultTeam}`);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (projectTeam) {
|
|
322
|
+
if (!projectName) {
|
|
323
|
+
throw new Error('Missing required flag: --project when using --team');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const settings = await loadSettings();
|
|
327
|
+
const projectId = await tryResolveProjectId(projectName, settings.linearApiKey);
|
|
328
|
+
|
|
329
|
+
if (!settings.projects[projectId]) {
|
|
330
|
+
settings.projects[projectId] = { scope: { team: null } };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (!settings.projects[projectId].scope) {
|
|
334
|
+
settings.projects[projectId].scope = { team: null };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
settings.projects[projectId].scope.team = projectTeam;
|
|
338
|
+
await saveSettings(settings);
|
|
339
|
+
console.log(`Team for project "${projectName}" set to: ${projectTeam}`);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const settings = await loadSettings();
|
|
344
|
+
const hasKey = !!(settings.linearApiKey || process.env.LINEAR_API_KEY);
|
|
345
|
+
const keySource = process.env.LINEAR_API_KEY ? 'environment' : (settings.linearApiKey ? 'settings' : 'not set');
|
|
346
|
+
|
|
347
|
+
console.log(`Configuration:
|
|
348
|
+
LINEAR_API_KEY: ${hasKey ? 'configured' : 'not set'} (source: ${keySource})
|
|
349
|
+
Default team: ${settings.defaultTeam || 'not set'}
|
|
350
|
+
Project team mappings: ${Object.keys(settings.projects || {}).length}
|
|
351
|
+
|
|
352
|
+
Commands:
|
|
353
|
+
pi-linear-tools config --api-key lin_xxx
|
|
354
|
+
pi-linear-tools config --default-team ENG
|
|
355
|
+
pi-linear-tools config --team ENG --project MyProject`);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ===== ISSUE HANDLERS =====
|
|
359
|
+
|
|
360
|
+
async function handleIssueList(args) {
|
|
361
|
+
const apiKey = await getLinearApiKey();
|
|
362
|
+
const client = createLinearClient(apiKey);
|
|
363
|
+
|
|
364
|
+
const params = {
|
|
365
|
+
project: readFlag(args, '--project'),
|
|
366
|
+
states: parseArrayValue(readFlag(args, '--states')),
|
|
367
|
+
assignee: readFlag(args, '--assignee'),
|
|
368
|
+
limit: parseNumber(readFlag(args, '--limit')),
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
const result = await executeIssueList(client, params);
|
|
372
|
+
console.log(result.content[0].text);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async function handleIssueView(args) {
|
|
376
|
+
const apiKey = await getLinearApiKey();
|
|
377
|
+
const client = createLinearClient(apiKey);
|
|
378
|
+
|
|
379
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
380
|
+
if (positional.length === 0) {
|
|
381
|
+
throw new Error('Missing required argument: issue key or ID');
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const params = {
|
|
385
|
+
issue: positional[0],
|
|
386
|
+
includeComments: !hasFlag(args, '--no-comments'),
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const result = await executeIssueView(client, params);
|
|
390
|
+
console.log(result.content[0].text);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function handleIssueCreate(args) {
|
|
394
|
+
const apiKey = await getLinearApiKey();
|
|
395
|
+
const client = createLinearClient(apiKey);
|
|
396
|
+
|
|
397
|
+
const params = {
|
|
398
|
+
title: readFlag(args, '--title'),
|
|
399
|
+
team: readFlag(args, '--team'),
|
|
400
|
+
project: readFlag(args, '--project'),
|
|
401
|
+
description: readFlag(args, '--description'),
|
|
402
|
+
priority: parseNumber(readFlag(args, '--priority')),
|
|
403
|
+
assignee: readFlag(args, '--assignee'),
|
|
404
|
+
parentId: readFlag(args, '--parent-id'),
|
|
405
|
+
state: readFlag(args, '--state'),
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
if (!params.title) {
|
|
409
|
+
throw new Error('Missing required flag: --title');
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const result = await executeIssueCreate(client, params, { resolveDefaultTeam });
|
|
413
|
+
console.log(result.content[0].text);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async function handleIssueUpdate(args) {
|
|
417
|
+
const apiKey = await getLinearApiKey();
|
|
418
|
+
const client = createLinearClient(apiKey);
|
|
419
|
+
|
|
420
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
421
|
+
if (positional.length === 0) {
|
|
422
|
+
throw new Error('Missing required argument: issue key or ID');
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const params = {
|
|
426
|
+
issue: positional[0],
|
|
427
|
+
title: readFlag(args, '--title'),
|
|
428
|
+
description: readFlag(args, '--description'),
|
|
429
|
+
state: readFlag(args, '--state'),
|
|
430
|
+
priority: parseNumber(readFlag(args, '--priority')),
|
|
431
|
+
assignee: readFlag(args, '--assignee'),
|
|
432
|
+
milestone: readFlag(args, '--milestone'),
|
|
433
|
+
subIssueOf: readFlag(args, '--sub-issue-of'),
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
const result = await executeIssueUpdate(client, params);
|
|
437
|
+
console.log(result.content[0].text);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async function handleIssueComment(args) {
|
|
441
|
+
const apiKey = await getLinearApiKey();
|
|
442
|
+
const client = createLinearClient(apiKey);
|
|
443
|
+
|
|
444
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
445
|
+
if (positional.length === 0) {
|
|
446
|
+
throw new Error('Missing required argument: issue key or ID');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const params = {
|
|
450
|
+
issue: positional[0],
|
|
451
|
+
body: readFlag(args, '--body'),
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
if (!params.body) {
|
|
455
|
+
throw new Error('Missing required flag: --body');
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const result = await executeIssueComment(client, params);
|
|
459
|
+
console.log(result.content[0].text);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async function handleIssueStart(args) {
|
|
463
|
+
const apiKey = await getLinearApiKey();
|
|
464
|
+
const client = createLinearClient(apiKey);
|
|
465
|
+
|
|
466
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
467
|
+
if (positional.length === 0) {
|
|
468
|
+
throw new Error('Missing required argument: issue key or ID');
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const params = {
|
|
472
|
+
issue: positional[0],
|
|
473
|
+
branch: readFlag(args, '--branch'),
|
|
474
|
+
fromRef: readFlag(args, '--from-ref'),
|
|
475
|
+
onBranchExists: readFlag(args, '--on-branch-exists'),
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
const result = await executeIssueStart(client, params);
|
|
479
|
+
console.log(result.content[0].text);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async function handleIssueDelete(args) {
|
|
483
|
+
const apiKey = await getLinearApiKey();
|
|
484
|
+
const client = createLinearClient(apiKey);
|
|
485
|
+
|
|
486
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
487
|
+
if (positional.length === 0) {
|
|
488
|
+
throw new Error('Missing required argument: issue key or ID');
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const params = {
|
|
492
|
+
issue: positional[0],
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
const result = await executeIssueDelete(client, params);
|
|
496
|
+
console.log(result.content[0].text);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
async function handleIssue(args) {
|
|
500
|
+
const [action, ...rest] = args;
|
|
501
|
+
|
|
502
|
+
if (!action || action === '--help' || action === '-h') {
|
|
503
|
+
printIssueHelp();
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
switch (action) {
|
|
508
|
+
case 'list':
|
|
509
|
+
return handleIssueList(rest);
|
|
510
|
+
case 'view':
|
|
511
|
+
return handleIssueView(rest);
|
|
512
|
+
case 'create':
|
|
513
|
+
return handleIssueCreate(rest);
|
|
514
|
+
case 'update':
|
|
515
|
+
return handleIssueUpdate(rest);
|
|
516
|
+
case 'comment':
|
|
517
|
+
return handleIssueComment(rest);
|
|
518
|
+
case 'start':
|
|
519
|
+
return handleIssueStart(rest);
|
|
520
|
+
case 'delete':
|
|
521
|
+
return handleIssueDelete(rest);
|
|
522
|
+
default:
|
|
523
|
+
throw new Error(`Unknown issue action: ${action}`);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// ===== PROJECT HANDLERS =====
|
|
528
|
+
|
|
529
|
+
async function handleProjectList() {
|
|
530
|
+
const apiKey = await getLinearApiKey();
|
|
531
|
+
const client = createLinearClient(apiKey);
|
|
532
|
+
|
|
533
|
+
const result = await executeProjectList(client);
|
|
534
|
+
console.log(result.content[0].text);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async function handleProject(args) {
|
|
538
|
+
const [action] = args;
|
|
539
|
+
|
|
540
|
+
if (!action || action === '--help' || action === '-h') {
|
|
541
|
+
printProjectHelp();
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
switch (action) {
|
|
546
|
+
case 'list':
|
|
547
|
+
return handleProjectList();
|
|
548
|
+
default:
|
|
549
|
+
throw new Error(`Unknown project action: ${action}`);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// ===== TEAM HANDLERS =====
|
|
554
|
+
|
|
555
|
+
async function handleTeamList() {
|
|
556
|
+
const apiKey = await getLinearApiKey();
|
|
557
|
+
const client = createLinearClient(apiKey);
|
|
558
|
+
|
|
559
|
+
const result = await executeTeamList(client);
|
|
560
|
+
console.log(result.content[0].text);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
async function handleTeam(args) {
|
|
564
|
+
const [action] = args;
|
|
565
|
+
|
|
566
|
+
if (!action || action === '--help' || action === '-h') {
|
|
567
|
+
printTeamHelp();
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
switch (action) {
|
|
572
|
+
case 'list':
|
|
573
|
+
return handleTeamList();
|
|
574
|
+
default:
|
|
575
|
+
throw new Error(`Unknown team action: ${action}`);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// ===== MILESTONE HANDLERS =====
|
|
580
|
+
|
|
581
|
+
async function handleMilestoneList(args) {
|
|
582
|
+
const apiKey = await getLinearApiKey();
|
|
583
|
+
const client = createLinearClient(apiKey);
|
|
584
|
+
|
|
585
|
+
const params = {
|
|
586
|
+
project: readFlag(args, '--project'),
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
const result = await executeMilestoneList(client, params);
|
|
590
|
+
console.log(result.content[0].text);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
async function handleMilestoneView(args) {
|
|
594
|
+
const apiKey = await getLinearApiKey();
|
|
595
|
+
const client = createLinearClient(apiKey);
|
|
596
|
+
|
|
597
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
598
|
+
if (positional.length === 0) {
|
|
599
|
+
throw new Error('Missing required argument: milestone ID');
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const params = {
|
|
603
|
+
milestone: positional[0],
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
const result = await executeMilestoneView(client, params);
|
|
607
|
+
console.log(result.content[0].text);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
async function handleMilestoneCreate(args) {
|
|
611
|
+
const apiKey = await getLinearApiKey();
|
|
612
|
+
const client = createLinearClient(apiKey);
|
|
613
|
+
|
|
614
|
+
const params = {
|
|
615
|
+
project: readFlag(args, '--project'),
|
|
616
|
+
name: readFlag(args, '--name'),
|
|
617
|
+
description: readFlag(args, '--description'),
|
|
618
|
+
targetDate: readFlag(args, '--target-date'),
|
|
619
|
+
status: readFlag(args, '--status'),
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
if (!params.name) {
|
|
623
|
+
throw new Error('Missing required flag: --name');
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const result = await executeMilestoneCreate(client, params);
|
|
627
|
+
console.log(result.content[0].text);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
async function handleMilestoneUpdate(args) {
|
|
631
|
+
const apiKey = await getLinearApiKey();
|
|
632
|
+
const client = createLinearClient(apiKey);
|
|
633
|
+
|
|
634
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
635
|
+
if (positional.length === 0) {
|
|
636
|
+
throw new Error('Missing required argument: milestone ID');
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const params = {
|
|
640
|
+
milestone: positional[0],
|
|
641
|
+
name: readFlag(args, '--name'),
|
|
642
|
+
description: readFlag(args, '--description'),
|
|
643
|
+
targetDate: readFlag(args, '--target-date'),
|
|
644
|
+
status: readFlag(args, '--status'),
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
const result = await executeMilestoneUpdate(client, params);
|
|
648
|
+
console.log(result.content[0].text);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
async function handleMilestoneDelete(args) {
|
|
652
|
+
const apiKey = await getLinearApiKey();
|
|
653
|
+
const client = createLinearClient(apiKey);
|
|
654
|
+
|
|
655
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
656
|
+
if (positional.length === 0) {
|
|
657
|
+
throw new Error('Missing required argument: milestone ID');
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const params = {
|
|
661
|
+
milestone: positional[0],
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
const result = await executeMilestoneDelete(client, params);
|
|
665
|
+
console.log(result.content[0].text);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
async function handleMilestone(args) {
|
|
669
|
+
const [action, ...rest] = args;
|
|
670
|
+
|
|
671
|
+
if (!action || action === '--help' || action === '-h') {
|
|
672
|
+
printMilestoneHelp();
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
switch (action) {
|
|
677
|
+
case 'list':
|
|
678
|
+
return handleMilestoneList(rest);
|
|
679
|
+
case 'view':
|
|
680
|
+
return handleMilestoneView(rest);
|
|
681
|
+
case 'create':
|
|
682
|
+
return handleMilestoneCreate(rest);
|
|
683
|
+
case 'update':
|
|
684
|
+
return handleMilestoneUpdate(rest);
|
|
685
|
+
case 'delete':
|
|
686
|
+
return handleMilestoneDelete(rest);
|
|
687
|
+
default:
|
|
688
|
+
throw new Error(`Unknown milestone action: ${action}`);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// ===== MAIN CLI ENTRY =====
|
|
693
|
+
|
|
694
|
+
export async function runCli(argv = process.argv.slice(2)) {
|
|
695
|
+
const [command, ...rest] = argv;
|
|
696
|
+
|
|
697
|
+
if (!command || command === '--help' || command === '-h' || command === 'help') {
|
|
698
|
+
printHelp();
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (command === 'config') {
|
|
703
|
+
await handleConfig(rest);
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (command === 'issue') {
|
|
708
|
+
await handleIssue(rest);
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (command === 'project') {
|
|
713
|
+
await handleProject(rest);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
if (command === 'team') {
|
|
718
|
+
await handleTeam(rest);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
if (command === 'milestone') {
|
|
723
|
+
await handleMilestone(rest);
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
printHelp();
|
|
728
|
+
process.exitCode = 1;
|
|
729
|
+
}
|