kanban-lite 1.0.21 → 1.0.23
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/{CLAUDE.md → AGENTS.md} +13 -0
- package/CHANGELOG.md +68 -0
- package/README.md +10 -0
- package/dist/cli.js +168 -102
- package/dist/extension.js +178 -104
- package/dist/mcp-server.js +145 -95
- package/dist/sdk/index.cjs +126 -93
- package/dist/sdk/index.mjs +126 -93
- package/dist/sdk/sdk/KanbanSDK.d.ts +39 -7
- package/dist/sdk/shared/config.d.ts +4 -0
- package/dist/sdk/shared/types.d.ts +4 -0
- package/dist/standalone-webview/index.js +58 -58
- package/dist/standalone-webview/index.js.map +1 -1
- package/dist/standalone-webview/style.css +1 -1
- package/dist/standalone.js +606 -364
- package/dist/webview/index.js +57 -57
- package/dist/webview/index.js.map +1 -1
- package/dist/webview/style.css +1 -1
- package/docs/plans/2026-02-26-settings-tabs-design.md +40 -0
- package/docs/plans/2026-02-26-settings-tabs.md +166 -0
- package/docs/plans/2026-02-27-zoom-settings-design.md +82 -0
- package/docs/plans/2026-02-27-zoom-settings.md +395 -0
- package/docs/sdk.md +3 -6
- package/package.json +1 -1
- package/src/cli/index.ts +12 -2
- package/src/extension/KanbanPanel.ts +25 -5
- package/src/mcp-server/index.ts +20 -2
- package/src/sdk/KanbanSDK.ts +64 -7
- package/src/sdk/__tests__/KanbanSDK.test.ts +17 -1
- package/src/sdk/__tests__/metadata.test.ts +3 -1
- package/src/sdk/__tests__/multi-board.test.ts +2 -0
- package/src/sdk/parser.ts +50 -83
- package/src/shared/config.ts +14 -2
- package/src/shared/types.ts +4 -0
- package/src/standalone/__tests__/server.integration.test.ts +2 -2
- package/src/standalone/index.ts +7 -4
- package/src/standalone/server.ts +31 -6
- package/src/webview/App.tsx +42 -3
- package/src/webview/assets/main.css +31 -2
- package/src/webview/components/KanbanBoard.tsx +35 -3
- package/src/webview/components/KanbanColumn.tsx +40 -4
- package/src/webview/components/SettingsPanel.tsx +179 -77
- package/src/webview/components/Toolbar.tsx +127 -32
- package/src/webview/store/index.ts +26 -28
package/dist/standalone.js
CHANGED
|
@@ -4327,6 +4327,215 @@ var init_open = __esm({
|
|
|
4327
4327
|
}
|
|
4328
4328
|
});
|
|
4329
4329
|
|
|
4330
|
+
// src/shared/config.ts
|
|
4331
|
+
var fs = __toESM(require("fs"));
|
|
4332
|
+
var path = __toESM(require("path"));
|
|
4333
|
+
|
|
4334
|
+
// src/shared/types.ts
|
|
4335
|
+
var CARD_FORMAT_VERSION = 1;
|
|
4336
|
+
function getTitleFromContent(content) {
|
|
4337
|
+
const match = content.match(/^#\s+(.+)$/m);
|
|
4338
|
+
if (match)
|
|
4339
|
+
return match[1].trim();
|
|
4340
|
+
const firstLine = content.split("\n").map((l) => l.trim()).find((l) => l.length > 0);
|
|
4341
|
+
return firstLine || "Untitled";
|
|
4342
|
+
}
|
|
4343
|
+
function generateSlug(title) {
|
|
4344
|
+
return title.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 50) || "feature";
|
|
4345
|
+
}
|
|
4346
|
+
function generateFeatureFilename(id, title) {
|
|
4347
|
+
const slug = generateSlug(title);
|
|
4348
|
+
return `${id}-${slug}`;
|
|
4349
|
+
}
|
|
4350
|
+
function extractNumericId(filenameOrId) {
|
|
4351
|
+
const match = filenameOrId.match(/^(\d+)(?:-|$)/);
|
|
4352
|
+
return match ? parseInt(match[1], 10) : null;
|
|
4353
|
+
}
|
|
4354
|
+
var DEFAULT_COLUMNS = [
|
|
4355
|
+
{ id: "backlog", name: "Backlog", color: "#6b7280" },
|
|
4356
|
+
{ id: "todo", name: "To Do", color: "#3b82f6" },
|
|
4357
|
+
{ id: "in-progress", name: "In Progress", color: "#f59e0b" },
|
|
4358
|
+
{ id: "review", name: "Review", color: "#8b5cf6" },
|
|
4359
|
+
{ id: "done", name: "Done", color: "#22c55e" }
|
|
4360
|
+
];
|
|
4361
|
+
var DELETED_STATUS_ID = "deleted";
|
|
4362
|
+
|
|
4363
|
+
// src/shared/config.ts
|
|
4364
|
+
var DEFAULT_BOARD_CONFIG = {
|
|
4365
|
+
name: "Default",
|
|
4366
|
+
columns: [...DEFAULT_COLUMNS],
|
|
4367
|
+
nextCardId: 1,
|
|
4368
|
+
defaultStatus: "backlog",
|
|
4369
|
+
defaultPriority: "medium"
|
|
4370
|
+
};
|
|
4371
|
+
var DEFAULT_CONFIG = {
|
|
4372
|
+
version: 2,
|
|
4373
|
+
boards: {
|
|
4374
|
+
default: { ...DEFAULT_BOARD_CONFIG, columns: [...DEFAULT_COLUMNS] }
|
|
4375
|
+
},
|
|
4376
|
+
defaultBoard: "default",
|
|
4377
|
+
featuresDirectory: ".kanban",
|
|
4378
|
+
aiAgent: "claude",
|
|
4379
|
+
defaultPriority: "medium",
|
|
4380
|
+
defaultStatus: "backlog",
|
|
4381
|
+
showPriorityBadges: true,
|
|
4382
|
+
showAssignee: true,
|
|
4383
|
+
showDueDate: true,
|
|
4384
|
+
showLabels: true,
|
|
4385
|
+
showBuildWithAI: true,
|
|
4386
|
+
showFileName: false,
|
|
4387
|
+
compactMode: false,
|
|
4388
|
+
markdownEditorMode: false,
|
|
4389
|
+
showDeletedColumn: false,
|
|
4390
|
+
boardZoom: 100,
|
|
4391
|
+
cardZoom: 100,
|
|
4392
|
+
port: 3e3,
|
|
4393
|
+
labels: {}
|
|
4394
|
+
};
|
|
4395
|
+
var CONFIG_FILENAME = ".kanban.json";
|
|
4396
|
+
function configPath(workspaceRoot) {
|
|
4397
|
+
return path.join(workspaceRoot, CONFIG_FILENAME);
|
|
4398
|
+
}
|
|
4399
|
+
function migrateConfigV1ToV2(raw) {
|
|
4400
|
+
const v1Defaults = {
|
|
4401
|
+
featuresDirectory: ".kanban",
|
|
4402
|
+
defaultPriority: "medium",
|
|
4403
|
+
defaultStatus: "backlog",
|
|
4404
|
+
columns: [...DEFAULT_COLUMNS],
|
|
4405
|
+
aiAgent: "claude",
|
|
4406
|
+
nextCardId: 1,
|
|
4407
|
+
showPriorityBadges: true,
|
|
4408
|
+
showAssignee: true,
|
|
4409
|
+
showDueDate: true,
|
|
4410
|
+
showLabels: true,
|
|
4411
|
+
showBuildWithAI: true,
|
|
4412
|
+
showFileName: false,
|
|
4413
|
+
compactMode: false,
|
|
4414
|
+
markdownEditorMode: false
|
|
4415
|
+
};
|
|
4416
|
+
const v1 = { ...v1Defaults, ...raw };
|
|
4417
|
+
return {
|
|
4418
|
+
version: 2,
|
|
4419
|
+
boards: {
|
|
4420
|
+
default: {
|
|
4421
|
+
name: "Default",
|
|
4422
|
+
columns: v1.columns,
|
|
4423
|
+
nextCardId: v1.nextCardId,
|
|
4424
|
+
defaultStatus: v1.defaultStatus,
|
|
4425
|
+
defaultPriority: v1.defaultPriority
|
|
4426
|
+
}
|
|
4427
|
+
},
|
|
4428
|
+
defaultBoard: "default",
|
|
4429
|
+
featuresDirectory: v1.featuresDirectory,
|
|
4430
|
+
aiAgent: v1.aiAgent,
|
|
4431
|
+
defaultPriority: v1.defaultPriority,
|
|
4432
|
+
defaultStatus: v1.defaultStatus,
|
|
4433
|
+
showPriorityBadges: v1.showPriorityBadges,
|
|
4434
|
+
showAssignee: v1.showAssignee,
|
|
4435
|
+
showDueDate: v1.showDueDate,
|
|
4436
|
+
showLabels: v1.showLabels,
|
|
4437
|
+
showBuildWithAI: v1.showBuildWithAI,
|
|
4438
|
+
showFileName: v1.showFileName,
|
|
4439
|
+
compactMode: v1.compactMode,
|
|
4440
|
+
markdownEditorMode: v1.markdownEditorMode,
|
|
4441
|
+
showDeletedColumn: false,
|
|
4442
|
+
boardZoom: 100,
|
|
4443
|
+
cardZoom: 100,
|
|
4444
|
+
port: 3e3
|
|
4445
|
+
};
|
|
4446
|
+
}
|
|
4447
|
+
function readConfig(workspaceRoot) {
|
|
4448
|
+
const filePath = configPath(workspaceRoot);
|
|
4449
|
+
const defaults = { ...DEFAULT_CONFIG, boards: { default: { ...DEFAULT_BOARD_CONFIG, columns: [...DEFAULT_COLUMNS] } } };
|
|
4450
|
+
try {
|
|
4451
|
+
const raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
4452
|
+
if (!raw.version || raw.version === 1) {
|
|
4453
|
+
const v2 = migrateConfigV1ToV2(raw);
|
|
4454
|
+
writeConfig(workspaceRoot, v2);
|
|
4455
|
+
return v2;
|
|
4456
|
+
}
|
|
4457
|
+
const config = { ...defaults, ...raw };
|
|
4458
|
+
if (!config.boards || Object.keys(config.boards).length === 0) {
|
|
4459
|
+
config.boards = defaults.boards;
|
|
4460
|
+
}
|
|
4461
|
+
return config;
|
|
4462
|
+
} catch {
|
|
4463
|
+
return defaults;
|
|
4464
|
+
}
|
|
4465
|
+
}
|
|
4466
|
+
function writeConfig(workspaceRoot, config) {
|
|
4467
|
+
const filePath = configPath(workspaceRoot);
|
|
4468
|
+
fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
4469
|
+
}
|
|
4470
|
+
function getBoardConfig(workspaceRoot, boardId) {
|
|
4471
|
+
const config = readConfig(workspaceRoot);
|
|
4472
|
+
const resolvedId = boardId || config.defaultBoard;
|
|
4473
|
+
const board = config.boards[resolvedId];
|
|
4474
|
+
if (!board) {
|
|
4475
|
+
throw new Error(`Board '${resolvedId}' not found`);
|
|
4476
|
+
}
|
|
4477
|
+
return board;
|
|
4478
|
+
}
|
|
4479
|
+
function allocateCardId(workspaceRoot, boardId) {
|
|
4480
|
+
const config = readConfig(workspaceRoot);
|
|
4481
|
+
const resolvedId = boardId || config.defaultBoard;
|
|
4482
|
+
const board = config.boards[resolvedId];
|
|
4483
|
+
if (!board) {
|
|
4484
|
+
throw new Error(`Board '${resolvedId}' not found`);
|
|
4485
|
+
}
|
|
4486
|
+
const id = board.nextCardId;
|
|
4487
|
+
board.nextCardId = id + 1;
|
|
4488
|
+
writeConfig(workspaceRoot, config);
|
|
4489
|
+
return id;
|
|
4490
|
+
}
|
|
4491
|
+
function syncCardIdCounter(workspaceRoot, boardId, existingIds) {
|
|
4492
|
+
if (existingIds.length === 0)
|
|
4493
|
+
return;
|
|
4494
|
+
const maxId = Math.max(...existingIds);
|
|
4495
|
+
const config = readConfig(workspaceRoot);
|
|
4496
|
+
const resolvedId = boardId || config.defaultBoard;
|
|
4497
|
+
const board = config.boards[resolvedId];
|
|
4498
|
+
if (!board)
|
|
4499
|
+
return;
|
|
4500
|
+
if (board.nextCardId <= maxId) {
|
|
4501
|
+
board.nextCardId = maxId + 1;
|
|
4502
|
+
writeConfig(workspaceRoot, config);
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4505
|
+
function configToSettings(config) {
|
|
4506
|
+
return {
|
|
4507
|
+
showPriorityBadges: config.showPriorityBadges,
|
|
4508
|
+
showAssignee: config.showAssignee,
|
|
4509
|
+
showDueDate: config.showDueDate,
|
|
4510
|
+
showLabels: config.showLabels,
|
|
4511
|
+
showBuildWithAI: config.showBuildWithAI,
|
|
4512
|
+
showFileName: config.showFileName,
|
|
4513
|
+
compactMode: config.compactMode,
|
|
4514
|
+
markdownEditorMode: config.markdownEditorMode,
|
|
4515
|
+
showDeletedColumn: config.showDeletedColumn,
|
|
4516
|
+
defaultPriority: config.defaultPriority,
|
|
4517
|
+
defaultStatus: config.defaultStatus,
|
|
4518
|
+
boardZoom: config.boardZoom ?? 100,
|
|
4519
|
+
cardZoom: config.cardZoom ?? 100
|
|
4520
|
+
};
|
|
4521
|
+
}
|
|
4522
|
+
function settingsToConfig(config, settings) {
|
|
4523
|
+
return {
|
|
4524
|
+
...config,
|
|
4525
|
+
showPriorityBadges: settings.showPriorityBadges,
|
|
4526
|
+
showAssignee: settings.showAssignee,
|
|
4527
|
+
showDueDate: settings.showDueDate,
|
|
4528
|
+
showLabels: settings.showLabels,
|
|
4529
|
+
showFileName: settings.showFileName,
|
|
4530
|
+
compactMode: settings.compactMode,
|
|
4531
|
+
showDeletedColumn: settings.showDeletedColumn,
|
|
4532
|
+
defaultPriority: settings.defaultPriority,
|
|
4533
|
+
defaultStatus: settings.defaultStatus,
|
|
4534
|
+
boardZoom: settings.boardZoom,
|
|
4535
|
+
cardZoom: settings.cardZoom
|
|
4536
|
+
};
|
|
4537
|
+
}
|
|
4538
|
+
|
|
4330
4539
|
// src/standalone/server.ts
|
|
4331
4540
|
var http2 = __toESM(require("http"));
|
|
4332
4541
|
var fs5 = __toESM(require("fs"));
|
|
@@ -6030,34 +6239,6 @@ function watch(paths, options = {}) {
|
|
|
6030
6239
|
}
|
|
6031
6240
|
var esm_default = { watch, FSWatcher };
|
|
6032
6241
|
|
|
6033
|
-
// src/shared/types.ts
|
|
6034
|
-
function getTitleFromContent(content) {
|
|
6035
|
-
const match = content.match(/^#\s+(.+)$/m);
|
|
6036
|
-
if (match)
|
|
6037
|
-
return match[1].trim();
|
|
6038
|
-
const firstLine = content.split("\n").map((l) => l.trim()).find((l) => l.length > 0);
|
|
6039
|
-
return firstLine || "Untitled";
|
|
6040
|
-
}
|
|
6041
|
-
function generateSlug(title) {
|
|
6042
|
-
return title.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 50) || "feature";
|
|
6043
|
-
}
|
|
6044
|
-
function generateFeatureFilename(id, title) {
|
|
6045
|
-
const slug = generateSlug(title);
|
|
6046
|
-
return `${id}-${slug}`;
|
|
6047
|
-
}
|
|
6048
|
-
function extractNumericId(filenameOrId) {
|
|
6049
|
-
const match = filenameOrId.match(/^(\d+)(?:-|$)/);
|
|
6050
|
-
return match ? parseInt(match[1], 10) : null;
|
|
6051
|
-
}
|
|
6052
|
-
var DEFAULT_COLUMNS = [
|
|
6053
|
-
{ id: "backlog", name: "Backlog", color: "#6b7280" },
|
|
6054
|
-
{ id: "todo", name: "To Do", color: "#3b82f6" },
|
|
6055
|
-
{ id: "in-progress", name: "In Progress", color: "#f59e0b" },
|
|
6056
|
-
{ id: "review", name: "Review", color: "#8b5cf6" },
|
|
6057
|
-
{ id: "done", name: "Done", color: "#22c55e" }
|
|
6058
|
-
];
|
|
6059
|
-
var DELETED_STATUS_ID = "deleted";
|
|
6060
|
-
|
|
6061
6242
|
// src/sdk/KanbanSDK.ts
|
|
6062
6243
|
var fs4 = __toESM(require("fs/promises"));
|
|
6063
6244
|
var path5 = __toESM(require("path"));
|
|
@@ -6206,240 +6387,70 @@ function generateKeyBetween(a, b, digits = BASE_62_DIGITS) {
|
|
|
6206
6387
|
if (ib2 === "A" + digits[0].repeat(26)) {
|
|
6207
6388
|
return ib2 + midpoint("", fb2, digits);
|
|
6208
6389
|
}
|
|
6209
|
-
if (ib2 < b) {
|
|
6210
|
-
return ib2;
|
|
6211
|
-
}
|
|
6212
|
-
const res = decrementInteger(ib2, digits);
|
|
6213
|
-
if (res == null) {
|
|
6214
|
-
throw new Error("cannot decrement any more");
|
|
6215
|
-
}
|
|
6216
|
-
return res;
|
|
6217
|
-
}
|
|
6218
|
-
if (b == null) {
|
|
6219
|
-
const ia2 = getIntegerPart(a);
|
|
6220
|
-
const fa2 = a.slice(ia2.length);
|
|
6221
|
-
const i2 = incrementInteger(ia2, digits);
|
|
6222
|
-
return i2 == null ? ia2 + midpoint(fa2, null, digits) : i2;
|
|
6223
|
-
}
|
|
6224
|
-
const ia = getIntegerPart(a);
|
|
6225
|
-
const fa = a.slice(ia.length);
|
|
6226
|
-
const ib = getIntegerPart(b);
|
|
6227
|
-
const fb = b.slice(ib.length);
|
|
6228
|
-
if (ia === ib) {
|
|
6229
|
-
return ia + midpoint(fa, fb, digits);
|
|
6230
|
-
}
|
|
6231
|
-
const i = incrementInteger(ia, digits);
|
|
6232
|
-
if (i == null) {
|
|
6233
|
-
throw new Error("cannot increment any more");
|
|
6234
|
-
}
|
|
6235
|
-
if (i < b) {
|
|
6236
|
-
return i;
|
|
6237
|
-
}
|
|
6238
|
-
return ia + midpoint(fa, null, digits);
|
|
6239
|
-
}
|
|
6240
|
-
function generateNKeysBetween(a, b, n, digits = BASE_62_DIGITS) {
|
|
6241
|
-
if (n === 0) {
|
|
6242
|
-
return [];
|
|
6243
|
-
}
|
|
6244
|
-
if (n === 1) {
|
|
6245
|
-
return [generateKeyBetween(a, b, digits)];
|
|
6246
|
-
}
|
|
6247
|
-
if (b == null) {
|
|
6248
|
-
let c2 = generateKeyBetween(a, b, digits);
|
|
6249
|
-
const result = [c2];
|
|
6250
|
-
for (let i = 0; i < n - 1; i++) {
|
|
6251
|
-
c2 = generateKeyBetween(c2, b, digits);
|
|
6252
|
-
result.push(c2);
|
|
6253
|
-
}
|
|
6254
|
-
return result;
|
|
6255
|
-
}
|
|
6256
|
-
if (a == null) {
|
|
6257
|
-
let c2 = generateKeyBetween(a, b, digits);
|
|
6258
|
-
const result = [c2];
|
|
6259
|
-
for (let i = 0; i < n - 1; i++) {
|
|
6260
|
-
c2 = generateKeyBetween(a, c2, digits);
|
|
6261
|
-
result.push(c2);
|
|
6262
|
-
}
|
|
6263
|
-
result.reverse();
|
|
6264
|
-
return result;
|
|
6265
|
-
}
|
|
6266
|
-
const mid = Math.floor(n / 2);
|
|
6267
|
-
const c = generateKeyBetween(a, b, digits);
|
|
6268
|
-
return [
|
|
6269
|
-
...generateNKeysBetween(a, c, mid, digits),
|
|
6270
|
-
c,
|
|
6271
|
-
...generateNKeysBetween(c, b, n - mid - 1, digits)
|
|
6272
|
-
];
|
|
6273
|
-
}
|
|
6274
|
-
|
|
6275
|
-
// src/shared/config.ts
|
|
6276
|
-
var fs = __toESM(require("fs"));
|
|
6277
|
-
var path = __toESM(require("path"));
|
|
6278
|
-
var DEFAULT_BOARD_CONFIG = {
|
|
6279
|
-
name: "Default",
|
|
6280
|
-
columns: [...DEFAULT_COLUMNS],
|
|
6281
|
-
nextCardId: 1,
|
|
6282
|
-
defaultStatus: "backlog",
|
|
6283
|
-
defaultPriority: "medium"
|
|
6284
|
-
};
|
|
6285
|
-
var DEFAULT_CONFIG = {
|
|
6286
|
-
version: 2,
|
|
6287
|
-
boards: {
|
|
6288
|
-
default: { ...DEFAULT_BOARD_CONFIG, columns: [...DEFAULT_COLUMNS] }
|
|
6289
|
-
},
|
|
6290
|
-
defaultBoard: "default",
|
|
6291
|
-
featuresDirectory: ".kanban",
|
|
6292
|
-
aiAgent: "claude",
|
|
6293
|
-
defaultPriority: "medium",
|
|
6294
|
-
defaultStatus: "backlog",
|
|
6295
|
-
showPriorityBadges: true,
|
|
6296
|
-
showAssignee: true,
|
|
6297
|
-
showDueDate: true,
|
|
6298
|
-
showLabels: true,
|
|
6299
|
-
showBuildWithAI: true,
|
|
6300
|
-
showFileName: false,
|
|
6301
|
-
compactMode: false,
|
|
6302
|
-
markdownEditorMode: false,
|
|
6303
|
-
showDeletedColumn: false,
|
|
6304
|
-
port: 3e3,
|
|
6305
|
-
labels: {}
|
|
6306
|
-
};
|
|
6307
|
-
var CONFIG_FILENAME = ".kanban.json";
|
|
6308
|
-
function configPath(workspaceRoot) {
|
|
6309
|
-
return path.join(workspaceRoot, CONFIG_FILENAME);
|
|
6310
|
-
}
|
|
6311
|
-
function migrateConfigV1ToV2(raw) {
|
|
6312
|
-
const v1Defaults = {
|
|
6313
|
-
featuresDirectory: ".kanban",
|
|
6314
|
-
defaultPriority: "medium",
|
|
6315
|
-
defaultStatus: "backlog",
|
|
6316
|
-
columns: [...DEFAULT_COLUMNS],
|
|
6317
|
-
aiAgent: "claude",
|
|
6318
|
-
nextCardId: 1,
|
|
6319
|
-
showPriorityBadges: true,
|
|
6320
|
-
showAssignee: true,
|
|
6321
|
-
showDueDate: true,
|
|
6322
|
-
showLabels: true,
|
|
6323
|
-
showBuildWithAI: true,
|
|
6324
|
-
showFileName: false,
|
|
6325
|
-
compactMode: false,
|
|
6326
|
-
markdownEditorMode: false
|
|
6327
|
-
};
|
|
6328
|
-
const v1 = { ...v1Defaults, ...raw };
|
|
6329
|
-
return {
|
|
6330
|
-
version: 2,
|
|
6331
|
-
boards: {
|
|
6332
|
-
default: {
|
|
6333
|
-
name: "Default",
|
|
6334
|
-
columns: v1.columns,
|
|
6335
|
-
nextCardId: v1.nextCardId,
|
|
6336
|
-
defaultStatus: v1.defaultStatus,
|
|
6337
|
-
defaultPriority: v1.defaultPriority
|
|
6338
|
-
}
|
|
6339
|
-
},
|
|
6340
|
-
defaultBoard: "default",
|
|
6341
|
-
featuresDirectory: v1.featuresDirectory,
|
|
6342
|
-
aiAgent: v1.aiAgent,
|
|
6343
|
-
defaultPriority: v1.defaultPriority,
|
|
6344
|
-
defaultStatus: v1.defaultStatus,
|
|
6345
|
-
showPriorityBadges: v1.showPriorityBadges,
|
|
6346
|
-
showAssignee: v1.showAssignee,
|
|
6347
|
-
showDueDate: v1.showDueDate,
|
|
6348
|
-
showLabels: v1.showLabels,
|
|
6349
|
-
showBuildWithAI: v1.showBuildWithAI,
|
|
6350
|
-
showFileName: v1.showFileName,
|
|
6351
|
-
compactMode: v1.compactMode,
|
|
6352
|
-
markdownEditorMode: v1.markdownEditorMode,
|
|
6353
|
-
showDeletedColumn: false,
|
|
6354
|
-
port: 3e3
|
|
6355
|
-
};
|
|
6356
|
-
}
|
|
6357
|
-
function readConfig(workspaceRoot) {
|
|
6358
|
-
const filePath = configPath(workspaceRoot);
|
|
6359
|
-
const defaults = { ...DEFAULT_CONFIG, boards: { default: { ...DEFAULT_BOARD_CONFIG, columns: [...DEFAULT_COLUMNS] } } };
|
|
6360
|
-
try {
|
|
6361
|
-
const raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
6362
|
-
if (!raw.version || raw.version === 1) {
|
|
6363
|
-
const v2 = migrateConfigV1ToV2(raw);
|
|
6364
|
-
writeConfig(workspaceRoot, v2);
|
|
6365
|
-
return v2;
|
|
6366
|
-
}
|
|
6367
|
-
const config = { ...defaults, ...raw };
|
|
6368
|
-
if (!config.boards || Object.keys(config.boards).length === 0) {
|
|
6369
|
-
config.boards = defaults.boards;
|
|
6390
|
+
if (ib2 < b) {
|
|
6391
|
+
return ib2;
|
|
6370
6392
|
}
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6393
|
+
const res = decrementInteger(ib2, digits);
|
|
6394
|
+
if (res == null) {
|
|
6395
|
+
throw new Error("cannot decrement any more");
|
|
6396
|
+
}
|
|
6397
|
+
return res;
|
|
6374
6398
|
}
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
function getBoardConfig(workspaceRoot, boardId) {
|
|
6381
|
-
const config = readConfig(workspaceRoot);
|
|
6382
|
-
const resolvedId = boardId || config.defaultBoard;
|
|
6383
|
-
const board = config.boards[resolvedId];
|
|
6384
|
-
if (!board) {
|
|
6385
|
-
throw new Error(`Board '${resolvedId}' not found`);
|
|
6399
|
+
if (b == null) {
|
|
6400
|
+
const ia2 = getIntegerPart(a);
|
|
6401
|
+
const fa2 = a.slice(ia2.length);
|
|
6402
|
+
const i2 = incrementInteger(ia2, digits);
|
|
6403
|
+
return i2 == null ? ia2 + midpoint(fa2, null, digits) : i2;
|
|
6386
6404
|
}
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
const
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
if (!board) {
|
|
6394
|
-
throw new Error(`Board '${resolvedId}' not found`);
|
|
6405
|
+
const ia = getIntegerPart(a);
|
|
6406
|
+
const fa = a.slice(ia.length);
|
|
6407
|
+
const ib = getIntegerPart(b);
|
|
6408
|
+
const fb = b.slice(ib.length);
|
|
6409
|
+
if (ia === ib) {
|
|
6410
|
+
return ia + midpoint(fa, fb, digits);
|
|
6395
6411
|
}
|
|
6396
|
-
const
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
return id;
|
|
6400
|
-
}
|
|
6401
|
-
function syncCardIdCounter(workspaceRoot, boardId, existingIds) {
|
|
6402
|
-
if (existingIds.length === 0)
|
|
6403
|
-
return;
|
|
6404
|
-
const maxId = Math.max(...existingIds);
|
|
6405
|
-
const config = readConfig(workspaceRoot);
|
|
6406
|
-
const resolvedId = boardId || config.defaultBoard;
|
|
6407
|
-
const board = config.boards[resolvedId];
|
|
6408
|
-
if (!board)
|
|
6409
|
-
return;
|
|
6410
|
-
if (board.nextCardId <= maxId) {
|
|
6411
|
-
board.nextCardId = maxId + 1;
|
|
6412
|
-
writeConfig(workspaceRoot, config);
|
|
6412
|
+
const i = incrementInteger(ia, digits);
|
|
6413
|
+
if (i == null) {
|
|
6414
|
+
throw new Error("cannot increment any more");
|
|
6413
6415
|
}
|
|
6416
|
+
if (i < b) {
|
|
6417
|
+
return i;
|
|
6418
|
+
}
|
|
6419
|
+
return ia + midpoint(fa, null, digits);
|
|
6414
6420
|
}
|
|
6415
|
-
function
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6421
|
+
function generateNKeysBetween(a, b, n, digits = BASE_62_DIGITS) {
|
|
6422
|
+
if (n === 0) {
|
|
6423
|
+
return [];
|
|
6424
|
+
}
|
|
6425
|
+
if (n === 1) {
|
|
6426
|
+
return [generateKeyBetween(a, b, digits)];
|
|
6427
|
+
}
|
|
6428
|
+
if (b == null) {
|
|
6429
|
+
let c2 = generateKeyBetween(a, b, digits);
|
|
6430
|
+
const result = [c2];
|
|
6431
|
+
for (let i = 0; i < n - 1; i++) {
|
|
6432
|
+
c2 = generateKeyBetween(c2, b, digits);
|
|
6433
|
+
result.push(c2);
|
|
6434
|
+
}
|
|
6435
|
+
return result;
|
|
6436
|
+
}
|
|
6437
|
+
if (a == null) {
|
|
6438
|
+
let c2 = generateKeyBetween(a, b, digits);
|
|
6439
|
+
const result = [c2];
|
|
6440
|
+
for (let i = 0; i < n - 1; i++) {
|
|
6441
|
+
c2 = generateKeyBetween(a, c2, digits);
|
|
6442
|
+
result.push(c2);
|
|
6443
|
+
}
|
|
6444
|
+
result.reverse();
|
|
6445
|
+
return result;
|
|
6446
|
+
}
|
|
6447
|
+
const mid = Math.floor(n / 2);
|
|
6448
|
+
const c = generateKeyBetween(a, b, digits);
|
|
6449
|
+
return [
|
|
6450
|
+
...generateNKeysBetween(a, c, mid, digits),
|
|
6451
|
+
c,
|
|
6452
|
+
...generateNKeysBetween(c, b, n - mid - 1, digits)
|
|
6453
|
+
];
|
|
6443
6454
|
}
|
|
6444
6455
|
|
|
6445
6456
|
// src/sdk/parser.ts
|
|
@@ -9098,6 +9109,7 @@ function renamed(from, to) {
|
|
|
9098
9109
|
throw new Error("Function yaml." + from + " is removed in js-yaml 4. Use yaml." + to + " instead, which is now safe by default.");
|
|
9099
9110
|
};
|
|
9100
9111
|
}
|
|
9112
|
+
var JSON_SCHEMA = json;
|
|
9101
9113
|
var load = loader.load;
|
|
9102
9114
|
var loadAll = loader.loadAll;
|
|
9103
9115
|
var dump = dumper.dump;
|
|
@@ -9136,48 +9148,26 @@ function parseFeatureFile(content, filePath) {
|
|
|
9136
9148
|
return null;
|
|
9137
9149
|
const frontmatter = frontmatterMatch[1];
|
|
9138
9150
|
const rest = frontmatterMatch[2] || "";
|
|
9139
|
-
|
|
9140
|
-
|
|
9141
|
-
|
|
9151
|
+
let parsed;
|
|
9152
|
+
try {
|
|
9153
|
+
const loaded = load(frontmatter, { schema: JSON_SCHEMA });
|
|
9154
|
+
if (!loaded || typeof loaded !== "object" || Array.isArray(loaded))
|
|
9155
|
+
return null;
|
|
9156
|
+
parsed = loaded;
|
|
9157
|
+
} catch {
|
|
9158
|
+
return null;
|
|
9159
|
+
}
|
|
9160
|
+
const str2 = (key) => {
|
|
9161
|
+
const val = parsed[key];
|
|
9162
|
+
if (val == null)
|
|
9142
9163
|
return "";
|
|
9143
|
-
|
|
9144
|
-
return value === "null" ? "" : value;
|
|
9164
|
+
return String(val);
|
|
9145
9165
|
};
|
|
9146
|
-
const
|
|
9147
|
-
const
|
|
9148
|
-
if (!
|
|
9166
|
+
const arr = (key) => {
|
|
9167
|
+
const val = parsed[key];
|
|
9168
|
+
if (!Array.isArray(val))
|
|
9149
9169
|
return [];
|
|
9150
|
-
return
|
|
9151
|
-
};
|
|
9152
|
-
const getMetadata = () => {
|
|
9153
|
-
const lines = frontmatter.split("\n");
|
|
9154
|
-
let metaStart = -1;
|
|
9155
|
-
for (let j = 0; j < lines.length; j++) {
|
|
9156
|
-
if (/^metadata:\s*$/.test(lines[j])) {
|
|
9157
|
-
metaStart = j + 1;
|
|
9158
|
-
break;
|
|
9159
|
-
}
|
|
9160
|
-
}
|
|
9161
|
-
if (metaStart === -1)
|
|
9162
|
-
return void 0;
|
|
9163
|
-
const indentedLines = [];
|
|
9164
|
-
for (let j = metaStart; j < lines.length; j++) {
|
|
9165
|
-
if (/^\s/.test(lines[j]) || lines[j].trim() === "") {
|
|
9166
|
-
indentedLines.push(lines[j]);
|
|
9167
|
-
} else {
|
|
9168
|
-
break;
|
|
9169
|
-
}
|
|
9170
|
-
}
|
|
9171
|
-
if (indentedLines.length === 0)
|
|
9172
|
-
return void 0;
|
|
9173
|
-
try {
|
|
9174
|
-
const parsed = load(indentedLines.join("\n"));
|
|
9175
|
-
if (parsed && typeof parsed === "object")
|
|
9176
|
-
return parsed;
|
|
9177
|
-
return void 0;
|
|
9178
|
-
} catch {
|
|
9179
|
-
return void 0;
|
|
9180
|
-
}
|
|
9170
|
+
return val.filter((v) => v != null).map(String);
|
|
9181
9171
|
};
|
|
9182
9172
|
const sections = rest.split(/\n---\n/);
|
|
9183
9173
|
let body = sections[0] || "";
|
|
@@ -9198,53 +9188,52 @@ ${section}`;
|
|
|
9198
9188
|
i += 1;
|
|
9199
9189
|
}
|
|
9200
9190
|
}
|
|
9201
|
-
const
|
|
9191
|
+
const actions = arr("actions");
|
|
9192
|
+
const rawMeta = parsed.metadata;
|
|
9193
|
+
const meta = rawMeta != null && typeof rawMeta === "object" && !Array.isArray(rawMeta) ? rawMeta : void 0;
|
|
9202
9194
|
return {
|
|
9203
|
-
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
|
|
9195
|
+
version: typeof parsed.version === "number" ? parsed.version : parseInt(str2("version"), 10) || 0,
|
|
9196
|
+
id: str2("id") || extractIdFromFilename(filePath),
|
|
9197
|
+
status: str2("status") || "backlog",
|
|
9198
|
+
priority: str2("priority") || "medium",
|
|
9199
|
+
assignee: parsed.assignee != null ? String(parsed.assignee) : null,
|
|
9200
|
+
dueDate: parsed.dueDate != null ? String(parsed.dueDate) : null,
|
|
9201
|
+
created: str2("created") || (/* @__PURE__ */ new Date()).toISOString(),
|
|
9202
|
+
modified: str2("modified") || (/* @__PURE__ */ new Date()).toISOString(),
|
|
9203
|
+
completedAt: parsed.completedAt != null ? String(parsed.completedAt) : null,
|
|
9204
|
+
labels: arr("labels"),
|
|
9205
|
+
attachments: arr("attachments"),
|
|
9213
9206
|
comments,
|
|
9214
|
-
order:
|
|
9207
|
+
order: str2("order") || "a0",
|
|
9215
9208
|
content: body.trim(),
|
|
9216
9209
|
...meta ? { metadata: meta } : {},
|
|
9210
|
+
...actions.length > 0 ? { actions } : {},
|
|
9217
9211
|
filePath
|
|
9218
9212
|
};
|
|
9219
9213
|
}
|
|
9220
9214
|
function serializeFeature(feature) {
|
|
9221
|
-
const
|
|
9222
|
-
|
|
9223
|
-
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
|
|
9230
|
-
|
|
9231
|
-
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
9235
|
-
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
lines.push("");
|
|
9244
|
-
const frontmatter = lines.join("\n");
|
|
9245
|
-
let result = frontmatter + feature.content;
|
|
9246
|
-
const comments = feature.comments || [];
|
|
9247
|
-
for (const comment of comments) {
|
|
9215
|
+
const frontmatterObj = {
|
|
9216
|
+
version: feature.version ?? CARD_FORMAT_VERSION,
|
|
9217
|
+
id: feature.id,
|
|
9218
|
+
status: feature.status,
|
|
9219
|
+
priority: feature.priority,
|
|
9220
|
+
assignee: feature.assignee ?? null,
|
|
9221
|
+
dueDate: feature.dueDate ?? null,
|
|
9222
|
+
created: feature.created,
|
|
9223
|
+
modified: feature.modified,
|
|
9224
|
+
completedAt: feature.completedAt ?? null,
|
|
9225
|
+
labels: feature.labels,
|
|
9226
|
+
attachments: feature.attachments || [],
|
|
9227
|
+
order: feature.order,
|
|
9228
|
+
...feature.actions?.length ? { actions: feature.actions } : {},
|
|
9229
|
+
...feature.metadata && Object.keys(feature.metadata).length > 0 ? { metadata: feature.metadata } : {}
|
|
9230
|
+
};
|
|
9231
|
+
const yamlStr = dump(frontmatterObj, { lineWidth: -1, quotingType: '"', forceQuotes: true });
|
|
9232
|
+
let result = `---
|
|
9233
|
+
${yamlStr}---
|
|
9234
|
+
|
|
9235
|
+
${feature.content}`;
|
|
9236
|
+
for (const comment of feature.comments || []) {
|
|
9248
9237
|
result += "\n\n---\n";
|
|
9249
9238
|
result += `comment: true
|
|
9250
9239
|
`;
|
|
@@ -9374,6 +9363,23 @@ async function migrateFileSystemToMultiBoard(featuresDir) {
|
|
|
9374
9363
|
}
|
|
9375
9364
|
}
|
|
9376
9365
|
|
|
9366
|
+
// src/sdk/metaUtils.ts
|
|
9367
|
+
function getNestedValue(obj, path8) {
|
|
9368
|
+
return path8.split(".").reduce((curr, key) => curr != null && typeof curr === "object" ? curr[key] : void 0, obj);
|
|
9369
|
+
}
|
|
9370
|
+
function matchesMetaFilter(metadata, filter) {
|
|
9371
|
+
if (!metadata)
|
|
9372
|
+
return false;
|
|
9373
|
+
for (const [path8, needle] of Object.entries(filter)) {
|
|
9374
|
+
const value = getNestedValue(metadata, path8);
|
|
9375
|
+
if (value == null)
|
|
9376
|
+
return false;
|
|
9377
|
+
if (!String(value).toLowerCase().includes(needle.toLowerCase()))
|
|
9378
|
+
return false;
|
|
9379
|
+
}
|
|
9380
|
+
return true;
|
|
9381
|
+
}
|
|
9382
|
+
|
|
9377
9383
|
// src/sdk/KanbanSDK.ts
|
|
9378
9384
|
var KanbanSDK = class {
|
|
9379
9385
|
/**
|
|
@@ -9712,12 +9718,18 @@ var KanbanSDK = class {
|
|
|
9712
9718
|
* - Migrates legacy integer ordering to fractional indexing
|
|
9713
9719
|
* - Syncs the card ID counter with existing cards
|
|
9714
9720
|
*
|
|
9715
|
-
*
|
|
9721
|
+
* By default cards are returned sorted by their fractional order key (board order).
|
|
9722
|
+
* Pass a {@link CardSortOption} to sort by creation or modification date instead.
|
|
9716
9723
|
*
|
|
9717
9724
|
* @param columns - Optional array of status/column IDs to filter by.
|
|
9718
9725
|
* When provided, ensures those subdirectories exist on disk.
|
|
9719
9726
|
* @param boardId - Optional board ID. Defaults to the workspace's default board.
|
|
9720
|
-
* @
|
|
9727
|
+
* @param metaFilter - Optional map of dot-notation metadata paths to required substrings.
|
|
9728
|
+
* Only cards whose metadata contains all specified values (case-insensitive substring match)
|
|
9729
|
+
* are returned.
|
|
9730
|
+
* @param sort - Optional sort order. One of `'created:asc'`, `'created:desc'`,
|
|
9731
|
+
* `'modified:asc'`, `'modified:desc'`. Defaults to fractional board order.
|
|
9732
|
+
* @returns A promise resolving to an array of {@link Feature} card objects.
|
|
9721
9733
|
*
|
|
9722
9734
|
* @example
|
|
9723
9735
|
* ```ts
|
|
@@ -9726,9 +9738,15 @@ var KanbanSDK = class {
|
|
|
9726
9738
|
*
|
|
9727
9739
|
* // List only cards in 'todo' and 'in-progress' columns on the 'bugs' board
|
|
9728
9740
|
* const filtered = await sdk.listCards(['todo', 'in-progress'], 'bugs')
|
|
9741
|
+
*
|
|
9742
|
+
* // List cards where metadata.sprint contains 'Q1' and metadata.links.jira contains 'PROJ'
|
|
9743
|
+
* const q1Jira = await sdk.listCards(undefined, undefined, { 'sprint': 'Q1', 'links.jira': 'PROJ' })
|
|
9744
|
+
*
|
|
9745
|
+
* // List all cards sorted by creation date, newest first
|
|
9746
|
+
* const newest = await sdk.listCards(undefined, undefined, undefined, 'created:desc')
|
|
9729
9747
|
* ```
|
|
9730
9748
|
*/
|
|
9731
|
-
async listCards(columns, boardId) {
|
|
9749
|
+
async listCards(columns, boardId, metaFilter, sort) {
|
|
9732
9750
|
await this._ensureMigrated();
|
|
9733
9751
|
const boardDir = this._boardDir(boardId);
|
|
9734
9752
|
const resolvedBoardId = this._resolveBoardId(boardId);
|
|
@@ -9802,7 +9820,16 @@ var KanbanSDK = class {
|
|
|
9802
9820
|
if (numericIds.length > 0) {
|
|
9803
9821
|
syncCardIdCounter(this.workspaceRoot, resolvedBoardId, numericIds);
|
|
9804
9822
|
}
|
|
9805
|
-
|
|
9823
|
+
const filtered = metaFilter && Object.keys(metaFilter).length > 0 ? cards.filter((c) => matchesMetaFilter(c.metadata, metaFilter)) : cards;
|
|
9824
|
+
if (sort) {
|
|
9825
|
+
const [field, dir2] = sort.split(":");
|
|
9826
|
+
return filtered.sort((a, b) => {
|
|
9827
|
+
const aVal = field === "created" ? a.created : a.modified;
|
|
9828
|
+
const bVal = field === "created" ? b.created : b.modified;
|
|
9829
|
+
return dir2 === "asc" ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
|
|
9830
|
+
});
|
|
9831
|
+
}
|
|
9832
|
+
return filtered.sort((a, b) => a.order < b.order ? -1 : a.order > b.order ? 1 : 0);
|
|
9806
9833
|
}
|
|
9807
9834
|
/**
|
|
9808
9835
|
* Retrieves a single card by its ID.
|
|
@@ -9875,6 +9902,7 @@ var KanbanSDK = class {
|
|
|
9875
9902
|
const cardsInStatus = cards.filter((c) => c.status === status).sort((a, b) => a.order < b.order ? -1 : a.order > b.order ? 1 : 0);
|
|
9876
9903
|
const lastOrder = cardsInStatus.length > 0 ? cardsInStatus[cardsInStatus.length - 1].order : null;
|
|
9877
9904
|
const card = {
|
|
9905
|
+
version: CARD_FORMAT_VERSION,
|
|
9878
9906
|
id: String(numericId),
|
|
9879
9907
|
boardId: resolvedBoardId,
|
|
9880
9908
|
status,
|
|
@@ -9890,6 +9918,7 @@ var KanbanSDK = class {
|
|
|
9890
9918
|
order: generateKeyBetween(lastOrder, null),
|
|
9891
9919
|
content: data.content,
|
|
9892
9920
|
...data.metadata && Object.keys(data.metadata).length > 0 ? { metadata: data.metadata } : {},
|
|
9921
|
+
...data.actions && data.actions.length > 0 ? { actions: data.actions } : {},
|
|
9893
9922
|
filePath: getFeatureFilePath(boardDir, status, filename)
|
|
9894
9923
|
};
|
|
9895
9924
|
await fs4.mkdir(path5.dirname(card.filePath), { recursive: true });
|
|
@@ -9949,6 +9978,54 @@ var KanbanSDK = class {
|
|
|
9949
9978
|
this.emitEvent("task.updated", sanitizeFeature(card));
|
|
9950
9979
|
return card;
|
|
9951
9980
|
}
|
|
9981
|
+
/**
|
|
9982
|
+
* Triggers a named action for a card by POSTing to the global `actionWebhookUrl`
|
|
9983
|
+
* configured in `.kanban.json`.
|
|
9984
|
+
*
|
|
9985
|
+
* The payload sent to the webhook is:
|
|
9986
|
+
* ```json
|
|
9987
|
+
* { "action": "retry", "board": "default", "list": "in-progress", "card": { ...sanitizedCard } }
|
|
9988
|
+
* ```
|
|
9989
|
+
*
|
|
9990
|
+
* @param cardId - The ID of the card to trigger the action for.
|
|
9991
|
+
* @param action - The action name string (e.g. `'retry'`, `'sendEmail'`).
|
|
9992
|
+
* @param boardId - Optional board ID. Defaults to the workspace's default board.
|
|
9993
|
+
* @returns A promise resolving when the webhook responds with 2xx.
|
|
9994
|
+
* @throws {Error} If no `actionWebhookUrl` is configured in `.kanban.json`.
|
|
9995
|
+
* @throws {Error} If the card is not found.
|
|
9996
|
+
* @throws {Error} If the webhook responds with a non-2xx status.
|
|
9997
|
+
*
|
|
9998
|
+
* @example
|
|
9999
|
+
* ```ts
|
|
10000
|
+
* await sdk.triggerAction('42', 'retry')
|
|
10001
|
+
* await sdk.triggerAction('42', 'sendEmail', 'bugs')
|
|
10002
|
+
* ```
|
|
10003
|
+
*/
|
|
10004
|
+
async triggerAction(cardId, action, boardId) {
|
|
10005
|
+
const config = readConfig(this.workspaceRoot);
|
|
10006
|
+
const { actionWebhookUrl } = config;
|
|
10007
|
+
if (!actionWebhookUrl) {
|
|
10008
|
+
throw new Error("No action webhook URL configured. Set actionWebhookUrl in .kanban.json");
|
|
10009
|
+
}
|
|
10010
|
+
const card = await this.getCard(cardId, boardId);
|
|
10011
|
+
if (!card)
|
|
10012
|
+
throw new Error(`Card not found: ${cardId}`);
|
|
10013
|
+
const resolvedBoardId = card.boardId || this._resolveBoardId(boardId);
|
|
10014
|
+
const payload = {
|
|
10015
|
+
action,
|
|
10016
|
+
board: resolvedBoardId,
|
|
10017
|
+
list: card.status,
|
|
10018
|
+
card: sanitizeFeature(card)
|
|
10019
|
+
};
|
|
10020
|
+
const response = await fetch(actionWebhookUrl, {
|
|
10021
|
+
method: "POST",
|
|
10022
|
+
headers: { "Content-Type": "application/json" },
|
|
10023
|
+
body: JSON.stringify(payload)
|
|
10024
|
+
});
|
|
10025
|
+
if (!response.ok) {
|
|
10026
|
+
throw new Error(`Action webhook responded with ${response.status}: ${response.statusText}`);
|
|
10027
|
+
}
|
|
10028
|
+
}
|
|
9952
10029
|
/**
|
|
9953
10030
|
* Moves a card to a different status column and/or position within that column.
|
|
9954
10031
|
*
|
|
@@ -10149,25 +10226,28 @@ var KanbanSDK = class {
|
|
|
10149
10226
|
writeConfig(this.workspaceRoot, config);
|
|
10150
10227
|
}
|
|
10151
10228
|
/**
|
|
10152
|
-
* Removes a label definition from the workspace configuration
|
|
10153
|
-
*
|
|
10154
|
-
* This only removes the color/group definition — cards that use this
|
|
10155
|
-
* label keep their label strings. Those labels will render with default
|
|
10156
|
-
* gray styling in the UI.
|
|
10229
|
+
* Removes a label definition from the workspace configuration and cascades
|
|
10230
|
+
* the deletion to all cards by removing the label from their `labels` array.
|
|
10157
10231
|
*
|
|
10158
10232
|
* @param name - The label name to remove.
|
|
10159
10233
|
*
|
|
10160
10234
|
* @example
|
|
10161
10235
|
* ```ts
|
|
10162
|
-
* sdk.deleteLabel('bug')
|
|
10236
|
+
* await sdk.deleteLabel('bug')
|
|
10163
10237
|
* ```
|
|
10164
10238
|
*/
|
|
10165
|
-
deleteLabel(name) {
|
|
10239
|
+
async deleteLabel(name) {
|
|
10166
10240
|
const config = readConfig(this.workspaceRoot);
|
|
10167
10241
|
if (config.labels) {
|
|
10168
10242
|
delete config.labels[name];
|
|
10169
10243
|
writeConfig(this.workspaceRoot, config);
|
|
10170
10244
|
}
|
|
10245
|
+
const cards = await this.listCards();
|
|
10246
|
+
for (const card of cards) {
|
|
10247
|
+
if (card.labels.includes(name)) {
|
|
10248
|
+
await this.updateCard(card.id, { labels: card.labels.filter((l) => l !== name) });
|
|
10249
|
+
}
|
|
10250
|
+
}
|
|
10171
10251
|
}
|
|
10172
10252
|
/**
|
|
10173
10253
|
* Renames a label in the configuration and cascades the change to all cards.
|
|
@@ -10592,6 +10672,57 @@ var KanbanSDK = class {
|
|
|
10592
10672
|
this.emitEvent("column.deleted", removed);
|
|
10593
10673
|
return board.columns;
|
|
10594
10674
|
}
|
|
10675
|
+
/**
|
|
10676
|
+
* Moves all cards in the specified column to the `deleted` (soft-delete) column.
|
|
10677
|
+
*
|
|
10678
|
+
* This is a non-destructive operation — cards are moved to the reserved
|
|
10679
|
+
* `deleted` status and can be restored or permanently deleted later.
|
|
10680
|
+
* The column itself is not removed.
|
|
10681
|
+
*
|
|
10682
|
+
* @param columnId - The ID of the column whose cards should be moved to `deleted`.
|
|
10683
|
+
* @param boardId - Optional board ID. Defaults to the workspace's default board.
|
|
10684
|
+
* @returns A promise resolving to the number of cards that were moved.
|
|
10685
|
+
* @throws {Error} If the column is `'deleted'` (no-op protection).
|
|
10686
|
+
*
|
|
10687
|
+
* @example
|
|
10688
|
+
* ```ts
|
|
10689
|
+
* const moved = await sdk.cleanupColumn('blocked')
|
|
10690
|
+
* console.log(`Moved ${moved} cards to deleted`)
|
|
10691
|
+
* ```
|
|
10692
|
+
*/
|
|
10693
|
+
async cleanupColumn(columnId, boardId) {
|
|
10694
|
+
if (columnId === DELETED_STATUS_ID)
|
|
10695
|
+
return 0;
|
|
10696
|
+
const cards = await this.listCards(void 0, boardId);
|
|
10697
|
+
const cardsToMove = cards.filter((c) => c.status === columnId);
|
|
10698
|
+
for (const card of cardsToMove) {
|
|
10699
|
+
await this.moveCard(card.id, DELETED_STATUS_ID, 0, boardId);
|
|
10700
|
+
}
|
|
10701
|
+
return cardsToMove.length;
|
|
10702
|
+
}
|
|
10703
|
+
/**
|
|
10704
|
+
* Permanently deletes all cards currently in the `deleted` column.
|
|
10705
|
+
*
|
|
10706
|
+
* This is equivalent to "empty trash". All soft-deleted cards are
|
|
10707
|
+
* removed from disk. This operation cannot be undone.
|
|
10708
|
+
*
|
|
10709
|
+
* @param boardId - Optional board ID. Defaults to the workspace's default board.
|
|
10710
|
+
* @returns A promise resolving to the number of cards that were permanently deleted.
|
|
10711
|
+
*
|
|
10712
|
+
* @example
|
|
10713
|
+
* ```ts
|
|
10714
|
+
* const count = await sdk.purgeDeletedCards()
|
|
10715
|
+
* console.log(`Permanently deleted ${count} cards`)
|
|
10716
|
+
* ```
|
|
10717
|
+
*/
|
|
10718
|
+
async purgeDeletedCards(boardId) {
|
|
10719
|
+
const cards = await this.listCards(void 0, boardId);
|
|
10720
|
+
const deleted = cards.filter((c) => c.status === DELETED_STATUS_ID);
|
|
10721
|
+
for (const card of deleted) {
|
|
10722
|
+
await this.permanentlyDeleteCard(card.id, boardId);
|
|
10723
|
+
}
|
|
10724
|
+
return deleted.length;
|
|
10725
|
+
}
|
|
10595
10726
|
/**
|
|
10596
10727
|
* Reorders the columns of a board.
|
|
10597
10728
|
*
|
|
@@ -10816,6 +10947,17 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
10816
10947
|
const sdk = new KanbanSDK(absoluteFeaturesDir, {
|
|
10817
10948
|
onEvent: (event, data) => fireWebhooks(workspaceRoot, event, data)
|
|
10818
10949
|
});
|
|
10950
|
+
const VALID_SORTS = ["created:asc", "created:desc", "modified:asc", "modified:desc"];
|
|
10951
|
+
function applySortParam(result, sortParam) {
|
|
10952
|
+
if (!sortParam || !VALID_SORTS.includes(sortParam))
|
|
10953
|
+
return result;
|
|
10954
|
+
const [field, dir2] = sortParam.split(":");
|
|
10955
|
+
return [...result].sort((a, b) => {
|
|
10956
|
+
const aVal = field === "created" ? a.created : a.modified;
|
|
10957
|
+
const bVal = field === "created" ? b.created : b.modified;
|
|
10958
|
+
return dir2 === "asc" ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
|
|
10959
|
+
});
|
|
10960
|
+
}
|
|
10819
10961
|
function readBody(req) {
|
|
10820
10962
|
return new Promise((resolve5, reject) => {
|
|
10821
10963
|
const chunks = [];
|
|
@@ -10918,6 +11060,7 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
10918
11060
|
dueDate: data.dueDate,
|
|
10919
11061
|
labels: data.labels,
|
|
10920
11062
|
metadata: data.metadata,
|
|
11063
|
+
actions: data.actions,
|
|
10921
11064
|
boardId: currentBoardId
|
|
10922
11065
|
});
|
|
10923
11066
|
await loadFeatures();
|
|
@@ -10986,10 +11129,7 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
10986
11129
|
}
|
|
10987
11130
|
async function doPurgeDeletedCards() {
|
|
10988
11131
|
try {
|
|
10989
|
-
|
|
10990
|
-
for (const card of deletedCards) {
|
|
10991
|
-
await sdk.permanentlyDeleteCard(card.id, currentBoardId);
|
|
10992
|
-
}
|
|
11132
|
+
await sdk.purgeDeletedCards(currentBoardId);
|
|
10993
11133
|
await loadFeatures();
|
|
10994
11134
|
broadcast(buildInitMessage());
|
|
10995
11135
|
return true;
|
|
@@ -11036,6 +11176,20 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11036
11176
|
return { removed: false, error: String(err) };
|
|
11037
11177
|
}
|
|
11038
11178
|
}
|
|
11179
|
+
async function doCleanupColumn(columnId) {
|
|
11180
|
+
try {
|
|
11181
|
+
migrating = true;
|
|
11182
|
+
await sdk.cleanupColumn(columnId, currentBoardId);
|
|
11183
|
+
await loadFeatures();
|
|
11184
|
+
broadcast(buildInitMessage());
|
|
11185
|
+
return true;
|
|
11186
|
+
} catch (err) {
|
|
11187
|
+
console.error("Failed to cleanup column:", err);
|
|
11188
|
+
return false;
|
|
11189
|
+
} finally {
|
|
11190
|
+
migrating = false;
|
|
11191
|
+
}
|
|
11192
|
+
}
|
|
11039
11193
|
function doSaveSettings(newSettings) {
|
|
11040
11194
|
sdk.updateSettings(newSettings);
|
|
11041
11195
|
broadcast(buildInitMessage());
|
|
@@ -11164,6 +11318,7 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11164
11318
|
break;
|
|
11165
11319
|
currentEditingFeatureId = featureId;
|
|
11166
11320
|
const frontmatter = {
|
|
11321
|
+
version: feature.version ?? 0,
|
|
11167
11322
|
id: feature.id,
|
|
11168
11323
|
status: feature.status,
|
|
11169
11324
|
priority: feature.priority,
|
|
@@ -11175,7 +11330,8 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11175
11330
|
labels: feature.labels,
|
|
11176
11331
|
attachments: feature.attachments,
|
|
11177
11332
|
order: feature.order,
|
|
11178
|
-
metadata: feature.metadata
|
|
11333
|
+
metadata: feature.metadata,
|
|
11334
|
+
actions: feature.actions
|
|
11179
11335
|
};
|
|
11180
11336
|
ws.send(JSON.stringify({ type: "featureContent", featureId: feature.id, content: feature.content, frontmatter, comments: feature.comments || [] }));
|
|
11181
11337
|
break;
|
|
@@ -11191,7 +11347,8 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11191
11347
|
assignee: fm.assignee,
|
|
11192
11348
|
dueDate: fm.dueDate,
|
|
11193
11349
|
labels: fm.labels,
|
|
11194
|
-
attachments: fm.attachments
|
|
11350
|
+
attachments: fm.attachments,
|
|
11351
|
+
actions: fm.actions
|
|
11195
11352
|
});
|
|
11196
11353
|
break;
|
|
11197
11354
|
}
|
|
@@ -11219,11 +11376,15 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11219
11376
|
case "removeColumn":
|
|
11220
11377
|
await doRemoveColumn(msg.columnId);
|
|
11221
11378
|
break;
|
|
11379
|
+
case "cleanupColumn":
|
|
11380
|
+
await doCleanupColumn(msg.columnId);
|
|
11381
|
+
break;
|
|
11222
11382
|
case "removeAttachment": {
|
|
11223
11383
|
const featureId = msg.featureId;
|
|
11224
11384
|
const feature = await doRemoveAttachment(featureId, msg.attachment);
|
|
11225
11385
|
if (feature && currentEditingFeatureId === featureId) {
|
|
11226
11386
|
const frontmatter = {
|
|
11387
|
+
version: feature.version ?? 0,
|
|
11227
11388
|
id: feature.id,
|
|
11228
11389
|
status: feature.status,
|
|
11229
11390
|
priority: feature.priority,
|
|
@@ -11234,7 +11395,8 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11234
11395
|
completedAt: feature.completedAt,
|
|
11235
11396
|
labels: feature.labels,
|
|
11236
11397
|
attachments: feature.attachments,
|
|
11237
|
-
order: feature.order
|
|
11398
|
+
order: feature.order,
|
|
11399
|
+
actions: feature.actions
|
|
11238
11400
|
};
|
|
11239
11401
|
ws.send(JSON.stringify({ type: "featureContent", featureId: feature.id, content: feature.content, frontmatter, comments: feature.comments || [] }));
|
|
11240
11402
|
}
|
|
@@ -11247,6 +11409,7 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11247
11409
|
const feature = features.find((f) => f.id === msg.featureId);
|
|
11248
11410
|
if (feature && currentEditingFeatureId === msg.featureId) {
|
|
11249
11411
|
const frontmatter = {
|
|
11412
|
+
version: feature.version ?? 0,
|
|
11250
11413
|
id: feature.id,
|
|
11251
11414
|
status: feature.status,
|
|
11252
11415
|
priority: feature.priority,
|
|
@@ -11257,7 +11420,8 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11257
11420
|
completedAt: feature.completedAt,
|
|
11258
11421
|
labels: feature.labels,
|
|
11259
11422
|
attachments: feature.attachments,
|
|
11260
|
-
order: feature.order
|
|
11423
|
+
order: feature.order,
|
|
11424
|
+
actions: feature.actions
|
|
11261
11425
|
};
|
|
11262
11426
|
ws.send(JSON.stringify({ type: "featureContent", featureId: feature.id, content: feature.content, frontmatter, comments: feature.comments || [] }));
|
|
11263
11427
|
}
|
|
@@ -11270,6 +11434,7 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11270
11434
|
const feature = features.find((f) => f.id === msg.featureId);
|
|
11271
11435
|
if (feature && currentEditingFeatureId === msg.featureId) {
|
|
11272
11436
|
const frontmatter = {
|
|
11437
|
+
version: feature.version ?? 0,
|
|
11273
11438
|
id: feature.id,
|
|
11274
11439
|
status: feature.status,
|
|
11275
11440
|
priority: feature.priority,
|
|
@@ -11280,7 +11445,8 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11280
11445
|
completedAt: feature.completedAt,
|
|
11281
11446
|
labels: feature.labels,
|
|
11282
11447
|
attachments: feature.attachments,
|
|
11283
|
-
order: feature.order
|
|
11448
|
+
order: feature.order,
|
|
11449
|
+
actions: feature.actions
|
|
11284
11450
|
};
|
|
11285
11451
|
ws.send(JSON.stringify({ type: "featureContent", featureId: feature.id, content: feature.content, frontmatter, comments: feature.comments || [] }));
|
|
11286
11452
|
}
|
|
@@ -11291,6 +11457,7 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11291
11457
|
const feature = features.find((f) => f.id === msg.featureId);
|
|
11292
11458
|
if (feature && currentEditingFeatureId === msg.featureId) {
|
|
11293
11459
|
const frontmatter = {
|
|
11460
|
+
version: feature.version ?? 0,
|
|
11294
11461
|
id: feature.id,
|
|
11295
11462
|
status: feature.status,
|
|
11296
11463
|
priority: feature.priority,
|
|
@@ -11301,7 +11468,8 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11301
11468
|
completedAt: feature.completedAt,
|
|
11302
11469
|
labels: feature.labels,
|
|
11303
11470
|
attachments: feature.attachments,
|
|
11304
|
-
order: feature.order
|
|
11471
|
+
order: feature.order,
|
|
11472
|
+
actions: feature.actions
|
|
11305
11473
|
};
|
|
11306
11474
|
ws.send(JSON.stringify({ type: "featureContent", featureId: feature.id, content: feature.content, frontmatter, comments: feature.comments || [] }));
|
|
11307
11475
|
}
|
|
@@ -11359,13 +11527,26 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11359
11527
|
}
|
|
11360
11528
|
case "renameLabel": {
|
|
11361
11529
|
await sdk.renameLabel(msg.oldName, msg.newName);
|
|
11530
|
+
await loadFeatures();
|
|
11362
11531
|
broadcast({ type: "labelsUpdated", labels: sdk.getLabels() });
|
|
11363
11532
|
broadcast(buildInitMessage());
|
|
11364
11533
|
break;
|
|
11365
11534
|
}
|
|
11366
11535
|
case "deleteLabel": {
|
|
11367
|
-
sdk.deleteLabel(msg.name);
|
|
11536
|
+
await sdk.deleteLabel(msg.name);
|
|
11537
|
+
await loadFeatures();
|
|
11368
11538
|
broadcast({ type: "labelsUpdated", labels: sdk.getLabels() });
|
|
11539
|
+
broadcast(buildInitMessage());
|
|
11540
|
+
break;
|
|
11541
|
+
}
|
|
11542
|
+
case "triggerAction": {
|
|
11543
|
+
const { featureId, action, callbackKey } = msg;
|
|
11544
|
+
try {
|
|
11545
|
+
await sdk.triggerAction(featureId, action);
|
|
11546
|
+
ws.send(JSON.stringify({ type: "actionResult", callbackKey }));
|
|
11547
|
+
} catch (err) {
|
|
11548
|
+
ws.send(JSON.stringify({ type: "actionResult", callbackKey, error: String(err) }));
|
|
11549
|
+
}
|
|
11369
11550
|
break;
|
|
11370
11551
|
}
|
|
11371
11552
|
case "openFile":
|
|
@@ -11486,6 +11667,14 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11486
11667
|
const groupLabels = sdk.getLabelsInGroup(labelGroup);
|
|
11487
11668
|
result = result.filter((f) => f.labels.some((l) => groupLabels.includes(l)));
|
|
11488
11669
|
}
|
|
11670
|
+
const metaFilter = {};
|
|
11671
|
+
for (const [param, value] of url.searchParams.entries()) {
|
|
11672
|
+
if (param.startsWith("meta."))
|
|
11673
|
+
metaFilter[param.slice(5)] = value;
|
|
11674
|
+
}
|
|
11675
|
+
if (Object.keys(metaFilter).length > 0)
|
|
11676
|
+
result = result.filter((f) => matchesMetaFilter(f.metadata, metaFilter));
|
|
11677
|
+
result = applySortParam(result, url.searchParams.get("sort"));
|
|
11489
11678
|
return jsonOk(res, result);
|
|
11490
11679
|
} catch (err) {
|
|
11491
11680
|
return jsonError(res, 400, String(err));
|
|
@@ -11507,6 +11696,7 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11507
11696
|
dueDate: body.dueDate || null,
|
|
11508
11697
|
labels: body.labels || [],
|
|
11509
11698
|
metadata: body.metadata,
|
|
11699
|
+
actions: body.actions,
|
|
11510
11700
|
boardId
|
|
11511
11701
|
});
|
|
11512
11702
|
return jsonOk(res, sanitizeFeature(feature), 201);
|
|
@@ -11552,6 +11742,21 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11552
11742
|
return jsonError(res, 400, String(err));
|
|
11553
11743
|
}
|
|
11554
11744
|
}
|
|
11745
|
+
params = route("POST", "/api/boards/:boardId/tasks/:id/actions/:action");
|
|
11746
|
+
if (params) {
|
|
11747
|
+
try {
|
|
11748
|
+
const { boardId, id, action } = params;
|
|
11749
|
+
await sdk.triggerAction(id, action, boardId);
|
|
11750
|
+
res.writeHead(204);
|
|
11751
|
+
res.end();
|
|
11752
|
+
return;
|
|
11753
|
+
} catch (err) {
|
|
11754
|
+
const msg = String(err);
|
|
11755
|
+
if (msg.includes("Card not found"))
|
|
11756
|
+
return jsonError(res, 404, msg);
|
|
11757
|
+
return jsonError(res, 400, msg);
|
|
11758
|
+
}
|
|
11759
|
+
}
|
|
11555
11760
|
params = route("DELETE", "/api/boards/:boardId/tasks/:id/permanent");
|
|
11556
11761
|
if (params) {
|
|
11557
11762
|
try {
|
|
@@ -11609,6 +11814,14 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11609
11814
|
const groupLabels = sdk.getLabelsInGroup(labelGroup);
|
|
11610
11815
|
result = result.filter((f) => f.labels.some((l) => groupLabels.includes(l)));
|
|
11611
11816
|
}
|
|
11817
|
+
const metaFilter = {};
|
|
11818
|
+
for (const [param, value] of url.searchParams.entries()) {
|
|
11819
|
+
if (param.startsWith("meta."))
|
|
11820
|
+
metaFilter[param.slice(5)] = value;
|
|
11821
|
+
}
|
|
11822
|
+
if (Object.keys(metaFilter).length > 0)
|
|
11823
|
+
result = result.filter((f) => matchesMetaFilter(f.metadata, metaFilter));
|
|
11824
|
+
result = applySortParam(result, url.searchParams.get("sort"));
|
|
11612
11825
|
return jsonOk(res, result);
|
|
11613
11826
|
}
|
|
11614
11827
|
params = route("POST", "/api/tasks");
|
|
@@ -11622,7 +11835,8 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11622
11835
|
assignee: body.assignee || null,
|
|
11623
11836
|
dueDate: body.dueDate || null,
|
|
11624
11837
|
labels: body.labels || [],
|
|
11625
|
-
metadata: body.metadata
|
|
11838
|
+
metadata: body.metadata,
|
|
11839
|
+
actions: body.actions
|
|
11626
11840
|
};
|
|
11627
11841
|
if (!data.content)
|
|
11628
11842
|
return jsonError(res, 400, "content is required");
|
|
@@ -11670,6 +11884,21 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11670
11884
|
return jsonError(res, 400, String(err));
|
|
11671
11885
|
}
|
|
11672
11886
|
}
|
|
11887
|
+
params = route("POST", "/api/tasks/:id/actions/:action");
|
|
11888
|
+
if (params) {
|
|
11889
|
+
try {
|
|
11890
|
+
const { id, action } = params;
|
|
11891
|
+
await sdk.triggerAction(id, action);
|
|
11892
|
+
res.writeHead(204);
|
|
11893
|
+
res.end();
|
|
11894
|
+
return;
|
|
11895
|
+
} catch (err) {
|
|
11896
|
+
const msg = String(err);
|
|
11897
|
+
if (msg.includes("Card not found"))
|
|
11898
|
+
return jsonError(res, 404, msg);
|
|
11899
|
+
return jsonError(res, 400, msg);
|
|
11900
|
+
}
|
|
11901
|
+
}
|
|
11673
11902
|
params = route("DELETE", "/api/tasks/:id/permanent");
|
|
11674
11903
|
if (params) {
|
|
11675
11904
|
const { id } = params;
|
|
@@ -11919,6 +12148,9 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11919
12148
|
if (!newName)
|
|
11920
12149
|
return jsonError(res, 400, "newName is required");
|
|
11921
12150
|
await sdk.renameLabel(name, newName);
|
|
12151
|
+
await loadFeatures();
|
|
12152
|
+
broadcast({ type: "labelsUpdated", labels: sdk.getLabels() });
|
|
12153
|
+
broadcast(buildInitMessage());
|
|
11922
12154
|
return jsonOk(res, sdk.getLabels());
|
|
11923
12155
|
} catch (err) {
|
|
11924
12156
|
return jsonError(res, 400, String(err));
|
|
@@ -11928,7 +12160,10 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11928
12160
|
if (params) {
|
|
11929
12161
|
try {
|
|
11930
12162
|
const name = decodeURIComponent(params.name);
|
|
11931
|
-
sdk.deleteLabel(name);
|
|
12163
|
+
await sdk.deleteLabel(name);
|
|
12164
|
+
await loadFeatures();
|
|
12165
|
+
broadcast({ type: "labelsUpdated", labels: sdk.getLabels() });
|
|
12166
|
+
broadcast(buildInitMessage());
|
|
11932
12167
|
return jsonOk(res, { success: true });
|
|
11933
12168
|
} catch (err) {
|
|
11934
12169
|
return jsonError(res, 400, String(err));
|
|
@@ -11954,6 +12189,7 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11954
12189
|
const feature = features.find((f) => f.id === featureId);
|
|
11955
12190
|
if (feature && currentEditingFeatureId === featureId) {
|
|
11956
12191
|
const frontmatter = {
|
|
12192
|
+
version: feature.version ?? 0,
|
|
11957
12193
|
id: feature.id,
|
|
11958
12194
|
status: feature.status,
|
|
11959
12195
|
priority: feature.priority,
|
|
@@ -11964,7 +12200,9 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
11964
12200
|
completedAt: feature.completedAt,
|
|
11965
12201
|
labels: feature.labels,
|
|
11966
12202
|
attachments: feature.attachments,
|
|
11967
|
-
order: feature.order
|
|
12203
|
+
order: feature.order,
|
|
12204
|
+
metadata: feature.metadata,
|
|
12205
|
+
actions: feature.actions
|
|
11968
12206
|
};
|
|
11969
12207
|
broadcast({ type: "featureContent", featureId: feature.id, content: feature.content, frontmatter, comments: feature.comments || [] });
|
|
11970
12208
|
}
|
|
@@ -12071,6 +12309,7 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
12071
12309
|
const currentContent = serializeFeature(editingFeature);
|
|
12072
12310
|
if (currentContent !== lastWrittenContent) {
|
|
12073
12311
|
const frontmatter = {
|
|
12312
|
+
version: editingFeature.version ?? 0,
|
|
12074
12313
|
id: editingFeature.id,
|
|
12075
12314
|
status: editingFeature.status,
|
|
12076
12315
|
priority: editingFeature.priority,
|
|
@@ -12081,7 +12320,9 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
12081
12320
|
completedAt: editingFeature.completedAt,
|
|
12082
12321
|
labels: editingFeature.labels,
|
|
12083
12322
|
attachments: editingFeature.attachments,
|
|
12084
|
-
order: editingFeature.order
|
|
12323
|
+
order: editingFeature.order,
|
|
12324
|
+
metadata: editingFeature.metadata,
|
|
12325
|
+
actions: editingFeature.actions
|
|
12085
12326
|
};
|
|
12086
12327
|
broadcast({ type: "featureContent", featureId: editingFeature.id, content: editingFeature.content, frontmatter, comments: editingFeature.comments || [] });
|
|
12087
12328
|
}
|
|
@@ -12105,9 +12346,9 @@ function startServer(featuresDir, port2, webviewDir) {
|
|
|
12105
12346
|
}
|
|
12106
12347
|
|
|
12107
12348
|
// src/standalone/index.ts
|
|
12108
|
-
function parseArgs(args) {
|
|
12349
|
+
function parseArgs(args, defaultPort) {
|
|
12109
12350
|
let dir2 = ".kanban";
|
|
12110
|
-
let port2 =
|
|
12351
|
+
let port2 = defaultPort;
|
|
12111
12352
|
let noBrowser2 = false;
|
|
12112
12353
|
for (let i = 0; i < args.length; i++) {
|
|
12113
12354
|
switch (args[i]) {
|
|
@@ -12129,7 +12370,7 @@ Usage: kanban-md [options]
|
|
|
12129
12370
|
|
|
12130
12371
|
Options:
|
|
12131
12372
|
-d, --dir <path> Features directory (default: .kanban)
|
|
12132
|
-
-p, --port <number> Port to listen on (default: 3000)
|
|
12373
|
+
-p, --port <number> Port to listen on (default: .kanban.json port or 3000)
|
|
12133
12374
|
--no-browser Don't open browser automatically
|
|
12134
12375
|
-h, --help Show this help message
|
|
12135
12376
|
|
|
@@ -12145,7 +12386,8 @@ REST API available at http://localhost:<port>/api
|
|
|
12145
12386
|
}
|
|
12146
12387
|
return { dir: dir2, port: port2, noBrowser: noBrowser2 };
|
|
12147
12388
|
}
|
|
12148
|
-
var
|
|
12389
|
+
var configPort = readConfig(process.cwd()).port;
|
|
12390
|
+
var { dir, port, noBrowser } = parseArgs(process.argv.slice(2), configPort);
|
|
12149
12391
|
var server = startServer(dir, port);
|
|
12150
12392
|
if (!noBrowser) {
|
|
12151
12393
|
server.on("listening", async () => {
|