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
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# linear-cli-agents
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/linear-cli-agents)
|
|
4
|
-
[](https://github.com/nchgn/linear-cli/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/nchgn/linear-cli-agents/actions/workflows/ci.yml)
|
|
5
5
|
[](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
|
|
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
|
-
|
|
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
|
+
}
|