@hasna/todos 0.11.10 → 0.11.12
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/cli/index.js
CHANGED
|
@@ -2388,6 +2388,36 @@ function ensureSchema(db) {
|
|
|
2388
2388
|
ensureColumn("projects", "org_id", "TEXT");
|
|
2389
2389
|
ensureColumn("plans", "task_list_id", "TEXT");
|
|
2390
2390
|
ensureColumn("plans", "agent_id", "TEXT");
|
|
2391
|
+
ensureColumn("task_templates", "variables", "TEXT DEFAULT '[]'");
|
|
2392
|
+
ensureColumn("task_templates", "version", "INTEGER NOT NULL DEFAULT 1");
|
|
2393
|
+
ensureColumn("template_tasks", "condition", "TEXT");
|
|
2394
|
+
ensureColumn("template_tasks", "include_template_id", "TEXT");
|
|
2395
|
+
ensureTable("template_versions", `
|
|
2396
|
+
CREATE TABLE template_versions (
|
|
2397
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
2398
|
+
template_id TEXT NOT NULL REFERENCES task_templates(id) ON DELETE CASCADE,
|
|
2399
|
+
version INTEGER NOT NULL,
|
|
2400
|
+
snapshot TEXT NOT NULL,
|
|
2401
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2402
|
+
)`);
|
|
2403
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_template_versions_template ON template_versions(template_id)");
|
|
2404
|
+
ensureColumn("webhooks", "project_id", "TEXT");
|
|
2405
|
+
ensureColumn("webhooks", "task_list_id", "TEXT");
|
|
2406
|
+
ensureColumn("webhooks", "agent_id", "TEXT");
|
|
2407
|
+
ensureColumn("webhooks", "task_id", "TEXT");
|
|
2408
|
+
ensureTable("webhook_deliveries", `
|
|
2409
|
+
CREATE TABLE webhook_deliveries (
|
|
2410
|
+
id TEXT PRIMARY KEY,
|
|
2411
|
+
webhook_id TEXT NOT NULL REFERENCES webhooks(id) ON DELETE CASCADE,
|
|
2412
|
+
event TEXT NOT NULL,
|
|
2413
|
+
payload TEXT NOT NULL,
|
|
2414
|
+
status_code INTEGER,
|
|
2415
|
+
response TEXT,
|
|
2416
|
+
attempt INTEGER NOT NULL DEFAULT 1,
|
|
2417
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2418
|
+
)`);
|
|
2419
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_webhook ON webhook_deliveries(webhook_id)");
|
|
2420
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_event ON webhook_deliveries(event)");
|
|
2391
2421
|
ensureColumn("task_comments", "type", "TEXT DEFAULT 'comment'");
|
|
2392
2422
|
ensureColumn("task_comments", "progress_pct", "INTEGER");
|
|
2393
2423
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_plan ON tasks(plan_id)");
|
|
@@ -3076,6 +3106,26 @@ var init_database = __esm(() => {
|
|
|
3076
3106
|
CREATE INDEX IF NOT EXISTS idx_template_tasks_template ON template_tasks(template_id);
|
|
3077
3107
|
|
|
3078
3108
|
INSERT OR IGNORE INTO _migrations (id) VALUES (37);
|
|
3109
|
+
`,
|
|
3110
|
+
`
|
|
3111
|
+
ALTER TABLE task_templates ADD COLUMN variables TEXT DEFAULT '[]';
|
|
3112
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (38);
|
|
3113
|
+
`,
|
|
3114
|
+
`
|
|
3115
|
+
ALTER TABLE template_tasks ADD COLUMN condition TEXT;
|
|
3116
|
+
ALTER TABLE template_tasks ADD COLUMN include_template_id TEXT;
|
|
3117
|
+
ALTER TABLE task_templates ADD COLUMN version INTEGER NOT NULL DEFAULT 1;
|
|
3118
|
+
|
|
3119
|
+
CREATE TABLE IF NOT EXISTS template_versions (
|
|
3120
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
3121
|
+
template_id TEXT NOT NULL REFERENCES task_templates(id) ON DELETE CASCADE,
|
|
3122
|
+
version INTEGER NOT NULL,
|
|
3123
|
+
snapshot TEXT NOT NULL,
|
|
3124
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
3125
|
+
);
|
|
3126
|
+
CREATE INDEX IF NOT EXISTS idx_template_versions_template ON template_versions(template_id);
|
|
3127
|
+
|
|
3128
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (39);
|
|
3079
3129
|
`
|
|
3080
3130
|
];
|
|
3081
3131
|
});
|
|
@@ -3713,18 +3763,37 @@ var init_recurrence = __esm(() => {
|
|
|
3713
3763
|
var exports_webhooks = {};
|
|
3714
3764
|
__export(exports_webhooks, {
|
|
3715
3765
|
listWebhooks: () => listWebhooks,
|
|
3766
|
+
listDeliveries: () => listDeliveries,
|
|
3716
3767
|
getWebhook: () => getWebhook,
|
|
3717
3768
|
dispatchWebhook: () => dispatchWebhook,
|
|
3718
3769
|
deleteWebhook: () => deleteWebhook,
|
|
3719
3770
|
createWebhook: () => createWebhook
|
|
3720
3771
|
});
|
|
3721
3772
|
function rowToWebhook(row) {
|
|
3722
|
-
return {
|
|
3773
|
+
return {
|
|
3774
|
+
...row,
|
|
3775
|
+
events: JSON.parse(row.events || "[]"),
|
|
3776
|
+
active: !!row.active,
|
|
3777
|
+
project_id: row.project_id || null,
|
|
3778
|
+
task_list_id: row.task_list_id || null,
|
|
3779
|
+
agent_id: row.agent_id || null,
|
|
3780
|
+
task_id: row.task_id || null
|
|
3781
|
+
};
|
|
3723
3782
|
}
|
|
3724
3783
|
function createWebhook(input, db) {
|
|
3725
3784
|
const d = db || getDatabase();
|
|
3726
3785
|
const id = uuid();
|
|
3727
|
-
d.run(`INSERT INTO webhooks (id, url, events, secret, created_at) VALUES (?, ?, ?, ?,
|
|
3786
|
+
d.run(`INSERT INTO webhooks (id, url, events, secret, project_id, task_list_id, agent_id, task_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3787
|
+
id,
|
|
3788
|
+
input.url,
|
|
3789
|
+
JSON.stringify(input.events || []),
|
|
3790
|
+
input.secret || null,
|
|
3791
|
+
input.project_id || null,
|
|
3792
|
+
input.task_list_id || null,
|
|
3793
|
+
input.agent_id || null,
|
|
3794
|
+
input.task_id || null,
|
|
3795
|
+
now()
|
|
3796
|
+
]);
|
|
3728
3797
|
return getWebhook(id, d);
|
|
3729
3798
|
}
|
|
3730
3799
|
function getWebhook(id, db) {
|
|
@@ -3740,22 +3809,69 @@ function deleteWebhook(id, db) {
|
|
|
3740
3809
|
const d = db || getDatabase();
|
|
3741
3810
|
return d.run("DELETE FROM webhooks WHERE id = ?", [id]).changes > 0;
|
|
3742
3811
|
}
|
|
3812
|
+
function listDeliveries(webhookId, limit = 50, db) {
|
|
3813
|
+
const d = db || getDatabase();
|
|
3814
|
+
if (webhookId) {
|
|
3815
|
+
return d.query("SELECT * FROM webhook_deliveries WHERE webhook_id = ? ORDER BY created_at DESC LIMIT ?").all(webhookId, limit);
|
|
3816
|
+
}
|
|
3817
|
+
return d.query("SELECT * FROM webhook_deliveries ORDER BY created_at DESC LIMIT ?").all(limit);
|
|
3818
|
+
}
|
|
3819
|
+
function logDelivery(d, webhookId, event, payload, statusCode, response, attempt) {
|
|
3820
|
+
const id = uuid();
|
|
3821
|
+
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()]);
|
|
3822
|
+
}
|
|
3823
|
+
function matchesScope(wh, payload) {
|
|
3824
|
+
if (wh.project_id && payload.project_id !== wh.project_id)
|
|
3825
|
+
return false;
|
|
3826
|
+
if (wh.task_list_id && payload.task_list_id !== wh.task_list_id)
|
|
3827
|
+
return false;
|
|
3828
|
+
if (wh.agent_id && payload.agent_id !== wh.agent_id && payload.assigned_to !== wh.agent_id)
|
|
3829
|
+
return false;
|
|
3830
|
+
if (wh.task_id && payload.id !== wh.task_id)
|
|
3831
|
+
return false;
|
|
3832
|
+
return true;
|
|
3833
|
+
}
|
|
3834
|
+
async function deliverWebhook(wh, event, body, attempt, db) {
|
|
3835
|
+
try {
|
|
3836
|
+
const headers = { "Content-Type": "application/json" };
|
|
3837
|
+
if (wh.secret) {
|
|
3838
|
+
const encoder = new TextEncoder;
|
|
3839
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
3840
|
+
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
3841
|
+
headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3842
|
+
}
|
|
3843
|
+
const resp = await fetch(wh.url, { method: "POST", headers, body });
|
|
3844
|
+
const respText = await resp.text().catch(() => "");
|
|
3845
|
+
logDelivery(db, wh.id, event, body, resp.status, respText.slice(0, 1000), attempt);
|
|
3846
|
+
if (resp.status >= 400 && attempt < MAX_RETRY_ATTEMPTS) {
|
|
3847
|
+
const delay = RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
3848
|
+
setTimeout(() => {
|
|
3849
|
+
deliverWebhook(wh, event, body, attempt + 1, db).catch(() => {});
|
|
3850
|
+
}, delay);
|
|
3851
|
+
}
|
|
3852
|
+
} catch (err) {
|
|
3853
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3854
|
+
logDelivery(db, wh.id, event, body, null, errorMsg.slice(0, 1000), attempt);
|
|
3855
|
+
if (attempt < MAX_RETRY_ATTEMPTS) {
|
|
3856
|
+
const delay = RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
3857
|
+
setTimeout(() => {
|
|
3858
|
+
deliverWebhook(wh, event, body, attempt + 1, db).catch(() => {});
|
|
3859
|
+
}, delay);
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
}
|
|
3743
3863
|
async function dispatchWebhook(event, payload, db) {
|
|
3744
|
-
const
|
|
3864
|
+
const d = db || getDatabase();
|
|
3865
|
+
const webhooks = listWebhooks(d).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
|
|
3866
|
+
const payloadObj = typeof payload === "object" && payload !== null ? payload : {};
|
|
3745
3867
|
for (const wh of webhooks) {
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
const encoder = new TextEncoder;
|
|
3751
|
-
const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
3752
|
-
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
3753
|
-
headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3754
|
-
}
|
|
3755
|
-
fetch(wh.url, { method: "POST", headers, body }).catch(() => {});
|
|
3756
|
-
} catch {}
|
|
3868
|
+
if (!matchesScope(wh, payloadObj))
|
|
3869
|
+
continue;
|
|
3870
|
+
const body = JSON.stringify({ event, payload, timestamp: now() });
|
|
3871
|
+
deliverWebhook(wh, event, body, 1, d).catch(() => {});
|
|
3757
3872
|
}
|
|
3758
3873
|
}
|
|
3874
|
+
var MAX_RETRY_ATTEMPTS = 3, RETRY_BASE_DELAY_MS = 1000;
|
|
3759
3875
|
var init_webhooks = __esm(() => {
|
|
3760
3876
|
init_database();
|
|
3761
3877
|
});
|
|
@@ -3766,10 +3882,17 @@ __export(exports_templates, {
|
|
|
3766
3882
|
updateTemplate: () => updateTemplate,
|
|
3767
3883
|
tasksFromTemplate: () => tasksFromTemplate,
|
|
3768
3884
|
taskFromTemplate: () => taskFromTemplate,
|
|
3885
|
+
resolveVariables: () => resolveVariables,
|
|
3886
|
+
previewTemplate: () => previewTemplate,
|
|
3769
3887
|
listTemplates: () => listTemplates,
|
|
3888
|
+
listTemplateVersions: () => listTemplateVersions,
|
|
3889
|
+
importTemplate: () => importTemplate,
|
|
3770
3890
|
getTemplateWithTasks: () => getTemplateWithTasks,
|
|
3891
|
+
getTemplateVersion: () => getTemplateVersion,
|
|
3771
3892
|
getTemplateTasks: () => getTemplateTasks,
|
|
3772
3893
|
getTemplate: () => getTemplate,
|
|
3894
|
+
exportTemplate: () => exportTemplate,
|
|
3895
|
+
evaluateCondition: () => evaluateCondition,
|
|
3773
3896
|
deleteTemplate: () => deleteTemplate,
|
|
3774
3897
|
createTemplate: () => createTemplate,
|
|
3775
3898
|
addTemplateTasks: () => addTemplateTasks
|
|
@@ -3778,8 +3901,10 @@ function rowToTemplate(row) {
|
|
|
3778
3901
|
return {
|
|
3779
3902
|
...row,
|
|
3780
3903
|
tags: JSON.parse(row.tags || "[]"),
|
|
3904
|
+
variables: JSON.parse(row.variables || "[]"),
|
|
3781
3905
|
metadata: JSON.parse(row.metadata || "{}"),
|
|
3782
|
-
priority: row.priority || "medium"
|
|
3906
|
+
priority: row.priority || "medium",
|
|
3907
|
+
version: row.version ?? 1
|
|
3783
3908
|
};
|
|
3784
3909
|
}
|
|
3785
3910
|
function rowToTemplateTask(row) {
|
|
@@ -3788,7 +3913,9 @@ function rowToTemplateTask(row) {
|
|
|
3788
3913
|
tags: JSON.parse(row.tags || "[]"),
|
|
3789
3914
|
depends_on_positions: JSON.parse(row.depends_on_positions || "[]"),
|
|
3790
3915
|
metadata: JSON.parse(row.metadata || "{}"),
|
|
3791
|
-
priority: row.priority || "medium"
|
|
3916
|
+
priority: row.priority || "medium",
|
|
3917
|
+
condition: row.condition ?? null,
|
|
3918
|
+
include_template_id: row.include_template_id ?? null
|
|
3792
3919
|
};
|
|
3793
3920
|
}
|
|
3794
3921
|
function resolveTemplateId(id, d) {
|
|
@@ -3797,14 +3924,15 @@ function resolveTemplateId(id, d) {
|
|
|
3797
3924
|
function createTemplate(input, db) {
|
|
3798
3925
|
const d = db || getDatabase();
|
|
3799
3926
|
const id = uuid();
|
|
3800
|
-
d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, project_id, plan_id, metadata, created_at)
|
|
3801
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3927
|
+
d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at)
|
|
3928
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3802
3929
|
id,
|
|
3803
3930
|
input.name,
|
|
3804
3931
|
input.title_pattern,
|
|
3805
3932
|
input.description || null,
|
|
3806
3933
|
input.priority || "medium",
|
|
3807
3934
|
JSON.stringify(input.tags || []),
|
|
3935
|
+
JSON.stringify(input.variables || []),
|
|
3808
3936
|
input.project_id || null,
|
|
3809
3937
|
input.plan_id || null,
|
|
3810
3938
|
JSON.stringify(input.metadata || {}),
|
|
@@ -3839,7 +3967,23 @@ function updateTemplate(id, updates, db) {
|
|
|
3839
3967
|
const resolved = resolveTemplateId(id, d);
|
|
3840
3968
|
if (!resolved)
|
|
3841
3969
|
return null;
|
|
3842
|
-
const
|
|
3970
|
+
const current = getTemplateWithTasks(resolved, d);
|
|
3971
|
+
if (current) {
|
|
3972
|
+
const snapshot = JSON.stringify({
|
|
3973
|
+
name: current.name,
|
|
3974
|
+
title_pattern: current.title_pattern,
|
|
3975
|
+
description: current.description,
|
|
3976
|
+
priority: current.priority,
|
|
3977
|
+
tags: current.tags,
|
|
3978
|
+
variables: current.variables,
|
|
3979
|
+
project_id: current.project_id,
|
|
3980
|
+
plan_id: current.plan_id,
|
|
3981
|
+
metadata: current.metadata,
|
|
3982
|
+
tasks: current.tasks
|
|
3983
|
+
});
|
|
3984
|
+
d.run(`INSERT INTO template_versions (id, template_id, version, snapshot, created_at) VALUES (?, ?, ?, ?, ?)`, [uuid(), resolved, current.version, snapshot, now()]);
|
|
3985
|
+
}
|
|
3986
|
+
const sets = ["version = version + 1"];
|
|
3843
3987
|
const values = [];
|
|
3844
3988
|
if (updates.name !== undefined) {
|
|
3845
3989
|
sets.push("name = ?");
|
|
@@ -3861,6 +4005,10 @@ function updateTemplate(id, updates, db) {
|
|
|
3861
4005
|
sets.push("tags = ?");
|
|
3862
4006
|
values.push(JSON.stringify(updates.tags));
|
|
3863
4007
|
}
|
|
4008
|
+
if (updates.variables !== undefined) {
|
|
4009
|
+
sets.push("variables = ?");
|
|
4010
|
+
values.push(JSON.stringify(updates.variables));
|
|
4011
|
+
}
|
|
3864
4012
|
if (updates.project_id !== undefined) {
|
|
3865
4013
|
sets.push("project_id = ?");
|
|
3866
4014
|
values.push(updates.project_id);
|
|
@@ -3873,8 +4021,6 @@ function updateTemplate(id, updates, db) {
|
|
|
3873
4021
|
sets.push("metadata = ?");
|
|
3874
4022
|
values.push(JSON.stringify(updates.metadata));
|
|
3875
4023
|
}
|
|
3876
|
-
if (sets.length === 0)
|
|
3877
|
-
return getTemplate(resolved, d);
|
|
3878
4024
|
values.push(resolved);
|
|
3879
4025
|
d.run(`UPDATE task_templates SET ${sets.join(", ")} WHERE id = ?`, values);
|
|
3880
4026
|
return getTemplate(resolved, d);
|
|
@@ -3904,8 +4050,8 @@ function addTemplateTasks(templateId, tasks, db) {
|
|
|
3904
4050
|
for (let i = 0;i < tasks.length; i++) {
|
|
3905
4051
|
const task = tasks[i];
|
|
3906
4052
|
const id = uuid();
|
|
3907
|
-
d.run(`INSERT INTO template_tasks (id, template_id, position, title_pattern, description, priority, tags, task_type, depends_on_positions, metadata, created_at)
|
|
3908
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
4053
|
+
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)
|
|
4054
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3909
4055
|
id,
|
|
3910
4056
|
templateId,
|
|
3911
4057
|
i,
|
|
@@ -3914,6 +4060,8 @@ function addTemplateTasks(templateId, tasks, db) {
|
|
|
3914
4060
|
task.priority || "medium",
|
|
3915
4061
|
JSON.stringify(task.tags || []),
|
|
3916
4062
|
task.task_type || null,
|
|
4063
|
+
task.condition || null,
|
|
4064
|
+
task.include_template_id || null,
|
|
3917
4065
|
JSON.stringify(task.depends_on || []),
|
|
3918
4066
|
JSON.stringify(task.metadata || {}),
|
|
3919
4067
|
now()
|
|
@@ -3941,11 +4089,142 @@ function getTemplateTasks(templateId, db) {
|
|
|
3941
4089
|
const rows = d.query("SELECT * FROM template_tasks WHERE template_id = ? ORDER BY position").all(resolved);
|
|
3942
4090
|
return rows.map(rowToTemplateTask);
|
|
3943
4091
|
}
|
|
3944
|
-
function
|
|
4092
|
+
function evaluateCondition(condition, variables) {
|
|
4093
|
+
if (!condition || condition.trim() === "")
|
|
4094
|
+
return true;
|
|
4095
|
+
const trimmed = condition.trim();
|
|
4096
|
+
const eqMatch = trimmed.match(/^\{([^}]+)\}\s*==\s*(.+)$/);
|
|
4097
|
+
if (eqMatch) {
|
|
4098
|
+
const varName = eqMatch[1];
|
|
4099
|
+
const expected = eqMatch[2].trim();
|
|
4100
|
+
return (variables[varName] ?? "") === expected;
|
|
4101
|
+
}
|
|
4102
|
+
const neqMatch = trimmed.match(/^\{([^}]+)\}\s*!=\s*(.+)$/);
|
|
4103
|
+
if (neqMatch) {
|
|
4104
|
+
const varName = neqMatch[1];
|
|
4105
|
+
const expected = neqMatch[2].trim();
|
|
4106
|
+
return (variables[varName] ?? "") !== expected;
|
|
4107
|
+
}
|
|
4108
|
+
const falsyMatch = trimmed.match(/^!\{([^}]+)\}$/);
|
|
4109
|
+
if (falsyMatch) {
|
|
4110
|
+
const varName = falsyMatch[1];
|
|
4111
|
+
const val = variables[varName];
|
|
4112
|
+
return !val || val === "" || val === "false";
|
|
4113
|
+
}
|
|
4114
|
+
const truthyMatch = trimmed.match(/^\{([^}]+)\}$/);
|
|
4115
|
+
if (truthyMatch) {
|
|
4116
|
+
const varName = truthyMatch[1];
|
|
4117
|
+
const val = variables[varName];
|
|
4118
|
+
return !!val && val !== "" && val !== "false";
|
|
4119
|
+
}
|
|
4120
|
+
return true;
|
|
4121
|
+
}
|
|
4122
|
+
function exportTemplate(id, db) {
|
|
4123
|
+
const d = db || getDatabase();
|
|
4124
|
+
const template = getTemplateWithTasks(id, d);
|
|
4125
|
+
if (!template)
|
|
4126
|
+
throw new Error(`Template not found: ${id}`);
|
|
4127
|
+
return {
|
|
4128
|
+
name: template.name,
|
|
4129
|
+
title_pattern: template.title_pattern,
|
|
4130
|
+
description: template.description,
|
|
4131
|
+
priority: template.priority,
|
|
4132
|
+
tags: template.tags,
|
|
4133
|
+
variables: template.variables,
|
|
4134
|
+
project_id: template.project_id,
|
|
4135
|
+
plan_id: template.plan_id,
|
|
4136
|
+
metadata: template.metadata,
|
|
4137
|
+
tasks: template.tasks.map((t) => ({
|
|
4138
|
+
position: t.position,
|
|
4139
|
+
title_pattern: t.title_pattern,
|
|
4140
|
+
description: t.description,
|
|
4141
|
+
priority: t.priority,
|
|
4142
|
+
tags: t.tags,
|
|
4143
|
+
task_type: t.task_type,
|
|
4144
|
+
condition: t.condition,
|
|
4145
|
+
include_template_id: t.include_template_id,
|
|
4146
|
+
depends_on_positions: t.depends_on_positions,
|
|
4147
|
+
metadata: t.metadata
|
|
4148
|
+
}))
|
|
4149
|
+
};
|
|
4150
|
+
}
|
|
4151
|
+
function importTemplate(json, db) {
|
|
4152
|
+
const d = db || getDatabase();
|
|
4153
|
+
const taskInputs = (json.tasks || []).map((t) => ({
|
|
4154
|
+
title_pattern: t.title_pattern,
|
|
4155
|
+
description: t.description ?? undefined,
|
|
4156
|
+
priority: t.priority,
|
|
4157
|
+
tags: t.tags,
|
|
4158
|
+
task_type: t.task_type ?? undefined,
|
|
4159
|
+
condition: t.condition ?? undefined,
|
|
4160
|
+
include_template_id: t.include_template_id ?? undefined,
|
|
4161
|
+
depends_on: t.depends_on_positions,
|
|
4162
|
+
metadata: t.metadata
|
|
4163
|
+
}));
|
|
4164
|
+
return createTemplate({
|
|
4165
|
+
name: json.name,
|
|
4166
|
+
title_pattern: json.title_pattern,
|
|
4167
|
+
description: json.description ?? undefined,
|
|
4168
|
+
priority: json.priority,
|
|
4169
|
+
tags: json.tags,
|
|
4170
|
+
variables: json.variables,
|
|
4171
|
+
project_id: json.project_id ?? undefined,
|
|
4172
|
+
plan_id: json.plan_id ?? undefined,
|
|
4173
|
+
metadata: json.metadata,
|
|
4174
|
+
tasks: taskInputs
|
|
4175
|
+
}, d);
|
|
4176
|
+
}
|
|
4177
|
+
function getTemplateVersion(id, version, db) {
|
|
4178
|
+
const d = db || getDatabase();
|
|
4179
|
+
const resolved = resolveTemplateId(id, d);
|
|
4180
|
+
if (!resolved)
|
|
4181
|
+
return null;
|
|
4182
|
+
const row = d.query("SELECT * FROM template_versions WHERE template_id = ? AND version = ?").get(resolved, version);
|
|
4183
|
+
return row || null;
|
|
4184
|
+
}
|
|
4185
|
+
function listTemplateVersions(id, db) {
|
|
4186
|
+
const d = db || getDatabase();
|
|
4187
|
+
const resolved = resolveTemplateId(id, d);
|
|
4188
|
+
if (!resolved)
|
|
4189
|
+
return [];
|
|
4190
|
+
return d.query("SELECT * FROM template_versions WHERE template_id = ? ORDER BY version DESC").all(resolved);
|
|
4191
|
+
}
|
|
4192
|
+
function resolveVariables(templateVars, provided) {
|
|
4193
|
+
const merged = { ...provided };
|
|
4194
|
+
for (const v of templateVars) {
|
|
4195
|
+
if (merged[v.name] === undefined && v.default !== undefined) {
|
|
4196
|
+
merged[v.name] = v.default;
|
|
4197
|
+
}
|
|
4198
|
+
}
|
|
4199
|
+
const missing = [];
|
|
4200
|
+
for (const v of templateVars) {
|
|
4201
|
+
if (v.required && merged[v.name] === undefined) {
|
|
4202
|
+
missing.push(v.name);
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
if (missing.length > 0) {
|
|
4206
|
+
throw new Error(`Missing required template variable(s): ${missing.join(", ")}`);
|
|
4207
|
+
}
|
|
4208
|
+
return merged;
|
|
4209
|
+
}
|
|
4210
|
+
function substituteVars(text, variables) {
|
|
4211
|
+
let result = text;
|
|
4212
|
+
for (const [key, val] of Object.entries(variables)) {
|
|
4213
|
+
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), val);
|
|
4214
|
+
}
|
|
4215
|
+
return result;
|
|
4216
|
+
}
|
|
4217
|
+
function tasksFromTemplate(templateId, projectId, variables, taskListId, db, _visitedTemplateIds) {
|
|
3945
4218
|
const d = db || getDatabase();
|
|
3946
4219
|
const template = getTemplateWithTasks(templateId, d);
|
|
3947
4220
|
if (!template)
|
|
3948
4221
|
throw new Error(`Template not found: ${templateId}`);
|
|
4222
|
+
const visited = _visitedTemplateIds || new Set;
|
|
4223
|
+
if (visited.has(template.id)) {
|
|
4224
|
+
throw new Error(`Circular template reference detected: ${template.id}`);
|
|
4225
|
+
}
|
|
4226
|
+
visited.add(template.id);
|
|
4227
|
+
const resolved = resolveVariables(template.variables, variables);
|
|
3949
4228
|
if (template.tasks.length === 0) {
|
|
3950
4229
|
const input = taskFromTemplate(templateId, { project_id: projectId, task_list_id: taskListId }, d);
|
|
3951
4230
|
const task = createTask(input, d);
|
|
@@ -3953,16 +4232,27 @@ function tasksFromTemplate(templateId, projectId, variables, taskListId, db) {
|
|
|
3953
4232
|
}
|
|
3954
4233
|
const createdTasks = [];
|
|
3955
4234
|
const positionToId = new Map;
|
|
4235
|
+
const skippedPositions = new Set;
|
|
3956
4236
|
for (const tt of template.tasks) {
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
4237
|
+
if (tt.include_template_id) {
|
|
4238
|
+
const includedTasks = tasksFromTemplate(tt.include_template_id, projectId, resolved, taskListId, d, visited);
|
|
4239
|
+
createdTasks.push(...includedTasks);
|
|
4240
|
+
if (includedTasks.length > 0) {
|
|
4241
|
+
positionToId.set(tt.position, includedTasks[0].id);
|
|
4242
|
+
} else {
|
|
4243
|
+
skippedPositions.add(tt.position);
|
|
3964
4244
|
}
|
|
4245
|
+
continue;
|
|
3965
4246
|
}
|
|
4247
|
+
if (tt.condition && !evaluateCondition(tt.condition, resolved)) {
|
|
4248
|
+
skippedPositions.add(tt.position);
|
|
4249
|
+
continue;
|
|
4250
|
+
}
|
|
4251
|
+
let title = tt.title_pattern;
|
|
4252
|
+
let desc = tt.description;
|
|
4253
|
+
title = substituteVars(title, resolved);
|
|
4254
|
+
if (desc)
|
|
4255
|
+
desc = substituteVars(desc, resolved);
|
|
3966
4256
|
const task = createTask({
|
|
3967
4257
|
title,
|
|
3968
4258
|
description: desc ?? undefined,
|
|
@@ -3977,8 +4267,14 @@ function tasksFromTemplate(templateId, projectId, variables, taskListId, db) {
|
|
|
3977
4267
|
positionToId.set(tt.position, task.id);
|
|
3978
4268
|
}
|
|
3979
4269
|
for (const tt of template.tasks) {
|
|
4270
|
+
if (skippedPositions.has(tt.position))
|
|
4271
|
+
continue;
|
|
4272
|
+
if (tt.include_template_id)
|
|
4273
|
+
continue;
|
|
3980
4274
|
const deps = tt.depends_on_positions;
|
|
3981
4275
|
for (const depPos of deps) {
|
|
4276
|
+
if (skippedPositions.has(depPos))
|
|
4277
|
+
continue;
|
|
3982
4278
|
const taskId = positionToId.get(tt.position);
|
|
3983
4279
|
const depId = positionToId.get(depPos);
|
|
3984
4280
|
if (taskId && depId) {
|
|
@@ -3988,6 +4284,47 @@ function tasksFromTemplate(templateId, projectId, variables, taskListId, db) {
|
|
|
3988
4284
|
}
|
|
3989
4285
|
return createdTasks;
|
|
3990
4286
|
}
|
|
4287
|
+
function previewTemplate(templateId, variables, db) {
|
|
4288
|
+
const d = db || getDatabase();
|
|
4289
|
+
const template = getTemplateWithTasks(templateId, d);
|
|
4290
|
+
if (!template)
|
|
4291
|
+
throw new Error(`Template not found: ${templateId}`);
|
|
4292
|
+
const resolved = resolveVariables(template.variables, variables);
|
|
4293
|
+
const tasks = [];
|
|
4294
|
+
if (template.tasks.length === 0) {
|
|
4295
|
+
tasks.push({
|
|
4296
|
+
position: 0,
|
|
4297
|
+
title: substituteVars(template.title_pattern, resolved),
|
|
4298
|
+
description: template.description ? substituteVars(template.description, resolved) : null,
|
|
4299
|
+
priority: template.priority,
|
|
4300
|
+
tags: template.tags,
|
|
4301
|
+
task_type: null,
|
|
4302
|
+
depends_on_positions: []
|
|
4303
|
+
});
|
|
4304
|
+
} else {
|
|
4305
|
+
for (const tt of template.tasks) {
|
|
4306
|
+
if (tt.condition && !evaluateCondition(tt.condition, resolved))
|
|
4307
|
+
continue;
|
|
4308
|
+
tasks.push({
|
|
4309
|
+
position: tt.position,
|
|
4310
|
+
title: substituteVars(tt.title_pattern, resolved),
|
|
4311
|
+
description: tt.description ? substituteVars(tt.description, resolved) : null,
|
|
4312
|
+
priority: tt.priority,
|
|
4313
|
+
tags: tt.tags,
|
|
4314
|
+
task_type: tt.task_type,
|
|
4315
|
+
depends_on_positions: tt.depends_on_positions
|
|
4316
|
+
});
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
return {
|
|
4320
|
+
template_id: template.id,
|
|
4321
|
+
template_name: template.name,
|
|
4322
|
+
description: template.description,
|
|
4323
|
+
variables: template.variables,
|
|
4324
|
+
resolved_variables: resolved,
|
|
4325
|
+
tasks
|
|
4326
|
+
};
|
|
4327
|
+
}
|
|
3991
4328
|
var init_templates = __esm(() => {
|
|
3992
4329
|
init_database();
|
|
3993
4330
|
init_tasks();
|
|
@@ -6550,6 +6887,118 @@ var init_task_commits = __esm(() => {
|
|
|
6550
6887
|
init_database();
|
|
6551
6888
|
});
|
|
6552
6889
|
|
|
6890
|
+
// src/db/builtin-templates.ts
|
|
6891
|
+
var exports_builtin_templates = {};
|
|
6892
|
+
__export(exports_builtin_templates, {
|
|
6893
|
+
initBuiltinTemplates: () => initBuiltinTemplates,
|
|
6894
|
+
BUILTIN_TEMPLATES: () => BUILTIN_TEMPLATES
|
|
6895
|
+
});
|
|
6896
|
+
function initBuiltinTemplates(db) {
|
|
6897
|
+
const d = db || getDatabase();
|
|
6898
|
+
const existing = listTemplates(d);
|
|
6899
|
+
const existingNames = new Set(existing.map((t) => t.name));
|
|
6900
|
+
let created = 0;
|
|
6901
|
+
let skipped = 0;
|
|
6902
|
+
const names = [];
|
|
6903
|
+
for (const bt of BUILTIN_TEMPLATES) {
|
|
6904
|
+
if (existingNames.has(bt.name)) {
|
|
6905
|
+
skipped++;
|
|
6906
|
+
continue;
|
|
6907
|
+
}
|
|
6908
|
+
const tasks = bt.tasks.map((t) => ({
|
|
6909
|
+
title_pattern: t.title_pattern,
|
|
6910
|
+
description: t.description,
|
|
6911
|
+
priority: t.priority,
|
|
6912
|
+
tags: t.tags,
|
|
6913
|
+
task_type: t.task_type,
|
|
6914
|
+
depends_on: t.depends_on_positions || t.depends_on,
|
|
6915
|
+
metadata: t.metadata
|
|
6916
|
+
}));
|
|
6917
|
+
createTemplate({
|
|
6918
|
+
name: bt.name,
|
|
6919
|
+
title_pattern: `${bt.name}: {${bt.variables[0]?.name || "name"}}`,
|
|
6920
|
+
description: bt.description,
|
|
6921
|
+
variables: bt.variables,
|
|
6922
|
+
tasks
|
|
6923
|
+
}, d);
|
|
6924
|
+
created++;
|
|
6925
|
+
names.push(bt.name);
|
|
6926
|
+
}
|
|
6927
|
+
return { created, skipped, names };
|
|
6928
|
+
}
|
|
6929
|
+
var BUILTIN_TEMPLATES;
|
|
6930
|
+
var init_builtin_templates = __esm(() => {
|
|
6931
|
+
init_database();
|
|
6932
|
+
init_templates();
|
|
6933
|
+
BUILTIN_TEMPLATES = [
|
|
6934
|
+
{
|
|
6935
|
+
name: "open-source-project",
|
|
6936
|
+
description: "Full open-source project bootstrap \u2014 scaffold to publish",
|
|
6937
|
+
variables: [
|
|
6938
|
+
{ name: "name", required: true, description: "Service name" },
|
|
6939
|
+
{ name: "org", required: false, default: "hasna", description: "GitHub org" }
|
|
6940
|
+
],
|
|
6941
|
+
tasks: [
|
|
6942
|
+
{ position: 0, title_pattern: "Scaffold {name} package structure", priority: "critical" },
|
|
6943
|
+
{ position: 1, title_pattern: "Create {name} SQLite database + migrations", priority: "critical", depends_on_positions: [0] },
|
|
6944
|
+
{ position: 2, title_pattern: "Implement {name} CRUD operations", priority: "high", depends_on_positions: [1] },
|
|
6945
|
+
{ position: 3, title_pattern: "Build {name} MCP server with standard tools", priority: "high", depends_on_positions: [2] },
|
|
6946
|
+
{ position: 4, title_pattern: "Build {name} CLI with Commander.js", priority: "high", depends_on_positions: [2] },
|
|
6947
|
+
{ position: 5, title_pattern: "Build {name} REST API", priority: "medium", depends_on_positions: [2] },
|
|
6948
|
+
{ position: 6, title_pattern: "Write unit tests for {name}", priority: "high", depends_on_positions: [2, 3, 4] },
|
|
6949
|
+
{ position: 7, title_pattern: "Add Apache 2.0 license and README", priority: "medium", depends_on_positions: [0] },
|
|
6950
|
+
{ position: 8, title_pattern: "Create GitHub repo {org}/{name}", priority: "medium", depends_on_positions: [7] },
|
|
6951
|
+
{ position: 9, title_pattern: "Add @hasna/cloud adapter", priority: "medium", depends_on_positions: [1] },
|
|
6952
|
+
{ position: 10, title_pattern: "Write PostgreSQL migrations for {name}", priority: "medium", depends_on_positions: [1] },
|
|
6953
|
+
{ position: 11, title_pattern: "Add feedback system + send_feedback MCP tool", priority: "medium", depends_on_positions: [3] },
|
|
6954
|
+
{ position: 12, title_pattern: "Add agent tools (register_agent, heartbeat, set_focus, list_agents)", priority: "medium", depends_on_positions: [3] },
|
|
6955
|
+
{ position: 13, title_pattern: "Create RDS database for {name}", priority: "low", depends_on_positions: [10] },
|
|
6956
|
+
{ position: 14, title_pattern: "Publish @hasna/{name} to npm", priority: "high", depends_on_positions: [6, 7, 8] },
|
|
6957
|
+
{ position: 15, title_pattern: "Install @hasna/{name} globally and verify", priority: "medium", depends_on_positions: [14] }
|
|
6958
|
+
]
|
|
6959
|
+
},
|
|
6960
|
+
{
|
|
6961
|
+
name: "bug-fix",
|
|
6962
|
+
description: "Standard bug fix workflow",
|
|
6963
|
+
variables: [{ name: "bug", required: true, description: "Bug description" }],
|
|
6964
|
+
tasks: [
|
|
6965
|
+
{ position: 0, title_pattern: "Reproduce: {bug}", priority: "critical" },
|
|
6966
|
+
{ position: 1, title_pattern: "Diagnose root cause of {bug}", priority: "critical", depends_on_positions: [0] },
|
|
6967
|
+
{ position: 2, title_pattern: "Implement fix for {bug}", priority: "critical", depends_on_positions: [1] },
|
|
6968
|
+
{ position: 3, title_pattern: "Write regression test for {bug}", priority: "high", depends_on_positions: [2] },
|
|
6969
|
+
{ position: 4, title_pattern: "Publish fix and verify in production", priority: "high", depends_on_positions: [3] }
|
|
6970
|
+
]
|
|
6971
|
+
},
|
|
6972
|
+
{
|
|
6973
|
+
name: "feature",
|
|
6974
|
+
description: "Standard feature development workflow",
|
|
6975
|
+
variables: [{ name: "feature", required: true }, { name: "scope", required: false, default: "medium" }],
|
|
6976
|
+
tasks: [
|
|
6977
|
+
{ position: 0, title_pattern: "Write spec for {feature}", priority: "high" },
|
|
6978
|
+
{ position: 1, title_pattern: "Design implementation approach for {feature}", priority: "high", depends_on_positions: [0] },
|
|
6979
|
+
{ position: 2, title_pattern: "Implement {feature}", priority: "critical", depends_on_positions: [1] },
|
|
6980
|
+
{ position: 3, title_pattern: "Write tests for {feature}", priority: "high", depends_on_positions: [2] },
|
|
6981
|
+
{ position: 4, title_pattern: "Code review for {feature}", priority: "medium", depends_on_positions: [3] },
|
|
6982
|
+
{ position: 5, title_pattern: "Update docs for {feature}", priority: "medium", depends_on_positions: [2] },
|
|
6983
|
+
{ position: 6, title_pattern: "Deploy {feature}", priority: "high", depends_on_positions: [4] }
|
|
6984
|
+
]
|
|
6985
|
+
},
|
|
6986
|
+
{
|
|
6987
|
+
name: "security-audit",
|
|
6988
|
+
description: "Security audit workflow",
|
|
6989
|
+
variables: [{ name: "target", required: true }],
|
|
6990
|
+
tasks: [
|
|
6991
|
+
{ position: 0, title_pattern: "Scan {target} for vulnerabilities", priority: "critical" },
|
|
6992
|
+
{ position: 1, title_pattern: "Review {target} security findings", priority: "critical", depends_on_positions: [0] },
|
|
6993
|
+
{ position: 2, title_pattern: "Fix critical issues in {target}", priority: "critical", depends_on_positions: [1] },
|
|
6994
|
+
{ position: 3, title_pattern: "Retest {target} after fixes", priority: "high", depends_on_positions: [2] },
|
|
6995
|
+
{ position: 4, title_pattern: "Write security report for {target}", priority: "medium", depends_on_positions: [3] },
|
|
6996
|
+
{ position: 5, title_pattern: "Close audit for {target}", priority: "low", depends_on_positions: [4] }
|
|
6997
|
+
]
|
|
6998
|
+
}
|
|
6999
|
+
];
|
|
7000
|
+
});
|
|
7001
|
+
|
|
6553
7002
|
// src/lib/extract.ts
|
|
6554
7003
|
var exports_extract = {};
|
|
6555
7004
|
__export(exports_extract, {
|
|
@@ -23102,6 +23551,26 @@ var init_pg_migrations = __esm(() => {
|
|
|
23102
23551
|
CREATE INDEX IF NOT EXISTS idx_template_tasks_template ON template_tasks(template_id);
|
|
23103
23552
|
|
|
23104
23553
|
INSERT INTO _migrations (id) VALUES (37) ON CONFLICT DO NOTHING;
|
|
23554
|
+
`,
|
|
23555
|
+
`
|
|
23556
|
+
ALTER TABLE task_templates ADD COLUMN IF NOT EXISTS variables TEXT DEFAULT '[]';
|
|
23557
|
+
INSERT INTO _migrations (id) VALUES (38) ON CONFLICT DO NOTHING;
|
|
23558
|
+
`,
|
|
23559
|
+
`
|
|
23560
|
+
ALTER TABLE template_tasks ADD COLUMN IF NOT EXISTS condition TEXT;
|
|
23561
|
+
ALTER TABLE template_tasks ADD COLUMN IF NOT EXISTS include_template_id TEXT;
|
|
23562
|
+
ALTER TABLE task_templates ADD COLUMN IF NOT EXISTS version INTEGER NOT NULL DEFAULT 1;
|
|
23563
|
+
|
|
23564
|
+
CREATE TABLE IF NOT EXISTS template_versions (
|
|
23565
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
23566
|
+
template_id TEXT NOT NULL REFERENCES task_templates(id) ON DELETE CASCADE,
|
|
23567
|
+
version INTEGER NOT NULL,
|
|
23568
|
+
snapshot TEXT NOT NULL,
|
|
23569
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
23570
|
+
);
|
|
23571
|
+
CREATE INDEX IF NOT EXISTS idx_template_versions_template ON template_versions(template_id);
|
|
23572
|
+
|
|
23573
|
+
INSERT INTO _migrations (id) VALUES (39) ON CONFLICT DO NOTHING;
|
|
23105
23574
|
`
|
|
23106
23575
|
];
|
|
23107
23576
|
});
|
|
@@ -23361,6 +23830,11 @@ var init_mcp = __esm(() => {
|
|
|
23361
23830
|
"create_task_from_template",
|
|
23362
23831
|
"delete_template",
|
|
23363
23832
|
"update_template",
|
|
23833
|
+
"init_templates",
|
|
23834
|
+
"preview_template",
|
|
23835
|
+
"export_template",
|
|
23836
|
+
"import_template",
|
|
23837
|
+
"template_history",
|
|
23364
23838
|
"approve_task"
|
|
23365
23839
|
]);
|
|
23366
23840
|
agentFocusMap = new Map;
|
|
@@ -24954,15 +25428,20 @@ ${data.chart}` }] };
|
|
|
24954
25428
|
});
|
|
24955
25429
|
}
|
|
24956
25430
|
if (shouldRegisterTool("create_webhook")) {
|
|
24957
|
-
server.tool("create_webhook", "Register a webhook for task change events.", {
|
|
25431
|
+
server.tool("create_webhook", "Register a webhook for task change events. Optionally scope to a project, task list, agent, or specific task.", {
|
|
24958
25432
|
url: exports_external.string(),
|
|
24959
|
-
events: exports_external.array(exports_external.string()).optional(),
|
|
24960
|
-
secret: exports_external.string().optional()
|
|
25433
|
+
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"),
|
|
25434
|
+
secret: exports_external.string().optional().describe("HMAC secret for signing webhook payloads"),
|
|
25435
|
+
project_id: exports_external.string().optional().describe("Only fire for events in this project"),
|
|
25436
|
+
task_list_id: exports_external.string().optional().describe("Only fire for events in this task list"),
|
|
25437
|
+
agent_id: exports_external.string().optional().describe("Only fire for events involving this agent"),
|
|
25438
|
+
task_id: exports_external.string().optional().describe("Only fire for events on this specific task")
|
|
24961
25439
|
}, async (params) => {
|
|
24962
25440
|
try {
|
|
24963
25441
|
const { createWebhook: createWebhook2 } = await Promise.resolve().then(() => (init_webhooks(), exports_webhooks));
|
|
24964
25442
|
const wh = createWebhook2(params);
|
|
24965
|
-
|
|
25443
|
+
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(", ");
|
|
25444
|
+
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}` : ""}` }] };
|
|
24966
25445
|
} catch (e) {
|
|
24967
25446
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
24968
25447
|
}
|
|
@@ -24998,7 +25477,7 @@ ${text}` }] };
|
|
|
24998
25477
|
});
|
|
24999
25478
|
}
|
|
25000
25479
|
if (shouldRegisterTool("create_template")) {
|
|
25001
|
-
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).", {
|
|
25480
|
+
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.", {
|
|
25002
25481
|
name: exports_external.string(),
|
|
25003
25482
|
title_pattern: exports_external.string(),
|
|
25004
25483
|
description: exports_external.string().optional(),
|
|
@@ -25006,6 +25485,12 @@ ${text}` }] };
|
|
|
25006
25485
|
tags: exports_external.array(exports_external.string()).optional(),
|
|
25007
25486
|
project_id: exports_external.string().optional(),
|
|
25008
25487
|
plan_id: exports_external.string().optional(),
|
|
25488
|
+
variables: exports_external.array(exports_external.object({
|
|
25489
|
+
name: exports_external.string().describe("Variable name (used as {name} in patterns)"),
|
|
25490
|
+
required: exports_external.boolean().describe("Whether this variable must be provided"),
|
|
25491
|
+
default: exports_external.string().optional().describe("Default value if not provided"),
|
|
25492
|
+
description: exports_external.string().optional().describe("Help text for the variable")
|
|
25493
|
+
})).optional().describe("Typed variable definitions with defaults and required flags"),
|
|
25009
25494
|
tasks: exports_external.array(exports_external.object({
|
|
25010
25495
|
title_pattern: exports_external.string().describe("Title pattern with optional {variable} placeholders"),
|
|
25011
25496
|
description: exports_external.string().optional(),
|
|
@@ -25035,7 +25520,10 @@ ${text}` }] };
|
|
|
25035
25520
|
const templates = listTemplates2();
|
|
25036
25521
|
if (templates.length === 0)
|
|
25037
25522
|
return { content: [{ type: "text", text: "No templates." }] };
|
|
25038
|
-
const text = templates.map((t) =>
|
|
25523
|
+
const text = templates.map((t) => {
|
|
25524
|
+
const vars = t.variables.length > 0 ? ` | vars: ${t.variables.map((v) => `${v.name}${v.required ? "*" : ""}${v.default ? `=${v.default}` : ""}`).join(", ")}` : "";
|
|
25525
|
+
return `${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}" | ${t.priority}${vars}`;
|
|
25526
|
+
}).join(`
|
|
25039
25527
|
`);
|
|
25040
25528
|
return { content: [{ type: "text", text: `${templates.length} template(s):
|
|
25041
25529
|
${text}` }] };
|
|
@@ -25060,7 +25548,8 @@ ${text}` }] };
|
|
|
25060
25548
|
const resolvedTemplateId = resolveId(params.template_id, "task_templates");
|
|
25061
25549
|
const templateWithTasks = getTemplateWithTasks2(resolvedTemplateId);
|
|
25062
25550
|
if (templateWithTasks && templateWithTasks.tasks.length > 0) {
|
|
25063
|
-
const
|
|
25551
|
+
const effectiveProjectId = params.project_id || templateWithTasks.project_id || undefined;
|
|
25552
|
+
const tasks = tasksFromTemplate2(resolvedTemplateId, effectiveProjectId, params.variables, params.task_list_id);
|
|
25064
25553
|
const text = tasks.map((t) => `${t.id.slice(0, 8)} | ${t.priority} | ${t.title}`).join(`
|
|
25065
25554
|
`);
|
|
25066
25555
|
return { content: [{ type: "text", text: `${tasks.length} task(s) created from template:
|
|
@@ -25117,6 +25606,93 @@ ${task.id.slice(0, 8)} | ${task.priority} | ${task.title}` }] };
|
|
|
25117
25606
|
}
|
|
25118
25607
|
});
|
|
25119
25608
|
}
|
|
25609
|
+
if (shouldRegisterTool("init_templates")) {
|
|
25610
|
+
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 () => {
|
|
25611
|
+
try {
|
|
25612
|
+
const { initBuiltinTemplates: initBuiltinTemplates2 } = await Promise.resolve().then(() => (init_builtin_templates(), exports_builtin_templates));
|
|
25613
|
+
const result = initBuiltinTemplates2();
|
|
25614
|
+
if (result.created === 0) {
|
|
25615
|
+
return { content: [{ type: "text", text: `All ${result.skipped} built-in template(s) already exist.` }] };
|
|
25616
|
+
}
|
|
25617
|
+
return { content: [{ type: "text", text: `Created ${result.created} template(s): ${result.names.join(", ")}. Skipped ${result.skipped} existing.` }] };
|
|
25618
|
+
} catch (e) {
|
|
25619
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25620
|
+
}
|
|
25621
|
+
});
|
|
25622
|
+
}
|
|
25623
|
+
if (shouldRegisterTool("preview_template")) {
|
|
25624
|
+
server.tool("preview_template", "Preview a template without creating tasks. Shows resolved titles (variables substituted), dependencies, and priorities.", {
|
|
25625
|
+
template_id: exports_external.string(),
|
|
25626
|
+
variables: exports_external.record(exports_external.string()).optional().describe("Variable substitution map for {name} placeholders")
|
|
25627
|
+
}, async (params) => {
|
|
25628
|
+
try {
|
|
25629
|
+
const { previewTemplate: previewTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
25630
|
+
const resolvedId = resolveId(params.template_id, "task_templates");
|
|
25631
|
+
const preview = previewTemplate2(resolvedId, params.variables);
|
|
25632
|
+
const lines = preview.tasks.map((t) => {
|
|
25633
|
+
const deps = t.depends_on_positions.length > 0 ? ` (after: ${t.depends_on_positions.join(", ")})` : "";
|
|
25634
|
+
return ` [${t.position}] ${t.priority} | ${t.title}${deps}`;
|
|
25635
|
+
});
|
|
25636
|
+
const varsInfo = preview.variables.length > 0 ? `
|
|
25637
|
+
Variables: ${preview.variables.map((v) => `${v.name}${v.required ? "*" : ""}${v.default ? `=${v.default}` : ""}`).join(", ")}` : "";
|
|
25638
|
+
const resolvedVars = Object.keys(preview.resolved_variables).length > 0 ? `
|
|
25639
|
+
Resolved: ${Object.entries(preview.resolved_variables).map(([k, v]) => `${k}=${v}`).join(", ")}` : "";
|
|
25640
|
+
return { content: [{ type: "text", text: `Preview: ${preview.template_name} (${preview.tasks.length} tasks)${varsInfo}${resolvedVars}
|
|
25641
|
+
${lines.join(`
|
|
25642
|
+
`)}` }] };
|
|
25643
|
+
} catch (e) {
|
|
25644
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25645
|
+
}
|
|
25646
|
+
});
|
|
25647
|
+
}
|
|
25648
|
+
if (shouldRegisterTool("export_template")) {
|
|
25649
|
+
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 }) => {
|
|
25650
|
+
try {
|
|
25651
|
+
const { exportTemplate: exportTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
25652
|
+
const resolvedId = resolveId(template_id, "task_templates");
|
|
25653
|
+
const json = exportTemplate2(resolvedId);
|
|
25654
|
+
return { content: [{ type: "text", text: JSON.stringify(json, null, 2) }] };
|
|
25655
|
+
} catch (e) {
|
|
25656
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25657
|
+
}
|
|
25658
|
+
});
|
|
25659
|
+
}
|
|
25660
|
+
if (shouldRegisterTool("import_template")) {
|
|
25661
|
+
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 }) => {
|
|
25662
|
+
try {
|
|
25663
|
+
const { importTemplate: importTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
25664
|
+
const parsed = JSON.parse(json);
|
|
25665
|
+
const t = importTemplate2(parsed);
|
|
25666
|
+
return { content: [{ type: "text", text: `Template imported: ${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}"` }] };
|
|
25667
|
+
} catch (e) {
|
|
25668
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25669
|
+
}
|
|
25670
|
+
});
|
|
25671
|
+
}
|
|
25672
|
+
if (shouldRegisterTool("template_history")) {
|
|
25673
|
+
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 }) => {
|
|
25674
|
+
try {
|
|
25675
|
+
const { listTemplateVersions: listTemplateVersions2, getTemplate: getTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
25676
|
+
const resolvedId = resolveId(template_id, "task_templates");
|
|
25677
|
+
const template = getTemplate2(resolvedId);
|
|
25678
|
+
if (!template)
|
|
25679
|
+
return { content: [{ type: "text", text: `Template not found: ${template_id}` }], isError: true };
|
|
25680
|
+
const versions = listTemplateVersions2(resolvedId);
|
|
25681
|
+
if (versions.length === 0) {
|
|
25682
|
+
return { content: [{ type: "text", text: `${template.name} v${template.version} \u2014 no previous versions.` }] };
|
|
25683
|
+
}
|
|
25684
|
+
const lines = versions.map((v) => {
|
|
25685
|
+
const snap = JSON.parse(v.snapshot);
|
|
25686
|
+
return `v${v.version} | ${v.created_at} | ${snap.name} | "${snap.title_pattern}"`;
|
|
25687
|
+
});
|
|
25688
|
+
return { content: [{ type: "text", text: `${template.name} \u2014 current: v${template.version}
|
|
25689
|
+
${lines.join(`
|
|
25690
|
+
`)}` }] };
|
|
25691
|
+
} catch (e) {
|
|
25692
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25693
|
+
}
|
|
25694
|
+
});
|
|
25695
|
+
}
|
|
25120
25696
|
if (shouldRegisterTool("approve_task")) {
|
|
25121
25697
|
server.tool("approve_task", "Approve a task with requires_approval=true.", {
|
|
25122
25698
|
id: exports_external.string(),
|
|
@@ -26234,6 +26810,11 @@ ${stack_trace.slice(0, 1500)}
|
|
|
26234
26810
|
"create_task_from_template",
|
|
26235
26811
|
"delete_template",
|
|
26236
26812
|
"update_template",
|
|
26813
|
+
"init_templates",
|
|
26814
|
+
"preview_template",
|
|
26815
|
+
"export_template",
|
|
26816
|
+
"import_template",
|
|
26817
|
+
"template_history",
|
|
26237
26818
|
"bulk_update_tasks",
|
|
26238
26819
|
"bulk_create_tasks",
|
|
26239
26820
|
"get_task_stats",
|
|
@@ -26473,8 +27054,21 @@ ${stack_trace.slice(0, 1500)}
|
|
|
26473
27054
|
Params: id(string, req)
|
|
26474
27055
|
Example: {id: 'a1b2c3d4'}`,
|
|
26475
27056
|
update_template: `Update a task template's name, title pattern, or other fields.
|
|
26476
|
-
Params: id(string, req), name(string), title_pattern(string), description(string), priority(low|medium|high|critical), tags(string[]), project_id(string), plan_id(string)
|
|
27057
|
+
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)
|
|
26477
27058
|
Example: {id: 'a1b2c3d4', name: 'Renamed Template', priority: 'critical'}`,
|
|
27059
|
+
init_templates: "Initialize built-in starter templates (open-source-project, bug-fix, feature, security-audit). Skips already existing. No params.",
|
|
27060
|
+
preview_template: `Preview a template without creating tasks. Shows resolved titles, deps, priorities.
|
|
27061
|
+
Params: template_id(string, req), variables(Record<string,string>)
|
|
27062
|
+
Example: {template_id: 'a1b2c3d4', variables: {name: 'invoices'}}`,
|
|
27063
|
+
export_template: `Export a template as JSON (template + tasks + variables). Use for sharing or backup.
|
|
27064
|
+
Params: template_id(string, req)
|
|
27065
|
+
Example: {template_id: 'a1b2c3d4'}`,
|
|
27066
|
+
import_template: `Import a template from a JSON string (as returned by export_template). Creates new template with new IDs.
|
|
27067
|
+
Params: json(string, req)
|
|
27068
|
+
Example: {json: '{"name":"My Template",...}'}`,
|
|
27069
|
+
template_history: `Show version history of a template. Each update creates a snapshot of the previous state.
|
|
27070
|
+
Params: template_id(string, req)
|
|
27071
|
+
Example: {template_id: 'a1b2c3d4'}`,
|
|
26478
27072
|
get_active_work: `See all in-progress tasks and who is working on them.
|
|
26479
27073
|
Params: project_id(string, optional), task_list_id(string, optional)
|
|
26480
27074
|
Example: {project_id: 'a1b2c3d4'}`,
|
|
@@ -27494,6 +28088,8 @@ async function startServer(port, options) {
|
|
|
27494
28088
|
continue;
|
|
27495
28089
|
if (client.agentId && event.agent_id !== client.agentId)
|
|
27496
28090
|
continue;
|
|
28091
|
+
if (client.projectId && event.project_id !== client.projectId)
|
|
28092
|
+
continue;
|
|
27497
28093
|
try {
|
|
27498
28094
|
client.controller.enqueue(`event: ${eventName}
|
|
27499
28095
|
data: ${data}
|
|
@@ -27532,6 +28128,31 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
27532
28128
|
});
|
|
27533
28129
|
}
|
|
27534
28130
|
if (path === "/api/events" && method === "GET") {
|
|
28131
|
+
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
28132
|
+
const projectId = url.searchParams.get("project_id") || undefined;
|
|
28133
|
+
if (agentId || projectId) {
|
|
28134
|
+
const client = { controller: null, agentId, projectId, events: undefined };
|
|
28135
|
+
const stream2 = new ReadableStream({
|
|
28136
|
+
start(controller) {
|
|
28137
|
+
client.controller = controller;
|
|
28138
|
+
filteredSseClients.add(client);
|
|
28139
|
+
controller.enqueue(`data: ${JSON.stringify({ type: "connected", agent_id: agentId, project_id: projectId, timestamp: new Date().toISOString() })}
|
|
28140
|
+
|
|
28141
|
+
`);
|
|
28142
|
+
},
|
|
28143
|
+
cancel() {
|
|
28144
|
+
filteredSseClients.delete(client);
|
|
28145
|
+
}
|
|
28146
|
+
});
|
|
28147
|
+
return new Response(stream2, {
|
|
28148
|
+
headers: {
|
|
28149
|
+
"Content-Type": "text/event-stream",
|
|
28150
|
+
"Cache-Control": "no-cache",
|
|
28151
|
+
Connection: "keep-alive",
|
|
28152
|
+
"Access-Control-Allow-Origin": "*"
|
|
28153
|
+
}
|
|
28154
|
+
});
|
|
28155
|
+
}
|
|
27535
28156
|
const stream = new ReadableStream({
|
|
27536
28157
|
start(controller) {
|
|
27537
28158
|
sseClients.add(controller);
|
|
@@ -27640,7 +28261,7 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
27640
28261
|
priority: body.priority,
|
|
27641
28262
|
project_id: body.project_id
|
|
27642
28263
|
});
|
|
27643
|
-
broadcastEvent({ type: "task", task_id: task.id, action: "created", agent_id: task.agent_id });
|
|
28264
|
+
broadcastEvent({ type: "task", task_id: task.id, action: "created", agent_id: task.agent_id, project_id: task.project_id });
|
|
27644
28265
|
return json(taskToSummary(task), 201, port);
|
|
27645
28266
|
} catch (e) {
|
|
27646
28267
|
return json({ error: e instanceof Error ? e.message : "Failed to create task" }, 500, port);
|
|
@@ -27861,7 +28482,7 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
27861
28482
|
const id = startMatch[1];
|
|
27862
28483
|
try {
|
|
27863
28484
|
const task = startTask(id, "dashboard");
|
|
27864
|
-
broadcastEvent({ type: "task", task_id: task.id, action: "started", agent_id: "dashboard" });
|
|
28485
|
+
broadcastEvent({ type: "task", task_id: task.id, action: "started", agent_id: "dashboard", project_id: task.project_id });
|
|
27865
28486
|
return json(taskToSummary(task), 200, port);
|
|
27866
28487
|
} catch (e) {
|
|
27867
28488
|
return json({ error: e instanceof Error ? e.message : "Failed to start task" }, 500, port);
|
|
@@ -27874,7 +28495,7 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
27874
28495
|
const body = await req.json().catch(() => ({}));
|
|
27875
28496
|
const { failTask: failTask2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
27876
28497
|
const result = failTask2(id, body.agent_id, body.reason, { retry: body.retry, error_code: body.error_code });
|
|
27877
|
-
broadcastEvent({ type: "task", task_id: id, action: "failed", agent_id: body.agent_id || null });
|
|
28498
|
+
broadcastEvent({ type: "task", task_id: id, action: "failed", agent_id: body.agent_id || null, project_id: result.task.project_id });
|
|
27878
28499
|
return json({ task: taskToSummary(result.task), retry_task: result.retryTask ? taskToSummary(result.retryTask) : null }, 200, port);
|
|
27879
28500
|
} catch (e) {
|
|
27880
28501
|
return json({ error: e instanceof Error ? e.message : "Failed to fail task" }, 500, port);
|
|
@@ -27885,7 +28506,7 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
27885
28506
|
const id = completeMatch[1];
|
|
27886
28507
|
try {
|
|
27887
28508
|
const task = completeTask(id, "dashboard");
|
|
27888
|
-
broadcastEvent({ type: "task", task_id: task.id, action: "completed", agent_id: "dashboard" });
|
|
28509
|
+
broadcastEvent({ type: "task", task_id: task.id, action: "completed", agent_id: "dashboard", project_id: task.project_id });
|
|
27889
28510
|
return json(taskToSummary(task), 200, port);
|
|
27890
28511
|
} catch (e) {
|
|
27891
28512
|
return json({ error: e instanceof Error ? e.message : "Failed to complete task" }, 500, port);
|
|
@@ -30886,7 +31507,117 @@ program2.command("templates").description("List and manage task templates").opti
|
|
|
30886
31507
|
console.log(chalk2.bold(`${templates.length} template(s):
|
|
30887
31508
|
`));
|
|
30888
31509
|
for (const t of templates) {
|
|
30889
|
-
|
|
31510
|
+
const vars = t.variables && t.variables.length > 0 ? ` ${chalk2.dim(`(${t.variables.map((v) => `${v.name}${v.required ? "*" : ""}${v.default ? `=${v.default}` : ""}`).join(", ")})`)}` : "";
|
|
31511
|
+
console.log(` ${chalk2.dim(t.id.slice(0, 8))} ${chalk2.bold(t.name)} ${chalk2.cyan(`"${t.title_pattern}"`)} ${chalk2.yellow(t.priority)}${vars}`);
|
|
31512
|
+
}
|
|
31513
|
+
});
|
|
31514
|
+
program2.command("template-init").alias("templates-init").description("Initialize built-in starter templates (open-source-project, bug-fix, feature, security-audit)").action(() => {
|
|
31515
|
+
const globalOpts = program2.opts();
|
|
31516
|
+
const { initBuiltinTemplates: initBuiltinTemplates2 } = (init_builtin_templates(), __toCommonJS(exports_builtin_templates));
|
|
31517
|
+
const result = initBuiltinTemplates2();
|
|
31518
|
+
if (globalOpts.json) {
|
|
31519
|
+
output(result, true);
|
|
31520
|
+
return;
|
|
31521
|
+
}
|
|
31522
|
+
if (result.created === 0) {
|
|
31523
|
+
console.log(chalk2.dim(`All ${result.skipped} built-in template(s) already exist.`));
|
|
31524
|
+
} else {
|
|
31525
|
+
console.log(chalk2.green(`Created ${result.created} template(s): ${result.names.join(", ")}. Skipped ${result.skipped} existing.`));
|
|
31526
|
+
}
|
|
31527
|
+
});
|
|
31528
|
+
program2.command("template-preview <id>").alias("templates-preview").description("Preview a template without creating tasks \u2014 shows resolved titles, deps, and priorities").option("--var <vars...>", "Variable substitution in key=value format (e.g. --var name=invoices)").action((id, opts) => {
|
|
31529
|
+
const globalOpts = program2.opts();
|
|
31530
|
+
const { previewTemplate: previewTemplate2 } = (init_templates(), __toCommonJS(exports_templates));
|
|
31531
|
+
const variables = {};
|
|
31532
|
+
if (opts.var) {
|
|
31533
|
+
for (const v of opts.var) {
|
|
31534
|
+
const eq = v.indexOf("=");
|
|
31535
|
+
if (eq === -1) {
|
|
31536
|
+
console.error(chalk2.red(`Invalid variable format: ${v} (expected key=value)`));
|
|
31537
|
+
process.exit(1);
|
|
31538
|
+
}
|
|
31539
|
+
variables[v.slice(0, eq)] = v.slice(eq + 1);
|
|
31540
|
+
}
|
|
31541
|
+
}
|
|
31542
|
+
try {
|
|
31543
|
+
const preview = previewTemplate2(id, Object.keys(variables).length > 0 ? variables : undefined);
|
|
31544
|
+
if (globalOpts.json) {
|
|
31545
|
+
output(preview, true);
|
|
31546
|
+
return;
|
|
31547
|
+
}
|
|
31548
|
+
console.log(chalk2.bold(`Preview: ${preview.template_name} (${preview.tasks.length} tasks)`));
|
|
31549
|
+
if (preview.description)
|
|
31550
|
+
console.log(chalk2.dim(` ${preview.description}`));
|
|
31551
|
+
if (preview.variables.length > 0) {
|
|
31552
|
+
console.log(chalk2.dim(` Variables: ${preview.variables.map((v) => `${v.name}${v.required ? "*" : ""}${v.default ? `=${v.default}` : ""}`).join(", ")}`));
|
|
31553
|
+
}
|
|
31554
|
+
if (Object.keys(preview.resolved_variables).length > 0) {
|
|
31555
|
+
console.log(chalk2.dim(` Resolved: ${Object.entries(preview.resolved_variables).map(([k, v]) => `${k}=${v}`).join(", ")}`));
|
|
31556
|
+
}
|
|
31557
|
+
console.log();
|
|
31558
|
+
for (const t of preview.tasks) {
|
|
31559
|
+
const deps = t.depends_on_positions.length > 0 ? chalk2.dim(` (after: ${t.depends_on_positions.join(", ")})`) : "";
|
|
31560
|
+
console.log(` ${chalk2.dim(`[${t.position}]`)} ${chalk2.yellow(t.priority)} | ${t.title}${deps}`);
|
|
31561
|
+
}
|
|
31562
|
+
} catch (e) {
|
|
31563
|
+
handleError(e);
|
|
31564
|
+
}
|
|
31565
|
+
});
|
|
31566
|
+
program2.command("template-export <id>").alias("templates-export").description("Export a template as JSON to stdout").action((id) => {
|
|
31567
|
+
const { exportTemplate: exportTemplate2 } = (init_templates(), __toCommonJS(exports_templates));
|
|
31568
|
+
try {
|
|
31569
|
+
const json2 = exportTemplate2(id);
|
|
31570
|
+
console.log(JSON.stringify(json2, null, 2));
|
|
31571
|
+
} catch (e) {
|
|
31572
|
+
handleError(e);
|
|
31573
|
+
}
|
|
31574
|
+
});
|
|
31575
|
+
program2.command("template-import").alias("templates-import").description("Import a template from a JSON file").option("--file <path>", "Path to template JSON file").action((opts) => {
|
|
31576
|
+
const globalOpts = program2.opts();
|
|
31577
|
+
const { importTemplate: importTemplate2 } = (init_templates(), __toCommonJS(exports_templates));
|
|
31578
|
+
const { readFileSync: readFileSync8 } = __require("fs");
|
|
31579
|
+
try {
|
|
31580
|
+
if (!opts.file) {
|
|
31581
|
+
console.error(chalk2.red("--file is required"));
|
|
31582
|
+
process.exit(1);
|
|
31583
|
+
}
|
|
31584
|
+
const content = readFileSync8(opts.file, "utf-8");
|
|
31585
|
+
const json2 = JSON.parse(content);
|
|
31586
|
+
const template = importTemplate2(json2);
|
|
31587
|
+
if (globalOpts.json) {
|
|
31588
|
+
output(template, true);
|
|
31589
|
+
} else {
|
|
31590
|
+
console.log(chalk2.green(`Template imported: ${template.id.slice(0, 8)} | ${template.name} | "${template.title_pattern}"`));
|
|
31591
|
+
}
|
|
31592
|
+
} catch (e) {
|
|
31593
|
+
handleError(e);
|
|
31594
|
+
}
|
|
31595
|
+
});
|
|
31596
|
+
program2.command("template-history <id>").alias("templates-history").description("Show version history of a template").action((id) => {
|
|
31597
|
+
const globalOpts = program2.opts();
|
|
31598
|
+
const { listTemplateVersions: listTemplateVersions2, getTemplate: getTemplate2 } = (init_templates(), __toCommonJS(exports_templates));
|
|
31599
|
+
try {
|
|
31600
|
+
const template = getTemplate2(id);
|
|
31601
|
+
if (!template) {
|
|
31602
|
+
console.error(chalk2.red("Template not found."));
|
|
31603
|
+
process.exit(1);
|
|
31604
|
+
}
|
|
31605
|
+
const versions = listTemplateVersions2(id);
|
|
31606
|
+
if (globalOpts.json) {
|
|
31607
|
+
output({ current_version: template.version, versions }, true);
|
|
31608
|
+
return;
|
|
31609
|
+
}
|
|
31610
|
+
console.log(chalk2.bold(`${template.name} \u2014 current version: ${template.version}`));
|
|
31611
|
+
if (versions.length === 0) {
|
|
31612
|
+
console.log(chalk2.dim(" No previous versions."));
|
|
31613
|
+
} else {
|
|
31614
|
+
for (const v of versions) {
|
|
31615
|
+
const snap = JSON.parse(v.snapshot);
|
|
31616
|
+
console.log(` ${chalk2.dim(`v${v.version}`)} | ${v.created_at} | ${snap.name} | "${snap.title_pattern}"`);
|
|
31617
|
+
}
|
|
31618
|
+
}
|
|
31619
|
+
} catch (e) {
|
|
31620
|
+
handleError(e);
|
|
30890
31621
|
}
|
|
30891
31622
|
});
|
|
30892
31623
|
program2.command("comment <id> <text>").description("Add a comment to a task").action((id, text) => {
|