issy 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/README.md +46 -0
- package/bin/issy +67 -0
- package/package.json +34 -0
- package/src/cli.ts +398 -0
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# issy
|
|
2
|
+
|
|
3
|
+
AI-native issue tracking. Tell your coding assistant what to track — it handles the rest.
|
|
4
|
+
|
|
5
|
+
Issues are stored as markdown files in `.issues/`, committed with your code.
|
|
6
|
+
|
|
7
|
+
## Install the Skill
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx skills add miketromba/issy
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Your AI assistant can now create, search, update, and close issues through natural language.
|
|
14
|
+
|
|
15
|
+
## Manual Usage
|
|
16
|
+
|
|
17
|
+
### Web UI
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx issy
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Opens a local UI at `http://localhost:1554`.
|
|
24
|
+
|
|
25
|
+
### CLI
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
issy list # List open issues
|
|
29
|
+
issy search "auth" # Fuzzy search
|
|
30
|
+
issy read 0001 # View issue
|
|
31
|
+
issy create --title "Bug" # Create issue
|
|
32
|
+
issy close 0001 # Close issue
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Run `issy help` for full options.
|
|
36
|
+
|
|
37
|
+
## Configuration
|
|
38
|
+
|
|
39
|
+
| Variable | Description | Default |
|
|
40
|
+
|----------|-------------|---------|
|
|
41
|
+
| `ISSUES_DIR` | Issues directory path | `./.issues` |
|
|
42
|
+
| `ISSUES_PORT` | UI server port | `1554` |
|
|
43
|
+
|
|
44
|
+
## License
|
|
45
|
+
|
|
46
|
+
MIT
|
package/bin/issy
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const cliCommands = new Set([
|
|
8
|
+
"list",
|
|
9
|
+
"search",
|
|
10
|
+
"read",
|
|
11
|
+
"create",
|
|
12
|
+
"update",
|
|
13
|
+
"close",
|
|
14
|
+
"help",
|
|
15
|
+
"--help",
|
|
16
|
+
"-h",
|
|
17
|
+
]);
|
|
18
|
+
const root = process.env.ISSUES_ROOT ?? process.cwd();
|
|
19
|
+
const issuesDir = process.env.ISSUES_DIR ?? join(root, ".issues");
|
|
20
|
+
process.env.ISSUES_ROOT = root;
|
|
21
|
+
process.env.ISSUES_DIR = issuesDir;
|
|
22
|
+
|
|
23
|
+
if (cliCommands.has(args[0] || "")) {
|
|
24
|
+
const here = resolve(fileURLToPath(import.meta.url), "..");
|
|
25
|
+
const entry = resolve(here, "..", "src", "cli.ts");
|
|
26
|
+
process.argv = [process.argv[0], process.argv[1], ...args];
|
|
27
|
+
await import(entry);
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const portIdx = args.findIndex((arg) => arg === "--port" || arg === "-p");
|
|
32
|
+
if (portIdx >= 0 && args[portIdx + 1]) {
|
|
33
|
+
process.env.ISSUES_PORT = args[portIdx + 1];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const shouldInitOnly = args.includes("init");
|
|
37
|
+
const skipSeed = args.includes("--no-seed");
|
|
38
|
+
|
|
39
|
+
if (!existsSync(issuesDir)) {
|
|
40
|
+
mkdirSync(issuesDir, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!skipSeed) {
|
|
44
|
+
const hasIssues =
|
|
45
|
+
existsSync(issuesDir) && readdirSync(issuesDir).some((f) => f.endsWith(".md"));
|
|
46
|
+
if (!hasIssues) {
|
|
47
|
+
const welcome = `---\n` +
|
|
48
|
+
`title: Welcome to issy\n` +
|
|
49
|
+
`description: Your first issue in this repo\n` +
|
|
50
|
+
`priority: medium\n` +
|
|
51
|
+
`type: improvement\n` +
|
|
52
|
+
`status: open\n` +
|
|
53
|
+
`created: ${new Date().toISOString().slice(0, 19)}\n` +
|
|
54
|
+
`---\n\n` +
|
|
55
|
+
`## Details\n\n` +
|
|
56
|
+
`- This issue was created automatically on first run.\n` +
|
|
57
|
+
`- Edit it, close it, or delete it to get started.\n`;
|
|
58
|
+
writeFileSync(join(issuesDir, "0001-welcome-to-issy.md"), welcome);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (shouldInitOnly) {
|
|
63
|
+
console.log(`Initialized ${issuesDir}`);
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await import("@miketromba/issy-app");
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "issy",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI-native issue tracking. Markdown files in .issues/, managed by your coding assistant.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"private": false,
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/miketromba/issy.git",
|
|
11
|
+
"directory": "packages/cli"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/miketromba/issy/issues"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/miketromba/issy#readme",
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"bin": {
|
|
21
|
+
"issy": "bin/issy"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"bin",
|
|
25
|
+
"src"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"cli": "bun src/cli.ts"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@miketromba/issy-app": "^0.1.0",
|
|
32
|
+
"@miketromba/issy-core": "^0.1.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* issy CLI
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* issy list [--all] [--priority <p>] [--type <t>] [--search <q>]
|
|
7
|
+
* issy read <id>
|
|
8
|
+
* issy search <query>
|
|
9
|
+
* issy create [--title <t>] [--description <d>] [--priority <p>] [--type <t>] [--labels <l>]
|
|
10
|
+
* issy update <id> [--title <t>] [--description <d>] [--priority <p>] [--type <t>] [--labels <l>] [--status <s>]
|
|
11
|
+
* issy close <id>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { parseArgs } from "node:util";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
|
|
17
|
+
// Import shared library (simple relative import since we're in the same package)
|
|
18
|
+
import {
|
|
19
|
+
setIssuesDir,
|
|
20
|
+
getAllIssues,
|
|
21
|
+
getIssue,
|
|
22
|
+
createIssue,
|
|
23
|
+
updateIssue,
|
|
24
|
+
closeIssue,
|
|
25
|
+
filterAndSearchIssues,
|
|
26
|
+
type CreateIssueInput,
|
|
27
|
+
} from "@miketromba/issy-core";
|
|
28
|
+
|
|
29
|
+
// Initialize issues directory from env or current working directory
|
|
30
|
+
const DEFAULT_ROOT = process.env.ISSUES_ROOT || process.cwd();
|
|
31
|
+
const ISSUES_DIR = process.env.ISSUES_DIR || join(DEFAULT_ROOT, ".issues");
|
|
32
|
+
setIssuesDir(ISSUES_DIR);
|
|
33
|
+
|
|
34
|
+
// Display helpers
|
|
35
|
+
function prioritySymbol(priority: string): string {
|
|
36
|
+
switch (priority) {
|
|
37
|
+
case "high":
|
|
38
|
+
return "🔴";
|
|
39
|
+
case "medium":
|
|
40
|
+
return "🟡";
|
|
41
|
+
case "low":
|
|
42
|
+
return "🟢";
|
|
43
|
+
default:
|
|
44
|
+
return "⚪";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function typeSymbol(type: string): string {
|
|
49
|
+
return type === "bug" ? "🐛" : "✨";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Commands
|
|
53
|
+
async function listIssues(options: {
|
|
54
|
+
all?: boolean;
|
|
55
|
+
priority?: string;
|
|
56
|
+
type?: string;
|
|
57
|
+
search?: string;
|
|
58
|
+
}) {
|
|
59
|
+
const allIssues = await getAllIssues();
|
|
60
|
+
|
|
61
|
+
// Apply filters
|
|
62
|
+
let issues = filterAndSearchIssues(allIssues, {
|
|
63
|
+
status: options.all ? undefined : "open",
|
|
64
|
+
priority: options.priority,
|
|
65
|
+
type: options.type,
|
|
66
|
+
search: options.search,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (issues.length === 0) {
|
|
70
|
+
console.log("No issues found.");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log("\n ID Pri Type Status Title");
|
|
75
|
+
console.log(" " + "-".repeat(70));
|
|
76
|
+
|
|
77
|
+
for (const issue of issues) {
|
|
78
|
+
const status = issue.frontmatter.status === "open" ? "OPEN " : "CLOSED";
|
|
79
|
+
console.log(
|
|
80
|
+
` ${issue.id} ${prioritySymbol(issue.frontmatter.priority)} ${typeSymbol(
|
|
81
|
+
issue.frontmatter.type
|
|
82
|
+
)} ${status} ${issue.frontmatter.title.slice(0, 45)}`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log(`\n Total: ${issues.length} issue(s)\n`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function readIssue(id: string) {
|
|
90
|
+
const issue = await getIssue(id);
|
|
91
|
+
|
|
92
|
+
if (!issue) {
|
|
93
|
+
console.error(`Issue not found: ${id}`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log("\n" + "=".repeat(70));
|
|
98
|
+
console.log(` ${typeSymbol(issue.frontmatter.type)} ${issue.frontmatter.title}`);
|
|
99
|
+
console.log("=".repeat(70));
|
|
100
|
+
console.log(` ID: ${issue.id}`);
|
|
101
|
+
console.log(` Status: ${issue.frontmatter.status.toUpperCase()}`);
|
|
102
|
+
console.log(
|
|
103
|
+
` Priority: ${prioritySymbol(issue.frontmatter.priority)} ${issue.frontmatter.priority}`
|
|
104
|
+
);
|
|
105
|
+
console.log(` Type: ${issue.frontmatter.type}`);
|
|
106
|
+
if (issue.frontmatter.labels) {
|
|
107
|
+
console.log(` Labels: ${issue.frontmatter.labels}`);
|
|
108
|
+
}
|
|
109
|
+
console.log(` Created: ${issue.frontmatter.created}`);
|
|
110
|
+
if (issue.frontmatter.updated) {
|
|
111
|
+
console.log(` Updated: ${issue.frontmatter.updated}`);
|
|
112
|
+
}
|
|
113
|
+
console.log("-".repeat(70));
|
|
114
|
+
console.log(issue.content);
|
|
115
|
+
console.log();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function searchIssuesCommand(query: string, options: { all?: boolean }) {
|
|
119
|
+
const allIssues = await getAllIssues();
|
|
120
|
+
|
|
121
|
+
const issues = filterAndSearchIssues(allIssues, {
|
|
122
|
+
status: options.all ? undefined : "open",
|
|
123
|
+
search: query,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (issues.length === 0) {
|
|
127
|
+
console.log(`No issues found matching "${query}".`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
console.log(`\n Search results for "${query}":`);
|
|
132
|
+
console.log("\n ID Pri Type Status Title");
|
|
133
|
+
console.log(" " + "-".repeat(70));
|
|
134
|
+
|
|
135
|
+
for (const issue of issues) {
|
|
136
|
+
const status = issue.frontmatter.status === "open" ? "OPEN " : "CLOSED";
|
|
137
|
+
console.log(
|
|
138
|
+
` ${issue.id} ${prioritySymbol(issue.frontmatter.priority)} ${typeSymbol(
|
|
139
|
+
issue.frontmatter.type
|
|
140
|
+
)} ${status} ${issue.frontmatter.title.slice(0, 45)}`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log(`\n Found: ${issues.length} issue(s)\n`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function createIssueCommand(options: {
|
|
148
|
+
title?: string;
|
|
149
|
+
description?: string;
|
|
150
|
+
priority?: string;
|
|
151
|
+
type?: string;
|
|
152
|
+
labels?: string;
|
|
153
|
+
}) {
|
|
154
|
+
// Interactive mode if no title provided
|
|
155
|
+
if (!options.title) {
|
|
156
|
+
console.log("\nCreate New Issue");
|
|
157
|
+
console.log("-".repeat(40));
|
|
158
|
+
|
|
159
|
+
const prompt = (question: string): Promise<string> => {
|
|
160
|
+
process.stdout.write(question);
|
|
161
|
+
return new Promise((resolve) => {
|
|
162
|
+
let input = "";
|
|
163
|
+
process.stdin.setRawMode?.(false);
|
|
164
|
+
process.stdin.resume();
|
|
165
|
+
process.stdin.setEncoding("utf8");
|
|
166
|
+
process.stdin.once("data", (data) => {
|
|
167
|
+
input = data.toString().trim();
|
|
168
|
+
resolve(input);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
options.title = await prompt("Title: ");
|
|
174
|
+
options.description = await prompt("Description: ");
|
|
175
|
+
options.priority = await prompt("Priority (high/medium/low) [medium]: ");
|
|
176
|
+
options.type = await prompt("Type (bug/improvement) [improvement]: ");
|
|
177
|
+
options.labels = await prompt("Labels (comma-separated) []: ");
|
|
178
|
+
|
|
179
|
+
// Apply defaults
|
|
180
|
+
if (!options.priority) options.priority = "medium";
|
|
181
|
+
if (!options.type) options.type = "improvement";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const input: CreateIssueInput = {
|
|
186
|
+
title: options.title!,
|
|
187
|
+
description: options.description,
|
|
188
|
+
priority: options.priority as "high" | "medium" | "low",
|
|
189
|
+
type: options.type as "bug" | "improvement",
|
|
190
|
+
labels: options.labels,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const issue = await createIssue(input);
|
|
194
|
+
console.log(`\nCreated issue: ${issue.filename}`);
|
|
195
|
+
} catch (e) {
|
|
196
|
+
console.error(e instanceof Error ? e.message : "Failed to create issue");
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function updateIssueCommand(
|
|
202
|
+
id: string,
|
|
203
|
+
options: {
|
|
204
|
+
title?: string;
|
|
205
|
+
description?: string;
|
|
206
|
+
priority?: string;
|
|
207
|
+
type?: string;
|
|
208
|
+
labels?: string;
|
|
209
|
+
status?: string;
|
|
210
|
+
}
|
|
211
|
+
) {
|
|
212
|
+
try {
|
|
213
|
+
const issue = await updateIssue(id, {
|
|
214
|
+
title: options.title,
|
|
215
|
+
description: options.description,
|
|
216
|
+
priority: options.priority as "high" | "medium" | "low" | undefined,
|
|
217
|
+
type: options.type as "bug" | "improvement" | undefined,
|
|
218
|
+
labels: options.labels,
|
|
219
|
+
status: options.status as "open" | "closed" | undefined,
|
|
220
|
+
});
|
|
221
|
+
console.log(`Updated issue: ${issue.filename}`);
|
|
222
|
+
} catch (e) {
|
|
223
|
+
console.error(e instanceof Error ? e.message : "Failed to update issue");
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function closeIssueCommand(id: string) {
|
|
229
|
+
try {
|
|
230
|
+
await closeIssue(id);
|
|
231
|
+
console.log("Issue closed.");
|
|
232
|
+
} catch (e) {
|
|
233
|
+
console.error(e instanceof Error ? e.message : "Failed to close issue");
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Main
|
|
239
|
+
async function main() {
|
|
240
|
+
const args = process.argv.slice(2);
|
|
241
|
+
const command = args[0];
|
|
242
|
+
|
|
243
|
+
if (
|
|
244
|
+
!command ||
|
|
245
|
+
command === "help" ||
|
|
246
|
+
command === "--help" ||
|
|
247
|
+
command === "-h"
|
|
248
|
+
) {
|
|
249
|
+
console.log(`
|
|
250
|
+
issy CLI
|
|
251
|
+
|
|
252
|
+
Usage:
|
|
253
|
+
issy <command> [options]
|
|
254
|
+
|
|
255
|
+
Commands:
|
|
256
|
+
list List all open issues
|
|
257
|
+
--all, -a Include closed issues
|
|
258
|
+
--priority, -p <p> Filter by priority (high, medium, low)
|
|
259
|
+
--type, -t <t> Filter by type (bug, improvement)
|
|
260
|
+
--search, -s <q> Fuzzy search issues
|
|
261
|
+
|
|
262
|
+
search <query> Fuzzy search issues
|
|
263
|
+
--all, -a Include closed issues
|
|
264
|
+
|
|
265
|
+
read <id> Read a specific issue
|
|
266
|
+
|
|
267
|
+
create Create a new issue (interactive)
|
|
268
|
+
--title, -t <t> Issue title
|
|
269
|
+
--description, -d <d> Short description
|
|
270
|
+
--priority, -p <p> Priority (high, medium, low)
|
|
271
|
+
--type <t> Type (bug, improvement)
|
|
272
|
+
--labels, -l <l> Comma-separated labels
|
|
273
|
+
|
|
274
|
+
update <id> Update an issue
|
|
275
|
+
--title, -t <t> New title
|
|
276
|
+
--description, -d <d> New description
|
|
277
|
+
--priority, -p <p> New priority
|
|
278
|
+
--type <t> New type
|
|
279
|
+
--labels, -l <l> New labels
|
|
280
|
+
--status, -s <s> New status (open, closed)
|
|
281
|
+
|
|
282
|
+
close <id> Close an issue
|
|
283
|
+
|
|
284
|
+
Examples:
|
|
285
|
+
issy list
|
|
286
|
+
issy list --priority high --type bug
|
|
287
|
+
issy search "dashboard"
|
|
288
|
+
issy search "k8s" --all
|
|
289
|
+
issy read 0001
|
|
290
|
+
issy create --title "Fix login bug" --type bug --priority high
|
|
291
|
+
issy update 0001 --priority low
|
|
292
|
+
issy close 0001
|
|
293
|
+
`);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
switch (command) {
|
|
298
|
+
case "list": {
|
|
299
|
+
const { values } = parseArgs({
|
|
300
|
+
args: args.slice(1),
|
|
301
|
+
options: {
|
|
302
|
+
all: { type: "boolean", short: "a" },
|
|
303
|
+
priority: { type: "string", short: "p" },
|
|
304
|
+
type: { type: "string", short: "t" },
|
|
305
|
+
search: { type: "string", short: "s" },
|
|
306
|
+
},
|
|
307
|
+
allowPositionals: true,
|
|
308
|
+
});
|
|
309
|
+
await listIssues(values);
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
case "search": {
|
|
314
|
+
const query = args[1];
|
|
315
|
+
if (!query) {
|
|
316
|
+
console.error("Usage: issy search <query>");
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
const { values } = parseArgs({
|
|
320
|
+
args: args.slice(2),
|
|
321
|
+
options: {
|
|
322
|
+
all: { type: "boolean", short: "a" },
|
|
323
|
+
},
|
|
324
|
+
allowPositionals: true,
|
|
325
|
+
});
|
|
326
|
+
await searchIssuesCommand(query, values);
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
case "read": {
|
|
331
|
+
const id = args[1];
|
|
332
|
+
if (!id) {
|
|
333
|
+
console.error("Usage: issy read <id>");
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
await readIssue(id);
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
case "create": {
|
|
341
|
+
const { values } = parseArgs({
|
|
342
|
+
args: args.slice(1),
|
|
343
|
+
options: {
|
|
344
|
+
title: { type: "string", short: "t" },
|
|
345
|
+
description: { type: "string", short: "d" },
|
|
346
|
+
priority: { type: "string", short: "p" },
|
|
347
|
+
type: { type: "string" },
|
|
348
|
+
labels: { type: "string", short: "l" },
|
|
349
|
+
},
|
|
350
|
+
allowPositionals: true,
|
|
351
|
+
});
|
|
352
|
+
await createIssueCommand(values);
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
case "update": {
|
|
357
|
+
const id = args[1];
|
|
358
|
+
if (!id) {
|
|
359
|
+
console.error("Usage: issy update <id> [options]");
|
|
360
|
+
process.exit(1);
|
|
361
|
+
}
|
|
362
|
+
const { values } = parseArgs({
|
|
363
|
+
args: args.slice(2),
|
|
364
|
+
options: {
|
|
365
|
+
title: { type: "string", short: "t" },
|
|
366
|
+
description: { type: "string", short: "d" },
|
|
367
|
+
priority: { type: "string", short: "p" },
|
|
368
|
+
type: { type: "string" },
|
|
369
|
+
labels: { type: "string", short: "l" },
|
|
370
|
+
status: { type: "string", short: "s" },
|
|
371
|
+
},
|
|
372
|
+
allowPositionals: true,
|
|
373
|
+
});
|
|
374
|
+
await updateIssueCommand(id, values);
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
case "close": {
|
|
379
|
+
const id = args[1];
|
|
380
|
+
if (!id) {
|
|
381
|
+
console.error("Usage: issy close <id>");
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
await closeIssueCommand(id);
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
default:
|
|
389
|
+
console.error(`Unknown command: ${command}`);
|
|
390
|
+
console.log('Run "issy help" for usage.');
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
main().catch((err) => {
|
|
396
|
+
console.error(err);
|
|
397
|
+
process.exit(1);
|
|
398
|
+
});
|