@hasna/todos 0.11.10 → 0.11.13
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/cli/index.js +775 -44
- package/dist/db/builtin-templates.d.ts +22 -0
- package/dist/db/builtin-templates.d.ts.map +1 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/templates.d.ts +71 -5
- package/dist/db/templates.d.ts.map +1 -1
- package/dist/db/webhooks.d.ts +11 -0
- package/dist/db/webhooks.d.ts.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +492 -31
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +633 -39
- package/dist/server/index.js +399 -35
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/types/index.d.ts +28 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/mcp/index.js
CHANGED
|
@@ -10954,6 +10954,36 @@ function ensureSchema(db) {
|
|
|
10954
10954
|
ensureColumn("projects", "org_id", "TEXT");
|
|
10955
10955
|
ensureColumn("plans", "task_list_id", "TEXT");
|
|
10956
10956
|
ensureColumn("plans", "agent_id", "TEXT");
|
|
10957
|
+
ensureColumn("task_templates", "variables", "TEXT DEFAULT '[]'");
|
|
10958
|
+
ensureColumn("task_templates", "version", "INTEGER NOT NULL DEFAULT 1");
|
|
10959
|
+
ensureColumn("template_tasks", "condition", "TEXT");
|
|
10960
|
+
ensureColumn("template_tasks", "include_template_id", "TEXT");
|
|
10961
|
+
ensureTable("template_versions", `
|
|
10962
|
+
CREATE TABLE template_versions (
|
|
10963
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
10964
|
+
template_id TEXT NOT NULL REFERENCES task_templates(id) ON DELETE CASCADE,
|
|
10965
|
+
version INTEGER NOT NULL,
|
|
10966
|
+
snapshot TEXT NOT NULL,
|
|
10967
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
10968
|
+
)`);
|
|
10969
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_template_versions_template ON template_versions(template_id)");
|
|
10970
|
+
ensureColumn("webhooks", "project_id", "TEXT");
|
|
10971
|
+
ensureColumn("webhooks", "task_list_id", "TEXT");
|
|
10972
|
+
ensureColumn("webhooks", "agent_id", "TEXT");
|
|
10973
|
+
ensureColumn("webhooks", "task_id", "TEXT");
|
|
10974
|
+
ensureTable("webhook_deliveries", `
|
|
10975
|
+
CREATE TABLE webhook_deliveries (
|
|
10976
|
+
id TEXT PRIMARY KEY,
|
|
10977
|
+
webhook_id TEXT NOT NULL REFERENCES webhooks(id) ON DELETE CASCADE,
|
|
10978
|
+
event TEXT NOT NULL,
|
|
10979
|
+
payload TEXT NOT NULL,
|
|
10980
|
+
status_code INTEGER,
|
|
10981
|
+
response TEXT,
|
|
10982
|
+
attempt INTEGER NOT NULL DEFAULT 1,
|
|
10983
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
10984
|
+
)`);
|
|
10985
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_webhook ON webhook_deliveries(webhook_id)");
|
|
10986
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_event ON webhook_deliveries(event)");
|
|
10957
10987
|
ensureColumn("task_comments", "type", "TEXT DEFAULT 'comment'");
|
|
10958
10988
|
ensureColumn("task_comments", "progress_pct", "INTEGER");
|
|
10959
10989
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_plan ON tasks(plan_id)");
|
|
@@ -11642,6 +11672,26 @@ var init_database = __esm(() => {
|
|
|
11642
11672
|
CREATE INDEX IF NOT EXISTS idx_template_tasks_template ON template_tasks(template_id);
|
|
11643
11673
|
|
|
11644
11674
|
INSERT OR IGNORE INTO _migrations (id) VALUES (37);
|
|
11675
|
+
`,
|
|
11676
|
+
`
|
|
11677
|
+
ALTER TABLE task_templates ADD COLUMN variables TEXT DEFAULT '[]';
|
|
11678
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (38);
|
|
11679
|
+
`,
|
|
11680
|
+
`
|
|
11681
|
+
ALTER TABLE template_tasks ADD COLUMN condition TEXT;
|
|
11682
|
+
ALTER TABLE template_tasks ADD COLUMN include_template_id TEXT;
|
|
11683
|
+
ALTER TABLE task_templates ADD COLUMN version INTEGER NOT NULL DEFAULT 1;
|
|
11684
|
+
|
|
11685
|
+
CREATE TABLE IF NOT EXISTS template_versions (
|
|
11686
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
11687
|
+
template_id TEXT NOT NULL REFERENCES task_templates(id) ON DELETE CASCADE,
|
|
11688
|
+
version INTEGER NOT NULL,
|
|
11689
|
+
snapshot TEXT NOT NULL,
|
|
11690
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
11691
|
+
);
|
|
11692
|
+
CREATE INDEX IF NOT EXISTS idx_template_versions_template ON template_versions(template_id);
|
|
11693
|
+
|
|
11694
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (39);
|
|
11645
11695
|
`
|
|
11646
11696
|
];
|
|
11647
11697
|
});
|
|
@@ -12080,18 +12130,37 @@ var init_recurrence = __esm(() => {
|
|
|
12080
12130
|
var exports_webhooks = {};
|
|
12081
12131
|
__export(exports_webhooks, {
|
|
12082
12132
|
listWebhooks: () => listWebhooks,
|
|
12133
|
+
listDeliveries: () => listDeliveries,
|
|
12083
12134
|
getWebhook: () => getWebhook,
|
|
12084
12135
|
dispatchWebhook: () => dispatchWebhook,
|
|
12085
12136
|
deleteWebhook: () => deleteWebhook,
|
|
12086
12137
|
createWebhook: () => createWebhook
|
|
12087
12138
|
});
|
|
12088
12139
|
function rowToWebhook(row) {
|
|
12089
|
-
return {
|
|
12140
|
+
return {
|
|
12141
|
+
...row,
|
|
12142
|
+
events: JSON.parse(row.events || "[]"),
|
|
12143
|
+
active: !!row.active,
|
|
12144
|
+
project_id: row.project_id || null,
|
|
12145
|
+
task_list_id: row.task_list_id || null,
|
|
12146
|
+
agent_id: row.agent_id || null,
|
|
12147
|
+
task_id: row.task_id || null
|
|
12148
|
+
};
|
|
12090
12149
|
}
|
|
12091
12150
|
function createWebhook(input, db) {
|
|
12092
12151
|
const d = db || getDatabase();
|
|
12093
12152
|
const id = uuid();
|
|
12094
|
-
d.run(`INSERT INTO webhooks (id, url, events, secret, created_at) VALUES (?, ?, ?, ?,
|
|
12153
|
+
d.run(`INSERT INTO webhooks (id, url, events, secret, project_id, task_list_id, agent_id, task_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
12154
|
+
id,
|
|
12155
|
+
input.url,
|
|
12156
|
+
JSON.stringify(input.events || []),
|
|
12157
|
+
input.secret || null,
|
|
12158
|
+
input.project_id || null,
|
|
12159
|
+
input.task_list_id || null,
|
|
12160
|
+
input.agent_id || null,
|
|
12161
|
+
input.task_id || null,
|
|
12162
|
+
now()
|
|
12163
|
+
]);
|
|
12095
12164
|
return getWebhook(id, d);
|
|
12096
12165
|
}
|
|
12097
12166
|
function getWebhook(id, db) {
|
|
@@ -12107,22 +12176,69 @@ function deleteWebhook(id, db) {
|
|
|
12107
12176
|
const d = db || getDatabase();
|
|
12108
12177
|
return d.run("DELETE FROM webhooks WHERE id = ?", [id]).changes > 0;
|
|
12109
12178
|
}
|
|
12179
|
+
function listDeliveries(webhookId, limit = 50, db) {
|
|
12180
|
+
const d = db || getDatabase();
|
|
12181
|
+
if (webhookId) {
|
|
12182
|
+
return d.query("SELECT * FROM webhook_deliveries WHERE webhook_id = ? ORDER BY created_at DESC LIMIT ?").all(webhookId, limit);
|
|
12183
|
+
}
|
|
12184
|
+
return d.query("SELECT * FROM webhook_deliveries ORDER BY created_at DESC LIMIT ?").all(limit);
|
|
12185
|
+
}
|
|
12186
|
+
function logDelivery(d, webhookId, event, payload, statusCode, response, attempt) {
|
|
12187
|
+
const id = uuid();
|
|
12188
|
+
d.run(`INSERT INTO webhook_deliveries (id, webhook_id, event, payload, status_code, response, attempt, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, webhookId, event, payload, statusCode, response, attempt, now()]);
|
|
12189
|
+
}
|
|
12190
|
+
function matchesScope(wh, payload) {
|
|
12191
|
+
if (wh.project_id && payload.project_id !== wh.project_id)
|
|
12192
|
+
return false;
|
|
12193
|
+
if (wh.task_list_id && payload.task_list_id !== wh.task_list_id)
|
|
12194
|
+
return false;
|
|
12195
|
+
if (wh.agent_id && payload.agent_id !== wh.agent_id && payload.assigned_to !== wh.agent_id)
|
|
12196
|
+
return false;
|
|
12197
|
+
if (wh.task_id && payload.id !== wh.task_id)
|
|
12198
|
+
return false;
|
|
12199
|
+
return true;
|
|
12200
|
+
}
|
|
12201
|
+
async function deliverWebhook(wh, event, body, attempt, db) {
|
|
12202
|
+
try {
|
|
12203
|
+
const headers = { "Content-Type": "application/json" };
|
|
12204
|
+
if (wh.secret) {
|
|
12205
|
+
const encoder = new TextEncoder;
|
|
12206
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
12207
|
+
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
12208
|
+
headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
12209
|
+
}
|
|
12210
|
+
const resp = await fetch(wh.url, { method: "POST", headers, body });
|
|
12211
|
+
const respText = await resp.text().catch(() => "");
|
|
12212
|
+
logDelivery(db, wh.id, event, body, resp.status, respText.slice(0, 1000), attempt);
|
|
12213
|
+
if (resp.status >= 400 && attempt < MAX_RETRY_ATTEMPTS) {
|
|
12214
|
+
const delay = RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
12215
|
+
setTimeout(() => {
|
|
12216
|
+
deliverWebhook(wh, event, body, attempt + 1, db).catch(() => {});
|
|
12217
|
+
}, delay);
|
|
12218
|
+
}
|
|
12219
|
+
} catch (err) {
|
|
12220
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
12221
|
+
logDelivery(db, wh.id, event, body, null, errorMsg.slice(0, 1000), attempt);
|
|
12222
|
+
if (attempt < MAX_RETRY_ATTEMPTS) {
|
|
12223
|
+
const delay = RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
12224
|
+
setTimeout(() => {
|
|
12225
|
+
deliverWebhook(wh, event, body, attempt + 1, db).catch(() => {});
|
|
12226
|
+
}, delay);
|
|
12227
|
+
}
|
|
12228
|
+
}
|
|
12229
|
+
}
|
|
12110
12230
|
async function dispatchWebhook(event, payload, db) {
|
|
12111
|
-
const
|
|
12231
|
+
const d = db || getDatabase();
|
|
12232
|
+
const webhooks = listWebhooks(d).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
|
|
12233
|
+
const payloadObj = typeof payload === "object" && payload !== null ? payload : {};
|
|
12112
12234
|
for (const wh of webhooks) {
|
|
12113
|
-
|
|
12114
|
-
|
|
12115
|
-
|
|
12116
|
-
|
|
12117
|
-
const encoder = new TextEncoder;
|
|
12118
|
-
const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
12119
|
-
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
12120
|
-
headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
12121
|
-
}
|
|
12122
|
-
fetch(wh.url, { method: "POST", headers, body }).catch(() => {});
|
|
12123
|
-
} catch {}
|
|
12235
|
+
if (!matchesScope(wh, payloadObj))
|
|
12236
|
+
continue;
|
|
12237
|
+
const body = JSON.stringify({ event, payload, timestamp: now() });
|
|
12238
|
+
deliverWebhook(wh, event, body, 1, d).catch(() => {});
|
|
12124
12239
|
}
|
|
12125
12240
|
}
|
|
12241
|
+
var MAX_RETRY_ATTEMPTS = 3, RETRY_BASE_DELAY_MS = 1000;
|
|
12126
12242
|
var init_webhooks = __esm(() => {
|
|
12127
12243
|
init_database();
|
|
12128
12244
|
});
|
|
@@ -12133,10 +12249,17 @@ __export(exports_templates, {
|
|
|
12133
12249
|
updateTemplate: () => updateTemplate,
|
|
12134
12250
|
tasksFromTemplate: () => tasksFromTemplate,
|
|
12135
12251
|
taskFromTemplate: () => taskFromTemplate,
|
|
12252
|
+
resolveVariables: () => resolveVariables,
|
|
12253
|
+
previewTemplate: () => previewTemplate,
|
|
12136
12254
|
listTemplates: () => listTemplates,
|
|
12255
|
+
listTemplateVersions: () => listTemplateVersions,
|
|
12256
|
+
importTemplate: () => importTemplate,
|
|
12137
12257
|
getTemplateWithTasks: () => getTemplateWithTasks,
|
|
12258
|
+
getTemplateVersion: () => getTemplateVersion,
|
|
12138
12259
|
getTemplateTasks: () => getTemplateTasks,
|
|
12139
12260
|
getTemplate: () => getTemplate,
|
|
12261
|
+
exportTemplate: () => exportTemplate,
|
|
12262
|
+
evaluateCondition: () => evaluateCondition,
|
|
12140
12263
|
deleteTemplate: () => deleteTemplate,
|
|
12141
12264
|
createTemplate: () => createTemplate,
|
|
12142
12265
|
addTemplateTasks: () => addTemplateTasks
|
|
@@ -12145,8 +12268,10 @@ function rowToTemplate(row) {
|
|
|
12145
12268
|
return {
|
|
12146
12269
|
...row,
|
|
12147
12270
|
tags: JSON.parse(row.tags || "[]"),
|
|
12271
|
+
variables: JSON.parse(row.variables || "[]"),
|
|
12148
12272
|
metadata: JSON.parse(row.metadata || "{}"),
|
|
12149
|
-
priority: row.priority || "medium"
|
|
12273
|
+
priority: row.priority || "medium",
|
|
12274
|
+
version: row.version ?? 1
|
|
12150
12275
|
};
|
|
12151
12276
|
}
|
|
12152
12277
|
function rowToTemplateTask(row) {
|
|
@@ -12155,7 +12280,9 @@ function rowToTemplateTask(row) {
|
|
|
12155
12280
|
tags: JSON.parse(row.tags || "[]"),
|
|
12156
12281
|
depends_on_positions: JSON.parse(row.depends_on_positions || "[]"),
|
|
12157
12282
|
metadata: JSON.parse(row.metadata || "{}"),
|
|
12158
|
-
priority: row.priority || "medium"
|
|
12283
|
+
priority: row.priority || "medium",
|
|
12284
|
+
condition: row.condition ?? null,
|
|
12285
|
+
include_template_id: row.include_template_id ?? null
|
|
12159
12286
|
};
|
|
12160
12287
|
}
|
|
12161
12288
|
function resolveTemplateId(id, d) {
|
|
@@ -12164,14 +12291,15 @@ function resolveTemplateId(id, d) {
|
|
|
12164
12291
|
function createTemplate(input, db) {
|
|
12165
12292
|
const d = db || getDatabase();
|
|
12166
12293
|
const id = uuid();
|
|
12167
|
-
d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, project_id, plan_id, metadata, created_at)
|
|
12168
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
12294
|
+
d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at)
|
|
12295
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
12169
12296
|
id,
|
|
12170
12297
|
input.name,
|
|
12171
12298
|
input.title_pattern,
|
|
12172
12299
|
input.description || null,
|
|
12173
12300
|
input.priority || "medium",
|
|
12174
12301
|
JSON.stringify(input.tags || []),
|
|
12302
|
+
JSON.stringify(input.variables || []),
|
|
12175
12303
|
input.project_id || null,
|
|
12176
12304
|
input.plan_id || null,
|
|
12177
12305
|
JSON.stringify(input.metadata || {}),
|
|
@@ -12206,7 +12334,23 @@ function updateTemplate(id, updates, db) {
|
|
|
12206
12334
|
const resolved = resolveTemplateId(id, d);
|
|
12207
12335
|
if (!resolved)
|
|
12208
12336
|
return null;
|
|
12209
|
-
const
|
|
12337
|
+
const current = getTemplateWithTasks(resolved, d);
|
|
12338
|
+
if (current) {
|
|
12339
|
+
const snapshot = JSON.stringify({
|
|
12340
|
+
name: current.name,
|
|
12341
|
+
title_pattern: current.title_pattern,
|
|
12342
|
+
description: current.description,
|
|
12343
|
+
priority: current.priority,
|
|
12344
|
+
tags: current.tags,
|
|
12345
|
+
variables: current.variables,
|
|
12346
|
+
project_id: current.project_id,
|
|
12347
|
+
plan_id: current.plan_id,
|
|
12348
|
+
metadata: current.metadata,
|
|
12349
|
+
tasks: current.tasks
|
|
12350
|
+
});
|
|
12351
|
+
d.run(`INSERT INTO template_versions (id, template_id, version, snapshot, created_at) VALUES (?, ?, ?, ?, ?)`, [uuid(), resolved, current.version, snapshot, now()]);
|
|
12352
|
+
}
|
|
12353
|
+
const sets = ["version = version + 1"];
|
|
12210
12354
|
const values = [];
|
|
12211
12355
|
if (updates.name !== undefined) {
|
|
12212
12356
|
sets.push("name = ?");
|
|
@@ -12228,6 +12372,10 @@ function updateTemplate(id, updates, db) {
|
|
|
12228
12372
|
sets.push("tags = ?");
|
|
12229
12373
|
values.push(JSON.stringify(updates.tags));
|
|
12230
12374
|
}
|
|
12375
|
+
if (updates.variables !== undefined) {
|
|
12376
|
+
sets.push("variables = ?");
|
|
12377
|
+
values.push(JSON.stringify(updates.variables));
|
|
12378
|
+
}
|
|
12231
12379
|
if (updates.project_id !== undefined) {
|
|
12232
12380
|
sets.push("project_id = ?");
|
|
12233
12381
|
values.push(updates.project_id);
|
|
@@ -12240,8 +12388,6 @@ function updateTemplate(id, updates, db) {
|
|
|
12240
12388
|
sets.push("metadata = ?");
|
|
12241
12389
|
values.push(JSON.stringify(updates.metadata));
|
|
12242
12390
|
}
|
|
12243
|
-
if (sets.length === 0)
|
|
12244
|
-
return getTemplate(resolved, d);
|
|
12245
12391
|
values.push(resolved);
|
|
12246
12392
|
d.run(`UPDATE task_templates SET ${sets.join(", ")} WHERE id = ?`, values);
|
|
12247
12393
|
return getTemplate(resolved, d);
|
|
@@ -12271,8 +12417,8 @@ function addTemplateTasks(templateId, tasks, db) {
|
|
|
12271
12417
|
for (let i = 0;i < tasks.length; i++) {
|
|
12272
12418
|
const task = tasks[i];
|
|
12273
12419
|
const id = uuid();
|
|
12274
|
-
d.run(`INSERT INTO template_tasks (id, template_id, position, title_pattern, description, priority, tags, task_type, depends_on_positions, metadata, created_at)
|
|
12275
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
12420
|
+
d.run(`INSERT INTO template_tasks (id, template_id, position, title_pattern, description, priority, tags, task_type, condition, include_template_id, depends_on_positions, metadata, created_at)
|
|
12421
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
12276
12422
|
id,
|
|
12277
12423
|
templateId,
|
|
12278
12424
|
i,
|
|
@@ -12281,6 +12427,8 @@ function addTemplateTasks(templateId, tasks, db) {
|
|
|
12281
12427
|
task.priority || "medium",
|
|
12282
12428
|
JSON.stringify(task.tags || []),
|
|
12283
12429
|
task.task_type || null,
|
|
12430
|
+
task.condition || null,
|
|
12431
|
+
task.include_template_id || null,
|
|
12284
12432
|
JSON.stringify(task.depends_on || []),
|
|
12285
12433
|
JSON.stringify(task.metadata || {}),
|
|
12286
12434
|
now()
|
|
@@ -12308,11 +12456,142 @@ function getTemplateTasks(templateId, db) {
|
|
|
12308
12456
|
const rows = d.query("SELECT * FROM template_tasks WHERE template_id = ? ORDER BY position").all(resolved);
|
|
12309
12457
|
return rows.map(rowToTemplateTask);
|
|
12310
12458
|
}
|
|
12311
|
-
function
|
|
12459
|
+
function evaluateCondition(condition, variables) {
|
|
12460
|
+
if (!condition || condition.trim() === "")
|
|
12461
|
+
return true;
|
|
12462
|
+
const trimmed = condition.trim();
|
|
12463
|
+
const eqMatch = trimmed.match(/^\{([^}]+)\}\s*==\s*(.+)$/);
|
|
12464
|
+
if (eqMatch) {
|
|
12465
|
+
const varName = eqMatch[1];
|
|
12466
|
+
const expected = eqMatch[2].trim();
|
|
12467
|
+
return (variables[varName] ?? "") === expected;
|
|
12468
|
+
}
|
|
12469
|
+
const neqMatch = trimmed.match(/^\{([^}]+)\}\s*!=\s*(.+)$/);
|
|
12470
|
+
if (neqMatch) {
|
|
12471
|
+
const varName = neqMatch[1];
|
|
12472
|
+
const expected = neqMatch[2].trim();
|
|
12473
|
+
return (variables[varName] ?? "") !== expected;
|
|
12474
|
+
}
|
|
12475
|
+
const falsyMatch = trimmed.match(/^!\{([^}]+)\}$/);
|
|
12476
|
+
if (falsyMatch) {
|
|
12477
|
+
const varName = falsyMatch[1];
|
|
12478
|
+
const val = variables[varName];
|
|
12479
|
+
return !val || val === "" || val === "false";
|
|
12480
|
+
}
|
|
12481
|
+
const truthyMatch = trimmed.match(/^\{([^}]+)\}$/);
|
|
12482
|
+
if (truthyMatch) {
|
|
12483
|
+
const varName = truthyMatch[1];
|
|
12484
|
+
const val = variables[varName];
|
|
12485
|
+
return !!val && val !== "" && val !== "false";
|
|
12486
|
+
}
|
|
12487
|
+
return true;
|
|
12488
|
+
}
|
|
12489
|
+
function exportTemplate(id, db) {
|
|
12490
|
+
const d = db || getDatabase();
|
|
12491
|
+
const template = getTemplateWithTasks(id, d);
|
|
12492
|
+
if (!template)
|
|
12493
|
+
throw new Error(`Template not found: ${id}`);
|
|
12494
|
+
return {
|
|
12495
|
+
name: template.name,
|
|
12496
|
+
title_pattern: template.title_pattern,
|
|
12497
|
+
description: template.description,
|
|
12498
|
+
priority: template.priority,
|
|
12499
|
+
tags: template.tags,
|
|
12500
|
+
variables: template.variables,
|
|
12501
|
+
project_id: template.project_id,
|
|
12502
|
+
plan_id: template.plan_id,
|
|
12503
|
+
metadata: template.metadata,
|
|
12504
|
+
tasks: template.tasks.map((t) => ({
|
|
12505
|
+
position: t.position,
|
|
12506
|
+
title_pattern: t.title_pattern,
|
|
12507
|
+
description: t.description,
|
|
12508
|
+
priority: t.priority,
|
|
12509
|
+
tags: t.tags,
|
|
12510
|
+
task_type: t.task_type,
|
|
12511
|
+
condition: t.condition,
|
|
12512
|
+
include_template_id: t.include_template_id,
|
|
12513
|
+
depends_on_positions: t.depends_on_positions,
|
|
12514
|
+
metadata: t.metadata
|
|
12515
|
+
}))
|
|
12516
|
+
};
|
|
12517
|
+
}
|
|
12518
|
+
function importTemplate(json, db) {
|
|
12519
|
+
const d = db || getDatabase();
|
|
12520
|
+
const taskInputs = (json.tasks || []).map((t) => ({
|
|
12521
|
+
title_pattern: t.title_pattern,
|
|
12522
|
+
description: t.description ?? undefined,
|
|
12523
|
+
priority: t.priority,
|
|
12524
|
+
tags: t.tags,
|
|
12525
|
+
task_type: t.task_type ?? undefined,
|
|
12526
|
+
condition: t.condition ?? undefined,
|
|
12527
|
+
include_template_id: t.include_template_id ?? undefined,
|
|
12528
|
+
depends_on: t.depends_on_positions,
|
|
12529
|
+
metadata: t.metadata
|
|
12530
|
+
}));
|
|
12531
|
+
return createTemplate({
|
|
12532
|
+
name: json.name,
|
|
12533
|
+
title_pattern: json.title_pattern,
|
|
12534
|
+
description: json.description ?? undefined,
|
|
12535
|
+
priority: json.priority,
|
|
12536
|
+
tags: json.tags,
|
|
12537
|
+
variables: json.variables,
|
|
12538
|
+
project_id: json.project_id ?? undefined,
|
|
12539
|
+
plan_id: json.plan_id ?? undefined,
|
|
12540
|
+
metadata: json.metadata,
|
|
12541
|
+
tasks: taskInputs
|
|
12542
|
+
}, d);
|
|
12543
|
+
}
|
|
12544
|
+
function getTemplateVersion(id, version, db) {
|
|
12545
|
+
const d = db || getDatabase();
|
|
12546
|
+
const resolved = resolveTemplateId(id, d);
|
|
12547
|
+
if (!resolved)
|
|
12548
|
+
return null;
|
|
12549
|
+
const row = d.query("SELECT * FROM template_versions WHERE template_id = ? AND version = ?").get(resolved, version);
|
|
12550
|
+
return row || null;
|
|
12551
|
+
}
|
|
12552
|
+
function listTemplateVersions(id, db) {
|
|
12553
|
+
const d = db || getDatabase();
|
|
12554
|
+
const resolved = resolveTemplateId(id, d);
|
|
12555
|
+
if (!resolved)
|
|
12556
|
+
return [];
|
|
12557
|
+
return d.query("SELECT * FROM template_versions WHERE template_id = ? ORDER BY version DESC").all(resolved);
|
|
12558
|
+
}
|
|
12559
|
+
function resolveVariables(templateVars, provided) {
|
|
12560
|
+
const merged = { ...provided };
|
|
12561
|
+
for (const v of templateVars) {
|
|
12562
|
+
if (merged[v.name] === undefined && v.default !== undefined) {
|
|
12563
|
+
merged[v.name] = v.default;
|
|
12564
|
+
}
|
|
12565
|
+
}
|
|
12566
|
+
const missing = [];
|
|
12567
|
+
for (const v of templateVars) {
|
|
12568
|
+
if (v.required && merged[v.name] === undefined) {
|
|
12569
|
+
missing.push(v.name);
|
|
12570
|
+
}
|
|
12571
|
+
}
|
|
12572
|
+
if (missing.length > 0) {
|
|
12573
|
+
throw new Error(`Missing required template variable(s): ${missing.join(", ")}`);
|
|
12574
|
+
}
|
|
12575
|
+
return merged;
|
|
12576
|
+
}
|
|
12577
|
+
function substituteVars(text, variables) {
|
|
12578
|
+
let result = text;
|
|
12579
|
+
for (const [key, val] of Object.entries(variables)) {
|
|
12580
|
+
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), val);
|
|
12581
|
+
}
|
|
12582
|
+
return result;
|
|
12583
|
+
}
|
|
12584
|
+
function tasksFromTemplate(templateId, projectId, variables, taskListId, db, _visitedTemplateIds) {
|
|
12312
12585
|
const d = db || getDatabase();
|
|
12313
12586
|
const template = getTemplateWithTasks(templateId, d);
|
|
12314
12587
|
if (!template)
|
|
12315
12588
|
throw new Error(`Template not found: ${templateId}`);
|
|
12589
|
+
const visited = _visitedTemplateIds || new Set;
|
|
12590
|
+
if (visited.has(template.id)) {
|
|
12591
|
+
throw new Error(`Circular template reference detected: ${template.id}`);
|
|
12592
|
+
}
|
|
12593
|
+
visited.add(template.id);
|
|
12594
|
+
const resolved = resolveVariables(template.variables, variables);
|
|
12316
12595
|
if (template.tasks.length === 0) {
|
|
12317
12596
|
const input = taskFromTemplate(templateId, { project_id: projectId, task_list_id: taskListId }, d);
|
|
12318
12597
|
const task = createTask(input, d);
|
|
@@ -12320,16 +12599,27 @@ function tasksFromTemplate(templateId, projectId, variables, taskListId, db) {
|
|
|
12320
12599
|
}
|
|
12321
12600
|
const createdTasks = [];
|
|
12322
12601
|
const positionToId = new Map;
|
|
12602
|
+
const skippedPositions = new Set;
|
|
12323
12603
|
for (const tt of template.tasks) {
|
|
12324
|
-
|
|
12325
|
-
|
|
12326
|
-
|
|
12327
|
-
|
|
12328
|
-
|
|
12329
|
-
|
|
12330
|
-
|
|
12604
|
+
if (tt.include_template_id) {
|
|
12605
|
+
const includedTasks = tasksFromTemplate(tt.include_template_id, projectId, resolved, taskListId, d, visited);
|
|
12606
|
+
createdTasks.push(...includedTasks);
|
|
12607
|
+
if (includedTasks.length > 0) {
|
|
12608
|
+
positionToId.set(tt.position, includedTasks[0].id);
|
|
12609
|
+
} else {
|
|
12610
|
+
skippedPositions.add(tt.position);
|
|
12331
12611
|
}
|
|
12612
|
+
continue;
|
|
12332
12613
|
}
|
|
12614
|
+
if (tt.condition && !evaluateCondition(tt.condition, resolved)) {
|
|
12615
|
+
skippedPositions.add(tt.position);
|
|
12616
|
+
continue;
|
|
12617
|
+
}
|
|
12618
|
+
let title = tt.title_pattern;
|
|
12619
|
+
let desc = tt.description;
|
|
12620
|
+
title = substituteVars(title, resolved);
|
|
12621
|
+
if (desc)
|
|
12622
|
+
desc = substituteVars(desc, resolved);
|
|
12333
12623
|
const task = createTask({
|
|
12334
12624
|
title,
|
|
12335
12625
|
description: desc ?? undefined,
|
|
@@ -12344,8 +12634,14 @@ function tasksFromTemplate(templateId, projectId, variables, taskListId, db) {
|
|
|
12344
12634
|
positionToId.set(tt.position, task.id);
|
|
12345
12635
|
}
|
|
12346
12636
|
for (const tt of template.tasks) {
|
|
12637
|
+
if (skippedPositions.has(tt.position))
|
|
12638
|
+
continue;
|
|
12639
|
+
if (tt.include_template_id)
|
|
12640
|
+
continue;
|
|
12347
12641
|
const deps = tt.depends_on_positions;
|
|
12348
12642
|
for (const depPos of deps) {
|
|
12643
|
+
if (skippedPositions.has(depPos))
|
|
12644
|
+
continue;
|
|
12349
12645
|
const taskId = positionToId.get(tt.position);
|
|
12350
12646
|
const depId = positionToId.get(depPos);
|
|
12351
12647
|
if (taskId && depId) {
|
|
@@ -12355,6 +12651,47 @@ function tasksFromTemplate(templateId, projectId, variables, taskListId, db) {
|
|
|
12355
12651
|
}
|
|
12356
12652
|
return createdTasks;
|
|
12357
12653
|
}
|
|
12654
|
+
function previewTemplate(templateId, variables, db) {
|
|
12655
|
+
const d = db || getDatabase();
|
|
12656
|
+
const template = getTemplateWithTasks(templateId, d);
|
|
12657
|
+
if (!template)
|
|
12658
|
+
throw new Error(`Template not found: ${templateId}`);
|
|
12659
|
+
const resolved = resolveVariables(template.variables, variables);
|
|
12660
|
+
const tasks = [];
|
|
12661
|
+
if (template.tasks.length === 0) {
|
|
12662
|
+
tasks.push({
|
|
12663
|
+
position: 0,
|
|
12664
|
+
title: substituteVars(template.title_pattern, resolved),
|
|
12665
|
+
description: template.description ? substituteVars(template.description, resolved) : null,
|
|
12666
|
+
priority: template.priority,
|
|
12667
|
+
tags: template.tags,
|
|
12668
|
+
task_type: null,
|
|
12669
|
+
depends_on_positions: []
|
|
12670
|
+
});
|
|
12671
|
+
} else {
|
|
12672
|
+
for (const tt of template.tasks) {
|
|
12673
|
+
if (tt.condition && !evaluateCondition(tt.condition, resolved))
|
|
12674
|
+
continue;
|
|
12675
|
+
tasks.push({
|
|
12676
|
+
position: tt.position,
|
|
12677
|
+
title: substituteVars(tt.title_pattern, resolved),
|
|
12678
|
+
description: tt.description ? substituteVars(tt.description, resolved) : null,
|
|
12679
|
+
priority: tt.priority,
|
|
12680
|
+
tags: tt.tags,
|
|
12681
|
+
task_type: tt.task_type,
|
|
12682
|
+
depends_on_positions: tt.depends_on_positions
|
|
12683
|
+
});
|
|
12684
|
+
}
|
|
12685
|
+
}
|
|
12686
|
+
return {
|
|
12687
|
+
template_id: template.id,
|
|
12688
|
+
template_name: template.name,
|
|
12689
|
+
description: template.description,
|
|
12690
|
+
variables: template.variables,
|
|
12691
|
+
resolved_variables: resolved,
|
|
12692
|
+
tasks
|
|
12693
|
+
};
|
|
12694
|
+
}
|
|
12358
12695
|
var init_templates = __esm(() => {
|
|
12359
12696
|
init_database();
|
|
12360
12697
|
init_tasks();
|
|
@@ -14439,6 +14776,118 @@ var init_burndown = __esm(() => {
|
|
|
14439
14776
|
init_database();
|
|
14440
14777
|
});
|
|
14441
14778
|
|
|
14779
|
+
// src/db/builtin-templates.ts
|
|
14780
|
+
var exports_builtin_templates = {};
|
|
14781
|
+
__export(exports_builtin_templates, {
|
|
14782
|
+
initBuiltinTemplates: () => initBuiltinTemplates,
|
|
14783
|
+
BUILTIN_TEMPLATES: () => BUILTIN_TEMPLATES
|
|
14784
|
+
});
|
|
14785
|
+
function initBuiltinTemplates(db) {
|
|
14786
|
+
const d = db || getDatabase();
|
|
14787
|
+
const existing = listTemplates(d);
|
|
14788
|
+
const existingNames = new Set(existing.map((t) => t.name));
|
|
14789
|
+
let created = 0;
|
|
14790
|
+
let skipped = 0;
|
|
14791
|
+
const names = [];
|
|
14792
|
+
for (const bt of BUILTIN_TEMPLATES) {
|
|
14793
|
+
if (existingNames.has(bt.name)) {
|
|
14794
|
+
skipped++;
|
|
14795
|
+
continue;
|
|
14796
|
+
}
|
|
14797
|
+
const tasks = bt.tasks.map((t) => ({
|
|
14798
|
+
title_pattern: t.title_pattern,
|
|
14799
|
+
description: t.description,
|
|
14800
|
+
priority: t.priority,
|
|
14801
|
+
tags: t.tags,
|
|
14802
|
+
task_type: t.task_type,
|
|
14803
|
+
depends_on: t.depends_on_positions || t.depends_on,
|
|
14804
|
+
metadata: t.metadata
|
|
14805
|
+
}));
|
|
14806
|
+
createTemplate({
|
|
14807
|
+
name: bt.name,
|
|
14808
|
+
title_pattern: `${bt.name}: {${bt.variables[0]?.name || "name"}}`,
|
|
14809
|
+
description: bt.description,
|
|
14810
|
+
variables: bt.variables,
|
|
14811
|
+
tasks
|
|
14812
|
+
}, d);
|
|
14813
|
+
created++;
|
|
14814
|
+
names.push(bt.name);
|
|
14815
|
+
}
|
|
14816
|
+
return { created, skipped, names };
|
|
14817
|
+
}
|
|
14818
|
+
var BUILTIN_TEMPLATES;
|
|
14819
|
+
var init_builtin_templates = __esm(() => {
|
|
14820
|
+
init_database();
|
|
14821
|
+
init_templates();
|
|
14822
|
+
BUILTIN_TEMPLATES = [
|
|
14823
|
+
{
|
|
14824
|
+
name: "open-source-project",
|
|
14825
|
+
description: "Full open-source project bootstrap \u2014 scaffold to publish",
|
|
14826
|
+
variables: [
|
|
14827
|
+
{ name: "name", required: true, description: "Service name" },
|
|
14828
|
+
{ name: "org", required: false, default: "hasna", description: "GitHub org" }
|
|
14829
|
+
],
|
|
14830
|
+
tasks: [
|
|
14831
|
+
{ position: 0, title_pattern: "Scaffold {name} package structure", priority: "critical" },
|
|
14832
|
+
{ position: 1, title_pattern: "Create {name} SQLite database + migrations", priority: "critical", depends_on_positions: [0] },
|
|
14833
|
+
{ position: 2, title_pattern: "Implement {name} CRUD operations", priority: "high", depends_on_positions: [1] },
|
|
14834
|
+
{ position: 3, title_pattern: "Build {name} MCP server with standard tools", priority: "high", depends_on_positions: [2] },
|
|
14835
|
+
{ position: 4, title_pattern: "Build {name} CLI with Commander.js", priority: "high", depends_on_positions: [2] },
|
|
14836
|
+
{ position: 5, title_pattern: "Build {name} REST API", priority: "medium", depends_on_positions: [2] },
|
|
14837
|
+
{ position: 6, title_pattern: "Write unit tests for {name}", priority: "high", depends_on_positions: [2, 3, 4] },
|
|
14838
|
+
{ position: 7, title_pattern: "Add Apache 2.0 license and README", priority: "medium", depends_on_positions: [0] },
|
|
14839
|
+
{ position: 8, title_pattern: "Create GitHub repo {org}/{name}", priority: "medium", depends_on_positions: [7] },
|
|
14840
|
+
{ position: 9, title_pattern: "Add @hasna/cloud adapter", priority: "medium", depends_on_positions: [1] },
|
|
14841
|
+
{ position: 10, title_pattern: "Write PostgreSQL migrations for {name}", priority: "medium", depends_on_positions: [1] },
|
|
14842
|
+
{ position: 11, title_pattern: "Add feedback system + send_feedback MCP tool", priority: "medium", depends_on_positions: [3] },
|
|
14843
|
+
{ position: 12, title_pattern: "Add agent tools (register_agent, heartbeat, set_focus, list_agents)", priority: "medium", depends_on_positions: [3] },
|
|
14844
|
+
{ position: 13, title_pattern: "Create RDS database for {name}", priority: "low", depends_on_positions: [10] },
|
|
14845
|
+
{ position: 14, title_pattern: "Publish @hasna/{name} to npm", priority: "high", depends_on_positions: [6, 7, 8] },
|
|
14846
|
+
{ position: 15, title_pattern: "Install @hasna/{name} globally and verify", priority: "medium", depends_on_positions: [14] }
|
|
14847
|
+
]
|
|
14848
|
+
},
|
|
14849
|
+
{
|
|
14850
|
+
name: "bug-fix",
|
|
14851
|
+
description: "Standard bug fix workflow",
|
|
14852
|
+
variables: [{ name: "bug", required: true, description: "Bug description" }],
|
|
14853
|
+
tasks: [
|
|
14854
|
+
{ position: 0, title_pattern: "Reproduce: {bug}", priority: "critical" },
|
|
14855
|
+
{ position: 1, title_pattern: "Diagnose root cause of {bug}", priority: "critical", depends_on_positions: [0] },
|
|
14856
|
+
{ position: 2, title_pattern: "Implement fix for {bug}", priority: "critical", depends_on_positions: [1] },
|
|
14857
|
+
{ position: 3, title_pattern: "Write regression test for {bug}", priority: "high", depends_on_positions: [2] },
|
|
14858
|
+
{ position: 4, title_pattern: "Publish fix and verify in production", priority: "high", depends_on_positions: [3] }
|
|
14859
|
+
]
|
|
14860
|
+
},
|
|
14861
|
+
{
|
|
14862
|
+
name: "feature",
|
|
14863
|
+
description: "Standard feature development workflow",
|
|
14864
|
+
variables: [{ name: "feature", required: true }, { name: "scope", required: false, default: "medium" }],
|
|
14865
|
+
tasks: [
|
|
14866
|
+
{ position: 0, title_pattern: "Write spec for {feature}", priority: "high" },
|
|
14867
|
+
{ position: 1, title_pattern: "Design implementation approach for {feature}", priority: "high", depends_on_positions: [0] },
|
|
14868
|
+
{ position: 2, title_pattern: "Implement {feature}", priority: "critical", depends_on_positions: [1] },
|
|
14869
|
+
{ position: 3, title_pattern: "Write tests for {feature}", priority: "high", depends_on_positions: [2] },
|
|
14870
|
+
{ position: 4, title_pattern: "Code review for {feature}", priority: "medium", depends_on_positions: [3] },
|
|
14871
|
+
{ position: 5, title_pattern: "Update docs for {feature}", priority: "medium", depends_on_positions: [2] },
|
|
14872
|
+
{ position: 6, title_pattern: "Deploy {feature}", priority: "high", depends_on_positions: [4] }
|
|
14873
|
+
]
|
|
14874
|
+
},
|
|
14875
|
+
{
|
|
14876
|
+
name: "security-audit",
|
|
14877
|
+
description: "Security audit workflow",
|
|
14878
|
+
variables: [{ name: "target", required: true }],
|
|
14879
|
+
tasks: [
|
|
14880
|
+
{ position: 0, title_pattern: "Scan {target} for vulnerabilities", priority: "critical" },
|
|
14881
|
+
{ position: 1, title_pattern: "Review {target} security findings", priority: "critical", depends_on_positions: [0] },
|
|
14882
|
+
{ position: 2, title_pattern: "Fix critical issues in {target}", priority: "critical", depends_on_positions: [1] },
|
|
14883
|
+
{ position: 3, title_pattern: "Retest {target} after fixes", priority: "high", depends_on_positions: [2] },
|
|
14884
|
+
{ position: 4, title_pattern: "Write security report for {target}", priority: "medium", depends_on_positions: [3] },
|
|
14885
|
+
{ position: 5, title_pattern: "Close audit for {target}", priority: "low", depends_on_positions: [4] }
|
|
14886
|
+
]
|
|
14887
|
+
}
|
|
14888
|
+
];
|
|
14889
|
+
});
|
|
14890
|
+
|
|
14442
14891
|
// src/lib/auto-assign.ts
|
|
14443
14892
|
var exports_auto_assign = {};
|
|
14444
14893
|
__export(exports_auto_assign, {
|
|
@@ -16146,6 +16595,26 @@ var init_pg_migrations = __esm(() => {
|
|
|
16146
16595
|
CREATE INDEX IF NOT EXISTS idx_template_tasks_template ON template_tasks(template_id);
|
|
16147
16596
|
|
|
16148
16597
|
INSERT INTO _migrations (id) VALUES (37) ON CONFLICT DO NOTHING;
|
|
16598
|
+
`,
|
|
16599
|
+
`
|
|
16600
|
+
ALTER TABLE task_templates ADD COLUMN IF NOT EXISTS variables TEXT DEFAULT '[]';
|
|
16601
|
+
INSERT INTO _migrations (id) VALUES (38) ON CONFLICT DO NOTHING;
|
|
16602
|
+
`,
|
|
16603
|
+
`
|
|
16604
|
+
ALTER TABLE template_tasks ADD COLUMN IF NOT EXISTS condition TEXT;
|
|
16605
|
+
ALTER TABLE template_tasks ADD COLUMN IF NOT EXISTS include_template_id TEXT;
|
|
16606
|
+
ALTER TABLE task_templates ADD COLUMN IF NOT EXISTS version INTEGER NOT NULL DEFAULT 1;
|
|
16607
|
+
|
|
16608
|
+
CREATE TABLE IF NOT EXISTS template_versions (
|
|
16609
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
16610
|
+
template_id TEXT NOT NULL REFERENCES task_templates(id) ON DELETE CASCADE,
|
|
16611
|
+
version INTEGER NOT NULL,
|
|
16612
|
+
snapshot TEXT NOT NULL,
|
|
16613
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
16614
|
+
);
|
|
16615
|
+
CREATE INDEX IF NOT EXISTS idx_template_versions_template ON template_versions(template_id);
|
|
16616
|
+
|
|
16617
|
+
INSERT INTO _migrations (id) VALUES (39) ON CONFLICT DO NOTHING;
|
|
16149
16618
|
`
|
|
16150
16619
|
];
|
|
16151
16620
|
});
|
|
@@ -21034,6 +21503,11 @@ var STANDARD_EXCLUDED = new Set([
|
|
|
21034
21503
|
"create_task_from_template",
|
|
21035
21504
|
"delete_template",
|
|
21036
21505
|
"update_template",
|
|
21506
|
+
"init_templates",
|
|
21507
|
+
"preview_template",
|
|
21508
|
+
"export_template",
|
|
21509
|
+
"import_template",
|
|
21510
|
+
"template_history",
|
|
21037
21511
|
"approve_task"
|
|
21038
21512
|
]);
|
|
21039
21513
|
function shouldRegisterTool(name) {
|
|
@@ -22766,15 +23240,20 @@ ${data.chart}` }] };
|
|
|
22766
23240
|
});
|
|
22767
23241
|
}
|
|
22768
23242
|
if (shouldRegisterTool("create_webhook")) {
|
|
22769
|
-
server.tool("create_webhook", "Register a webhook for task change events.", {
|
|
23243
|
+
server.tool("create_webhook", "Register a webhook for task change events. Optionally scope to a project, task list, agent, or specific task.", {
|
|
22770
23244
|
url: exports_external.string(),
|
|
22771
|
-
events: exports_external.array(exports_external.string()).optional(),
|
|
22772
|
-
secret: exports_external.string().optional()
|
|
23245
|
+
events: exports_external.array(exports_external.string()).optional().describe("Event types to subscribe to (empty = all). E.g. task.created, task.completed, task.failed, task.started, task.assigned, task.status_changed"),
|
|
23246
|
+
secret: exports_external.string().optional().describe("HMAC secret for signing webhook payloads"),
|
|
23247
|
+
project_id: exports_external.string().optional().describe("Only fire for events in this project"),
|
|
23248
|
+
task_list_id: exports_external.string().optional().describe("Only fire for events in this task list"),
|
|
23249
|
+
agent_id: exports_external.string().optional().describe("Only fire for events involving this agent"),
|
|
23250
|
+
task_id: exports_external.string().optional().describe("Only fire for events on this specific task")
|
|
22773
23251
|
}, async (params) => {
|
|
22774
23252
|
try {
|
|
22775
23253
|
const { createWebhook: createWebhook2 } = await Promise.resolve().then(() => (init_webhooks(), exports_webhooks));
|
|
22776
23254
|
const wh = createWebhook2(params);
|
|
22777
|
-
|
|
23255
|
+
const scope = [wh.project_id && `project:${wh.project_id}`, wh.task_list_id && `list:${wh.task_list_id}`, wh.agent_id && `agent:${wh.agent_id}`, wh.task_id && `task:${wh.task_id}`].filter(Boolean).join(", ");
|
|
23256
|
+
return { content: [{ type: "text", text: `Webhook created: ${wh.id.slice(0, 8)} | ${wh.url} | events: ${wh.events.length === 0 ? "all" : wh.events.join(",")}${scope ? ` | scope: ${scope}` : ""}` }] };
|
|
22778
23257
|
} catch (e) {
|
|
22779
23258
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22780
23259
|
}
|
|
@@ -22810,7 +23289,7 @@ if (shouldRegisterTool("delete_webhook")) {
|
|
|
22810
23289
|
});
|
|
22811
23290
|
}
|
|
22812
23291
|
if (shouldRegisterTool("create_template")) {
|
|
22813
|
-
server.tool("create_template", "Create a reusable task template. Optionally include a tasks array to define a multi-task template with dependencies and variable placeholders ({name} syntax).", {
|
|
23292
|
+
server.tool("create_template", "Create a reusable task template. Optionally include a tasks array to define a multi-task template with dependencies and variable placeholders ({name} syntax). Use variables to define typed variable definitions with defaults and required flags.", {
|
|
22814
23293
|
name: exports_external.string(),
|
|
22815
23294
|
title_pattern: exports_external.string(),
|
|
22816
23295
|
description: exports_external.string().optional(),
|
|
@@ -22818,6 +23297,12 @@ if (shouldRegisterTool("create_template")) {
|
|
|
22818
23297
|
tags: exports_external.array(exports_external.string()).optional(),
|
|
22819
23298
|
project_id: exports_external.string().optional(),
|
|
22820
23299
|
plan_id: exports_external.string().optional(),
|
|
23300
|
+
variables: exports_external.array(exports_external.object({
|
|
23301
|
+
name: exports_external.string().describe("Variable name (used as {name} in patterns)"),
|
|
23302
|
+
required: exports_external.boolean().describe("Whether this variable must be provided"),
|
|
23303
|
+
default: exports_external.string().optional().describe("Default value if not provided"),
|
|
23304
|
+
description: exports_external.string().optional().describe("Help text for the variable")
|
|
23305
|
+
})).optional().describe("Typed variable definitions with defaults and required flags"),
|
|
22821
23306
|
tasks: exports_external.array(exports_external.object({
|
|
22822
23307
|
title_pattern: exports_external.string().describe("Title pattern with optional {variable} placeholders"),
|
|
22823
23308
|
description: exports_external.string().optional(),
|
|
@@ -22847,7 +23332,10 @@ if (shouldRegisterTool("list_templates")) {
|
|
|
22847
23332
|
const templates = listTemplates2();
|
|
22848
23333
|
if (templates.length === 0)
|
|
22849
23334
|
return { content: [{ type: "text", text: "No templates." }] };
|
|
22850
|
-
const text = templates.map((t) =>
|
|
23335
|
+
const text = templates.map((t) => {
|
|
23336
|
+
const vars = t.variables.length > 0 ? ` | vars: ${t.variables.map((v) => `${v.name}${v.required ? "*" : ""}${v.default ? `=${v.default}` : ""}`).join(", ")}` : "";
|
|
23337
|
+
return `${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}" | ${t.priority}${vars}`;
|
|
23338
|
+
}).join(`
|
|
22851
23339
|
`);
|
|
22852
23340
|
return { content: [{ type: "text", text: `${templates.length} template(s):
|
|
22853
23341
|
${text}` }] };
|
|
@@ -22872,7 +23360,8 @@ if (shouldRegisterTool("create_task_from_template")) {
|
|
|
22872
23360
|
const resolvedTemplateId = resolveId(params.template_id, "task_templates");
|
|
22873
23361
|
const templateWithTasks = getTemplateWithTasks2(resolvedTemplateId);
|
|
22874
23362
|
if (templateWithTasks && templateWithTasks.tasks.length > 0) {
|
|
22875
|
-
const
|
|
23363
|
+
const effectiveProjectId = params.project_id || templateWithTasks.project_id || undefined;
|
|
23364
|
+
const tasks = tasksFromTemplate2(resolvedTemplateId, effectiveProjectId, params.variables, params.task_list_id);
|
|
22876
23365
|
const text = tasks.map((t) => `${t.id.slice(0, 8)} | ${t.priority} | ${t.title}`).join(`
|
|
22877
23366
|
`);
|
|
22878
23367
|
return { content: [{ type: "text", text: `${tasks.length} task(s) created from template:
|
|
@@ -22929,6 +23418,93 @@ if (shouldRegisterTool("update_template")) {
|
|
|
22929
23418
|
}
|
|
22930
23419
|
});
|
|
22931
23420
|
}
|
|
23421
|
+
if (shouldRegisterTool("init_templates")) {
|
|
23422
|
+
server.tool("init_templates", "Initialize built-in starter templates (open-source-project, bug-fix, feature, security-audit). Skips templates that already exist by name.", {}, async () => {
|
|
23423
|
+
try {
|
|
23424
|
+
const { initBuiltinTemplates: initBuiltinTemplates2 } = await Promise.resolve().then(() => (init_builtin_templates(), exports_builtin_templates));
|
|
23425
|
+
const result = initBuiltinTemplates2();
|
|
23426
|
+
if (result.created === 0) {
|
|
23427
|
+
return { content: [{ type: "text", text: `All ${result.skipped} built-in template(s) already exist.` }] };
|
|
23428
|
+
}
|
|
23429
|
+
return { content: [{ type: "text", text: `Created ${result.created} template(s): ${result.names.join(", ")}. Skipped ${result.skipped} existing.` }] };
|
|
23430
|
+
} catch (e) {
|
|
23431
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
23432
|
+
}
|
|
23433
|
+
});
|
|
23434
|
+
}
|
|
23435
|
+
if (shouldRegisterTool("preview_template")) {
|
|
23436
|
+
server.tool("preview_template", "Preview a template without creating tasks. Shows resolved titles (variables substituted), dependencies, and priorities.", {
|
|
23437
|
+
template_id: exports_external.string(),
|
|
23438
|
+
variables: exports_external.record(exports_external.string()).optional().describe("Variable substitution map for {name} placeholders")
|
|
23439
|
+
}, async (params) => {
|
|
23440
|
+
try {
|
|
23441
|
+
const { previewTemplate: previewTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
23442
|
+
const resolvedId = resolveId(params.template_id, "task_templates");
|
|
23443
|
+
const preview = previewTemplate2(resolvedId, params.variables);
|
|
23444
|
+
const lines = preview.tasks.map((t) => {
|
|
23445
|
+
const deps = t.depends_on_positions.length > 0 ? ` (after: ${t.depends_on_positions.join(", ")})` : "";
|
|
23446
|
+
return ` [${t.position}] ${t.priority} | ${t.title}${deps}`;
|
|
23447
|
+
});
|
|
23448
|
+
const varsInfo = preview.variables.length > 0 ? `
|
|
23449
|
+
Variables: ${preview.variables.map((v) => `${v.name}${v.required ? "*" : ""}${v.default ? `=${v.default}` : ""}`).join(", ")}` : "";
|
|
23450
|
+
const resolvedVars = Object.keys(preview.resolved_variables).length > 0 ? `
|
|
23451
|
+
Resolved: ${Object.entries(preview.resolved_variables).map(([k, v]) => `${k}=${v}`).join(", ")}` : "";
|
|
23452
|
+
return { content: [{ type: "text", text: `Preview: ${preview.template_name} (${preview.tasks.length} tasks)${varsInfo}${resolvedVars}
|
|
23453
|
+
${lines.join(`
|
|
23454
|
+
`)}` }] };
|
|
23455
|
+
} catch (e) {
|
|
23456
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
23457
|
+
}
|
|
23458
|
+
});
|
|
23459
|
+
}
|
|
23460
|
+
if (shouldRegisterTool("export_template")) {
|
|
23461
|
+
server.tool("export_template", "Export a template as a full JSON object (template + tasks + variables). Useful for sharing or backup.", { template_id: exports_external.string() }, async ({ template_id }) => {
|
|
23462
|
+
try {
|
|
23463
|
+
const { exportTemplate: exportTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
23464
|
+
const resolvedId = resolveId(template_id, "task_templates");
|
|
23465
|
+
const json = exportTemplate2(resolvedId);
|
|
23466
|
+
return { content: [{ type: "text", text: JSON.stringify(json, null, 2) }] };
|
|
23467
|
+
} catch (e) {
|
|
23468
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
23469
|
+
}
|
|
23470
|
+
});
|
|
23471
|
+
}
|
|
23472
|
+
if (shouldRegisterTool("import_template")) {
|
|
23473
|
+
server.tool("import_template", "Import a template from a JSON string (as returned by export_template). Creates new template with new IDs.", { json: exports_external.string().describe("JSON string of the template export") }, async ({ json }) => {
|
|
23474
|
+
try {
|
|
23475
|
+
const { importTemplate: importTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
23476
|
+
const parsed = JSON.parse(json);
|
|
23477
|
+
const t = importTemplate2(parsed);
|
|
23478
|
+
return { content: [{ type: "text", text: `Template imported: ${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}"` }] };
|
|
23479
|
+
} catch (e) {
|
|
23480
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
23481
|
+
}
|
|
23482
|
+
});
|
|
23483
|
+
}
|
|
23484
|
+
if (shouldRegisterTool("template_history")) {
|
|
23485
|
+
server.tool("template_history", "Show version history of a template. Each update creates a snapshot of the previous state.", { template_id: exports_external.string() }, async ({ template_id }) => {
|
|
23486
|
+
try {
|
|
23487
|
+
const { listTemplateVersions: listTemplateVersions2, getTemplate: getTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
23488
|
+
const resolvedId = resolveId(template_id, "task_templates");
|
|
23489
|
+
const template = getTemplate2(resolvedId);
|
|
23490
|
+
if (!template)
|
|
23491
|
+
return { content: [{ type: "text", text: `Template not found: ${template_id}` }], isError: true };
|
|
23492
|
+
const versions = listTemplateVersions2(resolvedId);
|
|
23493
|
+
if (versions.length === 0) {
|
|
23494
|
+
return { content: [{ type: "text", text: `${template.name} v${template.version} \u2014 no previous versions.` }] };
|
|
23495
|
+
}
|
|
23496
|
+
const lines = versions.map((v) => {
|
|
23497
|
+
const snap = JSON.parse(v.snapshot);
|
|
23498
|
+
return `v${v.version} | ${v.created_at} | ${snap.name} | "${snap.title_pattern}"`;
|
|
23499
|
+
});
|
|
23500
|
+
return { content: [{ type: "text", text: `${template.name} \u2014 current: v${template.version}
|
|
23501
|
+
${lines.join(`
|
|
23502
|
+
`)}` }] };
|
|
23503
|
+
} catch (e) {
|
|
23504
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
23505
|
+
}
|
|
23506
|
+
});
|
|
23507
|
+
}
|
|
22932
23508
|
if (shouldRegisterTool("approve_task")) {
|
|
22933
23509
|
server.tool("approve_task", "Approve a task with requires_approval=true.", {
|
|
22934
23510
|
id: exports_external.string(),
|
|
@@ -24046,6 +24622,11 @@ if (shouldRegisterTool("search_tools")) {
|
|
|
24046
24622
|
"create_task_from_template",
|
|
24047
24623
|
"delete_template",
|
|
24048
24624
|
"update_template",
|
|
24625
|
+
"init_templates",
|
|
24626
|
+
"preview_template",
|
|
24627
|
+
"export_template",
|
|
24628
|
+
"import_template",
|
|
24629
|
+
"template_history",
|
|
24049
24630
|
"bulk_update_tasks",
|
|
24050
24631
|
"bulk_create_tasks",
|
|
24051
24632
|
"get_task_stats",
|
|
@@ -24285,8 +24866,21 @@ if (shouldRegisterTool("describe_tools")) {
|
|
|
24285
24866
|
Params: id(string, req)
|
|
24286
24867
|
Example: {id: 'a1b2c3d4'}`,
|
|
24287
24868
|
update_template: `Update a task template's name, title pattern, or other fields.
|
|
24288
|
-
Params: id(string, req), name(string), title_pattern(string), description(string), priority(low|medium|high|critical), tags(string[]), project_id(string), plan_id(string)
|
|
24869
|
+
Params: id(string, req), name(string), title_pattern(string), description(string), priority(low|medium|high|critical), tags(string[]), variables(TemplateVariable[]), project_id(string), plan_id(string)
|
|
24289
24870
|
Example: {id: 'a1b2c3d4', name: 'Renamed Template', priority: 'critical'}`,
|
|
24871
|
+
init_templates: "Initialize built-in starter templates (open-source-project, bug-fix, feature, security-audit). Skips already existing. No params.",
|
|
24872
|
+
preview_template: `Preview a template without creating tasks. Shows resolved titles, deps, priorities.
|
|
24873
|
+
Params: template_id(string, req), variables(Record<string,string>)
|
|
24874
|
+
Example: {template_id: 'a1b2c3d4', variables: {name: 'invoices'}}`,
|
|
24875
|
+
export_template: `Export a template as JSON (template + tasks + variables). Use for sharing or backup.
|
|
24876
|
+
Params: template_id(string, req)
|
|
24877
|
+
Example: {template_id: 'a1b2c3d4'}`,
|
|
24878
|
+
import_template: `Import a template from a JSON string (as returned by export_template). Creates new template with new IDs.
|
|
24879
|
+
Params: json(string, req)
|
|
24880
|
+
Example: {json: '{"name":"My Template",...}'}`,
|
|
24881
|
+
template_history: `Show version history of a template. Each update creates a snapshot of the previous state.
|
|
24882
|
+
Params: template_id(string, req)
|
|
24883
|
+
Example: {template_id: 'a1b2c3d4'}`,
|
|
24290
24884
|
get_active_work: `See all in-progress tasks and who is working on them.
|
|
24291
24885
|
Params: project_id(string, optional), task_list_id(string, optional)
|
|
24292
24886
|
Example: {project_id: 'a1b2c3d4'}`,
|