baton-issue-tracker 1.10.0 → 1.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/package.json +1 -1
- package/source/cli.js +11 -15
- package/source/commands/agents.js +78 -0
- package/source/commands/claim.js +71 -0
- package/source/commands/init.js +1 -1
- package/source/services/agentsService.js +10 -0
- package/source/services/authService.js +11 -0
- package/source/commands/loop.js +0 -67
- package/source/commands/next.js +0 -53
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Fantastic Four
|
|
2
2
|
|
|
3
|
-
##
|
|
4
|
-
[
|
|
3
|
+
## Documentation
|
|
4
|
+
- [User Guide](docs/USER_GUIDE.md) — How to use Baton CLI.
|
|
5
|
+
- [Developer Guide](docs/CONTRIBUTING.md) — Setting up the development environment.
|
|
5
6
|
|
|
6
7
|
## Project Details:
|
|
7
8
|
TBD
|
package/package.json
CHANGED
package/source/cli.js
CHANGED
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
// usage: baton [command] [options]
|
|
6
6
|
// commands:
|
|
7
7
|
// init: initialize the tracker
|
|
8
|
-
// next: work on the next issue
|
|
9
|
-
// loop: run the agent autonomously for multiple steps
|
|
10
8
|
// status: show issue counts and overall progress
|
|
11
9
|
//
|
|
12
10
|
// see each command's file for more detailed flags specifications.
|
|
@@ -14,8 +12,6 @@
|
|
|
14
12
|
* Imports the run functions from each command.
|
|
15
13
|
*/
|
|
16
14
|
import { run as runInit } from './commands/init.js';
|
|
17
|
-
import { run as runNext } from './commands/next.js';
|
|
18
|
-
import { run as runLoop } from './commands/loop.js';
|
|
19
15
|
import { run as runStatus } from './commands/status.js';
|
|
20
16
|
import { run as runApprove } from './commands/approve.js';
|
|
21
17
|
import { run as runReject } from './commands/reject.js';
|
|
@@ -29,7 +25,9 @@ import { run as runDelete } from './commands/delete.js';
|
|
|
29
25
|
import { run as runPriority } from './commands/priority.js';
|
|
30
26
|
import { run as runLog } from './commands/log.js';
|
|
31
27
|
import { run as runRegister } from './commands/register.js';
|
|
28
|
+
import { run as runAgents } from './commands/agents.js';
|
|
32
29
|
import { run as runSubmit } from './commands/submit.js';
|
|
30
|
+
import { run as runClaim } from './commands/claim.js';
|
|
33
31
|
|
|
34
32
|
import { authenticateContext } from './services/authService.js';
|
|
35
33
|
|
|
@@ -41,8 +39,7 @@ Usage:
|
|
|
41
39
|
Commands:
|
|
42
40
|
init Initialize storage and seed issues from product specs
|
|
43
41
|
register Register a new AI agent or human user
|
|
44
|
-
|
|
45
|
-
loop Run the agent autonomously for multiple steps
|
|
42
|
+
agents List all registered agents and humans
|
|
46
43
|
status Show issue counts and overall progress
|
|
47
44
|
view View all issue fields for a given issue ID
|
|
48
45
|
search Search issues by title and description (case insensitive)
|
|
@@ -50,6 +47,7 @@ Commands:
|
|
|
50
47
|
create Creates an issue with specified fields
|
|
51
48
|
approve Move an issue from in-review to closed
|
|
52
49
|
submit Submit finished work for human review
|
|
50
|
+
claim Claim an issue as the authenticated agent
|
|
53
51
|
priority Set an issue's priority level
|
|
54
52
|
update Updates an issue's specified fields
|
|
55
53
|
delete Deletes an issue
|
|
@@ -63,10 +61,7 @@ Options:
|
|
|
63
61
|
Default specs: docs/specs/project-requirements.md
|
|
64
62
|
register --name <name> Name of the agent or user
|
|
65
63
|
register --type <type> agent | human (default: agent)
|
|
66
|
-
|
|
67
|
-
loop -n <n>
|
|
68
|
-
loop --json Output as JSON (for AI agents)
|
|
69
|
-
next --json Output as JSON (for AI agents)
|
|
64
|
+
agents [--json]
|
|
70
65
|
status --json Output as JSON (for AI agents)
|
|
71
66
|
view <id> [--json]
|
|
72
67
|
search <query> [--json]
|
|
@@ -82,6 +77,7 @@ Options:
|
|
|
82
77
|
create --json Output as JSON (for AI agents)
|
|
83
78
|
approve <id> [--json]
|
|
84
79
|
submit <id> [--json]
|
|
80
|
+
claim <id> [--json]
|
|
85
81
|
reject <id> --reason <text> Reject an issue with a given reason
|
|
86
82
|
priority <id> <level> [--json] low | medium | high
|
|
87
83
|
update --title <text> New title
|
|
@@ -99,8 +95,7 @@ Examples:
|
|
|
99
95
|
baton init ./my-specs.md
|
|
100
96
|
baton init --force
|
|
101
97
|
baton register --name claude-dev --type agent
|
|
102
|
-
baton
|
|
103
|
-
baton loop --steps 5
|
|
98
|
+
baton agents
|
|
104
99
|
baton status
|
|
105
100
|
baton view 29
|
|
106
101
|
baton search system
|
|
@@ -111,6 +106,7 @@ Examples:
|
|
|
111
106
|
baton create --title "Refactor auth" --description "Clean up JWT logic" --token-limit 4000
|
|
112
107
|
baton approve 5
|
|
113
108
|
baton submit 14
|
|
109
|
+
baton claim 14
|
|
114
110
|
baton priority 5 high
|
|
115
111
|
baton priority 3 low
|
|
116
112
|
baton update 3 --title "Revised title"
|
|
@@ -125,7 +121,7 @@ Examples:
|
|
|
125
121
|
async function main() {
|
|
126
122
|
const [, , command, ...args] = process.argv;
|
|
127
123
|
|
|
128
|
-
if (!command || command === 'help' ||
|
|
124
|
+
if (!command || command === 'help' || command === '--help') {
|
|
129
125
|
console.log(HELP);
|
|
130
126
|
process.exit(command ? 0 : 1);
|
|
131
127
|
return;
|
|
@@ -137,14 +133,14 @@ async function main() {
|
|
|
137
133
|
const handlers = {
|
|
138
134
|
init: () => runInit(args),
|
|
139
135
|
register: () => runRegister(args),
|
|
140
|
-
|
|
141
|
-
loop: () => runLoop(args),
|
|
136
|
+
agents: () => runAgents(args),
|
|
142
137
|
status: () => runStatus(args),
|
|
143
138
|
view: () => runView(args),
|
|
144
139
|
search: () => runSearch(args),
|
|
145
140
|
list: () => runList(args),
|
|
146
141
|
approve: () => runApprove(args),
|
|
147
142
|
reject: () => runReject(args),
|
|
143
|
+
claim: () => runClaim(args),
|
|
148
144
|
priority: () => runPriority(args),
|
|
149
145
|
create: () => runCreate(args),
|
|
150
146
|
update: () => runUpdate(args),
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// agents.js
|
|
2
|
+
// Lists all registered agents and humans in the tracker.
|
|
3
|
+
// Usage: baton agents [--json]
|
|
4
|
+
//
|
|
5
|
+
// Options:
|
|
6
|
+
// --json Output as JSON (for AI agents)
|
|
7
|
+
// -h, --help Show this help
|
|
8
|
+
//
|
|
9
|
+
// Examples:
|
|
10
|
+
// baton agents
|
|
11
|
+
|
|
12
|
+
import { listAgents } from '../services/agentsService.js';
|
|
13
|
+
import { hasFlag, renderOutput } from '../util.js';
|
|
14
|
+
|
|
15
|
+
const VALID_FLAGS = ['--json'];
|
|
16
|
+
|
|
17
|
+
const COL = { id: 4, name: 14, type: 5 };
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {Agent} agent
|
|
21
|
+
* @returns {{ id: number, name: string, type: string }}
|
|
22
|
+
*/
|
|
23
|
+
function serializeAgent(agent) {
|
|
24
|
+
return {
|
|
25
|
+
id: agent.id,
|
|
26
|
+
name: agent.name,
|
|
27
|
+
type: agent.type,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {Agent[]} agents
|
|
33
|
+
*/
|
|
34
|
+
function printAgentsTable(agents) {
|
|
35
|
+
console.log(
|
|
36
|
+
`${'ID'.padEnd(COL.id)} | ${'Name'.padEnd(COL.name)} | Type`,
|
|
37
|
+
);
|
|
38
|
+
for (const agent of agents) {
|
|
39
|
+
console.log(
|
|
40
|
+
`${String(agent.id).padEnd(COL.id)} | ${agent.name.padEnd(COL.name)} | ${agent.type}`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Lists all registered agents and humans.
|
|
47
|
+
* @param {string[]} args - The command line arguments
|
|
48
|
+
* @returns {Promise<number>} The exit code: 0 is success, 1 is error
|
|
49
|
+
*/
|
|
50
|
+
export async function run(args = []) {
|
|
51
|
+
const isJson = hasFlag(args, '--json');
|
|
52
|
+
|
|
53
|
+
for (const arg of args) {
|
|
54
|
+
if (arg.startsWith('--') && !VALID_FLAGS.includes(arg)) {
|
|
55
|
+
throw new Error(`Unknown flag provided: ${arg}.\nFlags: --json`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const agents = listAgents();
|
|
61
|
+
const records = agents.map(serializeAgent);
|
|
62
|
+
const envelope = { status: 'success', count: records.length, agents: records };
|
|
63
|
+
|
|
64
|
+
renderOutput(isJson, envelope, (data) => {
|
|
65
|
+
if (data.count === 0) {
|
|
66
|
+
console.log('No registered agents or humans found.');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
printAgentsTable(agents);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return 0;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error('Error: Failed to list agents.');
|
|
75
|
+
console.error(error.message);
|
|
76
|
+
return 1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// claim.js
|
|
2
|
+
// Allows an agent to officially claim an issue to work on.
|
|
3
|
+
// Usage: baton claim <id> [--json]
|
|
4
|
+
|
|
5
|
+
import { isTrackerReady, claimIssue } from '../services/issuesService.js';
|
|
6
|
+
import { getCurrentActor } from '../services/authService.js';
|
|
7
|
+
import {
|
|
8
|
+
hasFlag,
|
|
9
|
+
renderOutput,
|
|
10
|
+
renderError,
|
|
11
|
+
getFirstPositionalArg,
|
|
12
|
+
serializeIssue,
|
|
13
|
+
reportTrackerNotReady,
|
|
14
|
+
} from '../util.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Claims an issue for the authenticated agent.
|
|
18
|
+
* @param {string[]} args - The command line arguments
|
|
19
|
+
* @returns {Promise<number>} The exit code: 0 is success, 1 is error
|
|
20
|
+
*/
|
|
21
|
+
export async function run(args) {
|
|
22
|
+
const isJson = hasFlag(args, '--json');
|
|
23
|
+
|
|
24
|
+
if (hasFlag(args, '-h') || hasFlag(args, '--help')) {
|
|
25
|
+
console.log(`Usage: baton claim <id> [--json]\n\nOptions:\n --json Output as JSON (for AI agents)\n -h, --help Show this help\n\nExamples:\n baton claim 14`);
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const idArg = getFirstPositionalArg(args, { ignoreFlags: ['--json'] });
|
|
30
|
+
|
|
31
|
+
if (!idArg) {
|
|
32
|
+
renderError(isJson, 'Missing issue ID.\nUsage: baton claim <id>', 'MISSING_ID');
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const id = Number(idArg);
|
|
37
|
+
if (!Number.isInteger(id)) {
|
|
38
|
+
renderError(isJson, `Invalid ID "${idArg}". ID must be an integer.`, 'INVALID_ID');
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!isTrackerReady()) {
|
|
43
|
+
reportTrackerNotReady();
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const actor = getCurrentActor();
|
|
48
|
+
if (!actor) {
|
|
49
|
+
renderError(isJson, 'No authenticated actor found. Please sign in with a registered agent.', 'UNAUTHORIZED');
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (actor.type !== 'agent') {
|
|
54
|
+
renderError(isJson, 'Only an agent may claim an issue.', 'UNAUTHORIZED');
|
|
55
|
+
return 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const updatedIssue = claimIssue(id);
|
|
60
|
+
const envelope = { status: 'success', issue: serializeIssue(updatedIssue) };
|
|
61
|
+
|
|
62
|
+
renderOutput(isJson, envelope, () => {
|
|
63
|
+
console.log(`Success: Issue #${updatedIssue.id} claimed by agent "${actor.name}" and moved to ${updatedIssue.status}.`);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return 0;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
renderError(isJson, error.message);
|
|
69
|
+
return 1;
|
|
70
|
+
}
|
|
71
|
+
}
|
package/source/commands/init.js
CHANGED
|
@@ -176,7 +176,7 @@ export async function run(args = []) {
|
|
|
176
176
|
console.log(` #${issue.id} [${issue.priority}] ${issue.title}`);
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
|
-
console.log('Run `baton status` to review progress
|
|
179
|
+
console.log('Run `baton status` to review progress.');
|
|
180
180
|
});
|
|
181
181
|
|
|
182
182
|
return 0;
|
|
@@ -39,4 +39,14 @@ export function getAgentByName(name) {
|
|
|
39
39
|
} else {
|
|
40
40
|
return null;
|
|
41
41
|
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Lists all registered agents and humans, ordered by id.
|
|
46
|
+
* @returns {Agent[]}
|
|
47
|
+
*/
|
|
48
|
+
export function listAgents() {
|
|
49
|
+
const db = getDB();
|
|
50
|
+
const rows = db.select().from(agentsTable).orderBy(agentsTable.id).all();
|
|
51
|
+
return rows.map((row) => new Agent(row));
|
|
42
52
|
}
|
|
@@ -2,6 +2,8 @@ import os from 'os';
|
|
|
2
2
|
import { getAgentByName } from './agentsService.js';
|
|
3
3
|
import { setActiveActor } from './issuesService.js';
|
|
4
4
|
|
|
5
|
+
let currentActor = null;
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* Authenticates the current execution context.
|
|
7
9
|
* Looks for BATON_AGENT in the environment, falling back to the OS username.
|
|
@@ -30,6 +32,7 @@ export function authenticateContext(command) {
|
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
// Set the global state for the remainder of this command's lifecycle
|
|
35
|
+
currentActor = agent;
|
|
33
36
|
setActiveActor(agent.id);
|
|
34
37
|
} catch (error) {
|
|
35
38
|
// Gracefully handle the scenario where the database hasn't been initialized yet
|
|
@@ -41,4 +44,12 @@ export function authenticateContext(command) {
|
|
|
41
44
|
// Rethrow if it's a completely different error
|
|
42
45
|
throw error;
|
|
43
46
|
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Returns the authenticated actor object for this session.
|
|
51
|
+
* @returns {object|null}
|
|
52
|
+
*/
|
|
53
|
+
export function getCurrentActor() {
|
|
54
|
+
return currentActor;
|
|
44
55
|
}
|
package/source/commands/loop.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
// loop.js
|
|
2
|
-
// AI was consulted for some portions of this file.
|
|
3
|
-
// loop command for the tracker which allows the AI agent to work autonomously for multiple steps.
|
|
4
|
-
// usage: baton loop [options]
|
|
5
|
-
// options:
|
|
6
|
-
// --steps <n>: number of steps to run (default: 1)
|
|
7
|
-
// -n <n>: alias for --steps <n>
|
|
8
|
-
// <n>: same as --steps <n>
|
|
9
|
-
// examples usage of steps flag:
|
|
10
|
-
// baton loop --steps 5
|
|
11
|
-
// baton loop -n 5
|
|
12
|
-
|
|
13
|
-
import { isTrackerReady } from '../services/issuesService.js';
|
|
14
|
-
import { getNumericFlag, hasFlag, renderOutput, reportTrackerNotReady } from '../util.js';
|
|
15
|
-
import { run as runNext } from './next.js';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Parses the flags in the command line argument
|
|
19
|
-
* @param {string[]} args - The command line arguments
|
|
20
|
-
* @returns {{ steps: number }}
|
|
21
|
-
*/
|
|
22
|
-
function parseLoopFlags(args) {
|
|
23
|
-
const stepsFlag = getNumericFlag(args, '--steps') ?? getNumericFlag(args, '-n');
|
|
24
|
-
return { steps: stepsFlag ?? 1 };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Runs the loop command
|
|
29
|
-
* @param {string[]} args
|
|
30
|
-
* @returns {Promise<number>}
|
|
31
|
-
*/
|
|
32
|
-
export async function run(args = []) {
|
|
33
|
-
const isJson = hasFlag(args, '--json');
|
|
34
|
-
const loopArgs = args.filter((arg) => arg !== '--json');
|
|
35
|
-
const { steps } = parseLoopFlags(loopArgs);
|
|
36
|
-
|
|
37
|
-
if (!isTrackerReady()) {
|
|
38
|
-
reportTrackerNotReady();
|
|
39
|
-
return 1;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (!Number.isInteger(steps) || steps < 1) {
|
|
43
|
-
console.error('Error: --steps must be a positive integer.');
|
|
44
|
-
console.error('Usage: baton loop --steps <n>');
|
|
45
|
-
return 1;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
console.log(`Running baton for ${steps} step(s)...\n`);
|
|
49
|
-
|
|
50
|
-
let completed = 0;
|
|
51
|
-
for (let step = 1; step <= steps; step += 1) {
|
|
52
|
-
console.log(`--- Step ${step}/${steps} ---`);
|
|
53
|
-
const code = await runNext();
|
|
54
|
-
if (code !== 0) {
|
|
55
|
-
return code;
|
|
56
|
-
}
|
|
57
|
-
completed += 1;
|
|
58
|
-
if (step < steps) {
|
|
59
|
-
console.log('');
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
renderOutput(isJson, { status: 'success', steps, completed }, () => {
|
|
64
|
-
console.log(`\nCompleted ${completed} autonomous step(s).`);
|
|
65
|
-
});
|
|
66
|
-
return 0;
|
|
67
|
-
}
|
package/source/commands/next.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
// next.js
|
|
2
|
-
// AI was consulted for some portions of this file.
|
|
3
|
-
// next command for the issue tracker which allows the user to manually move the AI from issue to issue.
|
|
4
|
-
// usage: baton next
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
isTrackerReady,
|
|
8
|
-
selectNextIssue,
|
|
9
|
-
claimIssue,
|
|
10
|
-
} from '../services/issuesService.js';
|
|
11
|
-
import { formatTimestamp, hasFlag, renderOutput, reportTrackerNotReady, serializeIssue } from '../util.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Moves the AI agent to work on the next issue.
|
|
15
|
-
* Checks if the tracker is ready and if there are any open issues.
|
|
16
|
-
* Prompts user to initialize the tracker if it is not ready.
|
|
17
|
-
* Stats are updated on the issue through claimIssue function from init.js.
|
|
18
|
-
* @returns {Promise<number>} The exit code: 0 is success, 1 is error.
|
|
19
|
-
*/
|
|
20
|
-
export async function run(args = []) {
|
|
21
|
-
const isJson = hasFlag(args, '--json');
|
|
22
|
-
|
|
23
|
-
if (!isTrackerReady()) {
|
|
24
|
-
reportTrackerNotReady();
|
|
25
|
-
return 1;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const issue = selectNextIssue();
|
|
29
|
-
if (!issue) {
|
|
30
|
-
renderOutput(isJson, { status: 'success', issue: null }, () => {
|
|
31
|
-
console.log('No open issues available. All work is complete or the backlog is empty.');
|
|
32
|
-
});
|
|
33
|
-
return 0;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const updated = claimIssue(issue.id);
|
|
37
|
-
const envelope = { status: 'success', issue: serializeIssue(updated) };
|
|
38
|
-
|
|
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
|
-
});
|
|
51
|
-
|
|
52
|
-
return 0;
|
|
53
|
-
}
|