linear-cli-agents 0.1.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/LICENSE +21 -0
- package/README.md +168 -0
- package/bin/dev.js +5 -0
- package/bin/run.js +5 -0
- package/dist/commands/auth/login.d.ts +12 -0
- package/dist/commands/auth/login.js +52 -0
- package/dist/commands/auth/logout.d.ts +6 -0
- package/dist/commands/auth/logout.js +26 -0
- package/dist/commands/auth/status.d.ts +6 -0
- package/dist/commands/auth/status.js +40 -0
- package/dist/commands/issues/create.d.ts +18 -0
- package/dist/commands/issues/create.js +109 -0
- package/dist/commands/issues/delete.d.ts +12 -0
- package/dist/commands/issues/delete.js +61 -0
- package/dist/commands/issues/get.d.ts +9 -0
- package/dist/commands/issues/get.js +81 -0
- package/dist/commands/issues/list.d.ts +14 -0
- package/dist/commands/issues/list.js +101 -0
- package/dist/commands/issues/update.d.ts +20 -0
- package/dist/commands/issues/update.js +110 -0
- package/dist/commands/query.d.ts +11 -0
- package/dist/commands/query.js +94 -0
- package/dist/commands/schema.d.ts +13 -0
- package/dist/commands/schema.js +198 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/client.d.ts +17 -0
- package/dist/lib/client.js +23 -0
- package/dist/lib/config.d.ts +33 -0
- package/dist/lib/config.js +97 -0
- package/dist/lib/errors.d.ts +30 -0
- package/dist/lib/errors.js +79 -0
- package/dist/lib/issue-utils.d.ts +17 -0
- package/dist/lib/issue-utils.js +56 -0
- package/dist/lib/output.d.ts +16 -0
- package/dist/lib/output.js +33 -0
- package/dist/lib/types.d.ts +33 -0
- package/dist/lib/types.js +5 -0
- package/oclif.manifest.json +549 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# linear-cli
|
|
2
|
+
|
|
3
|
+
A CLI for interacting with [Linear](https://linear.app), designed for LLMs and agents.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **JSON output**: All commands return structured JSON, perfect for parsing by LLMs
|
|
8
|
+
- **Schema introspection**: Discover available operations programmatically
|
|
9
|
+
- **Full CRUD for issues**: List, create, update, and delete issues
|
|
10
|
+
- **Raw GraphQL queries**: Execute any GraphQL query directly
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g linear-cli
|
|
16
|
+
# or
|
|
17
|
+
pnpm add -g linear-cli
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Authentication
|
|
21
|
+
|
|
22
|
+
Get your API key from [Linear Settings > API](https://linear.app/settings/api).
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Login with API key
|
|
26
|
+
linear auth login --key lin_api_xxxxx
|
|
27
|
+
|
|
28
|
+
# Or use environment variable
|
|
29
|
+
export LINEAR_API_KEY=lin_api_xxxxx
|
|
30
|
+
|
|
31
|
+
# Check auth status
|
|
32
|
+
linear auth status
|
|
33
|
+
|
|
34
|
+
# Logout
|
|
35
|
+
linear auth logout
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
### Issues
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# List all issues
|
|
44
|
+
linear issues list
|
|
45
|
+
|
|
46
|
+
# List with filters
|
|
47
|
+
linear issues list --team ENG
|
|
48
|
+
linear issues list --assignee me
|
|
49
|
+
linear issues list --state "In Progress"
|
|
50
|
+
linear issues list --filter '{"priority":{"lte":2}}'
|
|
51
|
+
|
|
52
|
+
# Get a specific issue
|
|
53
|
+
linear issues get ENG-123
|
|
54
|
+
|
|
55
|
+
# Create an issue
|
|
56
|
+
linear issues create --title "Bug fix" --team-id <team-id>
|
|
57
|
+
linear issues create --input '{"title":"Feature","teamId":"xxx","priority":2}'
|
|
58
|
+
|
|
59
|
+
# Update an issue
|
|
60
|
+
linear issues update ENG-123 --title "Updated title"
|
|
61
|
+
linear issues update ENG-123 --state-id <state-id> --assignee-id <user-id>
|
|
62
|
+
|
|
63
|
+
# Delete an issue (moves to trash)
|
|
64
|
+
linear issues delete ENG-123
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Schema Introspection (for LLMs)
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# List available entities
|
|
71
|
+
linear schema
|
|
72
|
+
|
|
73
|
+
# Get schema for a specific entity
|
|
74
|
+
linear schema issues
|
|
75
|
+
|
|
76
|
+
# Get full schema (all entities)
|
|
77
|
+
linear schema --full
|
|
78
|
+
|
|
79
|
+
# Include usage examples
|
|
80
|
+
linear schema issues --include-examples
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Raw GraphQL Queries
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Execute any GraphQL query
|
|
87
|
+
linear query --gql "query { viewer { id name email } }"
|
|
88
|
+
|
|
89
|
+
# With variables
|
|
90
|
+
linear query --gql "query(\$id: String!) { issue(id: \$id) { title } }" \
|
|
91
|
+
--variables '{"id":"xxx"}'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Output Format
|
|
95
|
+
|
|
96
|
+
All commands return structured JSON:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
// Success
|
|
100
|
+
{
|
|
101
|
+
"success": true,
|
|
102
|
+
"data": { ... }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Success with pagination
|
|
106
|
+
{
|
|
107
|
+
"success": true,
|
|
108
|
+
"data": [...],
|
|
109
|
+
"pageInfo": {
|
|
110
|
+
"hasNextPage": true,
|
|
111
|
+
"endCursor": "abc123"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Error
|
|
116
|
+
{
|
|
117
|
+
"success": false,
|
|
118
|
+
"error": {
|
|
119
|
+
"code": "NOT_FOUND",
|
|
120
|
+
"message": "Issue ENG-123 not found"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## For LLM Integration
|
|
126
|
+
|
|
127
|
+
The CLI is designed to be easily used by LLMs and AI agents:
|
|
128
|
+
|
|
129
|
+
1. **Discover capabilities**: Use `linear schema` to understand available operations
|
|
130
|
+
2. **Structured output**: All responses are JSON with consistent format
|
|
131
|
+
3. **Error codes**: Programmatic error handling via error codes
|
|
132
|
+
4. **Raw queries**: Use `linear query` for complex operations not covered by built-in commands
|
|
133
|
+
|
|
134
|
+
### Example LLM Workflow
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# 1. Discover what operations are available
|
|
138
|
+
linear schema
|
|
139
|
+
|
|
140
|
+
# 2. Get details about issues
|
|
141
|
+
linear schema issues
|
|
142
|
+
|
|
143
|
+
# 3. List issues assigned to current user
|
|
144
|
+
linear issues list --assignee me
|
|
145
|
+
|
|
146
|
+
# 4. Create a new issue
|
|
147
|
+
linear issues create --input '{"title":"From LLM","teamId":"xxx"}'
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Development
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# Install dependencies
|
|
154
|
+
pnpm install
|
|
155
|
+
|
|
156
|
+
# Build
|
|
157
|
+
pnpm build
|
|
158
|
+
|
|
159
|
+
# Run in development
|
|
160
|
+
./bin/dev.js issues list
|
|
161
|
+
|
|
162
|
+
# Run tests
|
|
163
|
+
pnpm test
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT
|
package/bin/dev.js
ADDED
package/bin/run.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class AuthLogin extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
key: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
};
|
|
8
|
+
static args: {
|
|
9
|
+
key: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { createClient } from '../../lib/client.js';
|
|
3
|
+
import { saveApiKey, getConfigPath } from '../../lib/config.js';
|
|
4
|
+
import { success, print } from '../../lib/output.js';
|
|
5
|
+
import { handleError, CliError, ErrorCodes } from '../../lib/errors.js';
|
|
6
|
+
export default class AuthLogin extends Command {
|
|
7
|
+
static description = 'Authenticate with Linear using an API key';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> auth login --key lin_api_xxxxx',
|
|
10
|
+
'LINEAR_API_KEY=lin_api_xxxxx <%= config.bin %> auth login',
|
|
11
|
+
];
|
|
12
|
+
static flags = {
|
|
13
|
+
key: Flags.string({
|
|
14
|
+
char: 'k',
|
|
15
|
+
description: 'Linear API key (or set LINEAR_API_KEY env var)',
|
|
16
|
+
env: 'LINEAR_API_KEY',
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
static args = {
|
|
20
|
+
key: Args.string({
|
|
21
|
+
description: 'Linear API key (alternative to --key flag)',
|
|
22
|
+
required: false,
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
async run() {
|
|
26
|
+
try {
|
|
27
|
+
const { args, flags } = await this.parse(AuthLogin);
|
|
28
|
+
const apiKey = flags.key || args.key;
|
|
29
|
+
if (!apiKey) {
|
|
30
|
+
throw new CliError(ErrorCodes.MISSING_REQUIRED_FIELD, 'API key is required. Use --key flag or set LINEAR_API_KEY environment variable.');
|
|
31
|
+
}
|
|
32
|
+
// Validate the API key by making a test request
|
|
33
|
+
const client = createClient(apiKey);
|
|
34
|
+
const viewer = await client.viewer;
|
|
35
|
+
// Save the API key
|
|
36
|
+
saveApiKey(apiKey);
|
|
37
|
+
print(success({
|
|
38
|
+
message: 'Successfully authenticated',
|
|
39
|
+
user: {
|
|
40
|
+
id: viewer.id,
|
|
41
|
+
name: viewer.name,
|
|
42
|
+
email: viewer.email,
|
|
43
|
+
},
|
|
44
|
+
configPath: getConfigPath(),
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
handleError(err);
|
|
49
|
+
this.exit(1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { removeApiKey, getConfigPath, getApiKey } from '../../lib/config.js';
|
|
3
|
+
import { success, print } from '../../lib/output.js';
|
|
4
|
+
import { handleError, CliError, ErrorCodes } from '../../lib/errors.js';
|
|
5
|
+
export default class AuthLogout extends Command {
|
|
6
|
+
static description = 'Remove stored Linear API key';
|
|
7
|
+
static examples = ['<%= config.bin %> auth logout'];
|
|
8
|
+
async run() {
|
|
9
|
+
await this.parse(AuthLogout);
|
|
10
|
+
try {
|
|
11
|
+
const apiKey = getApiKey();
|
|
12
|
+
if (!apiKey) {
|
|
13
|
+
throw new CliError(ErrorCodes.NOT_AUTHENTICATED, 'Not currently authenticated');
|
|
14
|
+
}
|
|
15
|
+
removeApiKey();
|
|
16
|
+
print(success({
|
|
17
|
+
message: 'Successfully logged out',
|
|
18
|
+
configPath: getConfigPath(),
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
handleError(err);
|
|
23
|
+
this.exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { getApiKey, getConfigPath } from '../../lib/config.js';
|
|
3
|
+
import { createClient } from '../../lib/client.js';
|
|
4
|
+
import { success, print } from '../../lib/output.js';
|
|
5
|
+
import { handleError } from '../../lib/errors.js';
|
|
6
|
+
export default class AuthStatus extends Command {
|
|
7
|
+
static description = 'Check authentication status';
|
|
8
|
+
static examples = ['<%= config.bin %> auth status'];
|
|
9
|
+
async run() {
|
|
10
|
+
await this.parse(AuthStatus);
|
|
11
|
+
try {
|
|
12
|
+
const apiKey = getApiKey();
|
|
13
|
+
if (!apiKey) {
|
|
14
|
+
print(success({
|
|
15
|
+
authenticated: false,
|
|
16
|
+
message: 'Not authenticated',
|
|
17
|
+
configPath: getConfigPath(),
|
|
18
|
+
}));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// Verify the API key is still valid
|
|
22
|
+
const client = createClient(apiKey);
|
|
23
|
+
const viewer = await client.viewer;
|
|
24
|
+
print(success({
|
|
25
|
+
authenticated: true,
|
|
26
|
+
user: {
|
|
27
|
+
id: viewer.id,
|
|
28
|
+
name: viewer.name,
|
|
29
|
+
email: viewer.email,
|
|
30
|
+
},
|
|
31
|
+
source: process.env.LINEAR_API_KEY ? 'environment' : 'config',
|
|
32
|
+
configPath: getConfigPath(),
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
handleError(err);
|
|
37
|
+
this.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class IssuesCreate extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
input: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
title: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
'team-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
priority: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
'assignee-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
'state-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
'project-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
estimate: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
'label-ids': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
};
|
|
17
|
+
run(): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
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 IssuesCreate extends Command {
|
|
6
|
+
static description = 'Create a new issue';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> issues create --input \'{"title":"Bug fix","teamId":"xxx"}\'',
|
|
9
|
+
'<%= config.bin %> issues create --title "New feature" --team-id xxx',
|
|
10
|
+
'<%= config.bin %> issues create --title "Task" --team-id xxx --description "Details here" --priority 2',
|
|
11
|
+
];
|
|
12
|
+
static flags = {
|
|
13
|
+
input: Flags.string({
|
|
14
|
+
char: 'i',
|
|
15
|
+
description: 'JSON input object (IssueCreateInput)',
|
|
16
|
+
exclusive: ['title'],
|
|
17
|
+
}),
|
|
18
|
+
title: Flags.string({
|
|
19
|
+
description: 'Issue title',
|
|
20
|
+
exclusive: ['input'],
|
|
21
|
+
}),
|
|
22
|
+
'team-id': Flags.string({
|
|
23
|
+
description: 'Team ID',
|
|
24
|
+
}),
|
|
25
|
+
description: Flags.string({
|
|
26
|
+
char: 'd',
|
|
27
|
+
description: 'Issue description (markdown supported)',
|
|
28
|
+
}),
|
|
29
|
+
priority: Flags.integer({
|
|
30
|
+
char: 'p',
|
|
31
|
+
description: 'Priority (0=none, 1=urgent, 2=high, 3=medium, 4=low)',
|
|
32
|
+
}),
|
|
33
|
+
'assignee-id': Flags.string({
|
|
34
|
+
description: 'Assignee user ID',
|
|
35
|
+
}),
|
|
36
|
+
'state-id': Flags.string({
|
|
37
|
+
description: 'State ID',
|
|
38
|
+
}),
|
|
39
|
+
'project-id': Flags.string({
|
|
40
|
+
description: 'Project ID',
|
|
41
|
+
}),
|
|
42
|
+
estimate: Flags.integer({
|
|
43
|
+
description: 'Estimate points',
|
|
44
|
+
}),
|
|
45
|
+
'label-ids': Flags.string({
|
|
46
|
+
description: 'Comma-separated label IDs',
|
|
47
|
+
}),
|
|
48
|
+
};
|
|
49
|
+
async run() {
|
|
50
|
+
try {
|
|
51
|
+
const { flags } = await this.parse(IssuesCreate);
|
|
52
|
+
const client = getClient();
|
|
53
|
+
let input;
|
|
54
|
+
if (flags.input) {
|
|
55
|
+
// Parse JSON input
|
|
56
|
+
try {
|
|
57
|
+
input = JSON.parse(flags.input);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
throw new CliError(ErrorCodes.INVALID_INPUT, 'Invalid JSON in --input flag');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// Build input from individual flags
|
|
65
|
+
if (!flags.title) {
|
|
66
|
+
throw new CliError(ErrorCodes.MISSING_REQUIRED_FIELD, 'Title is required. Use --title or --input');
|
|
67
|
+
}
|
|
68
|
+
if (!flags['team-id']) {
|
|
69
|
+
throw new CliError(ErrorCodes.MISSING_REQUIRED_FIELD, 'Team ID is required. Use --team-id or --input');
|
|
70
|
+
}
|
|
71
|
+
input = {
|
|
72
|
+
title: flags.title,
|
|
73
|
+
teamId: flags['team-id'],
|
|
74
|
+
};
|
|
75
|
+
if (flags.description)
|
|
76
|
+
input.description = flags.description;
|
|
77
|
+
if (flags.priority !== undefined)
|
|
78
|
+
input.priority = flags.priority;
|
|
79
|
+
if (flags['assignee-id'])
|
|
80
|
+
input.assigneeId = flags['assignee-id'];
|
|
81
|
+
if (flags['state-id'])
|
|
82
|
+
input.stateId = flags['state-id'];
|
|
83
|
+
if (flags['project-id'])
|
|
84
|
+
input.projectId = flags['project-id'];
|
|
85
|
+
if (flags.estimate !== undefined)
|
|
86
|
+
input.estimate = flags.estimate;
|
|
87
|
+
if (flags['label-ids'])
|
|
88
|
+
input.labelIds = flags['label-ids'].split(',');
|
|
89
|
+
}
|
|
90
|
+
// Create the issue
|
|
91
|
+
const payload = await client.createIssue(input);
|
|
92
|
+
const issue = await payload.issue;
|
|
93
|
+
if (!issue) {
|
|
94
|
+
throw new CliError(ErrorCodes.API_ERROR, 'Failed to create issue');
|
|
95
|
+
}
|
|
96
|
+
print(success({
|
|
97
|
+
id: issue.id,
|
|
98
|
+
identifier: issue.identifier,
|
|
99
|
+
title: issue.title,
|
|
100
|
+
url: issue.url,
|
|
101
|
+
createdAt: issue.createdAt,
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
handleError(err);
|
|
106
|
+
this.exit(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class IssuesDelete 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
|
+
permanent: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
import { resolveIssueId } from '../../lib/issue-utils.js';
|
|
6
|
+
export default class IssuesDelete extends Command {
|
|
7
|
+
static description = 'Delete an issue (moves to trash)';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> issues delete abc123',
|
|
10
|
+
'<%= config.bin %> issues delete ENG-123',
|
|
11
|
+
'<%= config.bin %> issues delete ENG-123 --permanent',
|
|
12
|
+
];
|
|
13
|
+
static args = {
|
|
14
|
+
id: Args.string({
|
|
15
|
+
description: 'Issue ID or identifier (e.g., ENG-123)',
|
|
16
|
+
required: true,
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
static flags = {
|
|
20
|
+
permanent: Flags.boolean({
|
|
21
|
+
description: 'Permanently delete (cannot be undone)',
|
|
22
|
+
default: false,
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
async run() {
|
|
26
|
+
try {
|
|
27
|
+
const { args, flags } = await this.parse(IssuesDelete);
|
|
28
|
+
const client = getClient();
|
|
29
|
+
const issueId = await resolveIssueId(client, args.id);
|
|
30
|
+
// Get issue info before deletion
|
|
31
|
+
const issue = await client.issue(issueId);
|
|
32
|
+
if (!issue) {
|
|
33
|
+
throw new CliError(ErrorCodes.NOT_FOUND, `Issue ${args.id} not found`);
|
|
34
|
+
}
|
|
35
|
+
const identifier = issue.identifier;
|
|
36
|
+
// Delete the issue
|
|
37
|
+
if (flags.permanent) {
|
|
38
|
+
// Archive first, then delete permanently
|
|
39
|
+
await client.archiveIssue(issueId);
|
|
40
|
+
await client.deleteIssue(issueId);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
// Just archive (trash)
|
|
44
|
+
await client.archiveIssue(issueId);
|
|
45
|
+
}
|
|
46
|
+
print(success({
|
|
47
|
+
id: issueId,
|
|
48
|
+
identifier,
|
|
49
|
+
deleted: true,
|
|
50
|
+
permanent: flags.permanent,
|
|
51
|
+
message: flags.permanent
|
|
52
|
+
? `Issue ${identifier} permanently deleted`
|
|
53
|
+
: `Issue ${identifier} moved to trash`,
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
handleError(err);
|
|
58
|
+
this.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class IssuesGet 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,81 @@
|
|
|
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
|
+
import { resolveIssueId } from '../../lib/issue-utils.js';
|
|
6
|
+
export default class IssuesGet extends Command {
|
|
7
|
+
static description = 'Get a single issue by ID or identifier';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> issues get abc123',
|
|
10
|
+
'<%= config.bin %> issues get ENG-123',
|
|
11
|
+
];
|
|
12
|
+
static args = {
|
|
13
|
+
id: Args.string({
|
|
14
|
+
description: 'Issue ID or identifier (e.g., ENG-123)',
|
|
15
|
+
required: true,
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
async run() {
|
|
19
|
+
try {
|
|
20
|
+
const { args } = await this.parse(IssuesGet);
|
|
21
|
+
const client = getClient();
|
|
22
|
+
const issueId = await resolveIssueId(client, args.id);
|
|
23
|
+
const issue = await client.issue(issueId);
|
|
24
|
+
if (!issue) {
|
|
25
|
+
throw new CliError(ErrorCodes.NOT_FOUND, `Issue ${args.id} not found`);
|
|
26
|
+
}
|
|
27
|
+
// Fetch related data
|
|
28
|
+
const [state, assignee, team, labels, comments] = await Promise.all([
|
|
29
|
+
issue.state,
|
|
30
|
+
issue.assignee,
|
|
31
|
+
issue.team,
|
|
32
|
+
issue.labels(),
|
|
33
|
+
issue.comments(),
|
|
34
|
+
]);
|
|
35
|
+
print(success({
|
|
36
|
+
id: issue.id,
|
|
37
|
+
identifier: issue.identifier,
|
|
38
|
+
title: issue.title,
|
|
39
|
+
description: issue.description,
|
|
40
|
+
priority: issue.priority,
|
|
41
|
+
priorityLabel: issue.priorityLabel,
|
|
42
|
+
estimate: issue.estimate,
|
|
43
|
+
url: issue.url,
|
|
44
|
+
createdAt: issue.createdAt,
|
|
45
|
+
updatedAt: issue.updatedAt,
|
|
46
|
+
state: state
|
|
47
|
+
? {
|
|
48
|
+
id: state.id,
|
|
49
|
+
name: state.name,
|
|
50
|
+
color: state.color,
|
|
51
|
+
type: state.type,
|
|
52
|
+
}
|
|
53
|
+
: null,
|
|
54
|
+
assignee: assignee
|
|
55
|
+
? {
|
|
56
|
+
id: assignee.id,
|
|
57
|
+
name: assignee.name,
|
|
58
|
+
email: assignee.email,
|
|
59
|
+
}
|
|
60
|
+
: null,
|
|
61
|
+
team: team
|
|
62
|
+
? {
|
|
63
|
+
id: team.id,
|
|
64
|
+
key: team.key,
|
|
65
|
+
name: team.name,
|
|
66
|
+
}
|
|
67
|
+
: null,
|
|
68
|
+
labels: labels.nodes.map((label) => ({
|
|
69
|
+
id: label.id,
|
|
70
|
+
name: label.name,
|
|
71
|
+
color: label.color,
|
|
72
|
+
})),
|
|
73
|
+
commentsCount: comments.nodes.length,
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
handleError(err);
|
|
78
|
+
this.exit(1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class IssuesList extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
team: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
assignee: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
state: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
filter: import("@oclif/core/interfaces").OptionFlag<string | undefined, 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
|
+
}
|