issy 0.1.0 → 0.1.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.
Files changed (2) hide show
  1. package/package.json +5 -4
  2. package/src/cli.ts +317 -303
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "issy",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "AI-native issue tracking. Markdown files in .issues/, managed by your coding assistant.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -25,10 +25,11 @@
25
25
  "src"
26
26
  ],
27
27
  "scripts": {
28
- "cli": "bun src/cli.ts"
28
+ "cli": "bun src/cli.ts",
29
+ "lint": "biome check src bin"
29
30
  },
30
31
  "dependencies": {
31
- "@miketromba/issy-app": "^0.1.0",
32
- "@miketromba/issy-core": "^0.1.0"
32
+ "@miketromba/issy-app": "^0.1.1",
33
+ "@miketromba/issy-core": "^0.1.1"
33
34
  }
34
35
  }
package/src/cli.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env bun
2
+
2
3
  /**
3
4
  * issy CLI
4
5
  *
@@ -11,242 +12,255 @@
11
12
  * issy close <id>
12
13
  */
13
14
 
14
- import { parseArgs } from "node:util";
15
- import { join } from "node:path";
15
+ import { join } from 'node:path'
16
+ import { parseArgs } from 'node:util'
16
17
 
17
18
  // Import shared library (simple relative import since we're in the same package)
18
19
  import {
19
- setIssuesDir,
20
- getAllIssues,
21
- getIssue,
22
- createIssue,
23
- updateIssue,
24
- closeIssue,
25
- filterAndSearchIssues,
26
- type CreateIssueInput,
27
- } from "@miketromba/issy-core";
20
+ type CreateIssueInput,
21
+ closeIssue,
22
+ createIssue,
23
+ filterAndSearchIssues,
24
+ getAllIssues,
25
+ getIssue,
26
+ setIssuesDir,
27
+ updateIssue
28
+ } from '@miketromba/issy-core'
28
29
 
29
30
  // 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);
31
+ const DEFAULT_ROOT = process.env.ISSUES_ROOT || process.cwd()
32
+ const ISSUES_DIR = process.env.ISSUES_DIR || join(DEFAULT_ROOT, '.issues')
33
+ setIssuesDir(ISSUES_DIR)
33
34
 
34
35
  // Display helpers
35
36
  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
- }
37
+ switch (priority) {
38
+ case 'high':
39
+ return '🔴'
40
+ case 'medium':
41
+ return '🟡'
42
+ case 'low':
43
+ return '🟢'
44
+ default:
45
+ return ''
46
+ }
46
47
  }
47
48
 
48
49
  function typeSymbol(type: string): string {
49
- return type === "bug" ? "🐛" : "";
50
+ return type === 'bug' ? '🐛' : ''
50
51
  }
51
52
 
52
53
  // Commands
53
54
  async function listIssues(options: {
54
- all?: boolean;
55
- priority?: string;
56
- type?: string;
57
- search?: string;
55
+ all?: boolean
56
+ priority?: string
57
+ type?: string
58
+ search?: string
58
59
  }) {
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`);
60
+ const allIssues = await getAllIssues()
61
+
62
+ // Apply filters
63
+ const issues = filterAndSearchIssues(allIssues, {
64
+ status: options.all ? undefined : 'open',
65
+ priority: options.priority,
66
+ type: options.type,
67
+ search: options.search
68
+ })
69
+
70
+ if (issues.length === 0) {
71
+ console.log('No issues found.')
72
+ return
73
+ }
74
+
75
+ console.log('\n ID Pri Type Status Title')
76
+ console.log(` ${'-'.repeat(70)}`)
77
+
78
+ for (const issue of issues) {
79
+ const status = issue.frontmatter.status === 'open' ? 'OPEN ' : 'CLOSED'
80
+ console.log(
81
+ ` ${issue.id} ${prioritySymbol(
82
+ issue.frontmatter.priority
83
+ )} ${typeSymbol(
84
+ issue.frontmatter.type
85
+ )} ${status} ${issue.frontmatter.title.slice(0, 45)}`
86
+ )
87
+ }
88
+
89
+ console.log(`\n Total: ${issues.length} issue(s)\n`)
87
90
  }
88
91
 
89
92
  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();
93
+ const issue = await getIssue(id)
94
+
95
+ if (!issue) {
96
+ console.error(`Issue not found: ${id}`)
97
+ process.exit(1)
98
+ }
99
+
100
+ console.log(`\n${'='.repeat(70)}`)
101
+ console.log(
102
+ ` ${typeSymbol(issue.frontmatter.type)} ${issue.frontmatter.title}`
103
+ )
104
+ console.log('='.repeat(70))
105
+ console.log(` ID: ${issue.id}`)
106
+ console.log(` Status: ${issue.frontmatter.status.toUpperCase()}`)
107
+ console.log(
108
+ ` Priority: ${prioritySymbol(issue.frontmatter.priority)} ${
109
+ issue.frontmatter.priority
110
+ }`
111
+ )
112
+ console.log(` Type: ${issue.frontmatter.type}`)
113
+ if (issue.frontmatter.labels) {
114
+ console.log(` Labels: ${issue.frontmatter.labels}`)
115
+ }
116
+ console.log(` Created: ${issue.frontmatter.created}`)
117
+ if (issue.frontmatter.updated) {
118
+ console.log(` Updated: ${issue.frontmatter.updated}`)
119
+ }
120
+ console.log('-'.repeat(70))
121
+ console.log(issue.content)
122
+ console.log()
116
123
  }
117
124
 
118
125
  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`);
