baton-issue-tracker 1.4.0 → 1.5.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/package.json +1 -1
- package/source/cli.js +11 -4
- package/source/commands/approve.js +13 -5
- package/source/commands/create.js +8 -4
- package/source/commands/init.js +22 -10
- package/source/commands/list.js +30 -16
- package/source/commands/loop.js +7 -3
- package/source/commands/next.js +20 -13
- package/source/commands/search.js +24 -14
- package/source/commands/status.js +47 -17
- package/source/commands/update.js +24 -17
- package/source/commands/view.js +23 -13
- package/source/util.js +61 -0
package/package.json
CHANGED
package/source/cli.js
CHANGED
|
@@ -45,26 +45,33 @@ Commands:
|
|
|
45
45
|
Options:
|
|
46
46
|
init --force Re-initialize an existing tracker database
|
|
47
47
|
init --specs <path> Path to product specs file (overrides default)
|
|
48
|
+
init --json Output as JSON (for AI agents)
|
|
48
49
|
init <path> Same as --specs <path> (positional)
|
|
49
50
|
Default specs: docs/specs/project-requirements.md
|
|
50
51
|
loop --steps <n> Number of autonomous steps (alias: -n)
|
|
51
52
|
loop -n <n>
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
loop --json Output as JSON (for AI agents)
|
|
54
|
+
next --json Output as JSON (for AI agents)
|
|
55
|
+
status --json Output as JSON (for AI agents)
|
|
56
|
+
view <id> [--json]
|
|
57
|
+
search <query> [--json]
|
|
54
58
|
list --status <s> Filter by status: open | in-progress | closed
|
|
55
59
|
list --priority <p> Filter by priority: low | medium | high
|
|
56
60
|
list --limit <n> Max results (default: 50)
|
|
57
61
|
list --offset <n> Skip first n results (default: 0)
|
|
62
|
+
list --json Output as JSON (for AI agents)
|
|
58
63
|
create --title <text> Issue title (defaults to "Issue #<id>" if omitted)
|
|
59
64
|
create --description <text> Issue description
|
|
60
65
|
create --priority <level> low | medium | high (default: low)
|
|
61
66
|
create --token-limit <n> Optional token budget for this issue
|
|
62
|
-
|
|
67
|
+
create --json Output as JSON (for AI agents)
|
|
68
|
+
approve <id> [--json]
|
|
63
69
|
update --title <text> New title
|
|
64
70
|
update --description <text> New description
|
|
65
71
|
update --token-limit <n> New token budget
|
|
66
72
|
update --status <s> open | in-progress | closed
|
|
67
|
-
update --priority <level> low | medium | high
|
|
73
|
+
update --priority <level> low | medium | high
|
|
74
|
+
update --json Output as JSON (for AI agents)
|
|
68
75
|
|
|
69
76
|
|
|
70
77
|
Examples:
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// Usage: baton approve <id>
|
|
5
5
|
|
|
6
6
|
import { approveIssue } from '../services/issuesService.js';
|
|
7
|
+
import { hasFlag, renderOutput, serializeIssue } from '../util.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Approves an issue and moves it to the closed state.
|
|
@@ -11,14 +12,17 @@ import { approveIssue } from '../services/issuesService.js';
|
|
|
11
12
|
* @returns {Promise<number>} the exit code: 0 is success, 1 is error
|
|
12
13
|
*/
|
|
13
14
|
export async function run(args) {
|
|
15
|
+
const isJson = hasFlag(args, '--json');
|
|
16
|
+
const idArgs = args.filter((arg) => arg !== '--json');
|
|
17
|
+
|
|
14
18
|
//check if id argument is empty
|
|
15
|
-
if (
|
|
19
|
+
if (idArgs.length == 0) {
|
|
16
20
|
throw new Error(
|
|
17
21
|
'Invalid input: Missing issue ID.\nUsage: baton approve <id>'
|
|
18
22
|
);
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
const id =
|
|
25
|
+
const id = idArgs.join(' ');
|
|
22
26
|
|
|
23
27
|
//check if ID argument isn't a number
|
|
24
28
|
if (isNaN(id)) {
|
|
@@ -30,9 +34,13 @@ export async function run(args) {
|
|
|
30
34
|
//try to approve the issue
|
|
31
35
|
try {
|
|
32
36
|
const issue = await approveIssue(id);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
)
|
|
37
|
+
const envelope = { status: 'success', issue: serializeIssue(issue) };
|
|
38
|
+
|
|
39
|
+
renderOutput(isJson, envelope, () => {
|
|
40
|
+
console.log(
|
|
41
|
+
`Issue #${issue.id} approved and moved to ${issue.status}.`
|
|
42
|
+
);
|
|
43
|
+
});
|
|
36
44
|
|
|
37
45
|
return 0;
|
|
38
46
|
} catch (error) {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { createIssue } from "../services/issuesService.js";
|
|
17
17
|
import { getFlagValue, getNumericFlag } from "../util.js";
|
|
18
|
-
import { parseArgs } from '../util.js';
|
|
18
|
+
import { hasFlag, parseArgs, renderOutput, serializeIssue } from '../util.js';
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Initializes a new issue in the database with the specified fields
|
|
@@ -23,7 +23,8 @@ import { parseArgs } from '../util.js';
|
|
|
23
23
|
* @returns {Promise<number>} The exit code: 0 is success, 1 is error.
|
|
24
24
|
*/
|
|
25
25
|
export async function run(args) {
|
|
26
|
-
const
|
|
26
|
+
const isJson = hasFlag(args, '--json');
|
|
27
|
+
const validFlags = ['--title', '--description', '--priority', '--token-limit', '--json'];
|
|
27
28
|
// Check if user misspelled a flag
|
|
28
29
|
for (const arg of args) {
|
|
29
30
|
if (arg.startsWith('--')) {
|
|
@@ -37,9 +38,12 @@ export async function run(args) {
|
|
|
37
38
|
const options = parseArgs(args);
|
|
38
39
|
|
|
39
40
|
const issue = await createIssue(options);
|
|
41
|
+
const envelope = { status: 'success', issue: serializeIssue(issue) };
|
|
42
|
+
|
|
43
|
+
renderOutput(isJson, envelope, () => {
|
|
44
|
+
console.log(`Successfully created issue #${issue.id}: "${issue.title}"`);
|
|
45
|
+
});
|
|
40
46
|
|
|
41
|
-
// program reaches this line if issue was successfully created
|
|
42
|
-
console.log(`Successfully created issue #${issue.id}: "${issue.title}"`);
|
|
43
47
|
return 0;
|
|
44
48
|
} catch (error) {
|
|
45
49
|
console.error(`Failed to create issue: ${error.message}`);
|
package/source/commands/init.js
CHANGED
|
@@ -24,7 +24,9 @@ import {
|
|
|
24
24
|
getFlagValue,
|
|
25
25
|
getFirstPositionalArg,
|
|
26
26
|
hasFlag,
|
|
27
|
+
renderOutput,
|
|
27
28
|
resolvePath,
|
|
29
|
+
serializeIssue,
|
|
28
30
|
} from '../util.js';
|
|
29
31
|
|
|
30
32
|
/**
|
|
@@ -48,7 +50,7 @@ function parseInitFlags(args) {
|
|
|
48
50
|
const specsFromFlag = getFlagValue(args, '--specs');
|
|
49
51
|
const positionalSpecs = getFirstPositionalArg(args, {
|
|
50
52
|
valueFlags: ['--specs'],
|
|
51
|
-
ignoreFlags: ['--force'],
|
|
53
|
+
ignoreFlags: ['--force', '--json'],
|
|
52
54
|
});
|
|
53
55
|
|
|
54
56
|
if (specsFromFlag && positionalSpecs) {
|
|
@@ -132,6 +134,7 @@ function generateIssuesFromSpecs(specsPath) {
|
|
|
132
134
|
* @returns {Promise<number>} The exit code: 0 is success, 1 is error.
|
|
133
135
|
*/
|
|
134
136
|
export async function run(args = []) {
|
|
137
|
+
const isJson = hasFlag(args, '--json');
|
|
135
138
|
let flags;
|
|
136
139
|
try {
|
|
137
140
|
flags = parseInitFlags(args);
|
|
@@ -155,17 +158,26 @@ export async function run(args = []) {
|
|
|
155
158
|
|
|
156
159
|
const resolvedSpecsPath = resolvePath(flags.specs, DEFAULT_SPECS_PATH);
|
|
157
160
|
const createdIssues = generateIssuesFromSpecs(flags.specs);
|
|
161
|
+
const envelope = {
|
|
162
|
+
status: 'success',
|
|
163
|
+
db_path: join(process.cwd(), 'data', 'issues.db'),
|
|
164
|
+
specs_path: resolvedSpecsPath,
|
|
165
|
+
count: createdIssues.length,
|
|
166
|
+
issues: createdIssues.map(serializeIssue),
|
|
167
|
+
};
|
|
158
168
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
169
|
+
renderOutput(isJson, envelope, () => {
|
|
170
|
+
console.log(`Tracker initialized at ${join(process.cwd(), 'data', 'issues.db')}`);
|
|
171
|
+
console.log(`Specs: ${resolvedSpecsPath}`);
|
|
172
|
+
console.log(`Created ${createdIssues.length} issue(s) from product specs.`);
|
|
173
|
+
if (createdIssues.length > 0) {
|
|
174
|
+
console.log('Issues:');
|
|
175
|
+
for (const issue of createdIssues) {
|
|
176
|
+
console.log(` #${issue.id} [${issue.priority}] ${issue.title}`);
|
|
177
|
+
}
|
|
166
178
|
}
|
|
167
|
-
|
|
168
|
-
|
|
179
|
+
console.log('Run `baton status` to review progress or `baton next` to start work.');
|
|
180
|
+
});
|
|
169
181
|
|
|
170
182
|
return 0;
|
|
171
183
|
}
|
package/source/commands/list.js
CHANGED
|
@@ -8,11 +8,20 @@
|
|
|
8
8
|
// --priority <p> Filter by priority: low | medium | high
|
|
9
9
|
// --limit <n> Max results (default: 50)
|
|
10
10
|
// --offset <n> Skip first n results (default: 0)
|
|
11
|
+
// --json Output as JSON (for AI agents)
|
|
11
12
|
// -h, --help Show this help
|
|
12
13
|
|
|
13
14
|
import { listIssues } from '../services/issuesService.js';
|
|
14
|
-
import {
|
|
15
|
-
|
|
15
|
+
import {
|
|
16
|
+
getFlagValue,
|
|
17
|
+
getNumericFlag,
|
|
18
|
+
hasFlag,
|
|
19
|
+
parseArgs,
|
|
20
|
+
printIssueTable,
|
|
21
|
+
printTableHeader,
|
|
22
|
+
renderOutput,
|
|
23
|
+
serializeIssue,
|
|
24
|
+
} from '../util.js';
|
|
16
25
|
|
|
17
26
|
/**
|
|
18
27
|
* Lists issues matching the filters and pagination settings
|
|
@@ -21,12 +30,13 @@ import { parseArgs, printIssueTable, printTableHeader } from '../util.js';
|
|
|
21
30
|
*/
|
|
22
31
|
|
|
23
32
|
export async function run(args) {
|
|
24
|
-
const
|
|
33
|
+
const isJson = hasFlag(args, '--json');
|
|
34
|
+
const validFlags = ['--status', '--priority', '--limit', '--offset', '--json'];
|
|
25
35
|
// Check if user misspelled a flag
|
|
26
36
|
for (const arg of args) {
|
|
27
37
|
if (arg.startsWith('--')) {
|
|
28
38
|
if (!validFlags.includes(arg)) {
|
|
29
|
-
throw new Error(`Unknown flag provided: ${arg}. \nFlags: --status <s>, --priority <p>, --limit <n>, --offset <n
|
|
39
|
+
throw new Error(`Unknown flag provided: ${arg}. \nFlags: --status <s>, --priority <p>, --limit <n>, --offset <n>, --json`);
|
|
30
40
|
}
|
|
31
41
|
}
|
|
32
42
|
}
|
|
@@ -35,25 +45,29 @@ export async function run(args) {
|
|
|
35
45
|
const options = parseArgs(args);
|
|
36
46
|
|
|
37
47
|
const result = await listIssues(options);
|
|
48
|
+
const issues = result.map(serializeIssue);
|
|
49
|
+
const envelope = { status: 'success', count: issues.length, issues };
|
|
50
|
+
|
|
51
|
+
renderOutput(isJson, envelope, (data) => {
|
|
52
|
+
if (data.count === 0) {
|
|
53
|
+
console.log('No issues matching those filters were found.');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
38
56
|
|
|
39
|
-
if (result.length == 0) {
|
|
40
|
-
console.log("No issues matching those filters were found.");
|
|
41
|
-
return 0;
|
|
42
|
-
} else {
|
|
43
|
-
//Logic for table formatting
|
|
44
57
|
const activeFilters = Object.entries(options)
|
|
45
58
|
.map(([key, value]) => `${key}: ${value}`)
|
|
46
59
|
.join(', ');
|
|
47
60
|
|
|
48
|
-
const filterLog = activeFilters ? `matching filters: [${activeFilters}]` :
|
|
49
|
-
console.log(`\nFound ${
|
|
50
|
-
console.log(
|
|
61
|
+
const filterLog = activeFilters ? `matching filters: [${activeFilters}]` : 'with no filters applied';
|
|
62
|
+
console.log(`\nFound ${data.count} issue(s) ${filterLog}.`);
|
|
63
|
+
console.log('');
|
|
51
64
|
|
|
52
65
|
printTableHeader();
|
|
53
|
-
result.forEach(issue => printIssueTable(issue));
|
|
54
|
-
console.log(
|
|
55
|
-
|
|
56
|
-
|
|
66
|
+
result.forEach((issue) => printIssueTable(issue));
|
|
67
|
+
console.log('');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return 0;
|
|
57
71
|
} catch (error) {
|
|
58
72
|
// Separate error message for missing value
|
|
59
73
|
if (error.message.includes('Missing value')) {
|
package/source/commands/loop.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// baton loop -n 5
|
|
12
12
|
|
|
13
13
|
import { isTrackerReady } from '../services/issuesService.js';
|
|
14
|
-
import { getNumericFlag, reportTrackerNotReady } from '../util.js';
|
|
14
|
+
import { getNumericFlag, hasFlag, renderOutput, reportTrackerNotReady } from '../util.js';
|
|
15
15
|
import { run as runNext } from './next.js';
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -30,7 +30,9 @@ function parseLoopFlags(args) {
|
|
|
30
30
|
* @returns {Promise<number>}
|
|
31
31
|
*/
|
|
32
32
|
export async function run(args = []) {
|
|
33
|
-
const
|
|
33
|
+
const isJson = hasFlag(args, '--json');
|
|
34
|
+
const loopArgs = args.filter((arg) => arg !== '--json');
|
|
35
|
+
const { steps } = parseLoopFlags(loopArgs);
|
|
34
36
|
|
|
35
37
|
if (!isTrackerReady()) {
|
|
36
38
|
reportTrackerNotReady();
|
|
@@ -58,6 +60,8 @@ export async function run(args = []) {
|
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
renderOutput(isJson, { status: 'success', steps, completed }, () => {
|
|
64
|
+
console.log(`\nCompleted ${completed} autonomous step(s).`);
|
|
65
|
+
});
|
|
62
66
|
return 0;
|
|
63
67
|
}
|
package/source/commands/next.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
selectNextIssue,
|
|
9
9
|
workOnIssue,
|
|
10
10
|
} from '../services/issuesService.js';
|
|
11
|
-
import { formatTimestamp, reportTrackerNotReady } from '../util.js';
|
|
11
|
+
import { formatTimestamp, hasFlag, renderOutput, reportTrackerNotReady, serializeIssue } from '../util.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Moves the AI agent to work on the next issue.
|
|
@@ -17,7 +17,9 @@ import { formatTimestamp, reportTrackerNotReady } from '../util.js';
|
|
|
17
17
|
* Stats are updated on the issue through workOnIssue function from init.js.
|
|
18
18
|
* @returns {Promise<number>} The exit code: 0 is success, 1 is error.
|
|
19
19
|
*/
|
|
20
|
-
export async function run() {
|
|
20
|
+
export async function run(args = []) {
|
|
21
|
+
const isJson = hasFlag(args, '--json');
|
|
22
|
+
|
|
21
23
|
if (!isTrackerReady()) {
|
|
22
24
|
reportTrackerNotReady();
|
|
23
25
|
return 1;
|
|
@@ -25,22 +27,27 @@ export async function run() {
|
|
|
25
27
|
|
|
26
28
|
const issue = selectNextIssue();
|
|
27
29
|
if (!issue) {
|
|
28
|
-
|
|
30
|
+
renderOutput(isJson, { status: 'success', issue: null }, () => {
|
|
31
|
+
console.log('No open issues available. All work is complete or the backlog is empty.');
|
|
32
|
+
});
|
|
29
33
|
return 0;
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
const updated = workOnIssue(issue.id);
|
|
37
|
+
const envelope = { status: 'success', issue: serializeIssue(updated) };
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
39
|
+
renderOutput(isJson, envelope, () => {
|
|
40
|
+
console.log('Working on next issue:');
|
|
41
|
+
console.log(` ID: #${updated.id}`);
|
|
42
|
+
console.log(` Title: ${updated.title}`);
|
|
43
|
+
console.log(` Priority: ${updated.priority}`);
|
|
44
|
+
console.log(` Status: ${updated.status}`);
|
|
45
|
+
console.log(` Attempts: ${updated.attemptNum}`);
|
|
46
|
+
console.log(` Created: ${formatTimestamp(updated.createdAt)}`);
|
|
47
|
+
if (updated.description) {
|
|
48
|
+
console.log(` Description: ${updated.description}`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
44
51
|
|
|
45
52
|
return 0;
|
|
46
53
|
}
|
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
// Usage: baton search "login bug"
|
|
5
5
|
|
|
6
6
|
import { searchIssues } from "../services/issuesService.js";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
hasFlag,
|
|
9
|
+
printIssueTable,
|
|
10
|
+
printTableHeader,
|
|
11
|
+
renderOutput,
|
|
12
|
+
serializeIssue,
|
|
13
|
+
} from '../util.js';
|
|
8
14
|
|
|
9
15
|
/**
|
|
10
16
|
* Searches for a title or description matching the command line argument
|
|
@@ -12,27 +18,31 @@ import { printIssueTable, printTableHeader } from '../util.js';
|
|
|
12
18
|
* @returns {Promise<number>} The exit code: 0 is success, 1 is error.
|
|
13
19
|
*/
|
|
14
20
|
export async function run(args) {
|
|
15
|
-
|
|
21
|
+
const isJson = hasFlag(args, '--json');
|
|
22
|
+
const queryArgs = args.filter((arg) => arg !== '--json');
|
|
23
|
+
|
|
24
|
+
if (queryArgs.length === 0 || queryArgs === '') {
|
|
16
25
|
throw new Error("Invalid input: No search term entered.\nUsage: baton search <query>");
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
try {
|
|
20
|
-
const query =
|
|
29
|
+
const query = queryArgs.join(' ');
|
|
21
30
|
const result = await searchIssues(query);
|
|
31
|
+
const issues = result.map(serializeIssue);
|
|
32
|
+
const envelope = { status: 'success', count: issues.length, issues };
|
|
22
33
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
console.log(`\nFound ${result.length} issue(s) containing "${query}":\n`);
|
|
34
|
+
renderOutput(isJson, envelope, (data) => {
|
|
35
|
+
if (data.count === 0) {
|
|
36
|
+
console.log(`No issues containing "${query}" were found.`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
console.log(`\nFound ${data.count} issue(s) containing "${query}":\n`);
|
|
41
|
+
printTableHeader();
|
|
42
|
+
result.forEach((issue) => printIssueTable(issue));
|
|
43
|
+
console.log('');
|
|
44
|
+
});
|
|
34
45
|
|
|
35
|
-
console.log("");
|
|
36
46
|
return 0;
|
|
37
47
|
} catch (error) {
|
|
38
48
|
// Failed to query database
|
|
@@ -8,13 +8,15 @@ import {
|
|
|
8
8
|
getIssueStats,
|
|
9
9
|
getAllIssues,
|
|
10
10
|
} from '../services/issuesService.js';
|
|
11
|
-
import { reportTrackerNotReady } from '../util.js';
|
|
11
|
+
import { hasFlag, renderOutput, reportTrackerNotReady } from '../util.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Main function that runs the status command.
|
|
15
15
|
* @returns {Promise<number>} The exit code: 0 is success, 1 is error.
|
|
16
16
|
*/
|
|
17
|
-
export async function run() {
|
|
17
|
+
export async function run(args = []) {
|
|
18
|
+
const isJson = hasFlag(args, '--json');
|
|
19
|
+
|
|
18
20
|
if (!isTrackerReady()) {
|
|
19
21
|
reportTrackerNotReady();
|
|
20
22
|
return 1;
|
|
@@ -29,27 +31,55 @@ export async function run() {
|
|
|
29
31
|
progress = Math.round((stats.closed / stats.total) * 100);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
console.log(`Total issues: ${stats.total}`);
|
|
35
|
-
console.log(`Open: ${stats.open}`);
|
|
36
|
-
console.log(`In progress: ${stats.inProgress}`);
|
|
37
|
-
console.log(`In review: ${stats.inReview}`);
|
|
38
|
-
console.log(`Closed: ${stats.closed}`);
|
|
39
|
-
console.log(`Overall progress: ${progress}% complete`);
|
|
40
|
-
|
|
41
|
-
if (issues.length > 0) {
|
|
42
|
-
console.log('\nOpen issues by priority:');
|
|
43
|
-
const byPriority = { High: 0, Medium: 0, Low: 0 };
|
|
34
|
+
const byPriority = { High: 0, Medium: 0, Low: 0 };
|
|
35
|
+
if (isJson) {
|
|
44
36
|
for (const issue of issues) {
|
|
45
37
|
if (issue.status === 'Open') {
|
|
46
38
|
byPriority[issue.priority] += 1;
|
|
47
39
|
}
|
|
48
40
|
}
|
|
49
|
-
console.log(` High: ${byPriority.High}`);
|
|
50
|
-
console.log(` Medium: ${byPriority.Medium}`);
|
|
51
|
-
console.log(` Low: ${byPriority.Low}`);
|
|
52
41
|
}
|
|
53
42
|
|
|
43
|
+
const envelope = {
|
|
44
|
+
status: 'success',
|
|
45
|
+
stats: {
|
|
46
|
+
total: stats.total,
|
|
47
|
+
open: stats.open,
|
|
48
|
+
in_progress: stats.inProgress,
|
|
49
|
+
in_review: stats.inReview,
|
|
50
|
+
closed: stats.closed,
|
|
51
|
+
progress_percent: progress,
|
|
52
|
+
},
|
|
53
|
+
open_by_priority: {
|
|
54
|
+
high: byPriority.High,
|
|
55
|
+
medium: byPriority.Medium,
|
|
56
|
+
low: byPriority.Low,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
renderOutput(isJson, envelope, () => {
|
|
61
|
+
console.log('Issue Tracker Status');
|
|
62
|
+
console.log('──────────────────────────────────────────');
|
|
63
|
+
console.log(`Total issues: ${stats.total}`);
|
|
64
|
+
console.log(`Open: ${stats.open}`);
|
|
65
|
+
console.log(`In progress: ${stats.inProgress}`);
|
|
66
|
+
console.log(`In review: ${stats.inReview}`);
|
|
67
|
+
console.log(`Closed: ${stats.closed}`);
|
|
68
|
+
console.log(`Overall progress: ${progress}% complete`);
|
|
69
|
+
|
|
70
|
+
if (issues.length > 0) {
|
|
71
|
+
console.log('\nOpen issues by priority:');
|
|
72
|
+
const byPriority = { High: 0, Medium: 0, Low: 0 };
|
|
73
|
+
for (const issue of issues) {
|
|
74
|
+
if (issue.status === 'Open') {
|
|
75
|
+
byPriority[issue.priority] += 1;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
console.log(` High: ${byPriority.High}`);
|
|
79
|
+
console.log(` Medium: ${byPriority.Medium}`);
|
|
80
|
+
console.log(` Low: ${byPriority.Low}`);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
54
84
|
return 0;
|
|
55
85
|
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// baton update 7 --status closed --priority medium
|
|
17
17
|
|
|
18
18
|
import { updateIssue, getIssue } from "../services/issuesService.js";
|
|
19
|
-
import { parseArgs } from '../util.js';
|
|
19
|
+
import { hasFlag, parseArgs, renderOutput, serializeIssue } from '../util.js';
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -25,20 +25,23 @@ import { parseArgs } from '../util.js';
|
|
|
25
25
|
* @returns {Promise<number>} The exit code: 0 is success, 1 is error.
|
|
26
26
|
*/
|
|
27
27
|
export async function run(args) {
|
|
28
|
-
|
|
28
|
+
const isJson = hasFlag(args, '--json');
|
|
29
|
+
const cmdArgs = args.filter((arg) => arg !== '--json');
|
|
30
|
+
|
|
31
|
+
if (cmdArgs.length === 0 || cmdArgs === '') {
|
|
29
32
|
throw new Error("Invalid input: No arguments entered.\nUsage: baton update <id> [options] \nOptions: --title <text>, --description <text>, --token-limit <n>, --status <s>, --priority <level>");
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
// Convert id argument from string to base-10 integer
|
|
33
|
-
const id = parseInt(
|
|
36
|
+
const id = parseInt(cmdArgs[0], 10);
|
|
34
37
|
|
|
35
38
|
if (isNaN(id)) {
|
|
36
39
|
throw new Error ("Invalid input: No ID entered. \nUsage: baton update <id> [options]\nOptions: --title <text>, --description <text>, --token-limit <n>, --status <s>, --priority <level>");
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
const validFlags = ['--title', '--description', '--token-limit', '--status', '--priority'];
|
|
42
|
+
const validFlags = ['--title', '--description', '--token-limit', '--status', '--priority', '--json'];
|
|
40
43
|
// Check if user misspelled a flag
|
|
41
|
-
for (const arg of
|
|
44
|
+
for (const arg of cmdArgs) {
|
|
42
45
|
if (arg.startsWith('--')) {
|
|
43
46
|
if (!validFlags.includes(arg)) {
|
|
44
47
|
throw new Error(`Unknown flag provided: ${arg}. \nFlags: --title <text>, --description <text>, --token-limit <n>, --status <s>, --priority <level>`);
|
|
@@ -47,25 +50,29 @@ export async function run(args) {
|
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
try {
|
|
50
|
-
const options = parseArgs(
|
|
53
|
+
const options = parseArgs(cmdArgs.slice(1));
|
|
51
54
|
|
|
52
55
|
// Store the old issue fields for displaying purposes:
|
|
53
56
|
const oldIssue = getIssue(id);
|
|
54
57
|
|
|
55
58
|
const newIssue = await updateIssue(id, oldIssue, options);
|
|
59
|
+
const envelope = { status: 'success', issue: serializeIssue(newIssue) };
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
renderOutput(isJson, envelope, () => {
|
|
62
|
+
console.log('');
|
|
63
|
+
// Compare and print the changes:
|
|
64
|
+
console.log(`Successfully updated issue #${id}:`);
|
|
65
|
+
for (const key in options) {
|
|
66
|
+
if (oldIssue[key] !== newIssue[key]) {
|
|
67
|
+
console.log(` ${key}: "${oldIssue[key]}" -> "${newIssue[key]}"`);
|
|
68
|
+
} else {
|
|
69
|
+
// If the entered argument matches the old data
|
|
70
|
+
console.log(` ${key}: No change (already set to "${newIssue[key]}")`);
|
|
71
|
+
}
|
|
66
72
|
}
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
console.log('');
|
|
74
|
+
});
|
|
75
|
+
|
|
69
76
|
return 0;
|
|
70
77
|
} catch (error) {
|
|
71
78
|
console.error(`Failed to update issue: ${error.message}`);
|
package/source/commands/view.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// list.js
|
|
2
2
|
// AI was consulted for some portions of this file.
|
|
3
3
|
// view command which allows user to view all data fields for an issue
|
|
4
|
-
// Usage: baton view <id
|
|
4
|
+
// Usage: baton view <id> [--json]
|
|
5
5
|
|
|
6
6
|
import { getIssue } from '../services/issuesService.js';
|
|
7
|
+
import { hasFlag, renderOutput, serializeIssue } from '../util.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Displays all issue fields for a given id #
|
|
@@ -11,12 +12,15 @@ import { getIssue } from '../services/issuesService.js';
|
|
|
11
12
|
* @returns {Promise<number>} The exit code: 0 is success, 1 is error.
|
|
12
13
|
*/
|
|
13
14
|
export async function run(args) {
|
|
15
|
+
const isJson = hasFlag(args, '--json');
|
|
16
|
+
const idArgs = args.filter((arg) => arg !== '--json');
|
|
17
|
+
|
|
14
18
|
// checks if id # argument is empty
|
|
15
|
-
if (
|
|
19
|
+
if (idArgs.length == 0) {
|
|
16
20
|
throw new Error(`Invalid input: Missing Issue ID.\nUsage: baton view <id>`);
|
|
17
21
|
}
|
|
18
22
|
|
|
19
|
-
const id =
|
|
23
|
+
const id = idArgs.join(' ');
|
|
20
24
|
|
|
21
25
|
// checks if ID # argument isn't a number
|
|
22
26
|
if (isNaN(id)) {
|
|
@@ -25,18 +29,24 @@ export async function run(args) {
|
|
|
25
29
|
|
|
26
30
|
try {
|
|
27
31
|
const issue = await getIssue(id);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
const envelope = {
|
|
33
|
+
status: 'success',
|
|
34
|
+
issue: issue ? serializeIssue(issue) : null,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
renderOutput(isJson, envelope, () => {
|
|
38
|
+
if (!issue) {
|
|
39
|
+
console.log(`No issue with ID #"${issue}" was found.`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log('');
|
|
44
|
+
Object.entries(issue).forEach(([key, value]) => {
|
|
45
|
+
console.log(`${key}: ${value}`);
|
|
46
|
+
});
|
|
47
|
+
console.log('');
|
|
37
48
|
});
|
|
38
49
|
|
|
39
|
-
console.log("");
|
|
40
50
|
return 0;
|
|
41
51
|
} catch (error) {
|
|
42
52
|
console.error("Error: Failed to retrieve data.");
|
package/source/util.js
CHANGED
|
@@ -189,6 +189,67 @@ export function reportTrackerNotReady() {
|
|
|
189
189
|
console.error('Run `baton init` first.');
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Converts DB-style enum values to lowercase snake_case for JSON output.
|
|
194
|
+
* e.g. "In-Progress" → "in_progress"
|
|
195
|
+
* @param {string | null | undefined} value
|
|
196
|
+
* @returns {string | null}
|
|
197
|
+
*/
|
|
198
|
+
function toJsonEnum(value) {
|
|
199
|
+
return value?.toLowerCase().replace(/-/g, '_') ?? null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Normalizes a DB row or Issue instance into a stable JSON-friendly shape.
|
|
204
|
+
* All commands should serialize through this before building their envelope.
|
|
205
|
+
* @param {object} issue
|
|
206
|
+
* @returns {object}
|
|
207
|
+
*/
|
|
208
|
+
export function serializeIssue(issue) {
|
|
209
|
+
return {
|
|
210
|
+
id: issue.id,
|
|
211
|
+
title: issue.title,
|
|
212
|
+
status: toJsonEnum(issue.status),
|
|
213
|
+
priority: toJsonEnum(issue.priority),
|
|
214
|
+
description: issue.description ?? null,
|
|
215
|
+
token_limit: issue.tokenLimit ?? issue.token_limit ?? null,
|
|
216
|
+
attempt_num: issue.attemptNum ?? issue.attempt_num ?? 0,
|
|
217
|
+
created_at: issue.createdAt ?? issue.created_at ?? null,
|
|
218
|
+
last_updated: issue.lastUpdated ?? issue.last_updated ?? null,
|
|
219
|
+
assignees: issue.assignees ?? null,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Centralized output renderer.
|
|
225
|
+
* If isJson is true, outputs the envelope as JSON.
|
|
226
|
+
* Otherwise, runs the human-readable callback.
|
|
227
|
+
* @param {boolean} isJson - Whether the user passed the --json flag
|
|
228
|
+
* @param {object} envelope - Structured response payload (e.g. { status, issues })
|
|
229
|
+
* @param {Function} humanOutput - Callback for human terminal styling
|
|
230
|
+
*/
|
|
231
|
+
export function renderOutput(isJson, envelope, humanOutput) {
|
|
232
|
+
if (isJson) {
|
|
233
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
humanOutput(envelope);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Prints an error in JSON or plain text depending on output mode.
|
|
241
|
+
* @param {boolean} isJson
|
|
242
|
+
* @param {string} message
|
|
243
|
+
* @param {string} [code='ERROR']
|
|
244
|
+
*/
|
|
245
|
+
export function renderError(isJson, message, code = 'ERROR') {
|
|
246
|
+
if (isJson) {
|
|
247
|
+
console.error(JSON.stringify({ status: 'error', code, message }));
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
console.error(`Error: ${message}`);
|
|
251
|
+
}
|
|
252
|
+
|
|
192
253
|
/**
|
|
193
254
|
* Configuration for table column widths.
|
|
194
255
|
*/
|