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 CHANGED
@@ -1,7 +1,8 @@
1
1
  # Fantastic Four
2
2
 
3
- ## Developer Guide
4
- [Contributing guide](CONTRIBUTING.md)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baton-issue-tracker",
3
- "version": "1.10.0",
3
+ "version": "1.11.1",
4
4
  "description": "A CLI issue tracker for AI agents",
5
5
  "type": "module",
6
6
  "bin": {
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
- next Work on the highest-priority open issue
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
- loop --steps <n> Number of autonomous steps (alias: -n)
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 next
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' || wantsHelp(args) || 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
- next: () => runNext(args),
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
+ }
@@ -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 or `baton next` to start work.');
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
  }
@@ -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
- }
@@ -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
- }