linear-cli-agents 0.2.1 → 0.4.1

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.
Files changed (79) hide show
  1. package/README.md +166 -3
  2. package/bin/dev.js +0 -0
  3. package/dist/commands/comments/add.d.ts +13 -0
  4. package/dist/commands/comments/add.js +89 -0
  5. package/dist/commands/comments/delete.d.ts +12 -0
  6. package/dist/commands/comments/delete.js +50 -0
  7. package/dist/commands/comments/list.d.ts +14 -0
  8. package/dist/commands/comments/list.js +103 -0
  9. package/dist/commands/comments/update.d.ts +13 -0
  10. package/dist/commands/comments/update.js +81 -0
  11. package/dist/commands/issues/add-labels.d.ts +13 -0
  12. package/dist/commands/issues/add-labels.js +90 -0
  13. package/dist/commands/issues/archive.d.ts +13 -0
  14. package/dist/commands/issues/archive.js +83 -0
  15. package/dist/commands/issues/remove-labels.d.ts +13 -0
  16. package/dist/commands/issues/remove-labels.js +90 -0
  17. package/dist/commands/labels/create.d.ts +14 -0
  18. package/dist/commands/labels/create.js +102 -0
  19. package/dist/commands/labels/delete.d.ts +12 -0
  20. package/dist/commands/labels/delete.js +50 -0
  21. package/dist/commands/labels/list.d.ts +12 -0
  22. package/dist/commands/labels/list.js +117 -0
  23. package/dist/commands/labels/update.d.ts +16 -0
  24. package/dist/commands/labels/update.js +109 -0
  25. package/dist/commands/me.js +1 -5
  26. package/dist/commands/milestones/create.d.ts +15 -0
  27. package/dist/commands/milestones/create.js +90 -0
  28. package/dist/commands/milestones/get.d.ts +12 -0
  29. package/dist/commands/milestones/get.js +74 -0
  30. package/dist/commands/milestones/list.d.ts +14 -0
  31. package/dist/commands/milestones/list.js +97 -0
  32. package/dist/commands/milestones/update.d.ts +15 -0
  33. package/dist/commands/milestones/update.js +94 -0
  34. package/dist/commands/project-updates/create.d.ts +14 -0
  35. package/dist/commands/project-updates/create.js +96 -0
  36. package/dist/commands/project-updates/get.d.ts +12 -0
  37. package/dist/commands/project-updates/get.js +80 -0
  38. package/dist/commands/project-updates/list.d.ts +14 -0
  39. package/dist/commands/project-updates/list.js +120 -0
  40. package/dist/commands/project-updates/update.d.ts +14 -0
  41. package/dist/commands/project-updates/update.js +96 -0
  42. package/dist/commands/projects/archive.d.ts +13 -0
  43. package/dist/commands/projects/archive.js +79 -0
  44. package/dist/commands/projects/create.d.ts +16 -0
  45. package/dist/commands/projects/create.js +115 -0
  46. package/dist/commands/projects/delete.d.ts +12 -0
  47. package/dist/commands/projects/delete.js +50 -0
  48. package/dist/commands/projects/get.d.ts +12 -0
  49. package/dist/commands/projects/get.js +102 -0
  50. package/dist/commands/projects/list.d.ts +13 -0
  51. package/dist/commands/projects/list.js +141 -0
  52. package/dist/commands/projects/update.d.ts +18 -0
  53. package/dist/commands/projects/update.js +125 -0
  54. package/dist/commands/relations/create.d.ts +14 -0
  55. package/dist/commands/relations/create.js +98 -0
  56. package/dist/commands/relations/delete.d.ts +12 -0
  57. package/dist/commands/relations/delete.js +47 -0
  58. package/dist/commands/relations/list.d.ts +12 -0
  59. package/dist/commands/relations/list.js +128 -0
  60. package/dist/commands/search.d.ts +15 -0
  61. package/dist/commands/search.js +102 -0
  62. package/dist/commands/states/list.d.ts +12 -0
  63. package/dist/commands/states/list.js +151 -0
  64. package/dist/commands/teams/list.js +1 -1
  65. package/dist/commands/templates/create.d.ts +14 -0
  66. package/dist/commands/templates/create.js +102 -0
  67. package/dist/commands/templates/get.d.ts +12 -0
  68. package/dist/commands/templates/get.js +84 -0
  69. package/dist/commands/templates/list.d.ts +12 -0
  70. package/dist/commands/templates/list.js +110 -0
  71. package/dist/commands/templates/update.d.ts +15 -0
  72. package/dist/commands/templates/update.js +101 -0
  73. package/dist/commands/users/get.d.ts +12 -0
  74. package/dist/commands/users/get.js +91 -0
  75. package/dist/commands/users/list.d.ts +12 -0
  76. package/dist/commands/users/list.js +99 -0
  77. package/dist/lib/config.js +1 -1
  78. package/oclif.manifest.json +2402 -189
  79. package/package.json +47 -17
