linear-cli-agents 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -1
- package/dist/commands/cycles/current.d.ts +11 -0
- package/dist/commands/cycles/current.js +104 -0
- package/dist/commands/cycles/get.d.ts +12 -0
- package/dist/commands/cycles/get.js +86 -0
- package/dist/commands/cycles/list.d.ts +16 -0
- package/dist/commands/cycles/list.js +147 -0
- package/dist/commands/documents/create.d.ts +13 -0
- package/dist/commands/documents/create.js +68 -0
- package/dist/commands/documents/delete.d.ts +9 -0
- package/dist/commands/documents/delete.js +32 -0
- package/dist/commands/documents/get.d.ts +12 -0
- package/dist/commands/documents/get.js +79 -0
- package/dist/commands/documents/list.d.ts +12 -0
- package/dist/commands/documents/list.js +105 -0
- package/dist/commands/documents/update.d.ts +16 -0
- package/dist/commands/documents/update.js +75 -0
- package/dist/commands/info.js +209 -3
- package/dist/commands/initiatives/archive.d.ts +12 -0
- package/dist/commands/initiatives/archive.js +44 -0
- package/dist/commands/initiatives/create.d.ts +15 -0
- package/dist/commands/initiatives/create.js +84 -0
- package/dist/commands/initiatives/delete.d.ts +9 -0
- package/dist/commands/initiatives/delete.js +32 -0
- package/dist/commands/initiatives/get.d.ts +12 -0
- package/dist/commands/initiatives/get.js +90 -0
- package/dist/commands/initiatives/list.d.ts +11 -0
- package/dist/commands/initiatives/list.js +135 -0
- package/dist/commands/initiatives/update.d.ts +18 -0
- package/dist/commands/initiatives/update.js +90 -0
- package/dist/commands/issues/bulk-update.d.ts +2 -0
- package/dist/commands/issues/bulk-update.js +10 -0
- package/dist/commands/issues/create.d.ts +2 -0
- package/dist/commands/issues/create.js +10 -0
- package/dist/commands/issues/get.d.ts +1 -0
- package/dist/commands/issues/get.js +19 -1
- package/dist/commands/issues/update.d.ts +2 -0
- package/dist/commands/issues/update.js +12 -0
- package/dist/commands/upload.d.ts +13 -0
- package/dist/commands/upload.js +117 -0
- package/dist/lib/formatter.d.ts +4 -0
- package/dist/lib/formatter.js +15 -0
- package/oclif.manifest.json +1323 -405
- package/package.json +10 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ A CLI for interacting with [Linear](https://linear.app), designed for LLMs and a
|
|
|
14
14
|
- **Configurable defaults**: Set default team to skip `--team-id` on every command
|
|
15
15
|
- **Bulk operations**: Update multiple issues at once with `bulk-update` and `bulk-label`
|
|
16
16
|
- **Schema introspection**: Discover available operations programmatically
|
|
17
|
-
- **Full CRUD**: Issues, projects, labels, comments, templates, milestones
|
|
17
|
+
- **Full CRUD**: Issues, projects, labels, comments, templates, milestones, documents, initiatives
|
|
18
18
|
- **Issue relations**: Manage blocks, duplicates, and related issues
|
|
19
19
|
- **Project management**: Projects, milestones, and status updates
|
|
20
20
|
- **Team management**: List and browse teams, states, users
|
|
@@ -106,9 +106,12 @@ linear issues list --format plain
|
|
|
106
106
|
# Get a specific issue
|
|
107
107
|
linear issues get ENG-123
|
|
108
108
|
linear issues get ENG-123 --format table
|
|
109
|
+
linear issues get ENG-123 --with-attachments # Include linked PRs, commits
|
|
109
110
|
|
|
110
111
|
# Create an issue
|
|
111
112
|
linear issues create --title "Bug fix" --team-id <team-id>
|
|
113
|
+
linear issues create --title "Task" --team-id <team-id> --due-date 2024-12-31
|
|
114
|
+
linear issues create --title "Sprint work" --team-id <team-id> --cycle-id <cycle-id>
|
|
112
115
|
linear issues create --input '{"title":"Feature","teamId":"xxx","priority":2}'
|
|
113
116
|
|
|
114
117
|
# Update an issue
|
|
@@ -269,6 +272,89 @@ linear states list
|
|
|
269
272
|
linear states list --team ENG
|
|
270
273
|
```
|
|
271
274
|
|
|
275
|
+
### Cycles (Sprints)
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
# List all cycles
|
|
279
|
+
linear cycles list
|
|
280
|
+
linear cycles list --team ENG
|
|
281
|
+
|
|
282
|
+
# Filter by status
|
|
283
|
+
linear cycles list --active # Currently running
|
|
284
|
+
linear cycles list --upcoming # Future cycles
|
|
285
|
+
linear cycles list --completed # Past cycles
|
|
286
|
+
|
|
287
|
+
# Get current cycle for a team
|
|
288
|
+
linear cycles current --team ENG
|
|
289
|
+
|
|
290
|
+
# Get cycle details
|
|
291
|
+
linear cycles get CYCLE_ID
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Documents
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
# List documents
|
|
298
|
+
linear documents list
|
|
299
|
+
|
|
300
|
+
# Get document details
|
|
301
|
+
linear documents get DOCUMENT_ID
|
|
302
|
+
|
|
303
|
+
# Create a document
|
|
304
|
+
linear documents create --title "Meeting Notes"
|
|
305
|
+
linear documents create --title "Specs" --content "# Overview\n\nDetails here..."
|
|
306
|
+
linear documents create --title "Project Doc" --project-id PROJECT_ID
|
|
307
|
+
|
|
308
|
+
# Update a document
|
|
309
|
+
linear documents update DOCUMENT_ID --title "New Title"
|
|
310
|
+
linear documents update DOCUMENT_ID --content "Updated content"
|
|
311
|
+
|
|
312
|
+
# Delete a document
|
|
313
|
+
linear documents delete DOCUMENT_ID
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Initiatives
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
# List initiatives
|
|
320
|
+
linear initiatives list
|
|
321
|
+
linear initiatives list --status Active
|
|
322
|
+
linear initiatives list --status Completed
|
|
323
|
+
|
|
324
|
+
# Get initiative details
|
|
325
|
+
linear initiatives get INITIATIVE_ID
|
|
326
|
+
|
|
327
|
+
# Create an initiative
|
|
328
|
+
linear initiatives create --name "Q1 Goals"
|
|
329
|
+
linear initiatives create --name "Product Launch" --status Active --target-date 2024-12-31
|
|
330
|
+
|
|
331
|
+
# Update an initiative
|
|
332
|
+
linear initiatives update INITIATIVE_ID --name "New Name"
|
|
333
|
+
linear initiatives update INITIATIVE_ID --status Completed
|
|
334
|
+
|
|
335
|
+
# Archive/unarchive an initiative
|
|
336
|
+
linear initiatives archive INITIATIVE_ID
|
|
337
|
+
linear initiatives archive INITIATIVE_ID --unarchive
|
|
338
|
+
|
|
339
|
+
# Delete an initiative
|
|
340
|
+
linear initiatives delete INITIATIVE_ID
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Upload Files
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
# Upload a file and get the asset URL
|
|
347
|
+
linear upload ./screenshot.png
|
|
348
|
+
linear upload ./document.pdf
|
|
349
|
+
|
|
350
|
+
# Output as markdown (for embedding in descriptions)
|
|
351
|
+
linear upload ./image.png --markdown
|
|
352
|
+
# Returns: 
|
|
353
|
+
|
|
354
|
+
# Specify content type
|
|
355
|
+
linear upload ./file.dat --content-type application/octet-stream
|
|
356
|
+
```
|
|
357
|
+
|
|
272
358
|
### Users
|
|
273
359
|
|
|
274
360
|
```bash
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class CyclesCurrent 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-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
team: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
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 CyclesCurrent extends Command {
|
|
6
|
+
static description = 'Get the current active cycle for a team';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> cycles current --team ENG',
|
|
9
|
+
'<%= config.bin %> cycles current --team-id TEAM_ID',
|
|
10
|
+
'<%= config.bin %> cycles current --team ENG --format table',
|
|
11
|
+
];
|
|
12
|
+
static flags = {
|
|
13
|
+
format: Flags.string({
|
|
14
|
+
char: 'F',
|
|
15
|
+
description: 'Output format',
|
|
16
|
+
options: ['json', 'table', 'plain'],
|
|
17
|
+
default: 'json',
|
|
18
|
+
}),
|
|
19
|
+
'team-id': Flags.string({
|
|
20
|
+
description: 'Team ID',
|
|
21
|
+
exclusive: ['team'],
|
|
22
|
+
}),
|
|
23
|
+
team: Flags.string({
|
|
24
|
+
description: 'Team key (e.g., ENG)',
|
|
25
|
+
exclusive: ['team-id'],
|
|
26
|
+
}),
|
|
27
|
+
};
|
|
28
|
+
async run() {
|
|
29
|
+
try {
|
|
30
|
+
const { flags } = await this.parse(CyclesCurrent);
|
|
31
|
+
const format = flags.format;
|
|
32
|
+
const client = getClient();
|
|
33
|
+
if (!flags['team-id'] && !flags.team) {
|
|
34
|
+
throw new CliError(ErrorCodes.MISSING_REQUIRED_FIELD, 'Team is required. Use --team or --team-id');
|
|
35
|
+
}
|
|
36
|
+
// Build filter for active cycle
|
|
37
|
+
const now = new Date();
|
|
38
|
+
const filter = {
|
|
39
|
+
startsAt: { lte: now },
|
|
40
|
+
endsAt: { gte: now },
|
|
41
|
+
};
|
|
42
|
+
if (flags['team-id']) {
|
|
43
|
+
filter.team = { id: { eq: flags['team-id'] } };
|
|
44
|
+
}
|
|
45
|
+
else if (flags.team) {
|
|
46
|
+
filter.team = { key: { eq: flags.team } };
|
|
47
|
+
}
|
|
48
|
+
const cycles = await client.cycles({
|
|
49
|
+
first: 1,
|
|
50
|
+
filter,
|
|
51
|
+
});
|
|
52
|
+
if (cycles.nodes.length === 0) {
|
|
53
|
+
throw new CliError(ErrorCodes.NOT_FOUND, 'No active cycle found for this team');
|
|
54
|
+
}
|
|
55
|
+
const cycle = cycles.nodes[0];
|
|
56
|
+
const [team, issues] = await Promise.all([cycle.team, cycle.issues()]);
|
|
57
|
+
const issuesSummary = {
|
|
58
|
+
total: issues.nodes.length,
|
|
59
|
+
completed: issues.nodes.filter((i) => i.completedAt).length,
|
|
60
|
+
};
|
|
61
|
+
const data = {
|
|
62
|
+
id: cycle.id,
|
|
63
|
+
number: cycle.number,
|
|
64
|
+
name: cycle.name ?? null,
|
|
65
|
+
description: cycle.description ?? null,
|
|
66
|
+
startsAt: cycle.startsAt,
|
|
67
|
+
endsAt: cycle.endsAt,
|
|
68
|
+
progress: cycle.progress,
|
|
69
|
+
team: team
|
|
70
|
+
? {
|
|
71
|
+
id: team.id,
|
|
72
|
+
key: team.key,
|
|
73
|
+
name: team.name,
|
|
74
|
+
}
|
|
75
|
+
: null,
|
|
76
|
+
issues: issuesSummary,
|
|
77
|
+
daysRemaining: Math.ceil((new Date(cycle.endsAt).getTime() - now.getTime()) / (1000 * 60 * 60 * 24)),
|
|
78
|
+
};
|
|
79
|
+
if (format === 'json') {
|
|
80
|
+
print(success(data));
|
|
81
|
+
}
|
|
82
|
+
else if (format === 'table') {
|
|
83
|
+
printItem({
|
|
84
|
+
id: data.id,
|
|
85
|
+
number: data.number,
|
|
86
|
+
name: data.name ?? 'Unnamed',
|
|
87
|
+
team: data.team?.key ?? 'N/A',
|
|
88
|
+
startsAt: data.startsAt,
|
|
89
|
+
endsAt: data.endsAt,
|
|
90
|
+
progress: `${Math.round(data.progress * 100)}%`,
|
|
91
|
+
issues: `${issuesSummary.completed}/${issuesSummary.total} completed`,
|
|
92
|
+
daysRemaining: `${data.daysRemaining} days`,
|
|
93
|
+
}, format);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
console.log(data.id);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
handleError(err);
|
|
101
|
+
this.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class CyclesGet 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,86 @@
|
|
|
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 CyclesGet extends Command {
|
|
6
|
+
static description = 'Get cycle (sprint) details';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> cycles get CYCLE_ID',
|
|
9
|
+
'<%= config.bin %> cycles get CYCLE_ID --format table',
|
|
10
|
+
];
|
|
11
|
+
static args = {
|
|
12
|
+
id: Args.string({
|
|
13
|
+
description: 'Cycle 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(CyclesGet);
|
|
28
|
+
const format = flags.format;
|
|
29
|
+
const client = getClient();
|
|
30
|
+
const cycle = await client.cycle(args.id);
|
|
31
|
+
if (!cycle) {
|
|
32
|
+
throw new CliError(ErrorCodes.NOT_FOUND, `Cycle ${args.id} not found`);
|
|
33
|
+
}
|
|
34
|
+
const [team, issues] = await Promise.all([cycle.team, cycle.issues()]);
|
|
35
|
+
const issuesSummary = {
|
|
36
|
+
total: issues.nodes.length,
|
|
37
|
+
completed: issues.nodes.filter((i) => i.completedAt).length,
|
|
38
|
+
};
|
|
39
|
+
const data = {
|
|
40
|
+
id: cycle.id,
|
|
41
|
+
number: cycle.number,
|
|
42
|
+
name: cycle.name ?? null,
|
|
43
|
+
description: cycle.description ?? null,
|
|
44
|
+
startsAt: cycle.startsAt,
|
|
45
|
+
endsAt: cycle.endsAt,
|
|
46
|
+
completedAt: cycle.completedAt ?? null,
|
|
47
|
+
progress: cycle.progress,
|
|
48
|
+
scopeHistory: cycle.scopeHistory,
|
|
49
|
+
completedScopeHistory: cycle.completedScopeHistory,
|
|
50
|
+
team: team
|
|
51
|
+
? {
|
|
52
|
+
id: team.id,
|
|
53
|
+
key: team.key,
|
|
54
|
+
name: team.name,
|
|
55
|
+
}
|
|
56
|
+
: null,
|
|
57
|
+
issues: issuesSummary,
|
|
58
|
+
createdAt: cycle.createdAt,
|
|
59
|
+
updatedAt: cycle.updatedAt,
|
|
60
|
+
};
|
|
61
|
+
if (format === 'json') {
|
|
62
|
+
print(success(data));
|
|
63
|
+
}
|
|
64
|
+
else if (format === 'table') {
|
|
65
|
+
printItem({
|
|
66
|
+
id: data.id,
|
|
67
|
+
number: data.number,
|
|
68
|
+
name: data.name ?? 'Unnamed',
|
|
69
|
+
team: data.team?.key ?? 'N/A',
|
|
70
|
+
startsAt: data.startsAt,
|
|
71
|
+
endsAt: data.endsAt,
|
|
72
|
+
completedAt: data.completedAt ?? 'In progress',
|
|
73
|
+
progress: `${Math.round(data.progress * 100)}%`,
|
|
74
|
+
issues: `${issuesSummary.completed}/${issuesSummary.total} completed`,
|
|
75
|
+
}, format);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
console.log(data.id);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
handleError(err);
|
|
83
|
+
this.exit(1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class CyclesList 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-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
team: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
active: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
upcoming: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
completed: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
first: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
after: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
};
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { getClient } from '../../lib/client.js';
|
|
3
|
+
import { successList, print, printList } from '../../lib/output.js';
|
|
4
|
+
import { handleError } from '../../lib/errors.js';
|
|
5
|
+
import { colors, truncate, formatProgress } from '../../lib/formatter.js';
|
|
6
|
+
const COLUMNS = [
|
|
7
|
+
{
|
|
8
|
+
key: 'number',
|
|
9
|
+
header: '#',
|
|
10
|
+
format: (value) => colors.dim(String(value)),
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
key: 'name',
|
|
14
|
+
header: 'NAME',
|
|
15
|
+
format: (value) => (value ? colors.bold(truncate(String(value), 25)) : colors.gray('Unnamed')),
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
key: 'teamKey',
|
|
19
|
+
header: 'TEAM',
|
|
20
|
+
format: (value) => colors.cyan(String(value)),
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
key: 'startsAt',
|
|
24
|
+
header: 'START',
|
|
25
|
+
format: (value) => colors.dim(new Date(value).toISOString().split('T')[0]),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
key: 'endsAt',
|
|
29
|
+
header: 'END',
|
|
30
|
+
format: (value) => colors.dim(new Date(value).toISOString().split('T')[0]),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
key: 'progress',
|
|
34
|
+
header: 'PROGRESS',
|
|
35
|
+
format: (value) => formatProgress(Number(value)),
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
export default class CyclesList extends Command {
|
|
39
|
+
static description = 'List cycles (sprints)';
|
|
40
|
+
static examples = [
|
|
41
|
+
'<%= config.bin %> cycles list',
|
|
42
|
+
'<%= config.bin %> cycles list --team-id TEAM_ID',
|
|
43
|
+
'<%= config.bin %> cycles list --team ENG',
|
|
44
|
+
'<%= config.bin %> cycles list --format table',
|
|
45
|
+
'<%= config.bin %> cycles list --active',
|
|
46
|
+
];
|
|
47
|
+
static flags = {
|
|
48
|
+
format: Flags.string({
|
|
49
|
+
char: 'F',
|
|
50
|
+
description: 'Output format',
|
|
51
|
+
options: ['json', 'table', 'plain'],
|
|
52
|
+
default: 'json',
|
|
53
|
+
}),
|
|
54
|
+
'team-id': Flags.string({
|
|
55
|
+
description: 'Filter by team ID',
|
|
56
|
+
}),
|
|
57
|
+
team: Flags.string({
|
|
58
|
+
description: 'Filter by team key (e.g., ENG)',
|
|
59
|
+
}),
|
|
60
|
+
active: Flags.boolean({
|
|
61
|
+
description: 'Show only active cycles',
|
|
62
|
+
default: false,
|
|
63
|
+
}),
|
|
64
|
+
upcoming: Flags.boolean({
|
|
65
|
+
description: 'Show only upcoming cycles',
|
|
66
|
+
default: false,
|
|
67
|
+
}),
|
|
68
|
+
completed: Flags.boolean({
|
|
69
|
+
description: 'Show only completed cycles',
|
|
70
|
+
default: false,
|
|
71
|
+
}),
|
|
72
|
+
first: Flags.integer({
|
|
73
|
+
description: 'Number of cycles to fetch (default: 50)',
|
|
74
|
+
default: 50,
|
|
75
|
+
}),
|
|
76
|
+
after: Flags.string({
|
|
77
|
+
description: 'Cursor for pagination',
|
|
78
|
+
}),
|
|
79
|
+
};
|
|
80
|
+
async run() {
|
|
81
|
+
try {
|
|
82
|
+
const { flags } = await this.parse(CyclesList);
|
|
83
|
+
const format = flags.format;
|
|
84
|
+
const client = getClient();
|
|
85
|
+
// Build filter
|
|
86
|
+
const filter = {};
|
|
87
|
+
if (flags['team-id']) {
|
|
88
|
+
filter.team = { id: { eq: flags['team-id'] } };
|
|
89
|
+
}
|
|
90
|
+
else if (flags.team) {
|
|
91
|
+
filter.team = { key: { eq: flags.team } };
|
|
92
|
+
}
|
|
93
|
+
const now = new Date();
|
|
94
|
+
if (flags.active) {
|
|
95
|
+
filter.startsAt = { lte: now };
|
|
96
|
+
filter.endsAt = { gte: now };
|
|
97
|
+
}
|
|
98
|
+
else if (flags.upcoming) {
|
|
99
|
+
filter.startsAt = { gt: now };
|
|
100
|
+
}
|
|
101
|
+
else if (flags.completed) {
|
|
102
|
+
filter.completedAt = { neq: null };
|
|
103
|
+
}
|
|
104
|
+
const cycles = await client.cycles({
|
|
105
|
+
first: flags.first,
|
|
106
|
+
after: flags.after,
|
|
107
|
+
filter: Object.keys(filter).length > 0 ? filter : undefined,
|
|
108
|
+
});
|
|
109
|
+
const data = await Promise.all(cycles.nodes.map(async (cycle) => {
|
|
110
|
+
const team = await cycle.team;
|
|
111
|
+
return {
|
|
112
|
+
id: cycle.id,
|
|
113
|
+
number: cycle.number,
|
|
114
|
+
name: cycle.name ?? null,
|
|
115
|
+
startsAt: cycle.startsAt,
|
|
116
|
+
endsAt: cycle.endsAt,
|
|
117
|
+
completedAt: cycle.completedAt ?? null,
|
|
118
|
+
progress: cycle.progress,
|
|
119
|
+
teamId: team?.id ?? '',
|
|
120
|
+
teamKey: team?.key ?? '',
|
|
121
|
+
teamName: team?.name ?? '',
|
|
122
|
+
};
|
|
123
|
+
}));
|
|
124
|
+
const pageInfo = {
|
|
125
|
+
hasNextPage: cycles.pageInfo.hasNextPage,
|
|
126
|
+
hasPreviousPage: cycles.pageInfo.hasPreviousPage,
|
|
127
|
+
startCursor: cycles.pageInfo.startCursor,
|
|
128
|
+
endCursor: cycles.pageInfo.endCursor,
|
|
129
|
+
};
|
|
130
|
+
if (format === 'json') {
|
|
131
|
+
print(successList(data, pageInfo));
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
printList(data, format, {
|
|
135
|
+
columns: COLUMNS,
|
|
136
|
+
primaryKey: 'name',
|
|
137
|
+
secondaryKey: 'number',
|
|
138
|
+
pageInfo,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
handleError(err);
|
|
144
|
+
this.exit(1);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class DocumentsCreate extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
title: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
content: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
'project-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
icon: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
color: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { getClient } from '../../lib/client.js';
|
|
3
|
+
import { success, print } from '../../lib/output.js';
|
|
4
|
+
import { handleError, CliError, ErrorCodes } from '../../lib/errors.js';
|
|
5
|
+
export default class DocumentsCreate extends Command {
|
|
6
|
+
static description = 'Create a new document';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> documents create --title "My Document"',
|
|
9
|
+
'<%= config.bin %> documents create --title "Project Doc" --project-id PROJECT_ID',
|
|
10
|
+
'<%= config.bin %> documents create --title "Notes" --content "# Heading\\n\\nContent here"',
|
|
11
|
+
];
|
|
12
|
+
static flags = {
|
|
13
|
+
title: Flags.string({
|
|
14
|
+
char: 't',
|
|
15
|
+
description: 'Document title',
|
|
16
|
+
required: true,
|
|
17
|
+
}),
|
|
18
|
+
content: Flags.string({
|
|
19
|
+
char: 'c',
|
|
20
|
+
description: 'Document content (markdown)',
|
|
21
|
+
}),
|
|
22
|
+
'project-id': Flags.string({
|
|
23
|
+
description: 'Project ID to associate with',
|
|
24
|
+
}),
|
|
25
|
+
icon: Flags.string({
|
|
26
|
+
description: 'Document icon (emoji)',
|
|
27
|
+
}),
|
|
28
|
+
color: Flags.string({
|
|
29
|
+
description: 'Document color (hex)',
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
async run() {
|
|
33
|
+
try {
|
|
34
|
+
const { flags } = await this.parse(DocumentsCreate);
|
|
35
|
+
const client = getClient();
|
|
36
|
+
const input = {
|
|
37
|
+
title: flags.title,
|
|
38
|
+
};
|
|
39
|
+
if (flags.content)
|
|
40
|
+
input.content = flags.content;
|
|
41
|
+
if (flags['project-id'])
|
|
42
|
+
input.projectId = flags['project-id'];
|
|
43
|
+
if (flags.icon)
|
|
44
|
+
input.icon = flags.icon;
|
|
45
|
+
if (flags.color)
|
|
46
|
+
input.color = flags.color;
|
|
47
|
+
const payload = await client.createDocument(input);
|
|
48
|
+
if (!payload.success) {
|
|
49
|
+
throw new CliError(ErrorCodes.API_ERROR, 'Failed to create document');
|
|
50
|
+
}
|
|
51
|
+
const document = await payload.document;
|
|
52
|
+
if (!document) {
|
|
53
|
+
throw new CliError(ErrorCodes.API_ERROR, 'Document not returned');
|
|
54
|
+
}
|
|
55
|
+
const project = await document.project;
|
|
56
|
+
print(success({
|
|
57
|
+
id: document.id,
|
|
58
|
+
title: document.title,
|
|
59
|
+
project: project ? { id: project.id, name: project.name } : null,
|
|
60
|
+
createdAt: document.createdAt,
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
handleError(err);
|
|
65
|
+
this.exit(1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class DocumentsDelete extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Args, Command } from '@oclif/core';
|
|
2
|
+
import { getClient } from '../../lib/client.js';
|
|
3
|
+
import { success, print } from '../../lib/output.js';
|
|
4
|
+
import { handleError, CliError, ErrorCodes } from '../../lib/errors.js';
|
|
5
|
+
export default class DocumentsDelete extends Command {
|
|
6
|
+
static description = 'Delete a document (moves to trash)';
|
|
7
|
+
static examples = ['<%= config.bin %> documents delete DOCUMENT_ID'];
|
|
8
|
+
static args = {
|
|
9
|
+
id: Args.string({
|
|
10
|
+
description: 'Document ID',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
async run() {
|
|
15
|
+
try {
|
|
16
|
+
const { args } = await this.parse(DocumentsDelete);
|
|
17
|
+
const client = getClient();
|
|
18
|
+
const payload = await client.deleteDocument(args.id);
|
|
19
|
+
if (!payload.success) {
|
|
20
|
+
throw new CliError(ErrorCodes.API_ERROR, 'Failed to delete document');
|
|
21
|
+
}
|
|
22
|
+
print(success({
|
|
23
|
+
id: args.id,
|
|
24
|
+
deleted: true,
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
handleError(err);
|
|
29
|
+
this.exit(1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class DocumentsGet 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
|
+
}
|