linear-cli-agents 0.2.0 → 0.4.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 +253 -5
- package/bin/dev.js +0 -0
- package/dist/commands/comments/add.d.ts +13 -0
- package/dist/commands/comments/add.js +89 -0
- package/dist/commands/comments/delete.d.ts +12 -0
- package/dist/commands/comments/delete.js +50 -0
- package/dist/commands/comments/list.d.ts +14 -0
- package/dist/commands/comments/list.js +103 -0
- package/dist/commands/comments/update.d.ts +13 -0
- package/dist/commands/comments/update.js +81 -0
- package/dist/commands/issues/add-labels.d.ts +13 -0
- package/dist/commands/issues/add-labels.js +90 -0
- package/dist/commands/issues/archive.d.ts +13 -0
- package/dist/commands/issues/archive.js +83 -0
- package/dist/commands/issues/remove-labels.d.ts +13 -0
- package/dist/commands/issues/remove-labels.js +90 -0
- package/dist/commands/labels/create.d.ts +14 -0
- package/dist/commands/labels/create.js +102 -0
- package/dist/commands/labels/delete.d.ts +12 -0
- package/dist/commands/labels/delete.js +50 -0
- package/dist/commands/labels/list.d.ts +12 -0
- package/dist/commands/labels/list.js +117 -0
- package/dist/commands/labels/update.d.ts +16 -0
- package/dist/commands/labels/update.js +109 -0
- package/dist/commands/me.js +1 -5
- package/dist/commands/milestones/create.d.ts +15 -0
- package/dist/commands/milestones/create.js +90 -0
- package/dist/commands/milestones/get.d.ts +12 -0
- package/dist/commands/milestones/get.js +74 -0
- package/dist/commands/milestones/list.d.ts +14 -0
- package/dist/commands/milestones/list.js +97 -0
- package/dist/commands/milestones/update.d.ts +15 -0
- package/dist/commands/milestones/update.js +94 -0
- package/dist/commands/project-updates/create.d.ts +14 -0
- package/dist/commands/project-updates/create.js +96 -0
- package/dist/commands/project-updates/get.d.ts +12 -0
- package/dist/commands/project-updates/get.js +80 -0
- package/dist/commands/project-updates/list.d.ts +14 -0
- package/dist/commands/project-updates/list.js +120 -0
- package/dist/commands/project-updates/update.d.ts +14 -0
- package/dist/commands/project-updates/update.js +96 -0
- package/dist/commands/projects/archive.d.ts +13 -0
- package/dist/commands/projects/archive.js +79 -0
- package/dist/commands/projects/create.d.ts +16 -0
- package/dist/commands/projects/create.js +115 -0
- package/dist/commands/projects/delete.d.ts +12 -0
- package/dist/commands/projects/delete.js +50 -0
- package/dist/commands/projects/get.d.ts +12 -0
- package/dist/commands/projects/get.js +102 -0
- package/dist/commands/projects/list.d.ts +13 -0
- package/dist/commands/projects/list.js +141 -0
- package/dist/commands/projects/update.d.ts +18 -0
- package/dist/commands/projects/update.js +125 -0
- package/dist/commands/relations/create.d.ts +14 -0
- package/dist/commands/relations/create.js +98 -0
- package/dist/commands/relations/delete.d.ts +12 -0
- package/dist/commands/relations/delete.js +47 -0
- package/dist/commands/relations/list.d.ts +12 -0
- package/dist/commands/relations/list.js +128 -0
- package/dist/commands/search.d.ts +15 -0
- package/dist/commands/search.js +102 -0
- package/dist/commands/states/list.d.ts +12 -0
- package/dist/commands/states/list.js +151 -0
- package/dist/commands/templates/create.d.ts +14 -0
- package/dist/commands/templates/create.js +102 -0
- package/dist/commands/templates/get.d.ts +12 -0
- package/dist/commands/templates/get.js +84 -0
- package/dist/commands/templates/list.d.ts +12 -0
- package/dist/commands/templates/list.js +110 -0
- package/dist/commands/templates/update.d.ts +15 -0
- package/dist/commands/templates/update.js +101 -0
- package/dist/commands/users/get.d.ts +12 -0
- package/dist/commands/users/get.js +91 -0
- package/dist/commands/users/list.d.ts +12 -0
- package/dist/commands/users/list.js +99 -0
- package/dist/lib/config.js +1 -1
- package/oclif.manifest.json +2397 -184
- package/package.json +47 -17
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { getClient } from '../../lib/client.js';
|
|
3
|
+
import { successList, print, printList } from '../../lib/output.js';
|
|
4
|
+
import { handleError, CliError, ErrorCodes } from '../../lib/errors.js';
|
|
5
|
+
import { resolveIssueId } from '../../lib/issue-utils.js';
|
|
6
|
+
import { colors } from '../../lib/formatter.js';
|
|
7
|
+
const formatRelationType = (type) => {
|
|
8
|
+
switch (type) {
|
|
9
|
+
case 'blocks':
|
|
10
|
+
return colors.red('blocks');
|
|
11
|
+
case 'blocked':
|
|
12
|
+
return colors.yellow('blocked by');
|
|
13
|
+
case 'duplicate':
|
|
14
|
+
return colors.magenta('duplicate of');
|
|
15
|
+
case 'related':
|
|
16
|
+
return colors.blue('related to');
|
|
17
|
+
default:
|
|
18
|
+
return type;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const COLUMNS = [
|
|
22
|
+
{
|
|
23
|
+
key: 'issueIdentifier',
|
|
24
|
+
header: 'ISSUE',
|
|
25
|
+
format: (value) => colors.cyan(String(value)),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
key: 'type',
|
|
29
|
+
header: 'RELATION',
|
|
30
|
+
format: (value) => formatRelationType(String(value)),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
key: 'relatedIssueIdentifier',
|
|
34
|
+
header: 'RELATED ISSUE',
|
|
35
|
+
format: (value) => colors.cyan(String(value)),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: 'relatedIssueTitle',
|
|
39
|
+
header: 'TITLE',
|
|
40
|
+
format: (value) => colors.dim(String(value).slice(0, 30)),
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
export default class RelationsList extends Command {
|
|
44
|
+
static description = 'List relations for an issue';
|
|
45
|
+
static examples = [
|
|
46
|
+
'<%= config.bin %> relations list ENG-123',
|
|
47
|
+
'<%= config.bin %> relations list ENG-123 --format table',
|
|
48
|
+
];
|
|
49
|
+
static args = {
|
|
50
|
+
issue: Args.string({
|
|
51
|
+
description: 'Issue ID or identifier (e.g., ENG-123)',
|
|
52
|
+
required: true,
|
|
53
|
+
}),
|
|
54
|
+
};
|
|
55
|
+
static flags = {
|
|
56
|
+
format: Flags.string({
|
|
57
|
+
char: 'F',
|
|
58
|
+
description: 'Output format',
|
|
59
|
+
options: ['json', 'table', 'plain'],
|
|
60
|
+
default: 'json',
|
|
61
|
+
}),
|
|
62
|
+
};
|
|
63
|
+
async run() {
|
|
64
|
+
try {
|
|
65
|
+
const { args, flags } = await this.parse(RelationsList);
|
|
66
|
+
const format = flags.format;
|
|
67
|
+
const client = getClient();
|
|
68
|
+
const issueId = await resolveIssueId(client, args.issue);
|
|
69
|
+
const issue = await client.issue(issueId);
|
|
70
|
+
if (!issue) {
|
|
71
|
+
throw new CliError(ErrorCodes.NOT_FOUND, `Issue ${args.issue} not found`);
|
|
72
|
+
}
|
|
73
|
+
// Get both relations and inverse relations
|
|
74
|
+
const [relations, inverseRelations] = await Promise.all([issue.relations(), issue.inverseRelations()]);
|
|
75
|
+
const data = [];
|
|
76
|
+
// Process outgoing relations
|
|
77
|
+
for (const relation of relations.nodes) {
|
|
78
|
+
const relatedIssue = await relation.relatedIssue;
|
|
79
|
+
if (relatedIssue) {
|
|
80
|
+
data.push({
|
|
81
|
+
id: relation.id,
|
|
82
|
+
type: relation.type,
|
|
83
|
+
issueId: issue.id,
|
|
84
|
+
issueIdentifier: issue.identifier,
|
|
85
|
+
issueTitle: issue.title,
|
|
86
|
+
relatedIssueId: relatedIssue.id,
|
|
87
|
+
relatedIssueIdentifier: relatedIssue.identifier,
|
|
88
|
+
relatedIssueTitle: relatedIssue.title,
|
|
89
|
+
createdAt: relation.createdAt,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Process incoming relations (inverse)
|
|
94
|
+
for (const relation of inverseRelations.nodes) {
|
|
95
|
+
const sourceIssue = await relation.issue;
|
|
96
|
+
if (sourceIssue) {
|
|
97
|
+
// Flip the type for inverse relations
|
|
98
|
+
const inverseType = relation.type === 'blocks' ? 'blocked' : relation.type === 'blocked' ? 'blocks' : relation.type;
|
|
99
|
+
data.push({
|
|
100
|
+
id: relation.id,
|
|
101
|
+
type: inverseType,
|
|
102
|
+
issueId: issue.id,
|
|
103
|
+
issueIdentifier: issue.identifier,
|
|
104
|
+
issueTitle: issue.title,
|
|
105
|
+
relatedIssueId: sourceIssue.id,
|
|
106
|
+
relatedIssueIdentifier: sourceIssue.identifier,
|
|
107
|
+
relatedIssueTitle: sourceIssue.title,
|
|
108
|
+
createdAt: relation.createdAt,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (format === 'json') {
|
|
113
|
+
print(successList(data));
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
printList(data, format, {
|
|
117
|
+
columns: COLUMNS,
|
|
118
|
+
primaryKey: 'issueIdentifier',
|
|
119
|
+
secondaryKey: 'relatedIssueIdentifier',
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
handleError(err);
|
|
125
|
+
this.exit(1);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Search extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
query: 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
|
+
team: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
first: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
after: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Args, 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, formatPriority } from '../lib/formatter.js';
|
|
6
|
+
const COLUMNS = [
|
|
7
|
+
{
|
|
8
|
+
key: 'identifier',
|
|
9
|
+
header: 'ID',
|
|
10
|
+
format: (value) => colors.cyan(String(value)),
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
key: 'priority',
|
|
14
|
+
header: 'PRI',
|
|
15
|
+
format: (value) => formatPriority(Number(value)),
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
key: 'title',
|
|
19
|
+
header: 'TITLE',
|
|
20
|
+
format: (value) => truncate(String(value), 50),
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
export default class Search extends Command {
|
|
24
|
+
static description = 'Search issues by text query';
|
|
25
|
+
static examples = [
|
|
26
|
+
'<%= config.bin %> search "bug login"',
|
|
27
|
+
'<%= config.bin %> search "SSO" --team ENG',
|
|
28
|
+
'<%= config.bin %> search "authentication" --format table',
|
|
29
|
+
];
|
|
30
|
+
static args = {
|
|
31
|
+
query: Args.string({
|
|
32
|
+
description: 'Search query',
|
|
33
|
+
required: true,
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
static flags = {
|
|
37
|
+
format: Flags.string({
|
|
38
|
+
char: 'F',
|
|
39
|
+
description: 'Output format',
|
|
40
|
+
options: ['json', 'table', 'plain'],
|
|
41
|
+
default: 'json',
|
|
42
|
+
}),
|
|
43
|
+
team: Flags.string({
|
|
44
|
+
char: 't',
|
|
45
|
+
description: 'Filter by team key (e.g., ENG)',
|
|
46
|
+
}),
|
|
47
|
+
first: Flags.integer({
|
|
48
|
+
description: 'Number of results to fetch (default: 20)',
|
|
49
|
+
default: 20,
|
|
50
|
+
}),
|
|
51
|
+
after: Flags.string({
|
|
52
|
+
description: 'Cursor for pagination',
|
|
53
|
+
}),
|
|
54
|
+
};
|
|
55
|
+
async run() {
|
|
56
|
+
try {
|
|
57
|
+
const { args, flags } = await this.parse(Search);
|
|
58
|
+
const format = flags.format;
|
|
59
|
+
const client = getClient();
|
|
60
|
+
// Build filter for team if specified
|
|
61
|
+
const filter = flags.team ? { team: { key: { eq: flags.team } } } : undefined;
|
|
62
|
+
// Use the searchIssues method for text search
|
|
63
|
+
const searchResults = await client.searchIssues(args.query, {
|
|
64
|
+
filter,
|
|
65
|
+
first: flags.first,
|
|
66
|
+
after: flags.after,
|
|
67
|
+
});
|
|
68
|
+
const data = searchResults.nodes.map((issue) => ({
|
|
69
|
+
id: issue.id,
|
|
70
|
+
identifier: issue.identifier,
|
|
71
|
+
title: issue.title,
|
|
72
|
+
description: issue.description ?? undefined,
|
|
73
|
+
priority: issue.priority,
|
|
74
|
+
priorityLabel: issue.priorityLabel,
|
|
75
|
+
url: issue.url,
|
|
76
|
+
createdAt: issue.createdAt,
|
|
77
|
+
updatedAt: issue.updatedAt,
|
|
78
|
+
}));
|
|
79
|
+
const pageInfo = {
|
|
80
|
+
hasNextPage: searchResults.pageInfo.hasNextPage,
|
|
81
|
+
hasPreviousPage: searchResults.pageInfo.hasPreviousPage,
|
|
82
|
+
startCursor: searchResults.pageInfo.startCursor,
|
|
83
|
+
endCursor: searchResults.pageInfo.endCursor,
|
|
84
|
+
};
|
|
85
|
+
if (format === 'json') {
|
|
86
|
+
print(successList(data, pageInfo));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
printList(data, format, {
|
|
90
|
+
columns: COLUMNS,
|
|
91
|
+
primaryKey: 'identifier',
|
|
92
|
+
secondaryKey: 'title',
|
|
93
|
+
pageInfo,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
handleError(err);
|
|
99
|
+
this.exit(1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class StatesList 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
|
+
team: 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
|
+
after: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
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 } from '../../lib/formatter.js';
|
|
6
|
+
const STATE_TYPE_ORDER = {
|
|
7
|
+
backlog: 1,
|
|
8
|
+
unstarted: 2,
|
|
9
|
+
started: 3,
|
|
10
|
+
completed: 4,
|
|
11
|
+
canceled: 5,
|
|
12
|
+
};
|
|
13
|
+
const formatStateType = (type) => {
|
|
14
|
+
switch (type) {
|
|
15
|
+
case 'backlog':
|
|
16
|
+
return colors.gray('backlog');
|
|
17
|
+
case 'unstarted':
|
|
18
|
+
return colors.blue('unstarted');
|
|
19
|
+
case 'started':
|
|
20
|
+
return colors.yellow('started');
|
|
21
|
+
case 'completed':
|
|
22
|
+
return colors.green('completed');
|
|
23
|
+
case 'canceled':
|
|
24
|
+
return colors.red('canceled');
|
|
25
|
+
default:
|
|
26
|
+
return type;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const COLUMNS = [
|
|
30
|
+
{
|
|
31
|
+
key: 'teamKey',
|
|
32
|
+
header: 'TEAM',
|
|
33
|
+
format: (value) => colors.cyan(String(value)),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
key: 'name',
|
|
37
|
+
header: 'NAME',
|
|
38
|
+
format: (value) => colors.bold(String(value)),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
key: 'type',
|
|
42
|
+
header: 'TYPE',
|
|
43
|
+
format: (value) => formatStateType(String(value)),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
key: 'color',
|
|
47
|
+
header: 'COLOR',
|
|
48
|
+
format: (value) => colors.dim(String(value)),
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
export default class StatesList extends Command {
|
|
52
|
+
static description = 'List workflow states in the workspace';
|
|
53
|
+
static examples = [
|
|
54
|
+
'<%= config.bin %> states list',
|
|
55
|
+
'<%= config.bin %> states list --format table',
|
|
56
|
+
'<%= config.bin %> states list --team ENG',
|
|
57
|
+
];
|
|
58
|
+
static flags = {
|
|
59
|
+
format: Flags.string({
|
|
60
|
+
char: 'F',
|
|
61
|
+
description: 'Output format',
|
|
62
|
+
options: ['json', 'table', 'plain'],
|
|
63
|
+
default: 'json',
|
|
64
|
+
}),
|
|
65
|
+
team: Flags.string({
|
|
66
|
+
char: 't',
|
|
67
|
+
description: 'Filter by team key (e.g., ENG)',
|
|
68
|
+
}),
|
|
69
|
+
first: Flags.integer({
|
|
70
|
+
description: 'Number of states to fetch (default: 100)',
|
|
71
|
+
default: 100,
|
|
72
|
+
}),
|
|
73
|
+
after: Flags.string({
|
|
74
|
+
description: 'Cursor for pagination',
|
|
75
|
+
}),
|
|
76
|
+
};
|
|
77
|
+
async run() {
|
|
78
|
+
try {
|
|
79
|
+
const { flags } = await this.parse(StatesList);
|
|
80
|
+
const format = flags.format;
|
|
81
|
+
const client = getClient();
|
|
82
|
+
let states;
|
|
83
|
+
if (flags.team) {
|
|
84
|
+
// Fetch states for a specific team
|
|
85
|
+
const teams = await client.teams({
|
|
86
|
+
filter: { key: { eq: flags.team } },
|
|
87
|
+
});
|
|
88
|
+
const team = teams.nodes[0];
|
|
89
|
+
if (!team) {
|
|
90
|
+
throw new Error(`Team ${flags.team} not found`);
|
|
91
|
+
}
|
|
92
|
+
states = await team.states({
|
|
93
|
+
first: flags.first,
|
|
94
|
+
after: flags.after,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Fetch all workflow states
|
|
99
|
+
states = await client.workflowStates({
|
|
100
|
+
first: flags.first,
|
|
101
|
+
after: flags.after,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const data = await Promise.all(states.nodes.map(async (state) => {
|
|
105
|
+
const team = await state.team;
|
|
106
|
+
return {
|
|
107
|
+
id: state.id,
|
|
108
|
+
name: state.name,
|
|
109
|
+
color: state.color,
|
|
110
|
+
type: state.type,
|
|
111
|
+
position: state.position,
|
|
112
|
+
teamId: team?.id ?? '',
|
|
113
|
+
teamKey: team?.key ?? '',
|
|
114
|
+
};
|
|
115
|
+
}));
|
|
116
|
+
// Sort by team, then by type order, then by position
|
|
117
|
+
data.sort((a, b) => {
|
|
118
|
+
if (a.teamKey !== b.teamKey) {
|
|
119
|
+
return a.teamKey.localeCompare(b.teamKey);
|
|
120
|
+
}
|
|
121
|
+
const typeOrderA = STATE_TYPE_ORDER[a.type] ?? 99;
|
|
122
|
+
const typeOrderB = STATE_TYPE_ORDER[b.type] ?? 99;
|
|
123
|
+
if (typeOrderA !== typeOrderB) {
|
|
124
|
+
return typeOrderA - typeOrderB;
|
|
125
|
+
}
|
|
126
|
+
return a.position - b.position;
|
|
127
|
+
});
|
|
128
|
+
const pageInfo = {
|
|
129
|
+
hasNextPage: states.pageInfo.hasNextPage,
|
|
130
|
+
hasPreviousPage: states.pageInfo.hasPreviousPage,
|
|
131
|
+
startCursor: states.pageInfo.startCursor,
|
|
132
|
+
endCursor: states.pageInfo.endCursor,
|
|
133
|
+
};
|
|
134
|
+
if (format === 'json') {
|
|
135
|
+
print(successList(data, pageInfo));
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
printList(data, format, {
|
|
139
|
+
columns: COLUMNS,
|
|
140
|
+
primaryKey: 'name',
|
|
141
|
+
secondaryKey: 'type',
|
|
142
|
+
pageInfo,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
handleError(err);
|
|
148
|
+
this.exit(1);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class TemplatesCreate 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
|
+
name: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
type: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'team-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
'template-data': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { 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 TemplatesCreate extends Command {
|
|
6
|
+
static description = 'Create a template';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> templates create --name "Bug Report" --type issue --team-id TEAM_ID --template-data \'{"title":"Bug: ","priority":2}\'',
|
|
9
|
+
'<%= config.bin %> templates create --name "Feature Request" --type issue --template-data \'{"title":"Feature: "}\'',
|
|
10
|
+
];
|
|
11
|
+
static flags = {
|
|
12
|
+
format: Flags.string({
|
|
13
|
+
char: 'F',
|
|
14
|
+
description: 'Output format',
|
|
15
|
+
options: ['json', 'table', 'plain'],
|
|
16
|
+
default: 'json',
|
|
17
|
+
}),
|
|
18
|
+
name: Flags.string({
|
|
19
|
+
char: 'n',
|
|
20
|
+
description: 'Template name',
|
|
21
|
+
required: true,
|
|
22
|
+
}),
|
|
23
|
+
type: Flags.string({
|
|
24
|
+
char: 't',
|
|
25
|
+
description: 'Template type',
|
|
26
|
+
required: true,
|
|
27
|
+
options: ['issue', 'project'],
|
|
28
|
+
}),
|
|
29
|
+
description: Flags.string({
|
|
30
|
+
char: 'd',
|
|
31
|
+
description: 'Template description',
|
|
32
|
+
}),
|
|
33
|
+
'team-id': Flags.string({
|
|
34
|
+
description: 'Team ID (optional, for team-specific templates)',
|
|
35
|
+
}),
|
|
36
|
+
'template-data': Flags.string({
|
|
37
|
+
description: 'Template data as JSON (e.g., {"title":"Bug: ","priority":2})',
|
|
38
|
+
required: true,
|
|
39
|
+
}),
|
|
40
|
+
};
|
|
41
|
+
async run() {
|
|
42
|
+
try {
|
|
43
|
+
const { flags } = await this.parse(TemplatesCreate);
|
|
44
|
+
const format = flags.format;
|
|
45
|
+
const client = getClient();
|
|
46
|
+
let templateData;
|
|
47
|
+
try {
|
|
48
|
+
templateData = JSON.parse(flags['template-data']);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
throw new CliError(ErrorCodes.INVALID_INPUT, 'Invalid JSON in --template-data');
|
|
52
|
+
}
|
|
53
|
+
const payload = await client.createTemplate({
|
|
54
|
+
name: flags.name,
|
|
55
|
+
type: flags.type,
|
|
56
|
+
description: flags.description,
|
|
57
|
+
teamId: flags['team-id'],
|
|
58
|
+
templateData,
|
|
59
|
+
});
|
|
60
|
+
if (!payload.success || !payload.template) {
|
|
61
|
+
throw new CliError(ErrorCodes.API_ERROR, 'Failed to create template');
|
|
62
|
+
}
|
|
63
|
+
const template = await payload.template;
|
|
64
|
+
const team = await template.team;
|
|
65
|
+
const data = {
|
|
66
|
+
id: template.id,
|
|
67
|
+
name: template.name,
|
|
68
|
+
type: template.type,
|
|
69
|
+
description: template.description,
|
|
70
|
+
templateData: template.templateData,
|
|
71
|
+
team: team
|
|
72
|
+
? {
|
|
73
|
+
id: team.id,
|
|
74
|
+
key: team.key,
|
|
75
|
+
name: team.name,
|
|
76
|
+
}
|
|
77
|
+
: null,
|
|
78
|
+
createdAt: template.createdAt,
|
|
79
|
+
};
|
|
80
|
+
if (format === 'json') {
|
|
81
|
+
print(success(data));
|
|
82
|
+
}
|
|
83
|
+
else if (format === 'table') {
|
|
84
|
+
printItem({
|
|
85
|
+
id: data.id,
|
|
86
|
+
name: data.name,
|
|
87
|
+
type: data.type,
|
|
88
|
+
description: data.description ?? 'N/A',
|
|
89
|
+
team: data.team?.key ?? 'Organization',
|
|
90
|
+
createdAt: data.createdAt,
|
|
91
|
+
}, format);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
console.log(data.id);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
handleError(err);
|
|
99
|
+
this.exit(1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class TemplatesGet 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,84 @@
|
|
|
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 TemplatesGet extends Command {
|
|
6
|
+
static description = 'Get a template by ID';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> templates get TEMPLATE_ID',
|
|
9
|
+
'<%= config.bin %> templates get TEMPLATE_ID --format table',
|
|
10
|
+
];
|
|
11
|
+
static args = {
|
|
12
|
+
id: Args.string({
|
|
13
|
+
description: 'Template 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(TemplatesGet);
|
|
28
|
+
const format = flags.format;
|
|
29
|
+
const client = getClient();
|
|
30
|
+
// Templates need to be fetched via organization
|
|
31
|
+
const org = await client.organization;
|
|
32
|
+
const templates = await org.templates();
|
|
33
|
+
const template = templates.nodes.find((t) => t.id === args.id);
|
|
34
|
+
if (!template) {
|
|
35
|
+
throw new CliError(ErrorCodes.NOT_FOUND, `Template ${args.id} not found`);
|
|
36
|
+
}
|
|
37
|
+
const [team, creator] = await Promise.all([template.team, template.creator]);
|
|
38
|
+
const data = {
|
|
39
|
+
id: template.id,
|
|
40
|
+
name: template.name,
|
|
41
|
+
type: template.type,
|
|
42
|
+
description: template.description,
|
|
43
|
+
templateData: template.templateData,
|
|
44
|
+
team: team
|
|
45
|
+
? {
|
|
46
|
+
id: team.id,
|
|
47
|
+
key: team.key,
|
|
48
|
+
name: team.name,
|
|
49
|
+
}
|
|
50
|
+
: null,
|
|
51
|
+
creator: creator
|
|
52
|
+
? {
|
|
53
|
+
id: creator.id,
|
|
54
|
+
name: creator.name,
|
|
55
|
+
}
|
|
56
|
+
: null,
|
|
57
|
+
createdAt: template.createdAt,
|
|
58
|
+
updatedAt: template.updatedAt,
|
|
59
|
+
};
|
|
60
|
+
if (format === 'json') {
|
|
61
|
+
print(success(data));
|
|
62
|
+
}
|
|
63
|
+
else if (format === 'table') {
|
|
64
|
+
printItem({
|
|
65
|
+
id: data.id,
|
|
66
|
+
name: data.name,
|
|
67
|
+
type: data.type,
|
|
68
|
+
description: data.description ?? 'N/A',
|
|
69
|
+
team: data.team?.key ?? 'Organization',
|
|
70
|
+
creator: data.creator?.name ?? 'Unknown',
|
|
71
|
+
createdAt: data.createdAt,
|
|
72
|
+
updatedAt: data.updatedAt,
|
|
73
|
+
}, format);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
console.log(data.id);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
handleError(err);
|
|
81
|
+
this.exit(1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class TemplatesList 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
|
+
team: 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
|
+
after: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|