linear-cli-agents 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -4
- package/bin/dev.js +0 -0
- package/dist/commands/auth/__tests__/status.test.d.ts +1 -0
- package/dist/commands/auth/__tests__/status.test.js +23 -0
- package/dist/commands/auth/login.d.ts +1 -0
- package/dist/commands/auth/login.js +39 -3
- package/dist/commands/issues/delete.js +1 -3
- package/dist/commands/issues/get.d.ts +3 -0
- package/dist/commands/issues/get.js +39 -6
- package/dist/commands/issues/list.d.ts +1 -0
- package/dist/commands/issues/list.js +42 -5
- package/dist/commands/me.d.ts +10 -0
- package/dist/commands/me.js +76 -0
- package/dist/commands/open.d.ts +15 -0
- package/dist/commands/open.js +100 -0
- package/dist/commands/teams/list.d.ts +11 -0
- package/dist/commands/teams/list.js +99 -0
- package/dist/lib/__tests__/errors.test.d.ts +1 -0
- package/dist/lib/__tests__/errors.test.js +98 -0
- package/dist/lib/__tests__/output.test.d.ts +1 -0
- package/dist/lib/__tests__/output.test.js +112 -0
- package/dist/lib/formatter.d.ts +59 -0
- package/dist/lib/formatter.js +192 -0
- package/dist/lib/output.d.ts +17 -1
- package/dist/lib/output.js +43 -0
- package/dist/lib/types.d.ts +7 -0
- package/oclif.manifest.json +221 -4
- package/package.json +35 -15
package/README.md
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
# linear-cli
|
|
1
|
+
# linear-cli-agents
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/linear-cli-agents)
|
|
4
|
+
[](https://github.com/nchgn/linear-cli/actions/workflows/ci.yml)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
2
6
|
|
|
3
7
|
A CLI for interacting with [Linear](https://linear.app), designed for LLMs and agents.
|
|
4
8
|
|
|
5
9
|
## Features
|
|
6
10
|
|
|
7
11
|
- **JSON output**: All commands return structured JSON, perfect for parsing by LLMs
|
|
12
|
+
- **Multiple formats**: JSON (default), table (colored), or plain text output
|
|
8
13
|
- **Schema introspection**: Discover available operations programmatically
|
|
9
14
|
- **Full CRUD for issues**: List, create, update, and delete issues
|
|
15
|
+
- **Team management**: List and browse teams
|
|
16
|
+
- **Browser integration**: Open issues, teams, inbox directly in Linear
|
|
10
17
|
- **Raw GraphQL queries**: Execute any GraphQL query directly
|
|
11
18
|
|
|
12
19
|
## Installation
|
|
@@ -19,9 +26,10 @@ pnpm add -g linear-cli-agents
|
|
|
19
26
|
|
|
20
27
|
## Authentication
|
|
21
28
|
|
|
22
|
-
Get your API key from [Linear Settings > API](https://linear.app/settings/api).
|
|
23
|
-
|
|
24
29
|
```bash
|
|
30
|
+
# Open Linear API settings in browser to create a key
|
|
31
|
+
linear auth login --browser
|
|
32
|
+
|
|
25
33
|
# Login with API key
|
|
26
34
|
linear auth login --key lin_api_xxxxx
|
|
27
35
|
|
|
@@ -31,6 +39,9 @@ export LINEAR_API_KEY=lin_api_xxxxx
|
|
|
31
39
|
# Check auth status
|
|
32
40
|
linear auth status
|
|
33
41
|
|
|
42
|
+
# View current user info
|
|
43
|
+
linear me
|
|
44
|
+
|
|
34
45
|
# Logout
|
|
35
46
|
linear auth logout
|
|
36
47
|
```
|
|
@@ -49,8 +60,13 @@ linear issues list --assignee me
|
|
|
49
60
|
linear issues list --state "In Progress"
|
|
50
61
|
linear issues list --filter '{"priority":{"lte":2}}'
|
|
51
62
|
|
|
63
|
+
# Output formats: json (default), table (colored), plain (IDs only)
|
|
64
|
+
linear issues list --format table
|
|
65
|
+
linear issues list --format plain
|
|
66
|
+
|
|
52
67
|
# Get a specific issue
|
|
53
68
|
linear issues get ENG-123
|
|
69
|
+
linear issues get ENG-123 --format table
|
|
54
70
|
|
|
55
71
|
# Create an issue
|
|
56
72
|
linear issues create --title "Bug fix" --team-id <team-id>
|
|
@@ -64,6 +80,46 @@ linear issues update ENG-123 --state-id <state-id> --assignee-id <user-id>
|
|
|
64
80
|
linear issues delete ENG-123
|
|
65
81
|
```
|
|
66
82
|
|
|
83
|
+
### Teams
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# List all teams
|
|
87
|
+
linear teams list
|
|
88
|
+
|
|
89
|
+
# With table format
|
|
90
|
+
linear teams list --format table
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Open in Browser
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Open an issue
|
|
97
|
+
linear open ENG-123
|
|
98
|
+
|
|
99
|
+
# Open a team
|
|
100
|
+
linear open --team ENG
|
|
101
|
+
|
|
102
|
+
# Open inbox
|
|
103
|
+
linear open --inbox
|
|
104
|
+
|
|
105
|
+
# Open my issues
|
|
106
|
+
linear open --my-issues
|
|
107
|
+
|
|
108
|
+
# Open settings
|
|
109
|
+
linear open --settings
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### User Info
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Show current user
|
|
116
|
+
linear me
|
|
117
|
+
linear whoami
|
|
118
|
+
|
|
119
|
+
# With table format
|
|
120
|
+
linear me --format table
|
|
121
|
+
```
|
|
122
|
+
|
|
67
123
|
### Schema Introspection (for LLMs)
|
|
68
124
|
|
|
69
125
|
```bash
|
|
@@ -93,7 +149,9 @@ linear query --gql "query(\$id: String!) { issue(id: \$id) { title } }" \
|
|
|
93
149
|
|
|
94
150
|
## Output Format
|
|
95
151
|
|
|
96
|
-
|
|
152
|
+
### JSON (default)
|
|
153
|
+
|
|
154
|
+
All commands return structured JSON by default, ideal for LLMs and scripts:
|
|
97
155
|
|
|
98
156
|
```json
|
|
99
157
|
// Success
|
|
@@ -122,6 +180,37 @@ All commands return structured JSON:
|
|
|
122
180
|
}
|
|
123
181
|
```
|
|
124
182
|
|
|
183
|
+
### Table (human-readable)
|
|
184
|
+
|
|
185
|
+
Use `--format table` for colored, human-readable output:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
linear issues list --format table
|
|
189
|
+
# ID PRI TITLE
|
|
190
|
+
# ENG-123 High Fix login bug
|
|
191
|
+
# ENG-124 Medium Add dark mode
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Plain (minimal)
|
|
195
|
+
|
|
196
|
+
Use `--format plain` for minimal output (IDs/identifiers only):
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
linear issues list --format plain
|
|
200
|
+
# ENG-123 Fix login bug
|
|
201
|
+
# ENG-124 Add dark mode
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Disabling Colors
|
|
205
|
+
|
|
206
|
+
Colors are automatically disabled when piping output. You can also disable them manually:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
NO_COLOR=1 linear issues list --format table
|
|
210
|
+
# or
|
|
211
|
+
linear issues list --format table --no-color
|
|
212
|
+
```
|
|
213
|
+
|
|
125
214
|
## For LLM Integration
|
|
126
215
|
|
|
127
216
|
The CLI is designed to be easily used by LLMs and AI agents:
|
|
@@ -163,6 +252,51 @@ pnpm build
|
|
|
163
252
|
pnpm test
|
|
164
253
|
```
|
|
165
254
|
|
|
255
|
+
## Troubleshooting
|
|
256
|
+
|
|
257
|
+
### Authentication Issues
|
|
258
|
+
|
|
259
|
+
**"Not authenticated" error:**
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
# Check current auth status
|
|
263
|
+
linear auth status
|
|
264
|
+
|
|
265
|
+
# Re-authenticate
|
|
266
|
+
linear auth login --key lin_api_xxxxx
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Using environment variable:**
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
export LINEAR_API_KEY=lin_api_xxxxx
|
|
273
|
+
linear auth status # Should show source: environment
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Common Errors
|
|
277
|
+
|
|
278
|
+
| Error Code | Cause | Solution |
|
|
279
|
+
| ------------------- | -------------------------- | ----------------------------------------------- |
|
|
280
|
+
| `NOT_AUTHENTICATED` | No API key configured | Run `linear auth login` or set `LINEAR_API_KEY` |
|
|
281
|
+
| `INVALID_API_KEY` | API key expired or invalid | Generate a new key in Linear settings |
|
|
282
|
+
| `NOT_FOUND` | Resource doesn't exist | Check the issue/team identifier |
|
|
283
|
+
| `RATE_LIMITED` | Too many requests | Wait before retrying |
|
|
284
|
+
|
|
285
|
+
### Getting Help
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
# See all commands
|
|
289
|
+
linear --help
|
|
290
|
+
|
|
291
|
+
# Get help for a specific command
|
|
292
|
+
linear issues --help
|
|
293
|
+
linear issues create --help
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Contributing
|
|
297
|
+
|
|
298
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
|
|
299
|
+
|
|
166
300
|
## License
|
|
167
301
|
|
|
168
302
|
MIT
|
package/bin/dev.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
/**
|
|
3
|
+
* Note: Full integration tests for auth commands require mocking
|
|
4
|
+
* the Linear API client, which is complex with oclif's command runner.
|
|
5
|
+
*
|
|
6
|
+
* For now, we test the output format contracts and defer
|
|
7
|
+
* integration testing to manual verification or E2E tests.
|
|
8
|
+
*
|
|
9
|
+
* The underlying utilities (output.ts, errors.ts) are fully tested.
|
|
10
|
+
*/
|
|
11
|
+
describe('auth status command', () => {
|
|
12
|
+
it('should have proper command structure', async () => {
|
|
13
|
+
// Dynamic import to verify module loads correctly
|
|
14
|
+
const { default: AuthStatus } = await import('../status.js');
|
|
15
|
+
expect(AuthStatus.description).toBe('Check authentication status');
|
|
16
|
+
expect(AuthStatus.examples).toHaveLength(1);
|
|
17
|
+
});
|
|
18
|
+
it('should export as oclif Command', async () => {
|
|
19
|
+
const { default: AuthStatus } = await import('../status.js');
|
|
20
|
+
const { Command } = await import('@oclif/core');
|
|
21
|
+
expect(AuthStatus.prototype).toBeInstanceOf(Command.prototype.constructor);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -4,6 +4,7 @@ export default class AuthLogin extends Command {
|
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
6
|
key: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
browser: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
8
|
};
|
|
8
9
|
static args: {
|
|
9
10
|
key: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import open from 'open';
|
|
2
3
|
import { createClient } from '../../lib/client.js';
|
|
3
4
|
import { saveApiKey, getConfigPath } from '../../lib/config.js';
|
|
4
|
-
import { success, print } from '../../lib/output.js';
|
|
5
|
-
import { handleError
|
|
5
|
+
import { success, error, print } from '../../lib/output.js';
|
|
6
|
+
import { handleError } from '../../lib/errors.js';
|
|
7
|
+
import { colors } from '../../lib/formatter.js';
|
|
8
|
+
const LINEAR_API_SETTINGS_URL = 'https://linear.app/settings/api';
|
|
6
9
|
export default class AuthLogin extends Command {
|
|
7
10
|
static description = 'Authenticate with Linear using an API key';
|
|
8
11
|
static examples = [
|
|
9
12
|
'<%= config.bin %> auth login --key lin_api_xxxxx',
|
|
13
|
+
'<%= config.bin %> auth login --browser',
|
|
10
14
|
'LINEAR_API_KEY=lin_api_xxxxx <%= config.bin %> auth login',
|
|
11
15
|
];
|
|
12
16
|
static flags = {
|
|
@@ -15,6 +19,11 @@ export default class AuthLogin extends Command {
|
|
|
15
19
|
description: 'Linear API key (or set LINEAR_API_KEY env var)',
|
|
16
20
|
env: 'LINEAR_API_KEY',
|
|
17
21
|
}),
|
|
22
|
+
browser: Flags.boolean({
|
|
23
|
+
char: 'b',
|
|
24
|
+
description: 'Open Linear API settings in browser to create a key',
|
|
25
|
+
default: false,
|
|
26
|
+
}),
|
|
18
27
|
};
|
|
19
28
|
static args = {
|
|
20
29
|
key: Args.string({
|
|
@@ -26,8 +35,35 @@ export default class AuthLogin extends Command {
|
|
|
26
35
|
try {
|
|
27
36
|
const { args, flags } = await this.parse(AuthLogin);
|
|
28
37
|
const apiKey = flags.key || args.key;
|
|
38
|
+
// If --browser flag, open Linear API settings
|
|
39
|
+
if (flags.browser) {
|
|
40
|
+
await open(LINEAR_API_SETTINGS_URL);
|
|
41
|
+
print(success({
|
|
42
|
+
message: 'Opened Linear API settings in browser',
|
|
43
|
+
url: LINEAR_API_SETTINGS_URL,
|
|
44
|
+
instructions: [
|
|
45
|
+
'1. Click "Create key" or "New API key"',
|
|
46
|
+
'2. Give it a label (e.g., "CLI")',
|
|
47
|
+
'3. Copy the generated key (starts with lin_api_)',
|
|
48
|
+
'4. Run: linear auth login --key YOUR_KEY',
|
|
49
|
+
],
|
|
50
|
+
}));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// If no API key provided, show helpful instructions
|
|
29
54
|
if (!apiKey) {
|
|
30
|
-
|
|
55
|
+
console.log(colors.bold('\nTo authenticate with Linear CLI, you need an API key.\n'));
|
|
56
|
+
console.log(colors.cyan('Option 1: Open browser to create a key'));
|
|
57
|
+
console.log(' linear auth login --browser\n');
|
|
58
|
+
console.log(colors.cyan('Option 2: If you already have a key'));
|
|
59
|
+
console.log(' linear auth login --key lin_api_xxxxx\n');
|
|
60
|
+
console.log(colors.cyan('Option 3: Set environment variable'));
|
|
61
|
+
console.log(' export LINEAR_API_KEY=lin_api_xxxxx\n');
|
|
62
|
+
console.log(colors.dim(`Manual URL: ${LINEAR_API_SETTINGS_URL}`));
|
|
63
|
+
console.log('');
|
|
64
|
+
print(error('MISSING_API_KEY', 'No API key provided. Use --browser to create one or --key to provide an existing key.', { url: LINEAR_API_SETTINGS_URL }));
|
|
65
|
+
this.exit(1);
|
|
66
|
+
return;
|
|
31
67
|
}
|
|
32
68
|
// Validate the API key by making a test request
|
|
33
69
|
const client = createClient(apiKey);
|
|
@@ -48,9 +48,7 @@ export default class IssuesDelete extends Command {
|
|
|
48
48
|
identifier,
|
|
49
49
|
deleted: true,
|
|
50
50
|
permanent: flags.permanent,
|
|
51
|
-
message: flags.permanent
|
|
52
|
-
? `Issue ${identifier} permanently deleted`
|
|
53
|
-
: `Issue ${identifier} moved to trash`,
|
|
51
|
+
message: flags.permanent ? `Issue ${identifier} permanently deleted` : `Issue ${identifier} moved to trash`,
|
|
54
52
|
}));
|
|
55
53
|
}
|
|
56
54
|
catch (err) {
|
|
@@ -5,5 +5,8 @@ export default class IssuesGet extends Command {
|
|
|
5
5
|
static args: {
|
|
6
6
|
id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
7
|
};
|
|
8
|
+
static flags: {
|
|
9
|
+
format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
8
11
|
run(): Promise<void>;
|
|
9
12
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { Args, Command } from '@oclif/core';
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
2
|
import { getClient } from '../../lib/client.js';
|
|
3
|
-
import { success, print } from '../../lib/output.js';
|
|
3
|
+
import { success, print, printItem } from '../../lib/output.js';
|
|
4
4
|
import { handleError, CliError, ErrorCodes } from '../../lib/errors.js';
|
|
5
5
|
import { resolveIssueId } from '../../lib/issue-utils.js';
|
|
6
6
|
export default class IssuesGet extends Command {
|
|
7
7
|
static description = 'Get a single issue by ID or identifier';
|
|
8
8
|
static examples = [
|
|
9
|
-
'<%= config.bin %> issues get abc123',
|
|
10
9
|
'<%= config.bin %> issues get ENG-123',
|
|
10
|
+
'<%= config.bin %> issues get ENG-123 --format table',
|
|
11
|
+
'<%= config.bin %> issues get abc123',
|
|
11
12
|
];
|
|
12
13
|
static args = {
|
|
13
14
|
id: Args.string({
|
|
@@ -15,9 +16,18 @@ export default class IssuesGet extends Command {
|
|
|
15
16
|
required: true,
|
|
16
17
|
}),
|
|
17
18
|
};
|
|
19
|
+
static flags = {
|
|
20
|
+
format: Flags.string({
|
|
21
|
+
char: 'F',
|
|
22
|
+
description: 'Output format',
|
|
23
|
+
options: ['json', 'table', 'plain'],
|
|
24
|
+
default: 'json',
|
|
25
|
+
}),
|
|
26
|
+
};
|
|
18
27
|
async run() {
|
|
19
28
|
try {
|
|
20
|
-
const { args } = await this.parse(IssuesGet);
|
|
29
|
+
const { args, flags } = await this.parse(IssuesGet);
|
|
30
|
+
const format = flags.format;
|
|
21
31
|
const client = getClient();
|
|
22
32
|
const issueId = await resolveIssueId(client, args.id);
|
|
23
33
|
const issue = await client.issue(issueId);
|
|
@@ -32,7 +42,7 @@ export default class IssuesGet extends Command {
|
|
|
32
42
|
issue.labels(),
|
|
33
43
|
issue.comments(),
|
|
34
44
|
]);
|
|
35
|
-
|
|
45
|
+
const data = {
|
|
36
46
|
id: issue.id,
|
|
37
47
|
identifier: issue.identifier,
|
|
38
48
|
title: issue.title,
|
|
@@ -71,7 +81,30 @@ export default class IssuesGet extends Command {
|
|
|
71
81
|
color: label.color,
|
|
72
82
|
})),
|
|
73
83
|
commentsCount: comments.nodes.length,
|
|
74
|
-
}
|
|
84
|
+
};
|
|
85
|
+
if (format === 'json') {
|
|
86
|
+
print(success(data));
|
|
87
|
+
}
|
|
88
|
+
else if (format === 'table') {
|
|
89
|
+
printItem({
|
|
90
|
+
identifier: data.identifier,
|
|
91
|
+
title: data.title,
|
|
92
|
+
state: data.state?.name ?? 'N/A',
|
|
93
|
+
priority: data.priorityLabel,
|
|
94
|
+
team: data.team?.key ?? 'N/A',
|
|
95
|
+
assignee: data.assignee?.name ?? 'Unassigned',
|
|
96
|
+
labels: data.labels.map((l) => l.name).join(', ') || 'None',
|
|
97
|
+
estimate: data.estimate ?? 'None',
|
|
98
|
+
comments: data.commentsCount,
|
|
99
|
+
url: data.url,
|
|
100
|
+
createdAt: data.createdAt,
|
|
101
|
+
updatedAt: data.updatedAt,
|
|
102
|
+
}, format);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// plain: just the identifier
|
|
106
|
+
console.log(data.identifier);
|
|
107
|
+
}
|
|
75
108
|
}
|
|
76
109
|
catch (err) {
|
|
77
110
|
handleError(err);
|
|
@@ -3,6 +3,7 @@ export default class IssuesList extends Command {
|
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
|
+
format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
7
|
team: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
8
|
assignee: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
9
|
state: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -1,17 +1,42 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
2
|
import { getClient } from '../../lib/client.js';
|
|
3
|
-
import { successList, print } from '../../lib/output.js';
|
|
3
|
+
import { successList, print, printList } from '../../lib/output.js';
|
|
4
4
|
import { handleError } from '../../lib/errors.js';
|
|
5
|
+
import { colors, truncate, formatPriority } from '../../lib/formatter.js';
|
|
6
|
+
const COLUMNS = [
|
|
7
|
+
{
|
|
8
|
+
key: 'identifier',
|
|
9
|
+
header: 'ID',
|
|
10
|
+
format: (value) => colors.cyan(String(value)),
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
key: 'priority',
|
|
14
|
+
header: 'PRI',
|
|
15
|
+
format: (value) => formatPriority(Number(value)),
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
key: 'title',
|
|
19
|
+
header: 'TITLE',
|
|
20
|
+
format: (value) => truncate(String(value), 50),
|
|
21
|
+
},
|
|
22
|
+
];
|
|
5
23
|
export default class IssuesList extends Command {
|
|
6
24
|
static description = 'List issues with optional filtering';
|
|
7
25
|
static examples = [
|
|
8
26
|
'<%= config.bin %> issues list',
|
|
27
|
+
'<%= config.bin %> issues list --format table',
|
|
9
28
|
'<%= config.bin %> issues list --team ENG',
|
|
10
29
|
'<%= config.bin %> issues list --assignee me',
|
|
11
30
|
'<%= config.bin %> issues list --filter \'{"state":{"name":{"eq":"In Progress"}}}\'',
|
|
12
31
|
'<%= config.bin %> issues list --first 50 --after cursor123',
|
|
13
32
|
];
|
|
14
33
|
static flags = {
|
|
34
|
+
format: Flags.string({
|
|
35
|
+
char: 'F',
|
|
36
|
+
description: 'Output format',
|
|
37
|
+
options: ['json', 'table', 'plain'],
|
|
38
|
+
default: 'json',
|
|
39
|
+
}),
|
|
15
40
|
team: Flags.string({
|
|
16
41
|
char: 't',
|
|
17
42
|
description: 'Filter by team key (e.g., ENG)',
|
|
@@ -39,6 +64,7 @@ export default class IssuesList extends Command {
|
|
|
39
64
|
async run() {
|
|
40
65
|
try {
|
|
41
66
|
const { flags } = await this.parse(IssuesList);
|
|
67
|
+
const format = flags.format;
|
|
42
68
|
const client = getClient();
|
|
43
69
|
// Build filter
|
|
44
70
|
let filter = {};
|
|
@@ -78,20 +104,31 @@ export default class IssuesList extends Command {
|
|
|
78
104
|
id: issue.id,
|
|
79
105
|
identifier: issue.identifier,
|
|
80
106
|
title: issue.title,
|
|
81
|
-
description: issue.description,
|
|
107
|
+
description: issue.description ?? undefined,
|
|
82
108
|
priority: issue.priority,
|
|
83
109
|
priorityLabel: issue.priorityLabel,
|
|
84
|
-
estimate: issue.estimate,
|
|
110
|
+
estimate: issue.estimate ?? undefined,
|
|
85
111
|
url: issue.url,
|
|
86
112
|
createdAt: issue.createdAt,
|
|
87
113
|
updatedAt: issue.updatedAt,
|
|
88
114
|
}));
|
|
89
|
-
|
|
115
|
+
const pageInfo = {
|
|
90
116
|
hasNextPage: issues.pageInfo.hasNextPage,
|
|
91
117
|
hasPreviousPage: issues.pageInfo.hasPreviousPage,
|
|
92
118
|
startCursor: issues.pageInfo.startCursor,
|
|
93
119
|
endCursor: issues.pageInfo.endCursor,
|
|
94
|
-
}
|
|
120
|
+
};
|
|
121
|
+
if (format === 'json') {
|
|
122
|
+
print(successList(data, pageInfo));
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
printList(data, format, {
|
|
126
|
+
columns: COLUMNS,
|
|
127
|
+
primaryKey: 'identifier',
|
|
128
|
+
secondaryKey: 'title',
|
|
129
|
+
pageInfo,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
95
132
|
}
|
|
96
133
|
catch (err) {
|
|
97
134
|
handleError(err);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Me extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static aliases: string[];
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static flags: {
|
|
7
|
+
format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
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 } from '../lib/errors.js';
|
|
5
|
+
export default class Me extends Command {
|
|
6
|
+
static description = 'Show current user information';
|
|
7
|
+
static aliases = ['whoami'];
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> me',
|
|
10
|
+
'<%= config.bin %> me --format table',
|
|
11
|
+
'<%= config.bin %> whoami',
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
format: Flags.string({
|
|
15
|
+
char: 'F',
|
|
16
|
+
description: 'Output format',
|
|
17
|
+
options: ['json', 'table', 'plain'],
|
|
18
|
+
default: 'json',
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
async run() {
|
|
22
|
+
try {
|
|
23
|
+
const { flags } = await this.parse(Me);
|
|
24
|
+
const format = flags.format;
|
|
25
|
+
const client = getClient();
|
|
26
|
+
const viewer = await client.viewer;
|
|
27
|
+
const [teams, organization] = await Promise.all([viewer.teams(), viewer.organization]);
|
|
28
|
+
const data = {
|
|
29
|
+
id: viewer.id,
|
|
30
|
+
name: viewer.name,
|
|
31
|
+
email: viewer.email,
|
|
32
|
+
displayName: viewer.displayName,
|
|
33
|
+
active: viewer.active,
|
|
34
|
+
admin: viewer.admin,
|
|
35
|
+
timezone: viewer.timezone,
|
|
36
|
+
createdAt: viewer.createdAt,
|
|
37
|
+
organization: organization
|
|
38
|
+
? {
|
|
39
|
+
id: organization.id,
|
|
40
|
+
name: organization.name,
|
|
41
|
+
urlKey: organization.urlKey,
|
|
42
|
+
}
|
|
43
|
+
: null,
|
|
44
|
+
teams: teams.nodes.map((team) => ({
|
|
45
|
+
id: team.id,
|
|
46
|
+
key: team.key,
|
|
47
|
+
name: team.name,
|
|
48
|
+
})),
|
|
49
|
+
};
|
|
50
|
+
if (format === 'json') {
|
|
51
|
+
print(success(data));
|
|
52
|
+
}
|
|
53
|
+
else if (format === 'table') {
|
|
54
|
+
printItem({
|
|
55
|
+
id: data.id,
|
|
56
|
+
name: data.name,
|
|
57
|
+
email: data.email,
|
|
58
|
+
displayName: data.displayName,
|
|
59
|
+
active: data.active ? 'Yes' : 'No',
|
|
60
|
+
admin: data.admin ? 'Yes' : 'No',
|
|
61
|
+
timezone: data.timezone,
|
|
62
|
+
organization: data.organization?.name ?? 'N/A',
|
|
63
|
+
teams: data.teams.map((t) => t.key).join(', '),
|
|
64
|
+
}, format);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// plain: just the email
|
|
68
|
+
console.log(data.email);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
handleError(err);
|
|
73
|
+
this.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Open extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
issue: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
team: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
inbox: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
settings: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
'my-issues': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
}
|