linear-cli-agents 0.6.0 → 0.7.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/README.md +68 -1
- package/dist/commands/documents/create.d.ts +13 -0
- package/dist/commands/documents/create.js +68 -0
- package/dist/commands/documents/delete.d.ts +9 -0
- package/dist/commands/documents/delete.js +32 -0
- package/dist/commands/documents/get.d.ts +12 -0
- package/dist/commands/documents/get.js +79 -0
- package/dist/commands/documents/list.d.ts +12 -0
- package/dist/commands/documents/list.js +105 -0
- package/dist/commands/documents/update.d.ts +16 -0
- package/dist/commands/documents/update.js +75 -0
- package/dist/commands/info.js +161 -3
- package/dist/commands/initiatives/archive.d.ts +12 -0
- package/dist/commands/initiatives/archive.js +44 -0
- package/dist/commands/initiatives/create.d.ts +15 -0
- package/dist/commands/initiatives/create.js +84 -0
- package/dist/commands/initiatives/delete.d.ts +9 -0
- package/dist/commands/initiatives/delete.js +32 -0
- package/dist/commands/initiatives/get.d.ts +12 -0
- package/dist/commands/initiatives/get.js +90 -0
- package/dist/commands/initiatives/list.d.ts +11 -0
- package/dist/commands/initiatives/list.js +135 -0
- package/dist/commands/initiatives/update.d.ts +18 -0
- package/dist/commands/initiatives/update.js +90 -0
- package/dist/commands/issues/bulk-update.d.ts +2 -0
- package/dist/commands/issues/bulk-update.js +10 -0
- package/dist/commands/issues/create.d.ts +2 -0
- package/dist/commands/issues/create.js +10 -0
- package/dist/commands/issues/get.d.ts +1 -0
- package/dist/commands/issues/get.js +19 -1
- package/dist/commands/issues/update.d.ts +2 -0
- package/dist/commands/issues/update.js +12 -0
- package/dist/commands/upload.d.ts +13 -0
- package/dist/commands/upload.js +117 -0
- package/oclif.manifest.json +1129 -408
- package/package.json +7 -1
package/dist/commands/info.js
CHANGED
|
@@ -43,8 +43,9 @@ const COMMANDS = {
|
|
|
43
43
|
args: { id: { description: 'Issue ID or identifier (e.g., ENG-123)', required: true } },
|
|
44
44
|
flags: {
|
|
45
45
|
format: { type: 'string', options: ['json', 'table', 'plain'], default: 'json' },
|
|
46
|
+
'with-attachments': { type: 'boolean', description: 'Include attachments (linked PRs, commits, etc.)' },
|
|
46
47
|
},
|
|
47
|
-
examples: ['linear issues get ENG-123', 'linear issues get
|
|
48
|
+
examples: ['linear issues get ENG-123', 'linear issues get ENG-123 --with-attachments'],
|
|
48
49
|
},
|
|
49
50
|
'issues create': {
|
|
50
51
|
description: 'Create a new issue',
|
|
@@ -405,6 +406,122 @@ const COMMANDS = {
|
|
|
405
406
|
},
|
|
406
407
|
examples: ['linear templates update TEMPLATE_ID --name "New Name"'],
|
|
407
408
|
},
|
|
409
|
+
// Documents
|
|
410
|
+
'documents list': {
|
|
411
|
+
description: 'List documents',
|
|
412
|
+
flags: {
|
|
413
|
+
format: { type: 'string', options: ['json', 'table', 'plain'], default: 'json' },
|
|
414
|
+
first: { type: 'number', description: 'Number of results' },
|
|
415
|
+
},
|
|
416
|
+
examples: ['linear documents list'],
|
|
417
|
+
},
|
|
418
|
+
'documents get': {
|
|
419
|
+
description: 'Get document details',
|
|
420
|
+
args: { id: { description: 'Document ID', required: true } },
|
|
421
|
+
flags: { format: { type: 'string', options: ['json', 'table', 'plain'], default: 'json' } },
|
|
422
|
+
examples: ['linear documents get DOCUMENT_ID'],
|
|
423
|
+
},
|
|
424
|
+
'documents create': {
|
|
425
|
+
description: 'Create a new document',
|
|
426
|
+
flags: {
|
|
427
|
+
title: { type: 'string', char: 't', description: 'Document title', required: true },
|
|
428
|
+
content: { type: 'string', char: 'c', description: 'Document content (markdown)' },
|
|
429
|
+
'project-id': { type: 'string', description: 'Project ID to associate with' },
|
|
430
|
+
icon: { type: 'string', description: 'Document icon (emoji)' },
|
|
431
|
+
color: { type: 'string', description: 'Document color (hex)' },
|
|
432
|
+
},
|
|
433
|
+
examples: [
|
|
434
|
+
'linear documents create --title "My Document"',
|
|
435
|
+
'linear documents create --title "Notes" --content "# Heading\\n\\nContent"',
|
|
436
|
+
],
|
|
437
|
+
},
|
|
438
|
+
'documents update': {
|
|
439
|
+
description: 'Update a document',
|
|
440
|
+
args: { id: { description: 'Document ID', required: true } },
|
|
441
|
+
flags: {
|
|
442
|
+
title: { type: 'string', char: 't', description: 'New title' },
|
|
443
|
+
content: { type: 'string', char: 'c', description: 'New content (markdown)' },
|
|
444
|
+
'project-id': { type: 'string', description: 'New project ID (empty to remove)' },
|
|
445
|
+
icon: { type: 'string', description: 'New icon (emoji)' },
|
|
446
|
+
color: { type: 'string', description: 'New color (hex)' },
|
|
447
|
+
},
|
|
448
|
+
examples: ['linear documents update DOCUMENT_ID --title "New Title"'],
|
|
449
|
+
},
|
|
450
|
+
'documents delete': {
|
|
451
|
+
description: 'Delete a document (moves to trash)',
|
|
452
|
+
args: { id: { description: 'Document ID', required: true } },
|
|
453
|
+
flags: {},
|
|
454
|
+
examples: ['linear documents delete DOCUMENT_ID'],
|
|
455
|
+
},
|
|
456
|
+
// Initiatives
|
|
457
|
+
'initiatives list': {
|
|
458
|
+
description: 'List initiatives',
|
|
459
|
+
flags: {
|
|
460
|
+
format: { type: 'string', options: ['json', 'table', 'plain'], default: 'json' },
|
|
461
|
+
status: { type: 'string', options: ['Planned', 'Active', 'Completed'], description: 'Filter by status' },
|
|
462
|
+
first: { type: 'number', description: 'Number of results' },
|
|
463
|
+
},
|
|
464
|
+
examples: [
|
|
465
|
+
'linear initiatives list',
|
|
466
|
+
'linear initiatives list --status Active',
|
|
467
|
+
],
|
|
468
|
+
},
|
|
469
|
+
'initiatives get': {
|
|
470
|
+
description: 'Get initiative details',
|
|
471
|
+
args: { id: { description: 'Initiative ID', required: true } },
|
|
472
|
+
flags: { format: { type: 'string', options: ['json', 'table', 'plain'], default: 'json' } },
|
|
473
|
+
examples: ['linear initiatives get INITIATIVE_ID'],
|
|
474
|
+
},
|
|
475
|
+
'initiatives create': {
|
|
476
|
+
description: 'Create a new initiative',
|
|
477
|
+
flags: {
|
|
478
|
+
name: { type: 'string', char: 'n', description: 'Initiative name', required: true },
|
|
479
|
+
description: { type: 'string', char: 'd', description: 'Initiative description' },
|
|
480
|
+
status: { type: 'string', options: ['Planned', 'Active', 'Completed'], description: 'Initial status' },
|
|
481
|
+
'target-date': { type: 'string', description: 'Target completion date (YYYY-MM-DD)' },
|
|
482
|
+
'owner-id': { type: 'string', description: 'Owner user ID' },
|
|
483
|
+
icon: { type: 'string', description: 'Initiative icon (emoji)' },
|
|
484
|
+
color: { type: 'string', description: 'Initiative color (hex)' },
|
|
485
|
+
},
|
|
486
|
+
examples: [
|
|
487
|
+
'linear initiatives create --name "Q1 Goals"',
|
|
488
|
+
'linear initiatives create --name "Launch" --status Active --target-date 2024-12-31',
|
|
489
|
+
],
|
|
490
|
+
},
|
|
491
|
+
'initiatives update': {
|
|
492
|
+
description: 'Update an initiative',
|
|
493
|
+
args: { id: { description: 'Initiative ID', required: true } },
|
|
494
|
+
flags: {
|
|
495
|
+
name: { type: 'string', char: 'n', description: 'New name' },
|
|
496
|
+
description: { type: 'string', char: 'd', description: 'New description' },
|
|
497
|
+
status: { type: 'string', options: ['Planned', 'Active', 'Completed'], description: 'New status' },
|
|
498
|
+
'target-date': { type: 'string', description: 'New target date (YYYY-MM-DD)' },
|
|
499
|
+
'owner-id': { type: 'string', description: 'New owner user ID' },
|
|
500
|
+
icon: { type: 'string', description: 'New icon (emoji)' },
|
|
501
|
+
color: { type: 'string', description: 'New color (hex)' },
|
|
502
|
+
},
|
|
503
|
+
examples: [
|
|
504
|
+
'linear initiatives update INITIATIVE_ID --status Completed',
|
|
505
|
+
'linear initiatives update INITIATIVE_ID --name "New Name"',
|
|
506
|
+
],
|
|
507
|
+
},
|
|
508
|
+
'initiatives delete': {
|
|
509
|
+
description: 'Delete an initiative (moves to trash)',
|
|
510
|
+
args: { id: { description: 'Initiative ID', required: true } },
|
|
511
|
+
flags: {},
|
|
512
|
+
examples: ['linear initiatives delete INITIATIVE_ID'],
|
|
513
|
+
},
|
|
514
|
+
'initiatives archive': {
|
|
515
|
+
description: 'Archive or unarchive an initiative',
|
|
516
|
+
args: { id: { description: 'Initiative ID', required: true } },
|
|
517
|
+
flags: {
|
|
518
|
+
unarchive: { type: 'boolean', char: 'u', description: 'Unarchive instead of archive' },
|
|
519
|
+
},
|
|
520
|
+
examples: [
|
|
521
|
+
'linear initiatives archive INITIATIVE_ID',
|
|
522
|
+
'linear initiatives archive INITIATIVE_ID --unarchive',
|
|
523
|
+
],
|
|
524
|
+
},
|
|
408
525
|
// Config
|
|
409
526
|
'config set': {
|
|
410
527
|
description: 'Set a configuration value',
|
|
@@ -501,6 +618,19 @@ const COMMANDS = {
|
|
|
501
618
|
},
|
|
502
619
|
examples: ['linear schema', 'linear schema issues', 'linear schema --full'],
|
|
503
620
|
},
|
|
621
|
+
upload: {
|
|
622
|
+
description: 'Upload a file to Linear and get the asset URL',
|
|
623
|
+
args: { file: { description: 'Path to the file to upload', required: true } },
|
|
624
|
+
flags: {
|
|
625
|
+
'content-type': { type: 'string', description: 'Override the content type (MIME type)' },
|
|
626
|
+
markdown: { type: 'boolean', char: 'm', description: 'Output as markdown link/image' },
|
|
627
|
+
},
|
|
628
|
+
examples: [
|
|
629
|
+
'linear upload ./screenshot.png',
|
|
630
|
+
'linear upload ./document.pdf --content-type application/pdf',
|
|
631
|
+
'linear upload ./image.png --markdown',
|
|
632
|
+
],
|
|
633
|
+
},
|
|
504
634
|
info: {
|
|
505
635
|
description: 'Show comprehensive CLI documentation (this command)',
|
|
506
636
|
flags: {
|
|
@@ -616,6 +746,34 @@ const ENTITY_SCHEMAS = {
|
|
|
616
746
|
createdAt: 'Creation timestamp',
|
|
617
747
|
},
|
|
618
748
|
},
|
|
749
|
+
documents: {
|
|
750
|
+
entity: 'documents',
|
|
751
|
+
operations: ['list', 'get', 'create', 'update', 'delete'],
|
|
752
|
+
description: 'Rich text documents in Linear',
|
|
753
|
+
fields: {
|
|
754
|
+
id: 'Unique identifier',
|
|
755
|
+
title: 'Document title',
|
|
756
|
+
content: 'Markdown content',
|
|
757
|
+
icon: 'Document icon (emoji)',
|
|
758
|
+
color: 'Document color (hex)',
|
|
759
|
+
project: 'Associated project',
|
|
760
|
+
creator: 'User who created it',
|
|
761
|
+
},
|
|
762
|
+
},
|
|
763
|
+
initiatives: {
|
|
764
|
+
entity: 'initiatives',
|
|
765
|
+
operations: ['list', 'get', 'create', 'update', 'delete', 'archive'],
|
|
766
|
+
description: 'Strategic initiatives grouping projects',
|
|
767
|
+
fields: {
|
|
768
|
+
id: 'Unique identifier',
|
|
769
|
+
name: 'Initiative name',
|
|
770
|
+
description: 'Initiative description',
|
|
771
|
+
status: 'Planned/Active/Completed',
|
|
772
|
+
targetDate: 'Target completion date',
|
|
773
|
+
owner: 'Initiative owner',
|
|
774
|
+
projects: 'Associated projects',
|
|
775
|
+
},
|
|
776
|
+
},
|
|
619
777
|
};
|
|
620
778
|
const WORKFLOWS = {
|
|
621
779
|
createIssue: {
|
|
@@ -692,7 +850,7 @@ export default class Info extends Command {
|
|
|
692
850
|
return acc;
|
|
693
851
|
}, {});
|
|
694
852
|
print(success({
|
|
695
|
-
version: '0.
|
|
853
|
+
version: '0.7.0',
|
|
696
854
|
commands: compactCommands,
|
|
697
855
|
configKeys: CONFIG_KEYS,
|
|
698
856
|
note: 'Use "linear info" for full documentation with examples and workflows',
|
|
@@ -701,7 +859,7 @@ export default class Info extends Command {
|
|
|
701
859
|
}
|
|
702
860
|
// Full documentation
|
|
703
861
|
print(success({
|
|
704
|
-
version: '0.
|
|
862
|
+
version: '0.7.0',
|
|
705
863
|
overview: {
|
|
706
864
|
description: 'CLI for interacting with Linear, designed for LLMs and agents',
|
|
707
865
|
authentication: 'Run "linear auth login" or set LINEAR_API_KEY environment variable',
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class InitiativesArchive extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
unarchive: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { getClient } from '../../lib/client.js';
|
|
3
|
+
import { success, print } from '../../lib/output.js';
|
|
4
|
+
import { handleError, CliError, ErrorCodes } from '../../lib/errors.js';
|
|
5
|
+
export default class InitiativesArchive extends Command {
|
|
6
|
+
static description = 'Archive or unarchive an initiative';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> initiatives archive INITIATIVE_ID',
|
|
9
|
+
'<%= config.bin %> initiatives archive INITIATIVE_ID --unarchive',
|
|
10
|
+
];
|
|
11
|
+
static args = {
|
|
12
|
+
id: Args.string({
|
|
13
|
+
description: 'Initiative ID',
|
|
14
|
+
required: true,
|
|
15
|
+
}),
|
|
16
|
+
};
|
|
17
|
+
static flags = {
|
|
18
|
+
unarchive: Flags.boolean({
|
|
19
|
+
char: 'u',
|
|
20
|
+
description: 'Unarchive instead of archive',
|
|
21
|
+
default: false,
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
async run() {
|
|
25
|
+
try {
|
|
26
|
+
const { args, flags } = await this.parse(InitiativesArchive);
|
|
27
|
+
const client = getClient();
|
|
28
|
+
const payload = flags.unarchive
|
|
29
|
+
? await client.unarchiveInitiative(args.id)
|
|
30
|
+
: await client.archiveInitiative(args.id);
|
|
31
|
+
if (!payload.success) {
|
|
32
|
+
throw new CliError(ErrorCodes.API_ERROR, `Failed to ${flags.unarchive ? 'unarchive' : 'archive'} initiative`);
|
|
33
|
+
}
|
|
34
|
+
print(success({
|
|
35
|
+
id: args.id,
|
|
36
|
+
archived: !flags.unarchive,
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
handleError(err);
|
|
41
|
+
this.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class InitiativesCreate extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
name: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
'target-date': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'owner-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
icon: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
color: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { getClient } from '../../lib/client.js';
|
|
3
|
+
import { success, print } from '../../lib/output.js';
|
|
4
|
+
import { handleError, CliError, ErrorCodes } from '../../lib/errors.js';
|
|
5
|
+
import { LinearDocument } from '@linear/sdk';
|
|
6
|
+
const InitiativeStatus = LinearDocument.InitiativeStatus;
|
|
7
|
+
export default class InitiativesCreate extends Command {
|
|
8
|
+
static description = 'Create a new initiative';
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> initiatives create --name "Q1 Goals"',
|
|
11
|
+
'<%= config.bin %> initiatives create --name "Product Launch" --status Active',
|
|
12
|
+
'<%= config.bin %> initiatives create --name "H2 Objectives" --target-date 2024-12-31',
|
|
13
|
+
];
|
|
14
|
+
static flags = {
|
|
15
|
+
name: Flags.string({
|
|
16
|
+
char: 'n',
|
|
17
|
+
description: 'Initiative name',
|
|
18
|
+
required: true,
|
|
19
|
+
}),
|
|
20
|
+
description: Flags.string({
|
|
21
|
+
char: 'd',
|
|
22
|
+
description: 'Initiative description',
|
|
23
|
+
}),
|
|
24
|
+
status: Flags.string({
|
|
25
|
+
char: 's',
|
|
26
|
+
description: 'Initiative status',
|
|
27
|
+
options: ['Planned', 'Active', 'Completed'],
|
|
28
|
+
}),
|
|
29
|
+
'target-date': Flags.string({
|
|
30
|
+
description: 'Target completion date (YYYY-MM-DD)',
|
|
31
|
+
}),
|
|
32
|
+
'owner-id': Flags.string({
|
|
33
|
+
description: 'Owner user ID',
|
|
34
|
+
}),
|
|
35
|
+
icon: Flags.string({
|
|
36
|
+
description: 'Initiative icon (emoji)',
|
|
37
|
+
}),
|
|
38
|
+
color: Flags.string({
|
|
39
|
+
description: 'Initiative color (hex)',
|
|
40
|
+
}),
|
|
41
|
+
};
|
|
42
|
+
async run() {
|
|
43
|
+
try {
|
|
44
|
+
const { flags } = await this.parse(InitiativesCreate);
|
|
45
|
+
const client = getClient();
|
|
46
|
+
const input = {
|
|
47
|
+
name: flags.name,
|
|
48
|
+
};
|
|
49
|
+
if (flags.description)
|
|
50
|
+
input.description = flags.description;
|
|
51
|
+
if (flags.status) {
|
|
52
|
+
input.status = InitiativeStatus[flags.status];
|
|
53
|
+
}
|
|
54
|
+
if (flags['target-date'])
|
|
55
|
+
input.targetDate = flags['target-date'];
|
|
56
|
+
if (flags['owner-id'])
|
|
57
|
+
input.ownerId = flags['owner-id'];
|
|
58
|
+
if (flags.icon)
|
|
59
|
+
input.icon = flags.icon;
|
|
60
|
+
if (flags.color)
|
|
61
|
+
input.color = flags.color;
|
|
62
|
+
const payload = await client.createInitiative(input);
|
|
63
|
+
if (!payload.success) {
|
|
64
|
+
throw new CliError(ErrorCodes.API_ERROR, 'Failed to create initiative');
|
|
65
|
+
}
|
|
66
|
+
const initiative = await payload.initiative;
|
|
67
|
+
if (!initiative) {
|
|
68
|
+
throw new CliError(ErrorCodes.API_ERROR, 'Initiative not returned');
|
|
69
|
+
}
|
|
70
|
+
const owner = await initiative.owner;
|
|
71
|
+
print(success({
|
|
72
|
+
id: initiative.id,
|
|
73
|
+
name: initiative.name,
|
|
74
|
+
status: initiative.status,
|
|
75
|
+
owner: owner ? { id: owner.id, name: owner.name } : null,
|
|
76
|
+
createdAt: initiative.createdAt,
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
handleError(err);
|
|
81
|
+
this.exit(1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class InitiativesDelete extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Args, Command } from '@oclif/core';
|
|
2
|
+
import { getClient } from '../../lib/client.js';
|
|
3
|
+
import { success, print } from '../../lib/output.js';
|
|
4
|
+
import { handleError, CliError, ErrorCodes } from '../../lib/errors.js';
|
|
5
|
+
export default class InitiativesDelete extends Command {
|
|
6
|
+
static description = 'Delete an initiative (moves to trash)';
|
|
7
|
+
static examples = ['<%= config.bin %> initiatives delete INITIATIVE_ID'];
|
|
8
|
+
static args = {
|
|
9
|
+
id: Args.string({
|
|
10
|
+
description: 'Initiative ID',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
async run() {
|
|
15
|
+
try {
|
|
16
|
+
const { args } = await this.parse(InitiativesDelete);
|
|
17
|
+
const client = getClient();
|
|
18
|
+
const payload = await client.deleteInitiative(args.id);
|
|
19
|
+
if (!payload.success) {
|
|
20
|
+
throw new CliError(ErrorCodes.API_ERROR, 'Failed to delete initiative');
|
|
21
|
+
}
|
|
22
|
+
print(success({
|
|
23
|
+
id: args.id,
|
|
24
|
+
deleted: true,
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
handleError(err);
|
|
29
|
+
this.exit(1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class InitiativesGet extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { getClient } from '../../lib/client.js';
|
|
3
|
+
import { success, print, printItem } from '../../lib/output.js';
|
|
4
|
+
import { handleError, CliError, ErrorCodes } from '../../lib/errors.js';
|
|
5
|
+
export default class InitiativesGet extends Command {
|
|
6
|
+
static description = 'Get initiative details';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> initiatives get INITIATIVE_ID',
|
|
9
|
+
'<%= config.bin %> initiatives get INITIATIVE_ID --format table',
|
|
10
|
+
];
|
|
11
|
+
static args = {
|
|
12
|
+
id: Args.string({
|
|
13
|
+
description: 'Initiative ID',
|
|
14
|
+
required: true,
|
|
15
|
+
}),
|
|
16
|
+
};
|
|
17
|
+
static flags = {
|
|
18
|
+
format: Flags.string({
|
|
19
|
+
char: 'F',
|
|
20
|
+
description: 'Output format',
|
|
21
|
+
options: ['json', 'table', 'plain'],
|
|
22
|
+
default: 'json',
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
async run() {
|
|
26
|
+
try {
|
|
27
|
+
const { args, flags } = await this.parse(InitiativesGet);
|
|
28
|
+
const format = flags.format;
|
|
29
|
+
const client = getClient();
|
|
30
|
+
const initiative = await client.initiative(args.id);
|
|
31
|
+
if (!initiative) {
|
|
32
|
+
throw new CliError(ErrorCodes.NOT_FOUND, `Initiative ${args.id} not found`);
|
|
33
|
+
}
|
|
34
|
+
const [owner, creator, projects] = await Promise.all([
|
|
35
|
+
initiative.owner,
|
|
36
|
+
initiative.creator,
|
|
37
|
+
initiative.projects(),
|
|
38
|
+
]);
|
|
39
|
+
const data = {
|
|
40
|
+
id: initiative.id,
|
|
41
|
+
name: initiative.name,
|
|
42
|
+
description: initiative.description ?? null,
|
|
43
|
+
status: initiative.status,
|
|
44
|
+
icon: initiative.icon ?? null,
|
|
45
|
+
color: initiative.color ?? null,
|
|
46
|
+
owner: owner
|
|
47
|
+
? {
|
|
48
|
+
id: owner.id,
|
|
49
|
+
name: owner.name,
|
|
50
|
+
}
|
|
51
|
+
: null,
|
|
52
|
+
creator: creator
|
|
53
|
+
? {
|
|
54
|
+
id: creator.id,
|
|
55
|
+
name: creator.name,
|
|
56
|
+
}
|
|
57
|
+
: null,
|
|
58
|
+
targetDate: initiative.targetDate ?? null,
|
|
59
|
+
projects: projects.nodes.map((p) => ({
|
|
60
|
+
id: p.id,
|
|
61
|
+
name: p.name,
|
|
62
|
+
})),
|
|
63
|
+
createdAt: initiative.createdAt,
|
|
64
|
+
updatedAt: initiative.updatedAt,
|
|
65
|
+
};
|
|
66
|
+
if (format === 'json') {
|
|
67
|
+
print(success(data));
|
|
68
|
+
}
|
|
69
|
+
else if (format === 'table') {
|
|
70
|
+
printItem({
|
|
71
|
+
id: data.id,
|
|
72
|
+
name: data.name,
|
|
73
|
+
status: data.status,
|
|
74
|
+
owner: data.owner?.name ?? 'Unassigned',
|
|
75
|
+
targetDate: data.targetDate ?? 'None',
|
|
76
|
+
projects: data.projects.length,
|
|
77
|
+
description: data.description ? `${data.description.slice(0, 100)}...` : 'None',
|
|
78
|
+
createdAt: data.createdAt,
|
|
79
|
+
}, format);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
console.log(data.id);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
handleError(err);
|
|
87
|
+
this.exit(1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class InitiativesList extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
first: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { getClient } from '../../lib/client.js';
|
|
3
|
+
import { successList, print, printList } from '../../lib/output.js';
|
|
4
|
+
import { handleError } from '../../lib/errors.js';
|
|
5
|
+
import { colors, truncate, formatProgress } from '../../lib/formatter.js';
|
|
6
|
+
const COLUMNS = [
|
|
7
|
+
{
|
|
8
|
+
key: 'id',
|
|
9
|
+
header: 'ID',
|
|
10
|
+
format: (value) => colors.dim(truncate(String(value), 12)),
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
key: 'name',
|
|
14
|
+
header: 'NAME',
|
|
15
|
+
format: (value) => colors.bold(truncate(String(value), 30)),
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
key: 'status',
|
|
19
|
+
header: 'STATUS',
|
|
20
|
+
format: (value) => {
|
|
21
|
+
const status = String(value);
|
|
22
|
+
if (status === 'Active')
|
|
23
|
+
return colors.green(status);
|
|
24
|
+
if (status === 'Completed')
|
|
25
|
+
return colors.cyan(status);
|
|
26
|
+
return colors.gray(status);
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
key: 'owner',
|
|
31
|
+
header: 'OWNER',
|
|
32
|
+
format: (_value, row) => {
|
|
33
|
+
const owner = row.owner;
|
|
34
|
+
return owner ? truncate(owner.name, 15) : colors.gray('Unassigned');
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: 'targetDate',
|
|
39
|
+
header: 'TARGET',
|
|
40
|
+
format: (value) => (value ? colors.dim(String(value)) : colors.gray('None')),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
key: 'projectCount',
|
|
44
|
+
header: 'PROJECTS',
|
|
45
|
+
format: (value) => String(value),
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
key: 'progress',
|
|
49
|
+
header: 'PROGRESS',
|
|
50
|
+
format: (value) => String(value),
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
export default class InitiativesList extends Command {
|
|
54
|
+
static description = 'List initiatives';
|
|
55
|
+
static examples = [
|
|
56
|
+
'<%= config.bin %> initiatives list',
|
|
57
|
+
'<%= config.bin %> initiatives list --status Active',
|
|
58
|
+
'<%= config.bin %> initiatives list --format table',
|
|
59
|
+
];
|
|
60
|
+
static flags = {
|
|
61
|
+
format: Flags.string({
|
|
62
|
+
char: 'F',
|
|
63
|
+
description: 'Output format',
|
|
64
|
+
options: ['json', 'table', 'plain'],
|
|
65
|
+
default: 'json',
|
|
66
|
+
}),
|
|
67
|
+
status: Flags.string({
|
|
68
|
+
char: 's',
|
|
69
|
+
description: 'Filter by status (Planned, Active, Completed)',
|
|
70
|
+
options: ['Planned', 'Active', 'Completed'],
|
|
71
|
+
}),
|
|
72
|
+
first: Flags.integer({
|
|
73
|
+
description: 'Number of initiatives to fetch',
|
|
74
|
+
default: 50,
|
|
75
|
+
}),
|
|
76
|
+
};
|
|
77
|
+
async run() {
|
|
78
|
+
try {
|
|
79
|
+
const { flags } = await this.parse(InitiativesList);
|
|
80
|
+
const format = flags.format;
|
|
81
|
+
const client = getClient();
|
|
82
|
+
const initiatives = await client.initiatives({
|
|
83
|
+
first: flags.first,
|
|
84
|
+
});
|
|
85
|
+
// Client-side filtering since the API doesn't support filter
|
|
86
|
+
let filtered = initiatives.nodes;
|
|
87
|
+
if (flags.status) {
|
|
88
|
+
filtered = filtered.filter((i) => i.status === flags.status);
|
|
89
|
+
}
|
|
90
|
+
const data = await Promise.all(filtered.map(async (initiative) => {
|
|
91
|
+
const owner = await initiative.owner;
|
|
92
|
+
const projects = await initiative.projects();
|
|
93
|
+
// Calculate progress from projects
|
|
94
|
+
let totalProgress = 0;
|
|
95
|
+
if (projects.nodes.length > 0) {
|
|
96
|
+
for (const project of projects.nodes) {
|
|
97
|
+
totalProgress += project.progress;
|
|
98
|
+
}
|
|
99
|
+
totalProgress = totalProgress / projects.nodes.length;
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
id: initiative.id,
|
|
103
|
+
name: initiative.name,
|
|
104
|
+
status: initiative.status,
|
|
105
|
+
owner: owner ? { id: owner.id, name: owner.name } : null,
|
|
106
|
+
targetDate: initiative.targetDate ?? null,
|
|
107
|
+
projectCount: projects.nodes.length,
|
|
108
|
+
progress: formatProgress(totalProgress),
|
|
109
|
+
createdAt: initiative.createdAt,
|
|
110
|
+
};
|
|
111
|
+
}));
|
|
112
|
+
const pageInfo = {
|
|
113
|
+
hasNextPage: initiatives.pageInfo.hasNextPage,
|
|
114
|
+
hasPreviousPage: initiatives.pageInfo.hasPreviousPage,
|
|
115
|
+
startCursor: initiatives.pageInfo.startCursor,
|
|
116
|
+
endCursor: initiatives.pageInfo.endCursor,
|
|
117
|
+
};
|
|
118
|
+
if (format === 'json') {
|
|
119
|
+
print(successList(data, pageInfo));
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
printList(data, format, {
|
|
123
|
+
columns: COLUMNS,
|
|
124
|
+
primaryKey: 'name',
|
|
125
|
+
secondaryKey: 'id',
|
|
126
|
+
pageInfo,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
handleError(err);
|
|
132
|
+
this.exit(1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|