@kaban-board/cli 0.2.6 → 0.2.9
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/dist/index.js +657 -83
- package/dist/kaban-hook +2 -1
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// @bun
|
|
2
3
|
|
|
3
4
|
// src/index.ts
|
|
4
|
-
import { createRequire as createRequire2 } from "
|
|
5
|
-
import { Command as
|
|
5
|
+
import { createRequire as createRequire2 } from "module";
|
|
6
|
+
import { Command as Command15 } from "commander";
|
|
6
7
|
|
|
7
8
|
// src/commands/add.ts
|
|
8
9
|
import { KabanError } from "@kaban-board/core";
|
|
9
10
|
import { Command } from "commander";
|
|
10
11
|
|
|
11
12
|
// src/lib/context.ts
|
|
12
|
-
import { existsSync, readFileSync } from "
|
|
13
|
-
import { join } from "
|
|
13
|
+
import { existsSync, readFileSync } from "fs";
|
|
14
|
+
import { join } from "path";
|
|
14
15
|
import { BoardService, createDb, TaskService } from "@kaban-board/core";
|
|
15
16
|
async function getContext() {
|
|
16
17
|
const kabanDir = join(process.cwd(), ".kaban");
|
|
@@ -57,20 +58,30 @@ function outputError(code, message) {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
// src/commands/add.ts
|
|
60
|
-
var addCommand = new Command("add").description("Add a new task").argument("<title>", "Task title").option("-c, --column <column>", "Column to add task to").option("-a, --agent <agent>", "Agent creating the task").option("-D, --description <text>", "Task description").option("-d, --depends-on <ids>", "Comma-separated task IDs this depends on").option("-j, --json", "Output as JSON").action(async (title, options) => {
|
|
61
|
+
var addCommand = new Command("add").description("Add a new task").argument("<title>", "Task title").option("-c, --column <column>", "Column to add task to").option("-a, --agent <agent>", "Agent creating the task").option("-D, --description <text>", "Task description").option("-d, --depends-on <ids>", "Comma-separated task IDs this depends on").option("-f, --force", "Force creation even if similar tasks exist").option("-j, --json", "Output as JSON").action(async (title, options) => {
|
|
61
62
|
const json = options.json;
|
|
62
63
|
try {
|
|
63
64
|
const { taskService, config } = await getContext();
|
|
64
65
|
const agent = options.agent ?? getAgent();
|
|
65
66
|
const columnId = options.column ?? config.defaults.column;
|
|
66
67
|
const dependsOn = options.dependsOn ? options.dependsOn.split(",").map((s) => s.trim()) : [];
|
|
67
|
-
const
|
|
68
|
+
const result = await taskService.addTaskChecked({
|
|
68
69
|
title,
|
|
69
70
|
description: options.description,
|
|
70
71
|
columnId,
|
|
71
72
|
agent,
|
|
72
73
|
dependsOn
|
|
73
|
-
});
|
|
74
|
+
}, { force: options.force });
|
|
75
|
+
if (result.rejected) {
|
|
76
|
+
if (json) {
|
|
77
|
+
outputError(409, result.rejectionReason ?? "Similar task exists");
|
|
78
|
+
} else {
|
|
79
|
+
console.error(`Error: ${result.rejectionReason}`);
|
|
80
|
+
console.error("Use --force to create anyway.");
|
|
81
|
+
}
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
const task = result.task;
|
|
74
85
|
if (json) {
|
|
75
86
|
outputSuccess(task);
|
|
76
87
|
return;
|
|
@@ -78,6 +89,9 @@ var addCommand = new Command("add").description("Add a new task").argument("<tit
|
|
|
78
89
|
console.log(`Created task [${task.id.slice(0, 8)}] "${task.title}"`);
|
|
79
90
|
console.log(` Column: ${task.columnId}`);
|
|
80
91
|
console.log(` Agent: ${task.createdBy}`);
|
|
92
|
+
if (result.similarTasks.length > 0) {
|
|
93
|
+
console.log(` Note: Found ${result.similarTasks.length} similar task(s)`);
|
|
94
|
+
}
|
|
81
95
|
} catch (error2) {
|
|
82
96
|
if (error2 instanceof KabanError) {
|
|
83
97
|
if (json)
|
|
@@ -89,10 +103,237 @@ var addCommand = new Command("add").description("Add a new task").argument("<tit
|
|
|
89
103
|
}
|
|
90
104
|
});
|
|
91
105
|
|
|
92
|
-
// src/commands/
|
|
106
|
+
// src/commands/archive.ts
|
|
93
107
|
import { KabanError as KabanError2 } from "@kaban-board/core";
|
|
94
108
|
import { Command as Command2 } from "commander";
|
|
95
|
-
var
|
|
109
|
+
var archiveCommand = new Command2("archive").description("Archive completed tasks").option("-c, --column <column>", "Archive tasks from specific column").option("-o, --older-than <days>", "Archive tasks older than N days", parseInt).option("--all-columns", "Archive from all columns (requires --older-than)").option("--dry-run", "Show what would be archived without archiving").option("-j, --json", "Output as JSON").action(async (options) => {
|
|
110
|
+
const json = options.json;
|
|
111
|
+
try {
|
|
112
|
+
const { taskService, boardService } = await getContext();
|
|
113
|
+
if (!options.column && !options.allColumns) {
|
|
114
|
+
const terminal = await boardService.getTerminalColumn();
|
|
115
|
+
if (!terminal) {
|
|
116
|
+
if (json)
|
|
117
|
+
outputError(1, "No terminal column configured and no criteria specified");
|
|
118
|
+
console.error("Error: No terminal column configured and no criteria specified");
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
options.column = terminal.id;
|
|
122
|
+
}
|
|
123
|
+
if (options.allColumns && !options.olderThan) {
|
|
124
|
+
if (json)
|
|
125
|
+
outputError(1, "--all-columns requires --older-than");
|
|
126
|
+
console.error("Error: --all-columns requires --older-than");
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
const criteria = {};
|
|
130
|
+
if (options.column && !options.allColumns) {
|
|
131
|
+
criteria.status = options.column;
|
|
132
|
+
}
|
|
133
|
+
if (options.olderThan) {
|
|
134
|
+
const date = new Date;
|
|
135
|
+
date.setDate(date.getDate() - options.olderThan);
|
|
136
|
+
criteria.olderThan = date;
|
|
137
|
+
}
|
|
138
|
+
if (options.dryRun) {
|
|
139
|
+
const tasks = await taskService.listTasks({
|
|
140
|
+
columnId: criteria.status,
|
|
141
|
+
includeArchived: false
|
|
142
|
+
});
|
|
143
|
+
const olderThan = criteria.olderThan;
|
|
144
|
+
const filtered = olderThan ? tasks.filter((t) => t.createdAt < olderThan) : tasks;
|
|
145
|
+
if (json) {
|
|
146
|
+
outputSuccess({ dryRun: true, wouldArchive: filtered.length, tasks: filtered });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
console.log(`Would archive ${filtered.length} task(s):`);
|
|
150
|
+
for (const task of filtered) {
|
|
151
|
+
console.log(` [${task.id.slice(0, 8)}] ${task.title}`);
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const result = await taskService.archiveTasks(criteria);
|
|
156
|
+
if (json) {
|
|
157
|
+
outputSuccess(result);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
console.log(`Archived ${result.archivedCount} task(s)`);
|
|
161
|
+
} catch (error2) {
|
|
162
|
+
if (error2 instanceof KabanError2) {
|
|
163
|
+
if (json)
|
|
164
|
+
outputError(error2.code, error2.message);
|
|
165
|
+
console.error(`Error: ${error2.message}`);
|
|
166
|
+
process.exit(error2.code);
|
|
167
|
+
}
|
|
168
|
+
throw error2;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
var restoreCommand = new Command2("restore").description("Restore a task from archive").argument("<id>", "Task ID to restore (can be partial)").option("-t, --to <column>", "Restore to specific column").option("-j, --json", "Output as JSON").action(async (id, options) => {
|
|
172
|
+
const json = options.json;
|
|
173
|
+
try {
|
|
174
|
+
const { taskService } = await getContext();
|
|
175
|
+
const archivedTasks = await taskService.listTasks({ includeArchived: true });
|
|
176
|
+
const task = archivedTasks.find((t) => t.id.startsWith(id) && t.archived);
|
|
177
|
+
if (!task) {
|
|
178
|
+
if (json)
|
|
179
|
+
outputError(2, `Archived task '${id}' not found`);
|
|
180
|
+
console.error(`Error: Archived task '${id}' not found`);
|
|
181
|
+
process.exit(2);
|
|
182
|
+
}
|
|
183
|
+
const restored = await taskService.restoreTask(task.id, options.to);
|
|
184
|
+
if (json) {
|
|
185
|
+
outputSuccess(restored);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
console.log(`Restored [${restored.id.slice(0, 8)}] "${restored.title}" to ${restored.columnId}`);
|
|
189
|
+
} catch (error2) {
|
|
190
|
+
if (error2 instanceof KabanError2) {
|
|
191
|
+
if (json)
|
|
192
|
+
outputError(error2.code, error2.message);
|
|
193
|
+
console.error(`Error: ${error2.message}`);
|
|
194
|
+
process.exit(error2.code);
|
|
195
|
+
}
|
|
196
|
+
throw error2;
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
var purgeCommand = new Command2("purge").description("Permanently delete archived tasks").option("--confirm", "Confirm deletion (required)").option("--dry-run", "Show what would be deleted without deleting").option("-j, --json", "Output as JSON").action(async (options) => {
|
|
200
|
+
const json = options.json;
|
|
201
|
+
try {
|
|
202
|
+
const { taskService } = await getContext();
|
|
203
|
+
if (options.dryRun) {
|
|
204
|
+
const archived = await taskService.listTasks({ includeArchived: true });
|
|
205
|
+
const archivedOnly = archived.filter((t) => t.archived);
|
|
206
|
+
if (json) {
|
|
207
|
+
outputSuccess({ dryRun: true, wouldDelete: archivedOnly.length, tasks: archivedOnly });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
console.log(`Would permanently delete ${archivedOnly.length} archived task(s):`);
|
|
211
|
+
for (const task of archivedOnly) {
|
|
212
|
+
console.log(` [${task.id.slice(0, 8)}] ${task.title}`);
|
|
213
|
+
}
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (!options.confirm) {
|
|
217
|
+
if (json)
|
|
218
|
+
outputError(1, "Use --confirm to permanently delete archived tasks");
|
|
219
|
+
console.error("Error: Use --confirm to permanently delete archived tasks");
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
const result = await taskService.purgeArchive();
|
|
223
|
+
if (json) {
|
|
224
|
+
outputSuccess(result);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
console.log(`Permanently deleted ${result.deletedCount} archived task(s)`);
|
|
228
|
+
} catch (error2) {
|
|
229
|
+
if (error2 instanceof KabanError2) {
|
|
230
|
+
if (json)
|
|
231
|
+
outputError(error2.code, error2.message);
|
|
232
|
+
console.error(`Error: ${error2.message}`);
|
|
233
|
+
process.exit(error2.code);
|
|
234
|
+
}
|
|
235
|
+
throw error2;
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
var resetCommand = new Command2("reset").description("Delete ALL tasks (destructive)").option("--confirm", "Confirm deletion (required)").option("--dry-run", "Show what would be deleted without deleting").option("-j, --json", "Output as JSON").action(async (options) => {
|
|
239
|
+
const json = options.json;
|
|
240
|
+
try {
|
|
241
|
+
const { taskService } = await getContext();
|
|
242
|
+
if (options.dryRun) {
|
|
243
|
+
const allTasks = await taskService.listTasks({ includeArchived: true });
|
|
244
|
+
if (json) {
|
|
245
|
+
outputSuccess({ dryRun: true, wouldDelete: allTasks.length, tasks: allTasks });
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
console.log(`Would permanently delete ALL ${allTasks.length} task(s):`);
|
|
249
|
+
for (const task of allTasks) {
|
|
250
|
+
const archived = task.archived ? " [archived]" : "";
|
|
251
|
+
console.log(` [${task.id.slice(0, 8)}] ${task.title}${archived}`);
|
|
252
|
+
}
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (!options.confirm) {
|
|
256
|
+
if (json)
|
|
257
|
+
outputError(1, "Use --confirm to delete ALL tasks");
|
|
258
|
+
console.error("Error: Use --confirm to delete ALL tasks");
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
const result = await taskService.resetBoard();
|
|
262
|
+
if (json) {
|
|
263
|
+
outputSuccess(result);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
console.log(`Deleted ALL ${result.deletedCount} task(s)`);
|
|
267
|
+
} catch (error2) {
|
|
268
|
+
if (error2 instanceof KabanError2) {
|
|
269
|
+
if (json)
|
|
270
|
+
outputError(error2.code, error2.message);
|
|
271
|
+
console.error(`Error: ${error2.message}`);
|
|
272
|
+
process.exit(error2.code);
|
|
273
|
+
}
|
|
274
|
+
throw error2;
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// src/commands/assign.ts
|
|
279
|
+
import { KabanError as KabanError3 } from "@kaban-board/core";
|
|
280
|
+
import { Command as Command3 } from "commander";
|
|
281
|
+
var assignCommand = new Command3("assign").description("Assign a task to an agent").argument("<id>", "Task ID (can be partial)").argument("[agent]", "Agent to assign (omit with --clear to unassign)").option("-c, --clear", "Unassign the task").option("-j, --json", "Output as JSON").action(async (id, agent, options) => {
|
|
282
|
+
const json = options.json;
|
|
283
|
+
try {
|
|
284
|
+
const { taskService } = await getContext();
|
|
285
|
+
if (options.clear && agent) {
|
|
286
|
+
if (json)
|
|
287
|
+
outputError(4, "Cannot use --clear with agent argument");
|
|
288
|
+
console.error("Error: Cannot use --clear with agent argument");
|
|
289
|
+
process.exit(4);
|
|
290
|
+
}
|
|
291
|
+
const tasks = await taskService.listTasks();
|
|
292
|
+
const task = tasks.find((t) => t.id.startsWith(id));
|
|
293
|
+
if (!task) {
|
|
294
|
+
if (json)
|
|
295
|
+
outputError(2, `Task '${id}' not found`);
|
|
296
|
+
console.error(`Error: Task '${id}' not found`);
|
|
297
|
+
process.exit(2);
|
|
298
|
+
}
|
|
299
|
+
const previousAssignee = task.assignedTo;
|
|
300
|
+
if (options.clear) {
|
|
301
|
+
const updated2 = await taskService.updateTask(task.id, { assignedTo: null });
|
|
302
|
+
if (json) {
|
|
303
|
+
outputSuccess(updated2);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
console.log(`Unassigned [${updated2.id.slice(0, 8)}] "${updated2.title}"`);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (!agent) {
|
|
310
|
+
if (json)
|
|
311
|
+
outputError(4, "Specify an agent or use --clear");
|
|
312
|
+
console.error("Error: Specify an agent or use --clear to unassign");
|
|
313
|
+
process.exit(4);
|
|
314
|
+
}
|
|
315
|
+
const updated = await taskService.updateTask(task.id, { assignedTo: agent });
|
|
316
|
+
if (json) {
|
|
317
|
+
outputSuccess(updated);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const prevMsg = previousAssignee ? ` (was: ${previousAssignee})` : "";
|
|
321
|
+
console.log(`Assigned [${updated.id.slice(0, 8)}] "${updated.title}" to ${updated.assignedTo}${prevMsg}`);
|
|
322
|
+
} catch (error2) {
|
|
323
|
+
if (error2 instanceof KabanError3) {
|
|
324
|
+
if (json)
|
|
325
|
+
outputError(error2.code, error2.message);
|
|
326
|
+
console.error(`Error: ${error2.message}`);
|
|
327
|
+
process.exit(error2.code);
|
|
328
|
+
}
|
|
329
|
+
throw error2;
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// src/commands/done.ts
|
|
334
|
+
import { KabanError as KabanError4 } from "@kaban-board/core";
|
|
335
|
+
import { Command as Command4 } from "commander";
|
|
336
|
+
var doneCommand = new Command4("done").description("Mark a task as done").argument("<id>", "Task ID (can be partial)").option("-j, --json", "Output as JSON").action(async (id, options) => {
|
|
96
337
|
const json = options.json;
|
|
97
338
|
try {
|
|
98
339
|
const { taskService, boardService } = await getContext();
|
|
@@ -118,7 +359,7 @@ var doneCommand = new Command2("done").description("Mark a task as done").argume
|
|
|
118
359
|
}
|
|
119
360
|
console.log(`Completed [${moved.id.slice(0, 8)}] "${moved.title}"`);
|
|
120
361
|
} catch (error2) {
|
|
121
|
-
if (error2 instanceof
|
|
362
|
+
if (error2 instanceof KabanError4) {
|
|
122
363
|
if (json)
|
|
123
364
|
outputError(error2.code, error2.message);
|
|
124
365
|
console.error(`Error: ${error2.message}`);
|
|
@@ -129,20 +370,20 @@ var doneCommand = new Command2("done").description("Mark a task as done").argume
|
|
|
129
370
|
});
|
|
130
371
|
|
|
131
372
|
// src/commands/hook.ts
|
|
132
|
-
import { spawn } from "
|
|
133
|
-
import { existsSync as existsSync3, realpathSync } from "
|
|
134
|
-
import { chmod, copyFile as copyFile2, mkdir, readFile as readFile2, stat, unlink } from "
|
|
135
|
-
import { homedir as homedir2 } from "
|
|
136
|
-
import { dirname, join as join3 } from "
|
|
373
|
+
import { spawn } from "child_process";
|
|
374
|
+
import { existsSync as existsSync3, realpathSync } from "fs";
|
|
375
|
+
import { chmod, copyFile as copyFile2, mkdir, readFile as readFile2, stat, unlink } from "fs/promises";
|
|
376
|
+
import { homedir as homedir2 } from "os";
|
|
377
|
+
import { dirname, join as join3 } from "path";
|
|
137
378
|
import * as p from "@clack/prompts";
|
|
138
379
|
import chalk from "chalk";
|
|
139
|
-
import { Command as
|
|
380
|
+
import { Command as Command5 } from "commander";
|
|
140
381
|
|
|
141
382
|
// src/hook/settings-manager.ts
|
|
142
|
-
import { existsSync as existsSync2 } from "
|
|
143
|
-
import { copyFile, readFile, writeFile } from "
|
|
144
|
-
import { homedir } from "
|
|
145
|
-
import { join as join2 } from "
|
|
383
|
+
import { existsSync as existsSync2 } from "fs";
|
|
384
|
+
import { copyFile, readFile, writeFile } from "fs/promises";
|
|
385
|
+
import { homedir } from "os";
|
|
386
|
+
import { join as join2 } from "path";
|
|
146
387
|
|
|
147
388
|
// src/hook/schemas.ts
|
|
148
389
|
import { z } from "zod";
|
|
@@ -381,7 +622,7 @@ async function checkDependencies() {
|
|
|
381
622
|
function formatCheckResults(results) {
|
|
382
623
|
const maxNameLen = Math.max(...results.map((r) => r.name.length));
|
|
383
624
|
return results.map((r) => {
|
|
384
|
-
const icon = r.ok ? chalk.green("
|
|
625
|
+
const icon = r.ok ? chalk.green("\u2713") : chalk.red("\u2717");
|
|
385
626
|
const name = r.name.padEnd(maxNameLen);
|
|
386
627
|
const msg = r.ok ? chalk.dim(r.message) : chalk.red(r.message);
|
|
387
628
|
return ` ${icon} ${name} ${msg}`;
|
|
@@ -485,7 +726,7 @@ function formatSize(bytes) {
|
|
|
485
726
|
return `${bytes} B`;
|
|
486
727
|
return `${Math.round(bytes / 1024)} KB`;
|
|
487
728
|
}
|
|
488
|
-
var installCommand = new
|
|
729
|
+
var installCommand = new Command5("install").description("Install TodoWrite sync hook for Claude Code").option("-y, --yes", "Skip confirmation").action(async (options) => {
|
|
489
730
|
p.intro(chalk.bgCyan.black(" kaban hook install "));
|
|
490
731
|
const s = p.spinner();
|
|
491
732
|
s.start("Checking dependencies...");
|
|
@@ -527,7 +768,7 @@ var installCommand = new Command3("install").description("Install TodoWrite sync
|
|
|
527
768
|
summaryLines.push("");
|
|
528
769
|
}
|
|
529
770
|
if (!installResult.hookConfigured) {
|
|
530
|
-
summaryLines.push(` ${chalk.yellow("
|
|
771
|
+
summaryLines.push(` ${chalk.yellow("\u26A0")} Hook was already configured`);
|
|
531
772
|
summaryLines.push("");
|
|
532
773
|
}
|
|
533
774
|
p.note(summaryLines.join(`
|
|
@@ -546,7 +787,7 @@ var installCommand = new Command3("install").description("Install TodoWrite sync
|
|
|
546
787
|
process.exit(1);
|
|
547
788
|
}
|
|
548
789
|
});
|
|
549
|
-
var uninstallCommand = new
|
|
790
|
+
var uninstallCommand = new Command5("uninstall").description("Remove TodoWrite sync hook").option("-y, --yes", "Skip confirmation").option("--clean", "Also remove sync logs").action(async (options) => {
|
|
550
791
|
p.intro(chalk.bgRed.white(" kaban hook uninstall "));
|
|
551
792
|
const binaryExists = existsSync3(join3(HOOKS_DIR, HOOK_BINARY_NAME));
|
|
552
793
|
const logExists = existsSync3(join3(HOOKS_DIR, LOG_FILE));
|
|
@@ -564,7 +805,7 @@ var uninstallCommand = new Command3("uninstall").description("Remove TodoWrite s
|
|
|
564
805
|
process.exit(0);
|
|
565
806
|
}
|
|
566
807
|
const formatStatusLine = (exists, label) => {
|
|
567
|
-
const icon = exists ? chalk.green("
|
|
808
|
+
const icon = exists ? chalk.green("\u2713") : chalk.dim("\u25CB");
|
|
568
809
|
const text = exists ? "" : chalk.dim(" (not found)");
|
|
569
810
|
return ` ${icon} ${label}${text}`;
|
|
570
811
|
};
|
|
@@ -572,7 +813,7 @@ var uninstallCommand = new Command3("uninstall").description("Remove TodoWrite s
|
|
|
572
813
|
if (!logExists)
|
|
573
814
|
return formatStatusLine(false, "Sync log");
|
|
574
815
|
const suffix = options.clean ? chalk.yellow(" (will be removed)") : chalk.dim(" (will be preserved)");
|
|
575
|
-
return ` ${chalk.yellow("
|
|
816
|
+
return ` ${chalk.yellow("\u25CB")} Sync log${suffix}`;
|
|
576
817
|
};
|
|
577
818
|
const statusLines = [
|
|
578
819
|
formatStatusLine(binaryExists, "Hook binary"),
|
|
@@ -598,13 +839,13 @@ var uninstallCommand = new Command3("uninstall").description("Remove TodoWrite s
|
|
|
598
839
|
s.stop("Removal complete");
|
|
599
840
|
const summaryLines = [];
|
|
600
841
|
if (result.hookRemoved)
|
|
601
|
-
summaryLines.push(` ${chalk.green("
|
|
842
|
+
summaryLines.push(` ${chalk.green("\u2713")} Removed hook from settings.json`);
|
|
602
843
|
if (result.binaryRemoved)
|
|
603
|
-
summaryLines.push(` ${chalk.green("
|
|
844
|
+
summaryLines.push(` ${chalk.green("\u2713")} Removed ${HOOK_BINARY_NAME}`);
|
|
604
845
|
if (result.logRemoved)
|
|
605
|
-
summaryLines.push(` ${chalk.green("
|
|
846
|
+
summaryLines.push(` ${chalk.green("\u2713")} Removed ${LOG_FILE}`);
|
|
606
847
|
if (logExists && !options.clean)
|
|
607
|
-
summaryLines.push(` ${chalk.yellow("
|
|
848
|
+
summaryLines.push(` ${chalk.yellow("\u25CB")} Preserved ${LOG_FILE} (use --clean to remove)`);
|
|
608
849
|
if (summaryLines.length > 0) {
|
|
609
850
|
p.note(summaryLines.join(`
|
|
610
851
|
`), "Removed");
|
|
@@ -616,7 +857,7 @@ var uninstallCommand = new Command3("uninstall").description("Remove TodoWrite s
|
|
|
616
857
|
process.exit(1);
|
|
617
858
|
}
|
|
618
859
|
});
|
|
619
|
-
var statusCommand = new
|
|
860
|
+
var statusCommand = new Command5("status").description("Check hook installation status").action(async () => {
|
|
620
861
|
p.intro(chalk.bgBlue.white(" kaban hook status "));
|
|
621
862
|
const s = p.spinner();
|
|
622
863
|
s.start("Checking status...");
|
|
@@ -652,7 +893,7 @@ var statusCommand = new Command3("status").description("Check hook installation
|
|
|
652
893
|
];
|
|
653
894
|
const maxNameLen = Math.max(...results.map((r) => r.name.length));
|
|
654
895
|
const statusLines = results.map((r) => {
|
|
655
|
-
const icon = r.ok ? chalk.green("
|
|
896
|
+
const icon = r.ok ? chalk.green("\u2713") : chalk.red("\u2717");
|
|
656
897
|
const name = r.name.padEnd(maxNameLen);
|
|
657
898
|
const detail = r.ok ? chalk.dim(r.detail) : chalk.red(r.detail);
|
|
658
899
|
return ` ${icon} ${name} ${detail}`;
|
|
@@ -682,18 +923,18 @@ var statusCommand = new Command3("status").description("Check hook installation
|
|
|
682
923
|
process.exit(1);
|
|
683
924
|
}
|
|
684
925
|
});
|
|
685
|
-
var hookCommand = new
|
|
926
|
+
var hookCommand = new Command5("hook").description("Manage TodoWrite sync hook for Claude Code").addCommand(installCommand).addCommand(uninstallCommand).addCommand(statusCommand);
|
|
686
927
|
|
|
687
928
|
// src/commands/init.ts
|
|
688
|
-
import { existsSync as existsSync4, mkdirSync, writeFileSync } from "
|
|
929
|
+
import { existsSync as existsSync4, mkdirSync, writeFileSync } from "fs";
|
|
689
930
|
import {
|
|
690
931
|
BoardService as BoardService2,
|
|
691
932
|
createDb as createDb2,
|
|
692
933
|
DEFAULT_CONFIG as DEFAULT_CONFIG2,
|
|
693
934
|
initializeSchema
|
|
694
935
|
} from "@kaban-board/core";
|
|
695
|
-
import { Command as
|
|
696
|
-
var initCommand = new
|
|
936
|
+
import { Command as Command6 } from "commander";
|
|
937
|
+
var initCommand = new Command6("init").description("Initialize a new Kaban board in the current directory").option("-n, --name <name>", "Board name", "Kaban Board").action(async (options) => {
|
|
697
938
|
const { kabanDir, dbPath, configPath } = getKabanPaths();
|
|
698
939
|
if (existsSync4(dbPath)) {
|
|
699
940
|
console.error("Error: Board already exists in this directory");
|
|
@@ -715,8 +956,8 @@ var initCommand = new Command4("init").description("Initialize a new Kaban board
|
|
|
715
956
|
});
|
|
716
957
|
|
|
717
958
|
// src/commands/list.ts
|
|
718
|
-
import { KabanError as
|
|
719
|
-
import { Command as
|
|
959
|
+
import { KabanError as KabanError5 } from "@kaban-board/core";
|
|
960
|
+
import { Command as Command7 } from "commander";
|
|
720
961
|
function sortTasks(tasks, sortBy, reverse) {
|
|
721
962
|
const sorted = [...tasks].sort((a, b) => {
|
|
722
963
|
switch (sortBy) {
|
|
@@ -732,7 +973,7 @@ function sortTasks(tasks, sortBy, reverse) {
|
|
|
732
973
|
});
|
|
733
974
|
return reverse ? sorted.reverse() : sorted;
|
|
734
975
|
}
|
|
735
|
-
var listCommand = new
|
|
976
|
+
var listCommand = new Command7("list").description("List tasks").option("-c, --column <column>", "Filter by column").option("-a, --agent <agent>", "Filter by creator agent").option("-u, --assignee <assignee>", "Filter by assigned agent").option("-b, --blocked", "Show only blocked tasks").option("-s, --sort <field>", "Sort by: name, date, updated").option("-r, --reverse", "Reverse sort order").option("-j, --json", "Output as JSON").action(async (options) => {
|
|
736
977
|
const json = options.json;
|
|
737
978
|
try {
|
|
738
979
|
const { taskService, boardService } = await getContext();
|
|
@@ -764,7 +1005,7 @@ var listCommand = new Command5("list").description("List tasks").option("-c, --c
|
|
|
764
1005
|
const column = columnMap.get(task.columnId);
|
|
765
1006
|
const blocked = task.blockedReason ? " [blocked]" : "";
|
|
766
1007
|
const agent = task.createdBy !== "user" ? ` @${task.createdBy}` : "";
|
|
767
|
-
const assignee = task.assignedTo ? `
|
|
1008
|
+
const assignee = task.assignedTo ? ` \u2192 ${task.assignedTo}` : "";
|
|
768
1009
|
console.log(`[${task.id.slice(0, 8)}] ${task.title}${agent}${assignee}${blocked}`);
|
|
769
1010
|
console.log(` ${column?.name ?? task.columnId}`);
|
|
770
1011
|
if (task.description) {
|
|
@@ -773,7 +1014,7 @@ var listCommand = new Command5("list").description("List tasks").option("-c, --c
|
|
|
773
1014
|
}
|
|
774
1015
|
}
|
|
775
1016
|
} catch (error2) {
|
|
776
|
-
if (error2 instanceof
|
|
1017
|
+
if (error2 instanceof KabanError5) {
|
|
777
1018
|
if (json)
|
|
778
1019
|
outputError(error2.code, error2.message);
|
|
779
1020
|
console.error(`Error: ${error2.message}`);
|
|
@@ -784,8 +1025,8 @@ var listCommand = new Command5("list").description("List tasks").option("-c, --c
|
|
|
784
1025
|
});
|
|
785
1026
|
|
|
786
1027
|
// src/commands/mcp.ts
|
|
787
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "
|
|
788
|
-
import { join as join4 } from "
|
|
1028
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1029
|
+
import { join as join4 } from "path";
|
|
789
1030
|
import {
|
|
790
1031
|
BoardService as BoardService3,
|
|
791
1032
|
createDb as createDb3,
|
|
@@ -801,7 +1042,7 @@ import {
|
|
|
801
1042
|
ListToolsRequestSchema,
|
|
802
1043
|
ReadResourceRequestSchema
|
|
803
1044
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
804
|
-
import { Command as
|
|
1045
|
+
import { Command as Command8 } from "commander";
|
|
805
1046
|
var mcpHelpers = {
|
|
806
1047
|
getParam(args, primary, alias) {
|
|
807
1048
|
if (!args)
|
|
@@ -899,7 +1140,8 @@ async function startMcpServer(workingDirectory) {
|
|
|
899
1140
|
type: "object",
|
|
900
1141
|
properties: {
|
|
901
1142
|
columnId: { type: "string", description: "Filter by column ID" },
|
|
902
|
-
agent: { type: "string", description: "Filter by agent name" },
|
|
1143
|
+
agent: { type: "string", description: "Filter by creator agent name" },
|
|
1144
|
+
assignee: { type: "string", description: "Filter by assigned agent name" },
|
|
903
1145
|
blocked: { type: "boolean", description: "Show only blocked tasks" }
|
|
904
1146
|
}
|
|
905
1147
|
}
|
|
@@ -971,6 +1213,127 @@ async function startMcpServer(workingDirectory) {
|
|
|
971
1213
|
name: "kaban_status",
|
|
972
1214
|
description: "Get board status summary",
|
|
973
1215
|
inputSchema: { type: "object", properties: {} }
|
|
1216
|
+
},
|
|
1217
|
+
{
|
|
1218
|
+
name: "kaban_archive_tasks",
|
|
1219
|
+
description: "Archive completed or stale tasks. By default archives from terminal columns only.",
|
|
1220
|
+
inputSchema: {
|
|
1221
|
+
type: "object",
|
|
1222
|
+
properties: {
|
|
1223
|
+
columnId: { type: "string", description: "Archive from this column only" },
|
|
1224
|
+
olderThanDays: { type: "number", description: "Archive tasks older than N days" },
|
|
1225
|
+
allColumns: { type: "boolean", description: "Archive from ALL columns" },
|
|
1226
|
+
dryRun: { type: "boolean", description: "Preview without archiving" }
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
},
|
|
1230
|
+
{
|
|
1231
|
+
name: "kaban_search_archive",
|
|
1232
|
+
description: "Search archived tasks using full-text search",
|
|
1233
|
+
inputSchema: {
|
|
1234
|
+
type: "object",
|
|
1235
|
+
properties: {
|
|
1236
|
+
query: { type: "string", description: "Search query" },
|
|
1237
|
+
limit: { type: "number", description: "Max results (default: 50)" }
|
|
1238
|
+
},
|
|
1239
|
+
required: ["query"]
|
|
1240
|
+
}
|
|
1241
|
+
},
|
|
1242
|
+
{
|
|
1243
|
+
name: "kaban_restore_task",
|
|
1244
|
+
description: "Restore a task from archive",
|
|
1245
|
+
inputSchema: {
|
|
1246
|
+
type: "object",
|
|
1247
|
+
properties: {
|
|
1248
|
+
id: { type: "string", description: "Task ID to restore" },
|
|
1249
|
+
taskId: { type: "string", description: "Task ID - alias for id" },
|
|
1250
|
+
columnId: { type: "string", description: "Target column (default: todo)" }
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
},
|
|
1254
|
+
{
|
|
1255
|
+
name: "kaban_purge_archive",
|
|
1256
|
+
description: "Permanently delete all archived tasks",
|
|
1257
|
+
inputSchema: {
|
|
1258
|
+
type: "object",
|
|
1259
|
+
properties: {
|
|
1260
|
+
confirm: { type: "boolean", description: "Must be true to confirm" },
|
|
1261
|
+
dryRun: { type: "boolean", description: "Preview without deleting" }
|
|
1262
|
+
},
|
|
1263
|
+
required: ["confirm"]
|
|
1264
|
+
}
|
|
1265
|
+
},
|
|
1266
|
+
{
|
|
1267
|
+
name: "kaban_reset_board",
|
|
1268
|
+
description: "Delete ALL tasks (active and archived)",
|
|
1269
|
+
inputSchema: {
|
|
1270
|
+
type: "object",
|
|
1271
|
+
properties: {
|
|
1272
|
+
confirm: { type: "boolean", description: "Must be true to confirm" },
|
|
1273
|
+
dryRun: { type: "boolean", description: "Preview without deleting" }
|
|
1274
|
+
},
|
|
1275
|
+
required: ["confirm"]
|
|
1276
|
+
}
|
|
1277
|
+
},
|
|
1278
|
+
{
|
|
1279
|
+
name: "kaban_archive_stats",
|
|
1280
|
+
description: "Get archive statistics",
|
|
1281
|
+
inputSchema: { type: "object", properties: {} }
|
|
1282
|
+
},
|
|
1283
|
+
{
|
|
1284
|
+
name: "kaban_add_dependency",
|
|
1285
|
+
description: "Add a dependency to a task (task cannot start until dependency is done)",
|
|
1286
|
+
inputSchema: {
|
|
1287
|
+
type: "object",
|
|
1288
|
+
properties: {
|
|
1289
|
+
taskId: { type: "string", description: "Task that depends on another" },
|
|
1290
|
+
id: { type: "string", description: "Task ID - alias for taskId" },
|
|
1291
|
+
dependsOnId: { type: "string", description: "Task that must be completed first" }
|
|
1292
|
+
},
|
|
1293
|
+
required: ["dependsOnId"]
|
|
1294
|
+
}
|
|
1295
|
+
},
|
|
1296
|
+
{
|
|
1297
|
+
name: "kaban_remove_dependency",
|
|
1298
|
+
description: "Remove a dependency from a task",
|
|
1299
|
+
inputSchema: {
|
|
1300
|
+
type: "object",
|
|
1301
|
+
properties: {
|
|
1302
|
+
taskId: { type: "string", description: "Task ID" },
|
|
1303
|
+
id: { type: "string", description: "Task ID - alias" },
|
|
1304
|
+
dependsOnId: { type: "string", description: "Dependency to remove" }
|
|
1305
|
+
},
|
|
1306
|
+
required: ["dependsOnId"]
|
|
1307
|
+
}
|
|
1308
|
+
},
|
|
1309
|
+
{
|
|
1310
|
+
name: "kaban_check_dependencies",
|
|
1311
|
+
description: "Check if task dependencies are resolved",
|
|
1312
|
+
inputSchema: {
|
|
1313
|
+
type: "object",
|
|
1314
|
+
properties: {
|
|
1315
|
+
taskId: { type: "string", description: "Task ID" },
|
|
1316
|
+
id: { type: "string", description: "Task ID - alias" }
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
},
|
|
1320
|
+
{
|
|
1321
|
+
name: "kaban_add_task_checked",
|
|
1322
|
+
description: "Add task with duplicate detection. Rejects if very similar task exists.",
|
|
1323
|
+
inputSchema: {
|
|
1324
|
+
type: "object",
|
|
1325
|
+
properties: {
|
|
1326
|
+
title: { type: "string", description: "Task title" },
|
|
1327
|
+
description: { type: "string", description: "Task description" },
|
|
1328
|
+
columnId: { type: "string", description: "Target column (default: todo)" },
|
|
1329
|
+
createdBy: { type: "string", description: "Creator: user, claude, etc." },
|
|
1330
|
+
agent: { type: "string", description: "Creator (deprecated, use createdBy)" },
|
|
1331
|
+
assignedTo: { type: "string", description: "Assignee" },
|
|
1332
|
+
dependsOn: { type: "array", items: { type: "string" }, description: "Dependency IDs" },
|
|
1333
|
+
force: { type: "boolean", description: "Create even if similar exists" }
|
|
1334
|
+
},
|
|
1335
|
+
required: ["title"]
|
|
1336
|
+
}
|
|
974
1337
|
}
|
|
975
1338
|
]
|
|
976
1339
|
}));
|
|
@@ -1085,6 +1448,153 @@ async function startMcpServer(workingDirectory) {
|
|
|
1085
1448
|
totalTasks: tasks.length
|
|
1086
1449
|
});
|
|
1087
1450
|
}
|
|
1451
|
+
case "kaban_archive_tasks": {
|
|
1452
|
+
const { columnId, olderThanDays, allColumns, dryRun } = args ?? {};
|
|
1453
|
+
const columns = await boardService.getColumns();
|
|
1454
|
+
const terminalColumns = columns.filter((c) => c.isTerminal);
|
|
1455
|
+
let targetColumnIds = [];
|
|
1456
|
+
if (columnId) {
|
|
1457
|
+
targetColumnIds = [columnId];
|
|
1458
|
+
} else if (allColumns) {
|
|
1459
|
+
targetColumnIds = columns.map((c) => c.id);
|
|
1460
|
+
} else {
|
|
1461
|
+
targetColumnIds = terminalColumns.map((c) => c.id);
|
|
1462
|
+
}
|
|
1463
|
+
if (targetColumnIds.length === 0) {
|
|
1464
|
+
return jsonResponse({
|
|
1465
|
+
archivedCount: 0,
|
|
1466
|
+
taskIds: [],
|
|
1467
|
+
message: "No columns to archive from"
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
const allTasks = await taskService.listTasks();
|
|
1471
|
+
let tasksToArchive = allTasks.filter((t) => !t.archived && targetColumnIds.includes(t.columnId));
|
|
1472
|
+
if (olderThanDays !== undefined) {
|
|
1473
|
+
const cutoff = new Date;
|
|
1474
|
+
cutoff.setDate(cutoff.getDate() - olderThanDays);
|
|
1475
|
+
tasksToArchive = tasksToArchive.filter((t) => t.createdAt < cutoff);
|
|
1476
|
+
}
|
|
1477
|
+
if (tasksToArchive.length === 0) {
|
|
1478
|
+
return jsonResponse({
|
|
1479
|
+
archivedCount: 0,
|
|
1480
|
+
taskIds: [],
|
|
1481
|
+
message: "No matching tasks to archive"
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
if (dryRun) {
|
|
1485
|
+
return jsonResponse({
|
|
1486
|
+
dryRun: true,
|
|
1487
|
+
wouldArchive: tasksToArchive.length,
|
|
1488
|
+
taskIds: tasksToArchive.map((t) => t.id),
|
|
1489
|
+
tasks: tasksToArchive.map((t) => ({
|
|
1490
|
+
id: t.id,
|
|
1491
|
+
title: t.title,
|
|
1492
|
+
columnId: t.columnId
|
|
1493
|
+
}))
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
const result = await taskService.archiveTasks({
|
|
1497
|
+
taskIds: tasksToArchive.map((t) => t.id)
|
|
1498
|
+
});
|
|
1499
|
+
return jsonResponse(result);
|
|
1500
|
+
}
|
|
1501
|
+
case "kaban_search_archive": {
|
|
1502
|
+
const { query, limit } = args ?? {};
|
|
1503
|
+
if (!query)
|
|
1504
|
+
return errorResponse("Query required");
|
|
1505
|
+
const result = await taskService.searchArchive(query, { limit });
|
|
1506
|
+
return jsonResponse(result);
|
|
1507
|
+
}
|
|
1508
|
+
case "kaban_restore_task": {
|
|
1509
|
+
const id = getParam(taskArgs, "id", "taskId");
|
|
1510
|
+
if (!id)
|
|
1511
|
+
return errorResponse("Task ID required");
|
|
1512
|
+
const { columnId } = args ?? {};
|
|
1513
|
+
const task = await taskService.restoreTask(id, columnId);
|
|
1514
|
+
return jsonResponse(task);
|
|
1515
|
+
}
|
|
1516
|
+
case "kaban_purge_archive": {
|
|
1517
|
+
const { confirm: confirm2, dryRun } = args ?? {};
|
|
1518
|
+
if (!confirm2) {
|
|
1519
|
+
return errorResponse("Must set confirm: true to purge archive");
|
|
1520
|
+
}
|
|
1521
|
+
if (dryRun) {
|
|
1522
|
+
const result2 = await taskService.searchArchive("", { limit: 1000 });
|
|
1523
|
+
return jsonResponse({
|
|
1524
|
+
dryRun: true,
|
|
1525
|
+
wouldDelete: result2.total
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
const result = await taskService.purgeArchive();
|
|
1529
|
+
return jsonResponse(result);
|
|
1530
|
+
}
|
|
1531
|
+
case "kaban_reset_board": {
|
|
1532
|
+
const { confirm: confirm2, dryRun } = args ?? {};
|
|
1533
|
+
if (!confirm2) {
|
|
1534
|
+
return errorResponse("Must set confirm: true to reset board");
|
|
1535
|
+
}
|
|
1536
|
+
if (dryRun) {
|
|
1537
|
+
const allTasks = await taskService.listTasks();
|
|
1538
|
+
const archivedResult = await taskService.searchArchive("", { limit: 1000 });
|
|
1539
|
+
return jsonResponse({
|
|
1540
|
+
dryRun: true,
|
|
1541
|
+
wouldDelete: allTasks.length + archivedResult.total,
|
|
1542
|
+
activeTasks: allTasks.length,
|
|
1543
|
+
archivedTasks: archivedResult.total
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
const result = await taskService.resetBoard();
|
|
1547
|
+
return jsonResponse(result);
|
|
1548
|
+
}
|
|
1549
|
+
case "kaban_archive_stats": {
|
|
1550
|
+
const archivedResult = await taskService.searchArchive("", { limit: 1 });
|
|
1551
|
+
const allTasks = await taskService.listTasks();
|
|
1552
|
+
const columns = await boardService.getColumns();
|
|
1553
|
+
const terminalColumns = columns.filter((c) => c.isTerminal);
|
|
1554
|
+
const completedCount = allTasks.filter((t) => terminalColumns.some((c) => c.id === t.columnId)).length;
|
|
1555
|
+
return jsonResponse({
|
|
1556
|
+
archivedCount: archivedResult.total,
|
|
1557
|
+
activeTasks: allTasks.length,
|
|
1558
|
+
completedNotArchived: completedCount
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
case "kaban_add_dependency": {
|
|
1562
|
+
const id = getParam(taskArgs, "taskId", "id");
|
|
1563
|
+
if (!id)
|
|
1564
|
+
return errorResponse("Task ID required");
|
|
1565
|
+
const { dependsOnId } = args ?? {};
|
|
1566
|
+
if (!dependsOnId)
|
|
1567
|
+
return errorResponse("dependsOnId required");
|
|
1568
|
+
const task = await taskService.addDependency(id, dependsOnId);
|
|
1569
|
+
return jsonResponse(task);
|
|
1570
|
+
}
|
|
1571
|
+
case "kaban_remove_dependency": {
|
|
1572
|
+
const id = getParam(taskArgs, "taskId", "id");
|
|
1573
|
+
if (!id)
|
|
1574
|
+
return errorResponse("Task ID required");
|
|
1575
|
+
const { dependsOnId } = args ?? {};
|
|
1576
|
+
if (!dependsOnId)
|
|
1577
|
+
return errorResponse("dependsOnId required");
|
|
1578
|
+
const task = await taskService.removeDependency(id, dependsOnId);
|
|
1579
|
+
return jsonResponse(task);
|
|
1580
|
+
}
|
|
1581
|
+
case "kaban_check_dependencies": {
|
|
1582
|
+
const id = getParam(taskArgs, "taskId", "id");
|
|
1583
|
+
if (!id)
|
|
1584
|
+
return errorResponse("Task ID required");
|
|
1585
|
+
const result = await taskService.validateDependencies(id);
|
|
1586
|
+
return jsonResponse(result);
|
|
1587
|
+
}
|
|
1588
|
+
case "kaban_add_task_checked": {
|
|
1589
|
+
const addCheckedArgs = args;
|
|
1590
|
+
const title = addCheckedArgs?.title;
|
|
1591
|
+
if (typeof title !== "string" || !title.trim()) {
|
|
1592
|
+
return errorResponse("Title required");
|
|
1593
|
+
}
|
|
1594
|
+
const { force, ...taskInput } = addCheckedArgs;
|
|
1595
|
+
const result = await taskService.addTaskChecked(taskInput, { force });
|
|
1596
|
+
return jsonResponse(result);
|
|
1597
|
+
}
|
|
1088
1598
|
default:
|
|
1089
1599
|
return errorResponse(`Unknown tool: ${name}`);
|
|
1090
1600
|
}
|
|
@@ -1198,15 +1708,15 @@ async function startMcpServer(workingDirectory) {
|
|
|
1198
1708
|
await server.connect(transport);
|
|
1199
1709
|
console.error("Kaban MCP server running on stdio");
|
|
1200
1710
|
}
|
|
1201
|
-
var mcpCommand = new
|
|
1711
|
+
var mcpCommand = new Command8("mcp").description("Start MCP server for AI agent integration").option("-p, --path <path>", "Working directory for Kaban board").action(async (options) => {
|
|
1202
1712
|
const workingDirectory = options.path ?? process.env.KABAN_PATH ?? process.cwd();
|
|
1203
1713
|
await startMcpServer(workingDirectory);
|
|
1204
1714
|
});
|
|
1205
1715
|
|
|
1206
1716
|
// src/commands/move.ts
|
|
1207
|
-
import { KabanError as
|
|
1208
|
-
import { Command as
|
|
1209
|
-
var moveCommand = new
|
|
1717
|
+
import { KabanError as KabanError6 } from "@kaban-board/core";
|
|
1718
|
+
import { Command as Command9 } from "commander";
|
|
1719
|
+
var moveCommand = new Command9("move").description("Move a task to a different column").argument("<id>", "Task ID (can be partial)").argument("[column]", "Target column").option("-n, --next", "Move to next column").option("-f, --force", "Force move even if WIP limit exceeded").option("-A, --assign [agent]", "Assign task to agent (defaults to current agent)").option("-j, --json", "Output as JSON").action(async (id, column, options) => {
|
|
1210
1720
|
const json = options.json;
|
|
1211
1721
|
try {
|
|
1212
1722
|
const { taskService, boardService } = await getContext();
|
|
@@ -1240,14 +1750,24 @@ var moveCommand = new Command7("move").description("Move a task to a different c
|
|
|
1240
1750
|
const moved = await taskService.moveTask(task.id, targetColumn, {
|
|
1241
1751
|
force: options.force
|
|
1242
1752
|
});
|
|
1753
|
+
let assignAgent = null;
|
|
1754
|
+
if (options.assign !== undefined) {
|
|
1755
|
+
assignAgent = options.assign === true ? getAgent() : options.assign;
|
|
1756
|
+
await taskService.updateTask(moved.id, { assignedTo: assignAgent });
|
|
1757
|
+
}
|
|
1758
|
+
const finalTask = assignAgent ? await taskService.getTask(moved.id) : moved;
|
|
1243
1759
|
if (json) {
|
|
1244
|
-
outputSuccess(
|
|
1760
|
+
outputSuccess(finalTask);
|
|
1245
1761
|
return;
|
|
1246
1762
|
}
|
|
1247
1763
|
const col = await boardService.getColumn(moved.columnId);
|
|
1248
|
-
|
|
1764
|
+
let msg = `Moved [${moved.id.slice(0, 8)}] to ${col?.name}`;
|
|
1765
|
+
if (assignAgent) {
|
|
1766
|
+
msg += ` (assigned to ${assignAgent})`;
|
|
1767
|
+
}
|
|
1768
|
+
console.log(msg);
|
|
1249
1769
|
} catch (error2) {
|
|
1250
|
-
if (error2 instanceof
|
|
1770
|
+
if (error2 instanceof KabanError6) {
|
|
1251
1771
|
if (json)
|
|
1252
1772
|
outputError(error2.code, error2.message);
|
|
1253
1773
|
console.error(`Error: ${error2.message}`);
|
|
@@ -1259,9 +1779,9 @@ var moveCommand = new Command7("move").description("Move a task to a different c
|
|
|
1259
1779
|
|
|
1260
1780
|
// src/commands/schema.ts
|
|
1261
1781
|
import { jsonSchemas } from "@kaban-board/core";
|
|
1262
|
-
import { Command as
|
|
1782
|
+
import { Command as Command10 } from "commander";
|
|
1263
1783
|
var availableSchemas = Object.keys(jsonSchemas);
|
|
1264
|
-
var schemaCommand = new
|
|
1784
|
+
var schemaCommand = new Command10("schema").description("Output JSON schemas for AI agents").argument("[name]", "Schema name (omit to list available)").action((name) => {
|
|
1265
1785
|
if (!name) {
|
|
1266
1786
|
console.log("Available schemas:");
|
|
1267
1787
|
for (const schemaName of availableSchemas) {
|
|
@@ -1280,10 +1800,52 @@ Usage: kaban schema <name>`);
|
|
|
1280
1800
|
console.log(JSON.stringify(schema, null, 2));
|
|
1281
1801
|
});
|
|
1282
1802
|
|
|
1803
|
+
// src/commands/search.ts
|
|
1804
|
+
import { KabanError as KabanError7 } from "@kaban-board/core";
|
|
1805
|
+
import { Command as Command11 } from "commander";
|
|
1806
|
+
var searchCommand = new Command11("search").description("Search tasks").argument("<query>", "Search query").option("--archive", "Search in archive only").option("-l, --limit <n>", "Max results", parseInt, 50).option("-j, --json", "Output as JSON").action(async (query, options) => {
|
|
1807
|
+
const json = options.json;
|
|
1808
|
+
try {
|
|
1809
|
+
const { taskService } = await getContext();
|
|
1810
|
+
let tasks;
|
|
1811
|
+
if (options.archive) {
|
|
1812
|
+
const result = await taskService.searchArchive(query, {
|
|
1813
|
+
limit: options.limit
|
|
1814
|
+
});
|
|
1815
|
+
tasks = result.tasks;
|
|
1816
|
+
} else {
|
|
1817
|
+
const allTasks = await taskService.listTasks();
|
|
1818
|
+
const queryLower = query.toLowerCase();
|
|
1819
|
+
tasks = allTasks.filter((t) => t.title.toLowerCase().includes(queryLower) || t.description?.toLowerCase().includes(queryLower));
|
|
1820
|
+
}
|
|
1821
|
+
if (json) {
|
|
1822
|
+
outputSuccess(tasks);
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
if (tasks.length === 0) {
|
|
1826
|
+
console.log("No tasks found");
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
for (const task of tasks) {
|
|
1830
|
+
const archived = task.archived ? " [archived]" : "";
|
|
1831
|
+
console.log(`[${task.id.slice(0, 8)}] ${task.title}${archived}`);
|
|
1832
|
+
console.log(` Column: ${task.columnId} | By: ${task.createdBy}`);
|
|
1833
|
+
}
|
|
1834
|
+
} catch (error2) {
|
|
1835
|
+
if (error2 instanceof KabanError7) {
|
|
1836
|
+
if (json)
|
|
1837
|
+
outputError(error2.code, error2.message);
|
|
1838
|
+
console.error(`Error: ${error2.message}`);
|
|
1839
|
+
process.exit(error2.code);
|
|
1840
|
+
}
|
|
1841
|
+
throw error2;
|
|
1842
|
+
}
|
|
1843
|
+
});
|
|
1844
|
+
|
|
1283
1845
|
// src/commands/status.ts
|
|
1284
|
-
import { KabanError as
|
|
1285
|
-
import { Command as
|
|
1286
|
-
var statusCommand2 = new
|
|
1846
|
+
import { KabanError as KabanError8 } from "@kaban-board/core";
|
|
1847
|
+
import { Command as Command12 } from "commander";
|
|
1848
|
+
var statusCommand2 = new Command12("status").description("Show board status summary").option("-j, --json", "Output as JSON").action(async (options) => {
|
|
1287
1849
|
const json = options.json;
|
|
1288
1850
|
try {
|
|
1289
1851
|
const { taskService, boardService } = await getContext();
|
|
@@ -1326,7 +1888,7 @@ var statusCommand2 = new Command9("status").description("Show board status summa
|
|
|
1326
1888
|
}
|
|
1327
1889
|
console.log();
|
|
1328
1890
|
} catch (error2) {
|
|
1329
|
-
if (error2 instanceof
|
|
1891
|
+
if (error2 instanceof KabanError8) {
|
|
1330
1892
|
if (json)
|
|
1331
1893
|
outputError(error2.code, error2.message);
|
|
1332
1894
|
console.error(`Error: ${error2.message}`);
|
|
@@ -1337,7 +1899,7 @@ var statusCommand2 = new Command9("status").description("Show board status summa
|
|
|
1337
1899
|
});
|
|
1338
1900
|
|
|
1339
1901
|
// src/commands/sync.ts
|
|
1340
|
-
import { Command as
|
|
1902
|
+
import { Command as Command13 } from "commander";
|
|
1341
1903
|
|
|
1342
1904
|
// src/hook/constants.ts
|
|
1343
1905
|
var STATUS_PRIORITY = {
|
|
@@ -1431,23 +1993,26 @@ class ConflictResolver {
|
|
|
1431
1993
|
}
|
|
1432
1994
|
|
|
1433
1995
|
// src/hook/kaban-client.ts
|
|
1434
|
-
import { spawn as spawn2 } from "
|
|
1996
|
+
import { spawn as spawn2 } from "child_process";
|
|
1435
1997
|
|
|
1436
1998
|
class KabanClient {
|
|
1437
1999
|
cwd;
|
|
2000
|
+
kabanCmd;
|
|
1438
2001
|
constructor(cwd) {
|
|
1439
2002
|
this.cwd = cwd;
|
|
2003
|
+
const cliOverride = process.env.KABAN_CLI;
|
|
2004
|
+
this.kabanCmd = cliOverride ? cliOverride.split(" ") : ["kaban"];
|
|
1440
2005
|
}
|
|
1441
2006
|
async boardExists() {
|
|
1442
2007
|
try {
|
|
1443
|
-
const result = await this.exec([
|
|
2008
|
+
const result = await this.exec([...this.kabanCmd, "status", "--json"]);
|
|
1444
2009
|
return result.exitCode === 0;
|
|
1445
2010
|
} catch {
|
|
1446
2011
|
return false;
|
|
1447
2012
|
}
|
|
1448
2013
|
}
|
|
1449
2014
|
async listTasks(columnId) {
|
|
1450
|
-
const args = [
|
|
2015
|
+
const args = [...this.kabanCmd, "list", "--json"];
|
|
1451
2016
|
if (columnId) {
|
|
1452
2017
|
args.push("--column", columnId);
|
|
1453
2018
|
}
|
|
@@ -1456,7 +2021,8 @@ class KabanClient {
|
|
|
1456
2021
|
return [];
|
|
1457
2022
|
}
|
|
1458
2023
|
try {
|
|
1459
|
-
const
|
|
2024
|
+
const parsed = JSON.parse(result.stdout);
|
|
2025
|
+
const tasks = parsed.data ?? parsed;
|
|
1460
2026
|
return tasks.map((t) => ({
|
|
1461
2027
|
id: t.id,
|
|
1462
2028
|
title: t.title,
|
|
@@ -1469,12 +2035,13 @@ class KabanClient {
|
|
|
1469
2035
|
}
|
|
1470
2036
|
}
|
|
1471
2037
|
async getTaskById(id) {
|
|
1472
|
-
const result = await this.exec([
|
|
2038
|
+
const result = await this.exec([...this.kabanCmd, "get", id, "--json"]);
|
|
1473
2039
|
if (result.exitCode !== 0) {
|
|
1474
2040
|
return null;
|
|
1475
2041
|
}
|
|
1476
2042
|
try {
|
|
1477
|
-
const
|
|
2043
|
+
const parsed = JSON.parse(result.stdout);
|
|
2044
|
+
const task = parsed.data ?? parsed;
|
|
1478
2045
|
return {
|
|
1479
2046
|
id: task.id,
|
|
1480
2047
|
title: task.title,
|
|
@@ -1491,7 +2058,7 @@ class KabanClient {
|
|
|
1491
2058
|
return tasks.find((t) => t.title === title) ?? null;
|
|
1492
2059
|
}
|
|
1493
2060
|
async addTask(title, columnId = "todo") {
|
|
1494
|
-
const result = await this.exec([
|
|
2061
|
+
const result = await this.exec([...this.kabanCmd, "add", title, "--column", columnId, "--json"]);
|
|
1495
2062
|
if (result.exitCode !== 0) {
|
|
1496
2063
|
return null;
|
|
1497
2064
|
}
|
|
@@ -1504,20 +2071,21 @@ class KabanClient {
|
|
|
1504
2071
|
}
|
|
1505
2072
|
}
|
|
1506
2073
|
async moveTask(id, columnId) {
|
|
1507
|
-
const result = await this.exec([
|
|
2074
|
+
const result = await this.exec([...this.kabanCmd, "move", id, columnId]);
|
|
1508
2075
|
return result.exitCode === 0;
|
|
1509
2076
|
}
|
|
1510
2077
|
async completeTask(id) {
|
|
1511
|
-
const result = await this.exec([
|
|
2078
|
+
const result = await this.exec([...this.kabanCmd, "done", id]);
|
|
1512
2079
|
return result.exitCode === 0;
|
|
1513
2080
|
}
|
|
1514
2081
|
async getStatus() {
|
|
1515
|
-
const result = await this.exec([
|
|
2082
|
+
const result = await this.exec([...this.kabanCmd, "status", "--json"]);
|
|
1516
2083
|
if (result.exitCode !== 0) {
|
|
1517
2084
|
return null;
|
|
1518
2085
|
}
|
|
1519
2086
|
try {
|
|
1520
|
-
|
|
2087
|
+
const parsed = JSON.parse(result.stdout);
|
|
2088
|
+
return parsed.data ?? parsed;
|
|
1521
2089
|
} catch {
|
|
1522
2090
|
return null;
|
|
1523
2091
|
}
|
|
@@ -1634,10 +2202,10 @@ class SyncEngine {
|
|
|
1634
2202
|
}
|
|
1635
2203
|
|
|
1636
2204
|
// src/hook/sync-logger.ts
|
|
1637
|
-
import { existsSync as existsSync6 } from "
|
|
1638
|
-
import { appendFile, mkdir as mkdir2 } from "
|
|
1639
|
-
import { homedir as homedir3 } from "
|
|
1640
|
-
import { dirname as dirname2 } from "
|
|
2205
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2206
|
+
import { appendFile, mkdir as mkdir2 } from "fs/promises";
|
|
2207
|
+
import { homedir as homedir3 } from "os";
|
|
2208
|
+
import { dirname as dirname2 } from "path";
|
|
1641
2209
|
|
|
1642
2210
|
class SyncLogger {
|
|
1643
2211
|
logPath;
|
|
@@ -1673,7 +2241,7 @@ class SyncLogger {
|
|
|
1673
2241
|
}
|
|
1674
2242
|
|
|
1675
2243
|
// src/commands/sync.ts
|
|
1676
|
-
var syncCommand = new
|
|
2244
|
+
var syncCommand = new Command13("sync").description("Sync TodoWrite input to Kaban board (reads from stdin)").option("--no-log", "Disable sync logging").action(async (options) => {
|
|
1677
2245
|
const startTime = performance.now();
|
|
1678
2246
|
let input;
|
|
1679
2247
|
try {
|
|
@@ -1720,12 +2288,12 @@ var syncCommand = new Command10("sync").description("Sync TodoWrite input to Kab
|
|
|
1720
2288
|
});
|
|
1721
2289
|
|
|
1722
2290
|
// src/commands/tui.ts
|
|
1723
|
-
import { spawn as spawn3, spawnSync } from "
|
|
1724
|
-
import { existsSync as existsSync7 } from "
|
|
1725
|
-
import {
|
|
1726
|
-
import {
|
|
1727
|
-
import {
|
|
1728
|
-
import { Command as
|
|
2291
|
+
import { spawn as spawn3, spawnSync } from "child_process";
|
|
2292
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2293
|
+
import { createRequire } from "module";
|
|
2294
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
2295
|
+
import { fileURLToPath } from "url";
|
|
2296
|
+
import { Command as Command14 } from "commander";
|
|
1729
2297
|
var __dirname2 = dirname3(fileURLToPath(import.meta.url));
|
|
1730
2298
|
var require2 = createRequire(import.meta.url);
|
|
1731
2299
|
function hasBun() {
|
|
@@ -1751,7 +2319,7 @@ function runBinary(path, args) {
|
|
|
1751
2319
|
const child = spawn3(path, args, { stdio: "inherit", cwd: process.cwd() });
|
|
1752
2320
|
child.on("exit", (code) => process.exit(code ?? 0));
|
|
1753
2321
|
}
|
|
1754
|
-
var tuiCommand = new
|
|
2322
|
+
var tuiCommand = new Command14("tui").description("Start interactive Terminal UI").action(async () => {
|
|
1755
2323
|
const cwd = process.cwd();
|
|
1756
2324
|
const args = process.argv.slice(3);
|
|
1757
2325
|
const useBun = hasBun();
|
|
@@ -1784,17 +2352,23 @@ var tuiCommand = new Command11("tui").description("Start interactive Terminal UI
|
|
|
1784
2352
|
// src/index.ts
|
|
1785
2353
|
var require3 = createRequire2(import.meta.url);
|
|
1786
2354
|
var pkg = require3("../package.json");
|
|
1787
|
-
var program = new
|
|
2355
|
+
var program = new Command15;
|
|
1788
2356
|
program.name("kaban").description("Terminal Kanban for AI Code Agents").version(pkg.version);
|
|
1789
2357
|
program.addCommand(initCommand);
|
|
1790
2358
|
program.addCommand(addCommand);
|
|
1791
2359
|
program.addCommand(listCommand);
|
|
1792
2360
|
program.addCommand(moveCommand);
|
|
2361
|
+
program.addCommand(assignCommand);
|
|
1793
2362
|
program.addCommand(doneCommand);
|
|
1794
2363
|
program.addCommand(statusCommand2);
|
|
1795
2364
|
program.addCommand(schemaCommand);
|
|
2365
|
+
program.addCommand(searchCommand);
|
|
1796
2366
|
program.addCommand(mcpCommand);
|
|
1797
2367
|
program.addCommand(tuiCommand);
|
|
1798
2368
|
program.addCommand(hookCommand);
|
|
1799
2369
|
program.addCommand(syncCommand);
|
|
2370
|
+
program.addCommand(archiveCommand);
|
|
2371
|
+
program.addCommand(restoreCommand);
|
|
2372
|
+
program.addCommand(purgeCommand);
|
|
2373
|
+
program.addCommand(resetCommand);
|
|
1800
2374
|
program.parse();
|
package/dist/kaban-hook
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaban-board/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "Terminal Kanban for AI Code Agents - CLI and MCP server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"dist"
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
|
-
"build": "bun build ./src/index.ts --outdir ./dist --target
|
|
13
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target bun --packages external && bun build ./src/hook-entry.ts --outfile ./dist/kaban-hook --target bun && chmod +x ./dist/index.js ./dist/kaban-hook",
|
|
14
14
|
"dev": "bun run ./src/index.ts",
|
|
15
15
|
"test": "bun test",
|
|
16
16
|
"typecheck": "tsc --noEmit",
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@clack/prompts": "^0.11.0",
|
|
21
|
-
"@kaban-board/core": "0.2.
|
|
22
|
-
"@kaban-board/tui": "0.2.
|
|
23
|
-
"@modelcontextprotocol/sdk": "^1.25.
|
|
21
|
+
"@kaban-board/core": "0.2.5",
|
|
22
|
+
"@kaban-board/tui": "0.2.5",
|
|
23
|
+
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
24
24
|
"chalk": "^5.6.2",
|
|
25
|
-
"commander": "^
|
|
26
|
-
"zod": "^4.3.
|
|
25
|
+
"commander": "^14.0.2",
|
|
26
|
+
"zod": "^4.3.6"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/bun": "latest",
|