@@ -0,0 +1,141 @@
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 } from '../../lib/formatter.js';
6
+ const formatProgress = (progress) => {
7
+ const percentage = Math.round(progress * 100);
8
+ if (percentage === 100) {
9
+ return colors.green(`${percentage}%`);
10
+ }
11
+ if (percentage >= 75) {
12
+ return colors.cyan(`${percentage}%`);
13
+ }
14
+ if (percentage >= 50) {
15
+ return colors.yellow(`${percentage}%`);
16
+ }
17
+ return colors.dim(`${percentage}%`);
18
+ };
19
+ const formatState = (state) => {
20
+ switch (state) {
21
+ case 'planned':
22
+ return colors.blue('planned');
23
+ case 'started':
24
+ return colors.yellow('started');
25
+ case 'paused':
26
+ return colors.gray('paused');
27
+ case 'completed':
28
+ return colors.green('completed');
29
+ case 'canceled':
30
+ return colors.red('canceled');
31
+ default:
32
+ return state;
33
+ }
34
+ };
35
+ const COLUMNS = [
36
+ {
37
+ key: 'name',
38
+ header: 'NAME',
39
+ format: (value) => colors.bold(truncate(String(value), 30)),
40
+ },
41
+ {
42
+ key: 'state',
43
+ header: 'STATE',
44
+ format: (value) => formatState(String(value)),
45
+ },
46
+ {
47
+ key: 'progress',
48
+ header: 'PROGRESS',
49
+ format: (value) => formatProgress(Number(value)),
50
+ },
51
+ {
52
+ key: 'targetDate',
53
+ header: 'TARGET',
54
+ format: (value) => (value ? colors.dim(String(value)) : colors.gray('No date')),
55
+ },
56
+ ];
57
+ export default class ProjectsList extends Command {
58
+ static description = 'List projects in the workspace';
59
+ static examples = [
60
+ '<%= config.bin %> projects list',
61
+ '<%= config.bin %> projects list --format table',
62
+ '<%= config.bin %> projects list --team ENG',
63
+ '<%= config.bin %> projects list --state started',
64
+ ];
65
+ static flags = {
66
+ format: Flags.string({
67
+ char: 'F',
68
+ description: 'Output format',
69
+ options: ['json', 'table', 'plain'],
70
+ default: 'json',
71
+ }),
72
+ team: Flags.string({
73
+ char: 't',
74
+ description: 'Filter by team key (e.g., ENG)',
75
+ }),
76
+ state: Flags.string({
77
+ char: 's',
78
+ description: 'Filter by state (planned, started, paused, completed, canceled)',
79
+ }),
80
+ first: Flags.integer({
81
+ description: 'Number of projects to fetch (default: 50)',
82
+ default: 50,
83
+ }),
84
+ after: Flags.string({
85
+ description: 'Cursor for pagination',
86
+ }),
87
+ };
88
+ async run() {
89
+ try {
90
+ const { flags } = await this.parse(ProjectsList);
91
+ const format = flags.format;
92
+ const client = getClient();
93
+ // Build filter
94
+ const filter = {};
95
+ if (flags.state) {
96
+ filter.state = { eq: flags.state };
97
+ }
98
+ if (flags.team) {
99
+ // Filter projects by team
100
+ filter.accessibleTeams = { key: { eq: flags.team } };
101
+ }
102
+ const projects = await client.projects({
103
+ filter: Object.keys(filter).length > 0 ? filter : undefined,
104
+ first: flags.first,
105
+ after: flags.after,
106
+ });
107
+ const data = projects.nodes.map((project) => ({
108
+ id: project.id,
109
+ name: project.name,
110
+ description: project.description ?? undefined,
111
+ state: project.state,
112
+ progress: project.progress,
113
+ targetDate: project.targetDate ?? undefined,
114
+ url: project.url,
115
+ createdAt: project.createdAt,
116
+ updatedAt: project.updatedAt,
117
+ }));
118
+ const pageInfo = {
119
+ hasNextPage: projects.pageInfo.hasNextPage,
120
+ hasPreviousPage: projects.pageInfo.hasPreviousPage,
121
+ startCursor: projects.pageInfo.startCursor,
122
+ endCursor: projects.pageInfo.endCursor,
123
+ };
124
+ if (format === 'json') {
125
+ print(successList(data, pageInfo));
126
+ }
127
+ else {
128
+ printList(data, format, {
129
+ columns: COLUMNS,
130
+ primaryKey: 'name',
131
+ secondaryKey: 'state',
132
+ pageInfo,
133
+ });
134
+ }
135
+ }
136
+ catch (err) {
137
+ handleError(err);
138
+ this.exit(1);
139
+ }
140
+ }
141
+ }
@@ -0,0 +1,18 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class ProjectsUpdate 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
+ name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ state: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ 'lead-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ 'start-date': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ 'target-date': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ };
17
+ run(): Promise<void>;
18
+ }
@@ -0,0 +1,125 @@
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 ProjectsUpdate extends Command {
6
+ static description = 'Update a project';
7
+ static examples = [
8
+ '<%= config.bin %> projects update PROJECT_ID --name "Updated Name"',
9
+ '<%= config.bin %> projects update PROJECT_ID --state completed',
10
+ '<%= config.bin %> projects update PROJECT_ID --target-date 2024-06-30',
11
+ ];
12
+ static args = {
13
+ id: Args.string({
14
+ description: 'Project ID',
15
+ required: true,
16
+ }),
17
+ };
18
+ static flags = {
19
+ format: Flags.string({
20
+ char: 'F',
21
+ description: 'Output format',
22
+ options: ['json', 'table', 'plain'],
23
+ default: 'json',
24
+ }),
25
+ name: Flags.string({
26
+ char: 'n',
27
+ description: 'Project name',
28
+ }),
29
+ description: Flags.string({
30
+ char: 'd',
31
+ description: 'Project description',
32
+ }),
33
+ state: Flags.string({
34
+ char: 's',
35
+ description: 'Project state',
36
+ options: ['planned', 'started', 'paused', 'completed', 'canceled'],
37
+ }),
38
+ 'lead-id': Flags.string({
39
+ description: 'Lead user ID',
40
+ }),
41
+ 'start-date': Flags.string({
42
+ description: 'Start date (YYYY-MM-DD)',
43
+ }),
44
+ 'target-date': Flags.string({
45
+ description: 'Target date (YYYY-MM-DD)',
46
+ }),
47
+ };
48
+ async run() {
49
+ try {
50
+ const { args, flags } = await this.parse(ProjectsUpdate);
51
+ const format = flags.format;
52
+ const client = getClient();
53
+ const input = {};
54
+ if (flags.name)
55
+ input.name = flags.name;
56
+ if (flags.description)
57
+ input.description = flags.description;
58
+ if (flags.state)
59
+ input.state = flags.state;
60
+ if (flags['lead-id'])
61
+ input.leadId = flags['lead-id'];
62
+ if (flags['start-date'])
63
+ input.startDate = flags['start-date'];
64
+ if (flags['target-date'])
65
+ input.targetDate = flags['target-date'];
66
+ if (Object.keys(input).length === 0) {
67
+ throw new CliError(ErrorCodes.INVALID_INPUT, 'At least one field to update is required');
68
+ }
69
+ const payload = await client.updateProject(args.id, input);
70
+ if (!payload.success || !payload.project) {
71
+ throw new CliError(ErrorCodes.API_ERROR, 'Failed to update project');
72
+ }
73
+ const project = await payload.project;
74
+ const [lead, teams] = await Promise.all([project.lead, project.teams()]);
75
+ const data = {
76
+ id: project.id,
77
+ name: project.name,
78
+ description: project.description,
79
+ state: project.state,
80
+ progress: project.progress,
81
+ startDate: project.startDate,
82
+ targetDate: project.targetDate,
83
+ url: project.url,
84
+ lead: lead
85
+ ? {
86
+ id: lead.id,
87
+ name: lead.name,
88
+ }
89
+ : null,
90
+ teams: teams.nodes.map((team) => ({
91
+ id: team.id,
92
+ key: team.key,
93
+ name: team.name,
94
+ })),
95
+ createdAt: project.createdAt,
96
+ updatedAt: project.updatedAt,
97
+ };
98
+ if (format === 'json') {
99
+ print(success(data));
100
+ }
101
+ else if (format === 'table') {
102
+ printItem({
103
+ id: data.id,
104
+ name: data.name,
105
+ description: data.description ?? 'N/A',
106
+ state: data.state,
107
+ progress: `${Math.round(data.progress * 100)}%`,
108
+ startDate: data.startDate ?? 'N/A',
109
+ targetDate: data.targetDate ?? 'N/A',
110
+ lead: data.lead?.name ?? 'None',
111
+ teams: data.teams.map((t) => t.key).join(', '),
112
+ url: data.url,
113
+ updatedAt: data.updatedAt,
114
+ }, format);
115
+ }
116
+ else {
117
+ console.log(data.id);
118
+ }
119
+ }
120
+ catch (err) {
121
+ handleError(err);
122
+ this.exit(1);
123
+ }
124
+ }
125
+ }
@@ -0,0 +1,14 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class RelationsCreate extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ issue: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
+ relatedIssue: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
8
+ };
9
+ static flags: {
10
+ format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ type: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ };
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,98 @@
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
+ import { resolveIssueId } from '../../lib/issue-utils.js';
6
+ const RELATION_TYPES = ['blocks', 'duplicate', 'related'];
7
+ export default class RelationsCreate extends Command {
8
+ static description = 'Create a relation between two issues';
9
+ static examples = [
10
+ '<%= config.bin %> relations create ENG-123 ENG-456 --type blocks',
11
+ '<%= config.bin %> relations create ENG-123 ENG-456 --type duplicate',
12
+ '<%= config.bin %> relations create ENG-123 ENG-456 --type related',
13
+ ];
14
+ static args = {
15
+ issue: Args.string({
16
+ description: 'Source issue ID or identifier (e.g., ENG-123)',
17
+ required: true,
18
+ }),
19
+ relatedIssue: Args.string({
20
+ description: 'Related issue ID or identifier (e.g., ENG-456)',
21
+ required: true,
22
+ }),
23
+ };
24
+ static flags = {
25
+ format: Flags.string({
26
+ char: 'F',
27
+ description: 'Output format',
28
+ options: ['json', 'table', 'plain'],
29
+ default: 'json',
30
+ }),
31
+ type: Flags.string({
32
+ char: 't',
33
+ description: 'Relation type (blocks, duplicate, related)',
34
+ required: true,
35
+ options: [...RELATION_TYPES],
36
+ }),
37
+ };
38
+ async run() {
39
+ try {
40
+ const { args, flags } = await this.parse(RelationsCreate);
41
+ const format = flags.format;
42
+ const relationType = flags.type;
43
+ const client = getClient();
44
+ const [issueId, relatedIssueId] = await Promise.all([
45
+ resolveIssueId(client, args.issue),
46
+ resolveIssueId(client, args.relatedIssue),
47
+ ]);
48
+ const payload = await client.createIssueRelation({
49
+ issueId,
50
+ relatedIssueId,
51
+ type: relationType,
52
+ });
53
+ if (!payload.success || !payload.issueRelation) {
54
+ throw new CliError(ErrorCodes.API_ERROR, 'Failed to create relation');
55
+ }
56
+ const relation = await payload.issueRelation;
57
+ const [issue, relatedIssue] = await Promise.all([relation.issue, relation.relatedIssue]);
58
+ const data = {
59
+ id: relation.id,
60
+ type: relation.type,
61
+ issue: issue
62
+ ? {
63
+ id: issue.id,
64
+ identifier: issue.identifier,
65
+ title: issue.title,
66
+ }
67
+ : null,
68
+ relatedIssue: relatedIssue
69
+ ? {
70
+ id: relatedIssue.id,
71
+ identifier: relatedIssue.identifier,
72
+ title: relatedIssue.title,
73
+ }
74
+ : null,
75
+ createdAt: relation.createdAt,
76
+ };
77
+ if (format === 'json') {
78
+ print(success(data));
79
+ }
80
+ else if (format === 'table') {
81
+ printItem({
82
+ id: data.id,
83
+ type: data.type,
84
+ issue: data.issue?.identifier ?? 'N/A',
85
+ relatedIssue: data.relatedIssue?.identifier ?? 'N/A',
86
+ createdAt: data.createdAt,
87
+ }, format);
88
+ }
89
+ else {
90
+ console.log(data.id);
91
+ }
92
+ }
93
+ catch (err) {
94
+ handleError(err);
95
+ this.exit(1);
96
+ }
97
+ }
98
+ }
@@ -0,0 +1,12 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class RelationsDelete 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,47 @@
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 RelationsDelete extends Command {
6
+ static description = 'Delete an issue relation';
7
+ static examples = ['<%= config.bin %> relations delete RELATION_ID'];
8
+ static args = {
9
+ id: Args.string({
10
+ description: 'Relation ID',
11
+ required: true,
12
+ }),
13
+ };
14
+ static flags = {
15
+ format: Flags.string({
16
+ char: 'F',
17
+ description: 'Output format',
18
+ options: ['json', 'table', 'plain'],
19
+ default: 'json',
20
+ }),
21
+ };
22
+ async run() {
23
+ try {
24
+ const { args, flags } = await this.parse(RelationsDelete);
25
+ const format = flags.format;
26
+ const client = getClient();
27
+ const payload = await client.deleteIssueRelation(args.id);
28
+ if (!payload.success) {
29
+ throw new CliError(ErrorCodes.API_ERROR, 'Failed to delete relation');
30
+ }
31
+ const data = {
32
+ id: args.id,
33
+ deleted: true,
34
+ };
35
+ if (format === 'json') {
36
+ print(success(data));
37
+ }
38
+ else {
39
+ console.log(`Relation ${args.id} deleted`);
40
+ }
41
+ }
42
+ catch (err) {
43
+ handleError(err);
44
+ this.exit(1);
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,12 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class RelationsList extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ issue: 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,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
+ }