linear-cli-agents 0.5.1 → 0.6.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 +19 -0
- 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/info.js +50 -2
- package/dist/lib/formatter.d.ts +4 -0
- package/dist/lib/formatter.js +15 -0
- package/oclif.manifest.json +666 -469
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -269,6 +269,25 @@ linear states list
|
|
|
269
269
|
linear states list --team ENG
|
|
270
270
|
```
|
|
271
271
|
|
|
272
|
+
### Cycles (Sprints)
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
# List all cycles
|
|
276
|
+
linear cycles list
|
|
277
|
+
linear cycles list --team ENG
|
|
278
|
+
|
|
279
|
+
# Filter by status
|
|
280
|
+
linear cycles list --active # Currently running
|
|
281
|
+
linear cycles list --upcoming # Future cycles
|
|
282
|
+
linear cycles list --completed # Past cycles
|
|
283
|
+
|
|
284
|
+
# Get current cycle for a team
|
|
285
|
+
linear cycles current --team ENG
|
|
286
|
+
|
|
287
|
+
# Get cycle details
|
|
288
|
+
linear cycles get CYCLE_ID
|
|
289
|
+
```
|
|
290
|
+
|
|
272
291
|
### Users
|
|
273
292
|
|
|
274
293
|
```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
|
+
}
|
package/dist/commands/info.js
CHANGED
|
@@ -435,6 +435,40 @@ const COMMANDS = {
|
|
|
435
435
|
flags: { format: { type: 'string', options: ['json', 'table', 'plain'], default: 'json' } },
|
|
436
436
|
examples: ['linear me'],
|
|
437
437
|
},
|
|
438
|
+
// Cycles
|
|
439
|
+
'cycles list': {
|
|
440
|
+
description: 'List cycles (sprints)',
|
|
441
|
+
flags: {
|
|
442
|
+
format: { type: 'string', options: ['json', 'table', 'plain'], default: 'json' },
|
|
443
|
+
'team-id': { type: 'string', description: 'Filter by team ID' },
|
|
444
|
+
team: { type: 'string', description: 'Filter by team key (e.g., ENG)' },
|
|
445
|
+
active: { type: 'boolean', description: 'Show only active cycles' },
|
|
446
|
+
upcoming: { type: 'boolean', description: 'Show only upcoming cycles' },
|
|
447
|
+
completed: { type: 'boolean', description: 'Show only completed cycles' },
|
|
448
|
+
first: { type: 'number', description: 'Number of results' },
|
|
449
|
+
},
|
|
450
|
+
examples: [
|
|
451
|
+
'linear cycles list',
|
|
452
|
+
'linear cycles list --team ENG',
|
|
453
|
+
'linear cycles list --active',
|
|
454
|
+
],
|
|
455
|
+
},
|
|
456
|
+
'cycles get': {
|
|
457
|
+
description: 'Get cycle (sprint) details',
|
|
458
|
+
args: { id: { description: 'Cycle ID', required: true } },
|
|
459
|
+
flags: { format: { type: 'string', options: ['json', 'table', 'plain'], default: 'json' } },
|
|
460
|
+
examples: ['linear cycles get CYCLE_ID'],
|
|
461
|
+
},
|
|
462
|
+
'cycles current': {
|
|
463
|
+
description: 'Get the current active cycle for a team',
|
|
464
|
+
flags: {
|
|
465
|
+
format: { type: 'string', options: ['json', 'table', 'plain'], default: 'json' },
|
|
466
|
+
'team-id': { type: 'string', description: 'Team ID' },
|
|
467
|
+
team: { type: 'string', description: 'Team key (e.g., ENG)' },
|
|
468
|
+
},
|
|
469
|
+
examples: ['linear cycles current --team ENG'],
|
|
470
|
+
},
|
|
471
|
+
// Other
|
|
438
472
|
search: {
|
|
439
473
|
description: 'Search for issues',
|
|
440
474
|
args: { query: { description: 'Search query', required: true } },
|
|
@@ -516,6 +550,20 @@ const ENTITY_SCHEMAS = {
|
|
|
516
550
|
teams: 'Associated teams',
|
|
517
551
|
},
|
|
518
552
|
},
|
|
553
|
+
cycles: {
|
|
554
|
+
entity: 'cycles',
|
|
555
|
+
operations: ['list', 'get', 'current'],
|
|
556
|
+
description: 'Time-boxed iterations (sprints)',
|
|
557
|
+
fields: {
|
|
558
|
+
id: 'Unique identifier',
|
|
559
|
+
number: 'Cycle number',
|
|
560
|
+
name: 'Cycle name (optional)',
|
|
561
|
+
startsAt: 'Start date',
|
|
562
|
+
endsAt: 'End date',
|
|
563
|
+
progress: 'Completion progress (0-1)',
|
|
564
|
+
team: 'Associated team',
|
|
565
|
+
},
|
|
566
|
+
},
|
|
519
567
|
teams: {
|
|
520
568
|
entity: 'teams',
|
|
521
569
|
operations: ['list'],
|
|
@@ -644,7 +692,7 @@ export default class Info extends Command {
|
|
|
644
692
|
return acc;
|
|
645
693
|
}, {});
|
|
646
694
|
print(success({
|
|
647
|
-
version: '0.
|
|
695
|
+
version: '0.6.0',
|
|
648
696
|
commands: compactCommands,
|
|
649
697
|
configKeys: CONFIG_KEYS,
|
|
650
698
|
note: 'Use "linear info" for full documentation with examples and workflows',
|
|
@@ -653,7 +701,7 @@ export default class Info extends Command {
|
|
|
653
701
|
}
|
|
654
702
|
// Full documentation
|
|
655
703
|
print(success({
|
|
656
|
-
version: '0.
|
|
704
|
+
version: '0.6.0',
|
|
657
705
|
overview: {
|
|
658
706
|
description: 'CLI for interacting with Linear, designed for LLMs and agents',
|
|
659
707
|
authentication: 'Run "linear auth login" or set LINEAR_API_KEY environment variable',
|
package/dist/lib/formatter.d.ts
CHANGED
|
@@ -49,6 +49,10 @@ export declare const formatPriority: (priority: number) => string;
|
|
|
49
49
|
* Truncate string to max length with ellipsis.
|
|
50
50
|
*/
|
|
51
51
|
export declare const truncate: (str: string | undefined | null, maxLength: number) => string;
|
|
52
|
+
/**
|
|
53
|
+
* Format progress as a percentage with color.
|
|
54
|
+
*/
|
|
55
|
+
export declare const formatProgress: (progress: number) => string;
|
|
52
56
|
/**
|
|
53
57
|
* Generic formatter that outputs data in the specified format.
|
|
54
58
|
*/
|
package/dist/lib/formatter.js
CHANGED
|
@@ -166,6 +166,21 @@ export const truncate = (str, maxLength) => {
|
|
|
166
166
|
return str;
|
|
167
167
|
return str.slice(0, maxLength - 1) + '\u2026';
|
|
168
168
|
};
|
|
169
|
+
/**
|
|
170
|
+
* Format progress as a percentage with color.
|
|
171
|
+
*/
|
|
172
|
+
export const formatProgress = (progress) => {
|
|
173
|
+
const percent = Math.round(progress * 100);
|
|
174
|
+
if (percent >= 100)
|
|
175
|
+
return colors.green(`${percent}%`);
|
|
176
|
+
if (percent >= 75)
|
|
177
|
+
return colors.cyan(`${percent}%`);
|
|
178
|
+
if (percent >= 50)
|
|
179
|
+
return colors.blue(`${percent}%`);
|
|
180
|
+
if (percent >= 25)
|
|
181
|
+
return colors.yellow(`${percent}%`);
|
|
182
|
+
return colors.gray(`${percent}%`);
|
|
183
|
+
};
|
|
169
184
|
/**
|
|
170
185
|
* Generic formatter that outputs data in the specified format.
|
|
171
186
|
*/
|