126
+ const allIssues = await getAllIssues()
127
+
128
+ const issues = filterAndSearchIssues(allIssues, {
129
+ status: options.all ? undefined : 'open',
130
+ search: query
131
+ })
132
+
133
+ if (issues.length === 0) {
134
+ console.log(`No issues found matching "${query}".`)
135
+ return
136
+ }
137
+
138
+ console.log(`\n Search results for "${query}":`)
139
+ console.log('\n ID Pri Type Status Title')
140
+ console.log(` ${'-'.repeat(70)}`)
141
+
142
+ for (const issue of issues) {
143
+ const status = issue.frontmatter.status === 'open' ? 'OPEN ' : 'CLOSED'
144
+ console.log(
145
+ ` ${issue.id} ${prioritySymbol(
146
+ issue.frontmatter.priority
147
+ )} ${typeSymbol(
148
+ issue.frontmatter.type
149
+ )} ${status} ${issue.frontmatter.title.slice(0, 45)}`
150
+ )
151
+ }
152
+
153
+ console.log(`\n Found: ${issues.length} issue(s)\n`)
145
154
  }
146
155
 
147
156
  async function createIssueCommand(options: {
148
- title?: string;
149
- description?: string;
150
- priority?: string;
151
- type?: string;
152
- labels?: string;
157
+ title?: string
158
+ description?: string
159
+ priority?: string
160
+ type?: string
161
+ labels?: string
153
162
  }) {
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
- }
163
+ // Interactive mode if no title provided
164
+ if (!options.title) {
165
+ console.log('\nCreate New Issue')
166
+ console.log('-'.repeat(40))
167
+
168
+ const prompt = (question: string): Promise<string> => {
169
+ process.stdout.write(question)
170
+ return new Promise(resolve => {
171
+ let input = ''
172
+ process.stdin.setRawMode?.(false)
173
+ process.stdin.resume()
174
+ process.stdin.setEncoding('utf8')
175
+ process.stdin.once('data', data => {
176
+ input = data.toString().trim()
177
+ resolve(input)
178
+ })
179
+ })
180
+ }
181
+
182
+ options.title = await prompt('Title: ')
183
+ options.description = await prompt('Description: ')
184
+ options.priority = await prompt('Priority (high/medium/low) [medium]: ')
185
+ options.type = await prompt('Type (bug/improvement) [improvement]: ')
186
+ options.labels = await prompt('Labels (comma-separated) []: ')
187
+
188
+ // Apply defaults
189
+ if (!options.priority) options.priority = 'medium'
190
+ if (!options.type) options.type = 'improvement'
191
+ }
192
+
193
+ if (!options.title) {
194
+ console.error('Title is required')
195
+ process.exit(1)
196
+ }
197
+
198
+ try {
199
+ const input: CreateIssueInput = {
200
+ title: options.title,
201
+ description: options.description,
202
+ priority: options.priority as 'high' | 'medium' | 'low',
203
+ type: options.type as 'bug' | 'improvement',
204
+ labels: options.labels
205
+ }
206
+
207
+ const issue = await createIssue(input)
208
+ console.log(`\nCreated issue: ${issue.filename}`)
209
+ } catch (e) {
210
+ console.error(e instanceof Error ? e.message : 'Failed to create issue')
211
+ process.exit(1)
212
+ }
199
213
  }
200
214
 
201
215
  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
