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.
Files changed (78) hide show
  1. package/README.md +253 -5
  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/templates/create.d.ts +14 -0
  65. package/dist/commands/templates/create.js +102 -0
  66. package/dist/commands/templates/get.d.ts +12 -0
  67. package/dist/commands/templates/get.js +84 -0
  68. package/dist/commands/templates/list.d.ts +12 -0
  69. package/dist/commands/templates/list.js +110 -0
  70. package/dist/commands/templates/update.d.ts +15 -0
  71. package/dist/commands/templates/update.js +101 -0
  72. package/dist/commands/users/get.d.ts +12 -0
  73. package/dist/commands/users/get.js +91 -0
  74. package/dist/commands/users/list.d.ts +12 -0
  75. package/dist/commands/users/list.js +99 -0
  76. package/dist/lib/config.js +1 -1
  77. package/oclif.manifest.json +2397 -184
  78. package/package.json +47 -17
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # linear-cli-agents
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/linear-cli-agents.svg)](https://www.npmjs.com/package/linear-cli-agents)
4
- [![CI](https://github.com/nchgn/linear-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/nchgn/linear-cli/actions/workflows/ci.yml)
4
+ [![CI](https://github.com/nchgn/linear-cli-agents/actions/workflows/ci.yml/badge.svg)](https://github.com/nchgn/linear-cli-agents/actions/workflows/ci.yml)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
7
  A CLI for interacting with [Linear](https://linear.app), designed for LLMs and agents.
@@ -9,8 +9,14 @@ A CLI for interacting with [Linear](https://linear.app), designed for LLMs and a
9
9
  ## Features
10
10
 
11
11
  - **JSON output**: All commands return structured JSON, perfect for parsing by LLMs
12
+ - **Multiple formats**: JSON (default), table (colored), or plain text output
12
13
  - **Schema introspection**: Discover available operations programmatically
13
- - **Full CRUD for issues**: List, create, update, and delete issues
14
+ - **Full CRUD**: Issues, projects, labels, comments, templates, milestones
15
+ - **Issue relations**: Manage blocks, duplicates, and related issues
16
+ - **Project management**: Projects, milestones, and status updates
17
+ - **Team management**: List and browse teams, states, users
18
+ - **Browser integration**: Open issues, teams, inbox directly in Linear
19
+ - **Search**: Find issues across workspace
14
20
  - **Raw GraphQL queries**: Execute any GraphQL query directly
15
21
 
16
22
  ## Installation
@@ -23,9 +29,10 @@ pnpm add -g linear-cli-agents
23
29
 
24
30
  ## Authentication
25
31
 
26
- Get your API key from [Linear Settings > API](https://linear.app/settings/api).
27
-
28
32
  ```bash
33
+ # Open Linear API settings in browser to create a key
34
+ linear auth login --browser
35
+
29
36
  # Login with API key
30
37
  linear auth login --key lin_api_xxxxx
31
38
 
@@ -35,6 +42,9 @@ export LINEAR_API_KEY=lin_api_xxxxx
35
42
  # Check auth status
36
43
  linear auth status
37
44
 
45
+ # View current user info
46
+ linear me
47
+
38
48
  # Logout
39
49
  linear auth logout
40
50
  ```
@@ -53,8 +63,13 @@ linear issues list --assignee me
53
63
  linear issues list --state "In Progress"
54
64
  linear issues list --filter '{"priority":{"lte":2}}'
55
65
 
66
+ # Output formats: json (default), table (colored), plain (IDs only)
67
+ linear issues list --format table
68
+ linear issues list --format plain
69
+
56
70
  # Get a specific issue
57
71
  linear issues get ENG-123
72
+ linear issues get ENG-123 --format table
58
73
 
59
74
  # Create an issue
60
75
  linear issues create --title "Bug fix" --team-id <team-id>
@@ -66,6 +81,206 @@ linear issues update ENG-123 --state-id <state-id> --assignee-id <user-id>
66
81
 
67
82
  # Delete an issue (moves to trash)
68
83
  linear issues delete ENG-123
84
+
85
+ # Archive/unarchive an issue
86
+ linear issues archive ENG-123
87
+ linear issues archive ENG-123 --unarchive
88
+
89
+ # Manage labels on issues
90
+ linear issues add-labels ENG-123 --label-ids LABEL_ID1,LABEL_ID2
91
+ linear issues remove-labels ENG-123 --label-ids LABEL_ID1
92
+ ```
93
+
94
+ ### Projects
95
+
96
+ ```bash
97
+ # List projects
98
+ linear projects list
99
+ linear projects list --team ENG
100
+ linear projects list --state started
101
+
102
+ # Get project details
103
+ linear projects get PROJECT_ID
104
+
105
+ # Create a project
106
+ linear projects create --name "Q1 Goals" --team-ids TEAM_ID
107
+ linear projects create --name "Feature X" --team-ids TEAM_ID --target-date 2024-06-30
108
+
109
+ # Update a project
110
+ linear projects update PROJECT_ID --name "Updated Name"
111
+ linear projects update PROJECT_ID --state completed
112
+
113
+ # Delete a project
114
+ linear projects delete PROJECT_ID
115
+
116
+ # Archive/unarchive a project
117
+ linear projects archive PROJECT_ID
118
+ linear projects archive PROJECT_ID --unarchive
119
+ ```
120
+
121
+ ### Project Milestones
122
+
123
+ ```bash
124
+ # List milestones for a project
125
+ linear milestones list PROJECT_ID
126
+
127
+ # Get milestone details
128
+ linear milestones get MILESTONE_ID
129
+
130
+ # Create a milestone
131
+ linear milestones create PROJECT_ID --name "Alpha Release" --target-date 2024-03-01
132
+
133
+ # Update a milestone
134
+ linear milestones update MILESTONE_ID --name "Beta Release"
135
+ ```
136
+
137
+ ### Project Updates
138
+
139
+ ```bash
140
+ # List status updates for a project
141
+ linear project-updates list PROJECT_ID
142
+
143
+ # Get update details
144
+ linear project-updates get UPDATE_ID
145
+
146
+ # Create a status update
147
+ linear project-updates create PROJECT_ID --body "Sprint completed" --health onTrack
148
+
149
+ # Update a status update
150
+ linear project-updates update UPDATE_ID --body "Updated status"
151
+ ```
152
+
153
+ ### Issue Relations
154
+
155
+ ```bash
156
+ # List relations for an issue
157
+ linear relations list ENG-123
158
+
159
+ # Create a relation
160
+ linear relations create ENG-123 ENG-456 --type blocks
161
+ linear relations create ENG-123 ENG-456 --type duplicate
162
+ linear relations create ENG-123 ENG-456 --type related
163
+
164
+ # Delete a relation
165
+ linear relations delete RELATION_ID
166
+ ```
167
+
168
+ ### Labels
169
+
170
+ ```bash
171
+ # List labels
172
+ linear labels list
173
+ linear labels list --team ENG
174
+
175
+ # Create a label
176
+ linear labels create --name "Bug" --color "#FF0000"
177
+ linear labels create --name "Feature" --color "#00FF00" --team-id TEAM_ID
178
+
179
+ # Update a label
180
+ linear labels update LABEL_ID --name "Critical Bug" --color "#FF0000"
181
+
182
+ # Delete a label
183
+ linear labels delete LABEL_ID
184
+ ```
185
+
186
+ ### Templates
187
+
188
+ ```bash
189
+ # List templates
190
+ linear templates list
191
+ linear templates list --team ENG
192
+
193
+ # Get template details
194
+ linear templates get TEMPLATE_ID
195
+
196
+ # Create a template
197
+ linear templates create --name "Bug Report" --type issue --team-id TEAM_ID \
198
+ --template-data '{"title":"Bug: ","priority":2}'
199
+
200
+ # Update a template
201
+ linear templates update TEMPLATE_ID --name "Updated Template"
202
+ ```
203
+
204
+ ### Comments
205
+
206
+ ```bash
207
+ # List comments on an issue
208
+ linear comments list ENG-123
209
+
210
+ # Add a comment
211
+ linear comments add ENG-123 --body "This looks good!"
212
+
213
+ # Update a comment
214
+ linear comments update COMMENT_ID --body "Updated comment"
215
+
216
+ # Delete a comment
217
+ linear comments delete COMMENT_ID
218
+ ```
219
+
220
+ ### States
221
+
222
+ ```bash
223
+ # List workflow states
224
+ linear states list
225
+ linear states list --team ENG
226
+ ```
227
+
228
+ ### Users
229
+
230
+ ```bash
231
+ # List users
232
+ linear users list
233
+
234
+ # Get user details
235
+ linear users get USER_ID
236
+ ```
237
+
238
+ ### Search
239
+
240
+ ```bash
241
+ # Search issues
242
+ linear search "login bug"
243
+ linear search "SSO" --team ENG
244
+ ```
245
+
246
+ ### Teams
247
+
248
+ ```bash
249
+ # List all teams
250
+ linear teams list
251
+
252
+ # With table format
253
+ linear teams list --format table
254
+ ```
255
+
256
+ ### Open in Browser
257
+
258
+ ```bash
259
+ # Open an issue
260
+ linear open ENG-123
261
+
262
+ # Open a team
263
+ linear open --team ENG
264
+
265
+ # Open inbox
266
+ linear open --inbox
267
+
268
+ # Open my issues
269
+ linear open --my-issues
270
+
271
+ # Open settings
272
+ linear open --settings
273
+ ```
274
+
275
+ ### User Info
276
+
277
+ ```bash
278
+ # Show current user
279
+ linear me
280
+ linear whoami
281
+
282
+ # With table format
283
+ linear me --format table
69
284
  ```
70
285
 
71
286
  ### Schema Introspection (for LLMs)
@@ -97,7 +312,9 @@ linear query --gql "query(\$id: String!) { issue(id: \$id) { title } }" \
97
312
 
98
313
  ## Output Format
99
314
 
100
- All commands return structured JSON:
315
+ ### JSON (default)
316
+
317
+ All commands return structured JSON by default, ideal for LLMs and scripts:
101
318
 
102
319
  ```json
103
320
  // Success
@@ -126,6 +343,37 @@ All commands return structured JSON:
126
343
  }
127
344
  ```
128
345
 
346
+ ### Table (human-readable)
347
+
348
+ Use `--format table` for colored, human-readable output:
349
+
350
+ ```bash
351
+ linear issues list --format table
352
+ # ID PRI TITLE
353
+ # ENG-123 High Fix login bug
354
+ # ENG-124 Medium Add dark mode
355
+ ```
356
+
357
+ ### Plain (minimal)
358
+
359
+ Use `--format plain` for minimal output (IDs/identifiers only):
360
+
361
+ ```bash
362
+ linear issues list --format plain
363
+ # ENG-123 Fix login bug
364
+ # ENG-124 Add dark mode
365
+ ```
366
+
367
+ ### Disabling Colors
368
+
369
+ Colors are automatically disabled when piping output. You can also disable them manually:
370
+
371
+ ```bash
372
+ NO_COLOR=1 linear issues list --format table
373
+ # or
374
+ linear issues list --format table --no-color
375
+ ```
376
+
129
377
  ## For LLM Integration
130
378
 
131
379
  The CLI is designed to be easily used by LLMs and AI agents:
package/bin/dev.js CHANGED
File without changes
@@ -0,0 +1,13 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class CommentsAdd 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
+ body: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ };
12
+ run(): Promise<void>;
13
+ }
@@ -0,0 +1,89 @@
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
+ export default class CommentsAdd extends Command {
7
+ static description = 'Add a comment to an issue';
8
+ static examples = [
9
+ '<%= config.bin %> comments add ENG-123 --body "This is a comment"',
10
+ '<%= config.bin %> comments add ENG-123 --body "Looks good!" --format table',
11
+ ];
12
+ static args = {
13
+ issue: Args.string({
14
+ description: 'Issue ID or identifier (e.g., ENG-123)',
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
+ body: Flags.string({
26
+ char: 'b',
27
+ description: 'Comment body (supports markdown)',
28
+ required: true,
29
+ }),
30
+ };
31
+ async run() {
32
+ try {
33
+ const { args, flags } = await this.parse(CommentsAdd);
34
+ const format = flags.format;
35
+ const client = getClient();
36
+ const issueId = await resolveIssueId(client, args.issue);
37
+ const issue = await client.issue(issueId);
38
+ if (!issue) {
39
+ throw new CliError(ErrorCodes.NOT_FOUND, `Issue ${args.issue} not found`);
40
+ }
41
+ const payload = await client.createComment({
42
+ issueId,
43
+ body: flags.body,
44
+ });
45
+ if (!payload.success || !payload.comment) {
46
+ throw new CliError(ErrorCodes.API_ERROR, 'Failed to create comment');
47
+ }
48
+ const comment = await payload.comment;
49
+ const user = await comment.user;
50
+ const data = {
51
+ id: comment.id,
52
+ body: comment.body,
53
+ createdAt: comment.createdAt,
54
+ user: user
55
+ ? {
56
+ id: user.id,
57
+ name: user.name,
58
+ email: user.email,
59
+ }
60
+ : null,
61
+ issue: {
62
+ id: issue.id,
63
+ identifier: issue.identifier,
64
+ title: issue.title,
65
+ },
66
+ };
67
+ if (format === 'json') {
68
+ print(success(data));
69
+ }
70
+ else if (format === 'table') {
71
+ printItem({
72
+ id: data.id,
73
+ issue: data.issue.identifier,
74
+ user: data.user?.name ?? 'Unknown',
75
+ body: data.body,
76
+ createdAt: data.createdAt,
77
+ }, format);
78
+ }
79
+ else {
80
+ // plain: just the comment ID
81
+ console.log(data.id);
82
+ }
83
+ }
84
+ catch (err) {
85
+ handleError(err);
86
+ this.exit(1);
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,12 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class CommentsDelete 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,50 @@
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 CommentsDelete extends Command {
6
+ static description = 'Delete a comment';
7
+ static examples = ['<%= config.bin %> comments delete COMMENT_ID'];
8
+ static args = {
9
+ id: Args.string({
10
+ description: 'Comment 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(CommentsDelete);
25
+ const format = flags.format;
26
+ const client = getClient();
27
+ const payload = await client.deleteComment(args.id);
28
+ if (!payload.success) {
29
+ throw new CliError(ErrorCodes.API_ERROR, 'Failed to delete comment');
30
+ }
31
+ const data = {
32
+ id: args.id,
33
+ deleted: true,
34
+ };
35
+ if (format === 'json') {
36
+ print(success(data));
37
+ }
38
+ else if (format === 'table') {
39
+ console.log(`Comment ${args.id} deleted successfully`);
40
+ }
41
+ else {
42
+ console.log(args.id);
43
+ }
44
+ }
45
+ catch (err) {
46
+ handleError(err);
47
+ this.exit(1);
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,14 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class CommentsList 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
+ first: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
11
+ after: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ };
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,103 @@
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, truncate } from '../../lib/formatter.js';
7
+ const COLUMNS = [
8
+ {
9
+ key: 'userName',
10
+ header: 'USER',
11
+ format: (value) => colors.cyan(String(value)),
12
+ },
13
+ {
14
+ key: 'body',
15
+ header: 'COMMENT',
16
+ format: (value) => truncate(String(value).replace(/\n/g, ' '), 60),
17
+ },
18
+ {
19
+ key: 'createdAt',
20
+ header: 'DATE',
21
+ format: (value) => {
22
+ const date = new Date(value);
23
+ return colors.dim(date.toLocaleDateString());
24
+ },
25
+ },
26
+ ];
27
+ export default class CommentsList extends Command {
28
+ static description = 'List comments on an issue';
29
+ static examples = [
30
+ '<%= config.bin %> comments list ENG-123',
31
+ '<%= config.bin %> comments list ENG-123 --format table',
32
+ ];
33
+ static args = {
34
+ issue: Args.string({
35
+ description: 'Issue ID or identifier (e.g., ENG-123)',
36
+ required: true,
37
+ }),
38
+ };
39
+ static flags = {
40
+ format: Flags.string({
41
+ char: 'F',
42
+ description: 'Output format',
43
+ options: ['json', 'table', 'plain'],
44
+ default: 'json',
45
+ }),
46
+ first: Flags.integer({
47
+ description: 'Number of comments to fetch (default: 50)',
48
+ default: 50,
49
+ }),
50
+ after: Flags.string({
51
+ description: 'Cursor for pagination',
52
+ }),
53
+ };
54
+ async run() {
55
+ try {
56
+ const { args, flags } = await this.parse(CommentsList);
57
+ const format = flags.format;
58
+ const client = getClient();
59
+ const issueId = await resolveIssueId(client, args.issue);
60
+ const issue = await client.issue(issueId);
61
+ if (!issue) {
62
+ throw new CliError(ErrorCodes.NOT_FOUND, `Issue ${args.issue} not found`);
63
+ }
64
+ const comments = await issue.comments({
65
+ first: flags.first,
66
+ after: flags.after,
67
+ });
68
+ const data = await Promise.all(comments.nodes.map(async (comment) => {
69
+ const user = await comment.user;
70
+ return {
71
+ id: comment.id,
72
+ body: comment.body,
73
+ createdAt: comment.createdAt,
74
+ updatedAt: comment.updatedAt,
75
+ userId: user?.id ?? '',
76
+ userName: user?.name ?? 'Unknown',
77
+ userEmail: user?.email ?? '',
78
+ };
79
+ }));
80
+ const pageInfo = {
81
+ hasNextPage: comments.pageInfo.hasNextPage,
82
+ hasPreviousPage: comments.pageInfo.hasPreviousPage,
83
+ startCursor: comments.pageInfo.startCursor,
84
+ endCursor: comments.pageInfo.endCursor,
85
+ };
86
+ if (format === 'json') {
87
+ print(successList(data, pageInfo));
88
+ }
89
+ else {
90
+ printList(data, format, {
91
+ columns: COLUMNS,
92
+ primaryKey: 'userName',
93
+ secondaryKey: 'body',
94
+ pageInfo,
95
+ });
96
+ }
97
+ }
98
+ catch (err) {
99
+ handleError(err);
100
+ this.exit(1);
101
+ }
102
+ }
103
+ }
@@ -0,0 +1,13 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class CommentsUpdate 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
+ body: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ };
12
+ run(): Promise<void>;
13
+ }
@@ -0,0 +1,81 @@
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 CommentsUpdate extends Command {
6
+ static description = 'Update a comment';
7
+ static examples = ['<%= config.bin %> comments update COMMENT_ID --body "Updated comment text"'];
8
+ static args = {
9
+ id: Args.string({
10
+ description: 'Comment 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
+ body: Flags.string({
22
+ char: 'b',
23
+ description: 'Comment body (supports markdown)',
24
+ required: true,
25
+ }),
26
+ };
27
+ async run() {
28
+ try {
29
+ const { args, flags } = await this.parse(CommentsUpdate);
30
+ const format = flags.format;
31
+ const client = getClient();
32
+ const payload = await client.updateComment(args.id, {
33
+ body: flags.body,
34
+ });
35
+ if (!payload.success || !payload.comment) {
36
+ throw new CliError(ErrorCodes.API_ERROR, 'Failed to update comment');
37
+ }
38
+ const comment = await payload.comment;
39
+ const [user, issue] = await Promise.all([comment.user, comment.issue]);
40
+ const data = {
41
+ id: comment.id,
42
+ body: comment.body,
43
+ createdAt: comment.createdAt,
44
+ updatedAt: comment.updatedAt,
45
+ user: user
46
+ ? {
47
+ id: user.id,
48
+ name: user.name,
49
+ email: user.email,
50
+ }
51
+ : null,
52
+ issue: issue
53
+ ? {
54
+ id: issue.id,
55
+ identifier: issue.identifier,
56
+ title: issue.title,
57
+ }
58
+ : null,
59
+ };
60
+ if (format === 'json') {
61
+ print(success(data));
62
+ }
63
+ else if (format === 'table') {
64
+ printItem({
65
+ id: data.id,
66
+ issue: data.issue?.identifier ?? 'N/A',
67
+ user: data.user?.name ?? 'Unknown',
68
+ body: data.body,
69
+ updatedAt: data.updatedAt,
70
+ }, format);
71
+ }
72
+ else {
73
+ console.log(data.id);
74
+ }
75
+ }
76
+ catch (err) {
77
+ handleError(err);
78
+ this.exit(1);
79
+ }
80
+ }
81
+ }