@kaban-board/cli 0.2.5 → 0.2.8
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 +791 -194
- package/dist/kaban-hook +2 -1
- package/package.json +7 -6
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 } 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,26 @@ 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";
|
|
1046
|
+
var mcpHelpers = {
|
|
1047
|
+
getParam(args, primary, alias) {
|
|
1048
|
+
if (!args)
|
|
1049
|
+
return;
|
|
1050
|
+
const primaryVal = args[primary];
|
|
1051
|
+
if (typeof primaryVal === "string" && primaryVal.trim())
|
|
1052
|
+
return primaryVal;
|
|
1053
|
+
const aliasVal = args[alias];
|
|
1054
|
+
if (typeof aliasVal === "string" && aliasVal.trim())
|
|
1055
|
+
return aliasVal;
|
|
1056
|
+
return;
|
|
1057
|
+
},
|
|
1058
|
+
errorResponse(message) {
|
|
1059
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
|
|
1060
|
+
},
|
|
1061
|
+
jsonResponse(data) {
|
|
1062
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
805
1065
|
function getKabanPaths2(basePath) {
|
|
806
1066
|
const base = basePath ?? process.cwd();
|
|
807
1067
|
const kabanDir = join4(base, ".kaban");
|
|
@@ -867,8 +1127,10 @@ async function startMcpServer(workingDirectory) {
|
|
|
867
1127
|
description: "Get a task by its ID",
|
|
868
1128
|
inputSchema: {
|
|
869
1129
|
type: "object",
|
|
870
|
-
properties: {
|
|
871
|
-
|
|
1130
|
+
properties: {
|
|
1131
|
+
id: { type: "string", description: "Task ID (ULID)" },
|
|
1132
|
+
taskId: { type: "string", description: "Task ID (ULID) - alias for 'id'" }
|
|
1133
|
+
}
|
|
872
1134
|
}
|
|
873
1135
|
},
|
|
874
1136
|
{
|
|
@@ -878,7 +1140,8 @@ async function startMcpServer(workingDirectory) {
|
|
|
878
1140
|
type: "object",
|
|
879
1141
|
properties: {
|
|
880
1142
|
columnId: { type: "string", description: "Filter by column ID" },
|
|
881
|
-
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" },
|
|
882
1145
|
blocked: { type: "boolean", description: "Show only blocked tasks" }
|
|
883
1146
|
}
|
|
884
1147
|
}
|
|
@@ -890,10 +1153,11 @@ async function startMcpServer(workingDirectory) {
|
|
|
890
1153
|
type: "object",
|
|
891
1154
|
properties: {
|
|
892
1155
|
id: { type: "string", description: "Task ID (ULID)" },
|
|
1156
|
+
taskId: { type: "string", description: "Task ID (ULID) - alias for 'id'" },
|
|
893
1157
|
columnId: { type: "string", description: "Target column ID" },
|
|
1158
|
+
column: { type: "string", description: "Target column ID - alias for 'columnId'" },
|
|
894
1159
|
force: { type: "boolean", description: "Force move even if WIP limit exceeded" }
|
|
895
|
-
}
|
|
896
|
-
required: ["id", "columnId"]
|
|
1160
|
+
}
|
|
897
1161
|
}
|
|
898
1162
|
},
|
|
899
1163
|
{
|
|
@@ -903,6 +1167,7 @@ async function startMcpServer(workingDirectory) {
|
|
|
903
1167
|
type: "object",
|
|
904
1168
|
properties: {
|
|
905
1169
|
id: { type: "string", description: "Task ID (ULID)" },
|
|
1170
|
+
taskId: { type: "string", description: "Task ID (ULID) - alias for 'id'" },
|
|
906
1171
|
title: { type: "string", description: "New task title" },
|
|
907
1172
|
description: { type: ["string", "null"], description: "New task description" },
|
|
908
1173
|
assignedTo: { type: ["string", "null"], description: "Assigned agent name" },
|
|
@@ -916,8 +1181,7 @@ async function startMcpServer(workingDirectory) {
|
|
|
916
1181
|
type: "number",
|
|
917
1182
|
description: "Expected version for optimistic locking"
|
|
918
1183
|
}
|
|
919
|
-
}
|
|
920
|
-
required: ["id"]
|
|
1184
|
+
}
|
|
921
1185
|
}
|
|
922
1186
|
},
|
|
923
1187
|
{
|
|
@@ -925,8 +1189,10 @@ async function startMcpServer(workingDirectory) {
|
|
|
925
1189
|
description: "Delete a task",
|
|
926
1190
|
inputSchema: {
|
|
927
1191
|
type: "object",
|
|
928
|
-
properties: {
|
|
929
|
-
|
|
1192
|
+
properties: {
|
|
1193
|
+
id: { type: "string", description: "Task ID (ULID)" },
|
|
1194
|
+
taskId: { type: "string", description: "Task ID (ULID) - alias for 'id'" }
|
|
1195
|
+
}
|
|
930
1196
|
}
|
|
931
1197
|
},
|
|
932
1198
|
{
|
|
@@ -934,17 +1200,144 @@ async function startMcpServer(workingDirectory) {
|
|
|
934
1200
|
description: "Mark a task as completed (move to terminal column)",
|
|
935
1201
|
inputSchema: {
|
|
936
1202
|
type: "object",
|
|
937
|
-
properties: {
|
|
938
|
-
|
|
1203
|
+
properties: {
|
|
1204
|
+
id: { type: "string", description: "Task ID (ULID) or partial ID" },
|
|
1205
|
+
taskId: {
|
|
1206
|
+
type: "string",
|
|
1207
|
+
description: "Task ID (ULID) or partial ID - alias for 'id'"
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
939
1210
|
}
|
|
940
1211
|
},
|
|
941
1212
|
{
|
|
942
1213
|
name: "kaban_status",
|
|
943
1214
|
description: "Get board status summary",
|
|
944
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
|
+
}
|
|
945
1337
|
}
|
|
946
1338
|
]
|
|
947
1339
|
}));
|
|
1340
|
+
const { getParam, errorResponse, jsonResponse } = mcpHelpers;
|
|
948
1341
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
949
1342
|
const { name, arguments: args } = request.params;
|
|
950
1343
|
try {
|
|
@@ -953,15 +1346,7 @@ async function startMcpServer(workingDirectory) {
|
|
|
953
1346
|
const targetPath = basePath ?? workingDirectory;
|
|
954
1347
|
const { kabanDir, dbPath, configPath } = getKabanPaths2(targetPath);
|
|
955
1348
|
if (existsSync5(dbPath)) {
|
|
956
|
-
return
|
|
957
|
-
content: [
|
|
958
|
-
{
|
|
959
|
-
type: "text",
|
|
960
|
-
text: JSON.stringify({ error: "Board already exists in this directory" })
|
|
961
|
-
}
|
|
962
|
-
],
|
|
963
|
-
isError: true
|
|
964
|
-
};
|
|
1349
|
+
return errorResponse("Board already exists in this directory");
|
|
965
1350
|
}
|
|
966
1351
|
mkdirSync2(kabanDir, { recursive: true });
|
|
967
1352
|
const config = {
|
|
@@ -973,70 +1358,77 @@ async function startMcpServer(workingDirectory) {
|
|
|
973
1358
|
await initializeSchema2(db);
|
|
974
1359
|
const boardService2 = new BoardService3(db);
|
|
975
1360
|
await boardService2.initializeBoard(config);
|
|
976
|
-
return {
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
success: true,
|
|
982
|
-
board: boardName,
|
|
983
|
-
paths: { database: dbPath, config: configPath }
|
|
984
|
-
}, null, 2)
|
|
985
|
-
}
|
|
986
|
-
]
|
|
987
|
-
};
|
|
1361
|
+
return jsonResponse({
|
|
1362
|
+
success: true,
|
|
1363
|
+
board: boardName,
|
|
1364
|
+
paths: { database: dbPath, config: configPath }
|
|
1365
|
+
});
|
|
988
1366
|
}
|
|
989
1367
|
const { taskService, boardService } = await createContext(workingDirectory);
|
|
1368
|
+
const taskArgs = args;
|
|
1369
|
+
const taskId = getParam(taskArgs, "id", "taskId");
|
|
990
1370
|
switch (name) {
|
|
991
1371
|
case "kaban_add_task": {
|
|
1372
|
+
const addArgs = args;
|
|
1373
|
+
const title = addArgs?.title;
|
|
1374
|
+
if (typeof title !== "string" || !title.trim()) {
|
|
1375
|
+
return errorResponse("Title required (non-empty string)");
|
|
1376
|
+
}
|
|
992
1377
|
const task = await taskService.addTask(args);
|
|
993
|
-
return
|
|
1378
|
+
return jsonResponse(task);
|
|
994
1379
|
}
|
|
995
1380
|
case "kaban_get_task": {
|
|
996
|
-
|
|
1381
|
+
if (!taskId)
|
|
1382
|
+
return errorResponse("Task ID required (use 'id' or 'taskId')");
|
|
1383
|
+
const task = await taskService.getTask(taskId);
|
|
997
1384
|
if (!task)
|
|
998
|
-
return
|
|
999
|
-
|
|
1000
|
-
};
|
|
1001
|
-
return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }] };
|
|
1385
|
+
return errorResponse("Task not found");
|
|
1386
|
+
return jsonResponse(task);
|
|
1002
1387
|
}
|
|
1003
1388
|
case "kaban_list_tasks": {
|
|
1004
1389
|
const tasks = await taskService.listTasks(args);
|
|
1005
|
-
return
|
|
1390
|
+
return jsonResponse(tasks);
|
|
1006
1391
|
}
|
|
1007
1392
|
case "kaban_move_task": {
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1393
|
+
if (!taskId)
|
|
1394
|
+
return errorResponse("Task ID required (use 'id' or 'taskId')");
|
|
1395
|
+
const targetColumn = getParam(taskArgs, "columnId", "column");
|
|
1396
|
+
if (!targetColumn)
|
|
1397
|
+
return errorResponse("Column ID required (use 'columnId' or 'column')");
|
|
1398
|
+
const { force } = args ?? {};
|
|
1399
|
+
const task = await taskService.moveTask(taskId, targetColumn, { force });
|
|
1400
|
+
return jsonResponse(task);
|
|
1011
1401
|
}
|
|
1012
1402
|
case "kaban_update_task": {
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1403
|
+
if (!taskId)
|
|
1404
|
+
return errorResponse("Task ID required (use 'id' or 'taskId')");
|
|
1405
|
+
const {
|
|
1406
|
+
taskId: _t,
|
|
1407
|
+
id: _i,
|
|
1408
|
+
expectedVersion,
|
|
1409
|
+
...updates
|
|
1410
|
+
} = args ?? {};
|
|
1411
|
+
const task = await taskService.updateTask(taskId, updates, expectedVersion);
|
|
1412
|
+
return jsonResponse(task);
|
|
1016
1413
|
}
|
|
1017
1414
|
case "kaban_delete_task": {
|
|
1018
|
-
|
|
1019
|
-
|
|
1415
|
+
if (!taskId)
|
|
1416
|
+
return errorResponse("Task ID required (use 'id' or 'taskId')");
|
|
1417
|
+
await taskService.deleteTask(taskId);
|
|
1418
|
+
return jsonResponse({ success: true });
|
|
1020
1419
|
}
|
|
1021
1420
|
case "kaban_complete_task": {
|
|
1022
|
-
|
|
1421
|
+
if (!taskId)
|
|
1422
|
+
return errorResponse("Task ID required (use 'id' or 'taskId')");
|
|
1023
1423
|
const tasks = await taskService.listTasks();
|
|
1024
|
-
const task = tasks.find((t) => t.id.startsWith(
|
|
1424
|
+
const task = tasks.find((t) => t.id.startsWith(taskId));
|
|
1025
1425
|
if (!task)
|
|
1026
|
-
return {
|
|
1027
|
-
content: [
|
|
1028
|
-
{ type: "text", text: JSON.stringify({ error: `Task '${id}' not found` }) }
|
|
1029
|
-
]
|
|
1030
|
-
};
|
|
1426
|
+
return errorResponse(`Task '${taskId}' not found`);
|
|
1031
1427
|
const terminal = await boardService.getTerminalColumn();
|
|
1032
1428
|
if (!terminal)
|
|
1033
|
-
return
|
|
1034
|
-
content: [
|
|
1035
|
-
{ type: "text", text: JSON.stringify({ error: "No terminal column configured" }) }
|
|
1036
|
-
]
|
|
1037
|
-
};
|
|
1429
|
+
return errorResponse("No terminal column configured");
|
|
1038
1430
|
const completed = await taskService.moveTask(task.id, terminal.id);
|
|
1039
|
-
return
|
|
1431
|
+
return jsonResponse(completed);
|
|
1040
1432
|
}
|
|
1041
1433
|
case "kaban_status": {
|
|
1042
1434
|
const board = await boardService.getBoard();
|
|
@@ -1049,25 +1441,162 @@ async function startMcpServer(workingDirectory) {
|
|
|
1049
1441
|
wipLimit: column.wipLimit,
|
|
1050
1442
|
isTerminal: column.isTerminal
|
|
1051
1443
|
}));
|
|
1052
|
-
return {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1444
|
+
return jsonResponse({
|
|
1445
|
+
board: { name: board?.name ?? "Kaban Board" },
|
|
1446
|
+
columns: columnStats,
|
|
1447
|
+
blockedCount: tasks.filter((t) => t.blockedReason).length,
|
|
1448
|
+
totalTasks: tasks.length
|
|
1449
|
+
});
|
|
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);
|
|
1065
1597
|
}
|
|
1066
1598
|
default:
|
|
1067
|
-
return {
|
|
1068
|
-
content: [{ type: "text", text: JSON.stringify({ error: `Unknown tool: ${name}` }) }],
|
|
1069
|
-
isError: true
|
|
1070
|
-
};
|
|
1599
|
+
return errorResponse(`Unknown tool: ${name}`);
|
|
1071
1600
|
}
|
|
1072
1601
|
} catch (error2) {
|
|
1073
1602
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
@@ -1179,15 +1708,15 @@ async function startMcpServer(workingDirectory) {
|
|
|
1179
1708
|
await server.connect(transport);
|
|
1180
1709
|
console.error("Kaban MCP server running on stdio");
|
|
1181
1710
|
}
|
|
1182
|
-
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) => {
|
|
1183
1712
|
const workingDirectory = options.path ?? process.env.KABAN_PATH ?? process.cwd();
|
|
1184
1713
|
await startMcpServer(workingDirectory);
|
|
1185
1714
|
});
|
|
1186
1715
|
|
|
1187
1716
|
// src/commands/move.ts
|
|
1188
|
-
import { KabanError as
|
|
1189
|
-
import { Command as
|
|
1190
|
-
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) => {
|
|
1191
1720
|
const json = options.json;
|
|
1192
1721
|
try {
|
|
1193
1722
|
const { taskService, boardService } = await getContext();
|
|
@@ -1221,14 +1750,24 @@ var moveCommand = new Command7("move").description("Move a task to a different c
|
|
|
1221
1750
|
const moved = await taskService.moveTask(task.id, targetColumn, {
|
|
1222
1751
|
force: options.force
|
|
1223
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;
|
|
1224
1759
|
if (json) {
|
|
1225
|
-
outputSuccess(
|
|
1760
|
+
outputSuccess(finalTask);
|
|
1226
1761
|
return;
|
|
1227
1762
|
}
|
|
1228
1763
|
const col = await boardService.getColumn(moved.columnId);
|
|
1229
|
-
|
|
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);
|
|
1230
1769
|
} catch (error2) {
|
|
1231
|
-
if (error2 instanceof
|
|
1770
|
+
if (error2 instanceof KabanError6) {
|
|
1232
1771
|
if (json)
|
|
1233
1772
|
outputError(error2.code, error2.message);
|
|
1234
1773
|
console.error(`Error: ${error2.message}`);
|
|
@@ -1240,9 +1779,9 @@ var moveCommand = new Command7("move").description("Move a task to a different c
|
|
|
1240
1779
|
|
|
1241
1780
|
// src/commands/schema.ts
|
|
1242
1781
|
import { jsonSchemas } from "@kaban-board/core";
|
|
1243
|
-
import { Command as
|
|
1782
|
+
import { Command as Command10 } from "commander";
|
|
1244
1783
|
var availableSchemas = Object.keys(jsonSchemas);
|
|
1245
|
-
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) => {
|
|
1246
1785
|
if (!name) {
|
|
1247
1786
|
console.log("Available schemas:");
|
|
1248
1787
|
for (const schemaName of availableSchemas) {
|
|
@@ -1261,10 +1800,52 @@ Usage: kaban schema <name>`);
|
|
|
1261
1800
|
console.log(JSON.stringify(schema, null, 2));
|
|
1262
1801
|
});
|
|
1263
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
|
+
|
|
1264
1845
|
// src/commands/status.ts
|
|
1265
|
-
import { KabanError as
|
|
1266
|
-
import { Command as
|
|
1267
|
-
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) => {
|
|
1268
1849
|
const json = options.json;
|
|
1269
1850
|
try {
|
|
1270
1851
|
const { taskService, boardService } = await getContext();
|
|
@@ -1307,7 +1888,7 @@ var statusCommand2 = new Command9("status").description("Show board status summa
|
|
|
1307
1888
|
}
|
|
1308
1889
|
console.log();
|
|
1309
1890
|
} catch (error2) {
|
|
1310
|
-
if (error2 instanceof
|
|
1891
|
+
if (error2 instanceof KabanError8) {
|
|
1311
1892
|
if (json)
|
|
1312
1893
|
outputError(error2.code, error2.message);
|
|
1313
1894
|
console.error(`Error: ${error2.message}`);
|
|
@@ -1318,7 +1899,7 @@ var statusCommand2 = new Command9("status").description("Show board status summa
|
|
|
1318
1899
|
});
|
|
1319
1900
|
|
|
1320
1901
|
// src/commands/sync.ts
|
|
1321
|
-
import { Command as
|
|
1902
|
+
import { Command as Command13 } from "commander";
|
|
1322
1903
|
|
|
1323
1904
|
// src/hook/constants.ts
|
|
1324
1905
|
var STATUS_PRIORITY = {
|
|
@@ -1412,23 +1993,26 @@ class ConflictResolver {
|
|
|
1412
1993
|
}
|
|
1413
1994
|
|
|
1414
1995
|
// src/hook/kaban-client.ts
|
|
1415
|
-
import { spawn as spawn2 } from "
|
|
1996
|
+
import { spawn as spawn2 } from "child_process";
|
|
1416
1997
|
|
|
1417
1998
|
class KabanClient {
|
|
1418
1999
|
cwd;
|
|
2000
|
+
kabanCmd;
|
|
1419
2001
|
constructor(cwd) {
|
|
1420
2002
|
this.cwd = cwd;
|
|
2003
|
+
const cliOverride = process.env.KABAN_CLI;
|
|
2004
|
+
this.kabanCmd = cliOverride ? cliOverride.split(" ") : ["kaban"];
|
|
1421
2005
|
}
|
|
1422
2006
|
async boardExists() {
|
|
1423
2007
|
try {
|
|
1424
|
-
const result = await this.exec([
|
|
2008
|
+
const result = await this.exec([...this.kabanCmd, "status", "--json"]);
|
|
1425
2009
|
return result.exitCode === 0;
|
|
1426
2010
|
} catch {
|
|
1427
2011
|
return false;
|
|
1428
2012
|
}
|
|
1429
2013
|
}
|
|
1430
2014
|
async listTasks(columnId) {
|
|
1431
|
-
const args = [
|
|
2015
|
+
const args = [...this.kabanCmd, "list", "--json"];
|
|
1432
2016
|
if (columnId) {
|
|
1433
2017
|
args.push("--column", columnId);
|
|
1434
2018
|
}
|
|
@@ -1437,7 +2021,8 @@ class KabanClient {
|
|
|
1437
2021
|
return [];
|
|
1438
2022
|
}
|
|
1439
2023
|
try {
|
|
1440
|
-
const
|
|
2024
|
+
const parsed = JSON.parse(result.stdout);
|
|
2025
|
+
const tasks = parsed.data ?? parsed;
|
|
1441
2026
|
return tasks.map((t) => ({
|
|
1442
2027
|
id: t.id,
|
|
1443
2028
|
title: t.title,
|
|
@@ -1450,12 +2035,13 @@ class KabanClient {
|
|
|
1450
2035
|
}
|
|
1451
2036
|
}
|
|
1452
2037
|
async getTaskById(id) {
|
|
1453
|
-
const result = await this.exec([
|
|
2038
|
+
const result = await this.exec([...this.kabanCmd, "get", id, "--json"]);
|
|
1454
2039
|
if (result.exitCode !== 0) {
|
|
1455
2040
|
return null;
|
|
1456
2041
|
}
|
|
1457
2042
|
try {
|
|
1458
|
-
const
|
|
2043
|
+
const parsed = JSON.parse(result.stdout);
|
|
2044
|
+
const task = parsed.data ?? parsed;
|
|
1459
2045
|
return {
|
|
1460
2046
|
id: task.id,
|
|
1461
2047
|
title: task.title,
|
|
@@ -1472,7 +2058,7 @@ class KabanClient {
|
|
|
1472
2058
|
return tasks.find((t) => t.title === title) ?? null;
|
|
1473
2059
|
}
|
|
1474
2060
|
async addTask(title, columnId = "todo") {
|
|
1475
|
-
const result = await this.exec([
|
|
2061
|
+
const result = await this.exec([...this.kabanCmd, "add", title, "--column", columnId, "--json"]);
|
|
1476
2062
|
if (result.exitCode !== 0) {
|
|
1477
2063
|
return null;
|
|
1478
2064
|
}
|
|
@@ -1485,20 +2071,21 @@ class KabanClient {
|
|
|
1485
2071
|
}
|
|
1486
2072
|
}
|
|
1487
2073
|
async moveTask(id, columnId) {
|
|
1488
|
-
const result = await this.exec([
|
|
2074
|
+
const result = await this.exec([...this.kabanCmd, "move", id, columnId]);
|
|
1489
2075
|
return result.exitCode === 0;
|
|
1490
2076
|
}
|
|
1491
2077
|
async completeTask(id) {
|
|
1492
|
-
const result = await this.exec([
|
|
2078
|
+
const result = await this.exec([...this.kabanCmd, "done", id]);
|
|
1493
2079
|
return result.exitCode === 0;
|
|
1494
2080
|
}
|
|
1495
2081
|
async getStatus() {
|
|
1496
|
-
const result = await this.exec([
|
|
2082
|
+
const result = await this.exec([...this.kabanCmd, "status", "--json"]);
|
|
1497
2083
|
if (result.exitCode !== 0) {
|
|
1498
2084
|
return null;
|
|
1499
2085
|
}
|
|
1500
2086
|
try {
|
|
1501
|
-
|
|
2087
|
+
const parsed = JSON.parse(result.stdout);
|
|
2088
|
+
return parsed.data ?? parsed;
|
|
1502
2089
|
} catch {
|
|
1503
2090
|
return null;
|
|
1504
2091
|
}
|
|
@@ -1615,10 +2202,10 @@ class SyncEngine {
|
|
|
1615
2202
|
}
|
|
1616
2203
|
|
|
1617
2204
|
// src/hook/sync-logger.ts
|
|
1618
|
-
import { existsSync as existsSync6 } from "
|
|
1619
|
-
import { appendFile, mkdir as mkdir2 } from "
|
|
1620
|
-
import { homedir as homedir3 } from "
|
|
1621
|
-
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";
|
|
1622
2209
|
|
|
1623
2210
|
class SyncLogger {
|
|
1624
2211
|
logPath;
|
|
@@ -1654,7 +2241,7 @@ class SyncLogger {
|
|
|
1654
2241
|
}
|
|
1655
2242
|
|
|
1656
2243
|
// src/commands/sync.ts
|
|
1657
|
-
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) => {
|
|
1658
2245
|
const startTime = performance.now();
|
|
1659
2246
|
let input;
|
|
1660
2247
|
try {
|
|
@@ -1701,77 +2288,87 @@ var syncCommand = new Command10("sync").description("Sync TodoWrite input to Kab
|
|
|
1701
2288
|
});
|
|
1702
2289
|
|
|
1703
2290
|
// src/commands/tui.ts
|
|
1704
|
-
import { spawn as spawn3, spawnSync } from "
|
|
1705
|
-
import { existsSync as existsSync7 } from "
|
|
1706
|
-
import {
|
|
1707
|
-
import {
|
|
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";
|
|
2297
|
+
var __dirname2 = dirname3(fileURLToPath(import.meta.url));
|
|
2298
|
+
var require2 = createRequire(import.meta.url);
|
|
2299
|
+
function hasBun() {
|
|
2300
|
+
try {
|
|
2301
|
+
const result = spawnSync("bun", ["--version"], { stdio: "ignore" });
|
|
2302
|
+
return result.status === 0;
|
|
2303
|
+
} catch {
|
|
2304
|
+
return false;
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
1708
2307
|
function findInPath(name) {
|
|
1709
2308
|
const result = spawnSync("which", [name], { encoding: "utf-8" });
|
|
1710
2309
|
return result.status === 0 ? result.stdout.trim() : null;
|
|
1711
2310
|
}
|
|
2311
|
+
function resolveTuiPackage() {
|
|
2312
|
+
try {
|
|
2313
|
+
return require2.resolve("@kaban-board/tui");
|
|
2314
|
+
} catch {
|
|
2315
|
+
return null;
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
1712
2318
|
function runBinary(path, args) {
|
|
1713
2319
|
const child = spawn3(path, args, { stdio: "inherit", cwd: process.cwd() });
|
|
1714
2320
|
child.on("exit", (code) => process.exit(code ?? 0));
|
|
1715
2321
|
}
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
const child = spawn3(bunPath, ["x", "@kaban-board/tui", ...args], {
|
|
1719
|
-
stdio: "inherit",
|
|
1720
|
-
cwd: process.cwd()
|
|
1721
|
-
});
|
|
1722
|
-
child.on("spawn", () => {
|
|
1723
|
-
started = true;
|
|
1724
|
-
});
|
|
1725
|
-
child.on("error", () => {
|
|
1726
|
-
if (!started)
|
|
1727
|
-
showInstallError();
|
|
1728
|
-
});
|
|
1729
|
-
child.on("exit", (code) => process.exit(code ?? 0));
|
|
1730
|
-
return true;
|
|
1731
|
-
}
|
|
1732
|
-
function showInstallError() {
|
|
1733
|
-
console.error(`
|
|
1734
|
-
Error: kaban-tui not found
|
|
1735
|
-
|
|
1736
|
-
The TUI requires Bun runtime. Install with one of:
|
|
1737
|
-
|
|
1738
|
-
# Homebrew (recommended)
|
|
1739
|
-
brew install beshkenadze/tap/kaban-tui
|
|
1740
|
-
|
|
1741
|
-
# Or install Bun, then run via bunx
|
|
1742
|
-
curl -fsSL https://bun.sh/install | bash
|
|
1743
|
-
bunx @kaban-board/tui
|
|
1744
|
-
`);
|
|
1745
|
-
process.exit(1);
|
|
1746
|
-
}
|
|
1747
|
-
var tuiCommand = new Command11("tui").description("Start interactive Terminal UI (requires Bun)").action(async () => {
|
|
2322
|
+
var tuiCommand = new Command14("tui").description("Start interactive Terminal UI").action(async () => {
|
|
2323
|
+
const cwd = process.cwd();
|
|
1748
2324
|
const args = process.argv.slice(3);
|
|
2325
|
+
const useBun = hasBun();
|
|
1749
2326
|
const siblingBinary = join5(dirname3(process.execPath), "kaban-tui");
|
|
1750
2327
|
if (existsSync7(siblingBinary))
|
|
1751
2328
|
return runBinary(siblingBinary, args);
|
|
1752
2329
|
const pathBinary = findInPath("kaban-tui");
|
|
1753
2330
|
if (pathBinary)
|
|
1754
2331
|
return runBinary(pathBinary, args);
|
|
1755
|
-
const
|
|
1756
|
-
if (
|
|
1757
|
-
|
|
1758
|
-
|
|
2332
|
+
const tuiDevEntry = join5(__dirname2, "../../../tui/src/index.ts");
|
|
2333
|
+
if (existsSync7(tuiDevEntry) && useBun) {
|
|
2334
|
+
const child2 = spawn3("bun", ["run", tuiDevEntry], { stdio: "inherit", cwd });
|
|
2335
|
+
child2.on("exit", (code) => process.exit(code ?? 0));
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
if (!useBun) {
|
|
2339
|
+
console.error("TUI requires Bun. Install: curl -fsSL https://bun.sh/install | bash");
|
|
2340
|
+
process.exit(1);
|
|
2341
|
+
}
|
|
2342
|
+
const tuiEntry = resolveTuiPackage();
|
|
2343
|
+
if (tuiEntry) {
|
|
2344
|
+
const child2 = spawn3("bun", [tuiEntry, ...args], { stdio: "inherit", cwd });
|
|
2345
|
+
child2.on("exit", (code) => process.exit(code ?? 0));
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2348
|
+
const child = spawn3("bun", ["x", "@kaban-board/tui", ...args], { stdio: "inherit", cwd });
|
|
2349
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
1759
2350
|
});
|
|
1760
2351
|
|
|
1761
2352
|
// src/index.ts
|
|
1762
|
-
var
|
|
1763
|
-
var pkg =
|
|
1764
|
-
var program = new
|
|
2353
|
+
var require3 = createRequire2(import.meta.url);
|
|
2354
|
+
var pkg = require3("../package.json");
|
|
2355
|
+
var program = new Command15;
|
|
1765
2356
|
program.name("kaban").description("Terminal Kanban for AI Code Agents").version(pkg.version);
|
|
1766
2357
|
program.addCommand(initCommand);
|
|
1767
2358
|
program.addCommand(addCommand);
|
|
1768
2359
|
program.addCommand(listCommand);
|
|
1769
2360
|
program.addCommand(moveCommand);
|
|
2361
|
+
program.addCommand(assignCommand);
|
|
1770
2362
|
program.addCommand(doneCommand);
|
|
1771
2363
|
program.addCommand(statusCommand2);
|
|
1772
2364
|
program.addCommand(schemaCommand);
|
|
2365
|
+
program.addCommand(searchCommand);
|
|
1773
2366
|
program.addCommand(mcpCommand);
|
|
1774
2367
|
program.addCommand(tuiCommand);
|
|
1775
2368
|
program.addCommand(hookCommand);
|
|
1776
2369
|
program.addCommand(syncCommand);
|
|
2370
|
+
program.addCommand(archiveCommand);
|
|
2371
|
+
program.addCommand(restoreCommand);
|
|
2372
|
+
program.addCommand(purgeCommand);
|
|
2373
|
+
program.addCommand(resetCommand);
|
|
1777
2374
|
program.parse();
|