- }
216
+ id: string,
217
+ options: {
218
+ title?: string
219
+ description?: string
220
+ priority?: string
221
+ type?: string
222
+ labels?: string
223
+ status?: string
224
+ }
211
225
  ) {
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
+ try {
227
+ const issue = await updateIssue(id, {
228
+ title: options.title,
229
+ description: options.description,
230
+ priority: options.priority as 'high' | 'medium' | 'low' | undefined,
231
+ type: options.type as 'bug' | 'improvement' | undefined,
232
+ labels: options.labels,
233
+ status: options.status as 'open' | 'closed' | undefined
234
+ })
235
+ console.log(`Updated issue: ${issue.filename}`)
236
+ } catch (e) {
237
+ console.error(e instanceof Error ? e.message : 'Failed to update issue')
238
+ process.exit(1)
239
+ }
226
240
  }
227
241
 
228
242
  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
- }
243
+ try {
244
+ await closeIssue(id)
245
+ console.log('Issue closed.')
246
+ } catch (e) {
247
+ console.error(e instanceof Error ? e.message : 'Failed to close issue')
248
+ process.exit(1)
249
+ }
236
250
  }
237
251
 
238
252
  // Main
239
253
  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(`
254
+ const args = process.argv.slice(2)
255
+ const command = args[0]
256
+
257
+ if (
258
+ !command ||
259
+ command === 'help' ||
260
+ command === '--help' ||
261
+ command === '-h'
262
+ ) {
263
+ console.log(`
250
264
  issy CLI
251
265
 
252
266
  Usage:
@@ -290,109 +304,109 @@ Examples:
290
304
  issy create --title "Fix login bug" --type bug --priority high
291
305
  issy update 0001 --priority low
292
306
  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
- }
307
+ `)
308
+ return
309
+ }
310
+
311
+ switch (command) {
312
+ case 'list': {
313
+ const { values } = parseArgs({
314
+ args: args.slice(1),
315
+ options: {
316
+ all: { type: 'boolean', short: 'a' },
317
+ priority: { type: 'string', short: 'p' },
318
+ type: { type: 'string', short: 't' },
319
+ search: { type: 'string', short: 's' }
320
+ },
321
+ allowPositionals: true
322
+ })
323
+ await listIssues(values)
324
+ break
325
+ }
326
+
327
+ case 'search': {
328
+ const query = args[1]
329
+ if (!query) {
330
+ console.error('Usage: issy search <query>')
331
+ process.exit(1)
332
+ }
333
+ const { values } = parseArgs({
334
+ args: args.slice(2),
335
+ options: {
336
+ all: { type: 'boolean', short: 'a' }
337
+ },
338
+ allowPositionals: true
339
+ })
340
+ await searchIssuesCommand(query, values)
341
+ break
342
+ }
343
+
344
+ case 'read': {
345
+ const id = args[1]
346
+ if (!id) {
347
+ console.error('Usage: issy read <id>')
348
+ process.exit(1)
349
+ }
350
+ await readIssue(id)
351
+ break
352
+ }
353
+
354
+ case 'create': {
355
+ const { values } = parseArgs({
356
+ args: args.slice(1),
357
+ options: {
358
+ title: { type: 'string', short: 't' },
359
+ description: { type: 'string', short: 'd' },
360
+ priority: { type: 'string', short: 'p' },
361
+ type: { type: 'string' },
362
+ labels: { type: 'string', short: 'l' }
363
+ },
364
+ allowPositionals: true
365
+ })
366
+ await createIssueCommand(values)
367
+ break
368
+ }
369
+
370
+ case 'update': {
371
+ const id = args[1]
372
+ if (!id) {
373
+ console.error('Usage: issy update <id> [options]')
374
+ process.exit(1)
375
+ }
376
+ const { values } = parseArgs({
377
+ args: args.slice(2),
378
+ options: {
379
+ title: { type: 'string', short: 't' },
380
+ description: { type: 'string', short: 'd' },
381
+ priority: { type: 'string', short: 'p' },
382
+ type: { type: 'string' },
383
+ labels: { type: 'string', short: 'l' },
384
+ status: { type: 'string', short: 's' }
385
+ },
386
+ allowPositionals: true
387
+ })
388
+ await updateIssueCommand(id, values)
389
+ break
390
+ }
391
+
392
+ case 'close': {
393
+ const id = args[1]
394
+ if (!id) {
395
+ console.error('Usage: issy close <id>')
396
+ process.exit(1)
397
+ }
398
+ await closeIssueCommand(id)
399
+ break
400
+ }
401
+
402
+ default:
403
+ console.error(`Unknown command: ${command}`)
404
+ console.log('Run "issy help" for usage.')
405
+ process.exit(1)
406
+ }
393
407
  }
394
408
 
395
- main().catch((err) => {
396
- console.error(err);
397
- process.exit(1);
398
- });
409
+ main().catch(err => {
410
+ console.error(err)
411
+ process.exit(1)
412
+ })