@hasna/todos 0.11.93 → 0.11.95
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/cloud-router.d.ts +12 -1
- package/dist/cli/cloud-router.d.ts.map +1 -1
- package/dist/cli/commands/plan-template-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +885 -76
- package/dist/contracts.js +56 -50
- package/dist/db/storage-tombstones.d.ts +1 -1
- package/dist/db/storage-tombstones.d.ts.map +1 -1
- package/dist/db/templates.d.ts.map +1 -1
- package/dist/index.js +170 -56
- package/dist/lib/template-semantics.d.ts +8 -0
- package/dist/lib/template-semantics.d.ts.map +1 -0
- package/dist/mcp/index.js +605 -61
- package/dist/mcp/tools/task-crud.d.ts.map +1 -1
- package/dist/mcp/tools/task-meta-tools.d.ts.map +1 -1
- package/dist/mcp/tools/task-project-tools.d.ts.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/registry.js +56 -50
- package/dist/release-provenance.json +5 -5
- package/dist/sdk/index.js +35 -0
- package/dist/sdk/v1.generated.d.ts +105 -1
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/index.js +611 -67
- package/dist/server/openapi.d.ts +496 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage/interfaces.d.ts +3 -2
- package/dist/storage/interfaces.d.ts.map +1 -1
- package/dist/storage/postgres-adapter.d.ts.map +1 -1
- package/dist/storage/postgres-sync.d.ts +1 -1
- package/dist/storage/postgres-sync.d.ts.map +1 -1
- package/dist/storage/shadow-outbox.d.ts.map +1 -1
- package/dist/storage/shadow.d.ts +1 -1
- package/dist/storage/shadow.d.ts.map +1 -1
- package/dist/storage/sqlite-snapshot.d.ts.map +1 -1
- package/dist/storage.js +169 -55
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -11043,6 +11043,50 @@ var init_recurrence = __esm(() => {
|
|
|
11043
11043
|
};
|
|
11044
11044
|
});
|
|
11045
11045
|
|
|
11046
|
+
// src/lib/template-semantics.ts
|
|
11047
|
+
function resolveTemplateVariables(templateVars, provided) {
|
|
11048
|
+
const merged = { ...provided };
|
|
11049
|
+
for (const variable of templateVars) {
|
|
11050
|
+
if (merged[variable.name] === undefined && variable.default !== undefined) {
|
|
11051
|
+
merged[variable.name] = variable.default;
|
|
11052
|
+
}
|
|
11053
|
+
}
|
|
11054
|
+
const missing = templateVars.filter((variable) => variable.required && merged[variable.name] === undefined).map((variable) => variable.name);
|
|
11055
|
+
if (missing.length > 0) {
|
|
11056
|
+
throw new Error(`Missing required template variable(s): ${missing.join(", ")}`);
|
|
11057
|
+
}
|
|
11058
|
+
return merged;
|
|
11059
|
+
}
|
|
11060
|
+
function substituteTemplateVariables(text, variables) {
|
|
11061
|
+
let result = text;
|
|
11062
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
11063
|
+
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), value);
|
|
11064
|
+
}
|
|
11065
|
+
return result;
|
|
11066
|
+
}
|
|
11067
|
+
function evaluateTemplateCondition(condition, variables) {
|
|
11068
|
+
if (!condition || condition.trim() === "")
|
|
11069
|
+
return true;
|
|
11070
|
+
const trimmed = condition.trim();
|
|
11071
|
+
const equal = trimmed.match(/^\{([^}]+)\}\s*==\s*(.+)$/);
|
|
11072
|
+
if (equal)
|
|
11073
|
+
return (variables[equal[1]] ?? "") === equal[2].trim();
|
|
11074
|
+
const unequal = trimmed.match(/^\{([^}]+)\}\s*!=\s*(.+)$/);
|
|
11075
|
+
if (unequal)
|
|
11076
|
+
return (variables[unequal[1]] ?? "") !== unequal[2].trim();
|
|
11077
|
+
const falsy = trimmed.match(/^!\{([^}]+)\}$/);
|
|
11078
|
+
if (falsy) {
|
|
11079
|
+
const value = variables[falsy[1]];
|
|
11080
|
+
return !value || value === "" || value === "false";
|
|
11081
|
+
}
|
|
11082
|
+
const truthy = trimmed.match(/^\{([^}]+)\}$/);
|
|
11083
|
+
if (truthy) {
|
|
11084
|
+
const value = variables[truthy[1]];
|
|
11085
|
+
return !!value && value !== "" && value !== "false";
|
|
11086
|
+
}
|
|
11087
|
+
return true;
|
|
11088
|
+
}
|
|
11089
|
+
|
|
11046
11090
|
// src/db/templates.ts
|
|
11047
11091
|
var exports_templates = {};
|
|
11048
11092
|
__export(exports_templates, {
|
|
@@ -11138,6 +11182,14 @@ function deleteTemplate(id, db) {
|
|
|
11138
11182
|
payload: template,
|
|
11139
11183
|
version: template.version
|
|
11140
11184
|
}, d);
|
|
11185
|
+
for (const task of getTemplateTasks(resolved, d)) {
|
|
11186
|
+
recordStorageTombstone({
|
|
11187
|
+
object_type: "template_tasks",
|
|
11188
|
+
object_id: task.id,
|
|
11189
|
+
payload: task,
|
|
11190
|
+
version: 1
|
|
11191
|
+
}, d);
|
|
11192
|
+
}
|
|
11141
11193
|
return d.run("DELETE FROM task_templates WHERE id = ?", [resolved]).changes > 0;
|
|
11142
11194
|
}
|
|
11143
11195
|
function updateTemplate(id, updates, db) {
|
|
@@ -11269,34 +11321,7 @@ function getTemplateTasks(templateId, db) {
|
|
|
11269
11321
|
return rows.map(rowToTemplateTask);
|
|
11270
11322
|
}
|
|
11271
11323
|
function evaluateCondition(condition, variables) {
|
|
11272
|
-
|
|
11273
|
-
return true;
|
|
11274
|
-
const trimmed = condition.trim();
|
|
11275
|
-
const eqMatch = trimmed.match(/^\{([^}]+)\}\s*==\s*(.+)$/);
|
|
11276
|
-
if (eqMatch) {
|
|
11277
|
-
const varName = eqMatch[1];
|
|
11278
|
-
const expected = eqMatch[2].trim();
|
|
11279
|
-
return (variables[varName] ?? "") === expected;
|
|
11280
|
-
}
|
|
11281
|
-
const neqMatch = trimmed.match(/^\{([^}]+)\}\s*!=\s*(.+)$/);
|
|
11282
|
-
if (neqMatch) {
|
|
11283
|
-
const varName = neqMatch[1];
|
|
11284
|
-
const expected = neqMatch[2].trim();
|
|
11285
|
-
return (variables[varName] ?? "") !== expected;
|
|
11286
|
-
}
|
|
11287
|
-
const falsyMatch = trimmed.match(/^!\{([^}]+)\}$/);
|
|
11288
|
-
if (falsyMatch) {
|
|
11289
|
-
const varName = falsyMatch[1];
|
|
11290
|
-
const val = variables[varName];
|
|
11291
|
-
return !val || val === "" || val === "false";
|
|
11292
|
-
}
|
|
11293
|
-
const truthyMatch = trimmed.match(/^\{([^}]+)\}$/);
|
|
11294
|
-
if (truthyMatch) {
|
|
11295
|
-
const varName = truthyMatch[1];
|
|
11296
|
-
const val = variables[varName];
|
|
11297
|
-
return !!val && val !== "" && val !== "false";
|
|
11298
|
-
}
|
|
11299
|
-
return true;
|
|
11324
|
+
return evaluateTemplateCondition(condition, variables);
|
|
11300
11325
|
}
|
|
11301
11326
|
function exportTemplate(id, db) {
|
|
11302
11327
|
const d = db || getDatabase();
|
|
@@ -11369,29 +11394,10 @@ function listTemplateVersions(id, db) {
|
|
|
11369
11394
|
return d.query("SELECT * FROM template_versions WHERE template_id = ? ORDER BY version DESC").all(resolved);
|
|
11370
11395
|
}
|
|
11371
11396
|
function resolveVariables(templateVars, provided) {
|
|
11372
|
-
|
|
11373
|
-
for (const v of templateVars) {
|
|
11374
|
-
if (merged[v.name] === undefined && v.default !== undefined) {
|
|
11375
|
-
merged[v.name] = v.default;
|
|
11376
|
-
}
|
|
11377
|
-
}
|
|
11378
|
-
const missing = [];
|
|
11379
|
-
for (const v of templateVars) {
|
|
11380
|
-
if (v.required && merged[v.name] === undefined) {
|
|
11381
|
-
missing.push(v.name);
|
|
11382
|
-
}
|
|
11383
|
-
}
|
|
11384
|
-
if (missing.length > 0) {
|
|
11385
|
-
throw new Error(`Missing required template variable(s): ${missing.join(", ")}`);
|
|
11386
|
-
}
|
|
11387
|
-
return merged;
|
|
11397
|
+
return resolveTemplateVariables(templateVars, provided);
|
|
11388
11398
|
}
|
|
11389
11399
|
function substituteVars(text, variables) {
|
|
11390
|
-
|
|
11391
|
-
for (const [key, val] of Object.entries(variables)) {
|
|
11392
|
-
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), val);
|
|
11393
|
-
}
|
|
11394
|
-
return result;
|
|
11400
|
+
return substituteTemplateVariables(text, variables);
|
|
11395
11401
|
}
|
|
11396
11402
|
function tasksFromTemplate(templateId, projectId, variables, taskListId, db, _visitedTemplateIds) {
|
|
11397
11403
|
const d = db || getDatabase();
|
|
@@ -16658,6 +16664,7 @@ var init_token_utils = __esm(() => {
|
|
|
16658
16664
|
|
|
16659
16665
|
// src/cli/cloud-router.ts
|
|
16660
16666
|
import { resolveStorageClient } from "@hasna/contracts/client/storage";
|
|
16667
|
+
import { resolve as resolvePath } from "path";
|
|
16661
16668
|
function cleanMode(value) {
|
|
16662
16669
|
const normalized = value?.trim().toLowerCase();
|
|
16663
16670
|
return normalized || null;
|
|
@@ -16940,6 +16947,39 @@ async function cloudListProjects(client) {
|
|
|
16940
16947
|
const envelope = res.raw;
|
|
16941
16948
|
return Array.isArray(envelope?.projects) ? envelope.projects : res.items;
|
|
16942
16949
|
}
|
|
16950
|
+
function cloudProjectSlug(value) {
|
|
16951
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
16952
|
+
}
|
|
16953
|
+
function cloudProjectPathBasename(value) {
|
|
16954
|
+
return value.replace(/[\\/]+$/, "").split(/[\\/]/).pop() ?? value;
|
|
16955
|
+
}
|
|
16956
|
+
function uniqueProjectMatches(projects, predicate) {
|
|
16957
|
+
return [...new Map(projects.filter(predicate).map((project) => [project.id, project])).values()];
|
|
16958
|
+
}
|
|
16959
|
+
function resolveCloudProjectRef(projects, ref) {
|
|
16960
|
+
const input = ref.trim();
|
|
16961
|
+
const normalizedRef = input.toLowerCase();
|
|
16962
|
+
const pathLike = input.startsWith(".") || input.includes("/") || input.includes("\\");
|
|
16963
|
+
const normalizedPath = pathLike ? resolvePath(input) : undefined;
|
|
16964
|
+
const slug = cloudProjectSlug(pathLike ? cloudProjectPathBasename(input) : input);
|
|
16965
|
+
const matchGroups = [
|
|
16966
|
+
uniqueProjectMatches(projects, (project) => project.id.toLowerCase() === normalizedRef),
|
|
16967
|
+
uniqueProjectMatches(projects, (project) => project.path === input || normalizedPath !== undefined && resolvePath(project.path) === normalizedPath),
|
|
16968
|
+
uniqueProjectMatches(projects, (project) => project.name.toLowerCase() === normalizedRef),
|
|
16969
|
+
uniqueProjectMatches(projects, (project) => project.task_list_id === input || cloudProjectSlug(project.name) === slug || cloudProjectSlug(cloudProjectPathBasename(project.path)) === slug),
|
|
16970
|
+
uniqueProjectMatches(projects, (project) => project.id.toLowerCase().startsWith(normalizedRef))
|
|
16971
|
+
];
|
|
16972
|
+
for (const matches of matchGroups) {
|
|
16973
|
+
if (matches.length === 1)
|
|
16974
|
+
return matches[0].id;
|
|
16975
|
+
if (matches.length > 1)
|
|
16976
|
+
throw new Error(`Project reference is ambiguous: "${input}"`);
|
|
16977
|
+
}
|
|
16978
|
+
throw new Error(`Project not found: "${input}"`);
|
|
16979
|
+
}
|
|
16980
|
+
async function cloudResolveProjectRef(client, ref) {
|
|
16981
|
+
return resolveCloudProjectRef(await cloudListProjects(client), ref);
|
|
16982
|
+
}
|
|
16943
16983
|
async function cloudAddComment(client, taskId, input) {
|
|
16944
16984
|
const raw = await client.transport.post(`/tasks/${encodeURIComponent(taskId)}/comments`, input);
|
|
16945
16985
|
const comment = raw && typeof raw === "object" && "comment" in raw ? raw.comment : raw;
|
|
@@ -16984,9 +17024,46 @@ async function cloudReleaseAgent(client, idOrName, sessionId) {
|
|
|
16984
17024
|
const env = raw ?? {};
|
|
16985
17025
|
return { agent: env.agent ?? null, released: env.released !== false };
|
|
16986
17026
|
}
|
|
16987
|
-
|
|
17027
|
+
async function cloudListTaskLists(client, projectId) {
|
|
17028
|
+
const query = projectId ? { project_id: projectId } : {};
|
|
17029
|
+
const raw = await requiredRemoteRoute(client, "/v1/task-lists", () => client.transport.get("/task-lists", { query }));
|
|
17030
|
+
const envelope = raw ?? {};
|
|
17031
|
+
if (Array.isArray(envelope.task_lists))
|
|
17032
|
+
return envelope.task_lists;
|
|
17033
|
+
if (Array.isArray(envelope.taskLists))
|
|
17034
|
+
return envelope.taskLists;
|
|
17035
|
+
return Array.isArray(raw) ? raw : [];
|
|
17036
|
+
}
|
|
17037
|
+
async function cloudResolveTaskListRef(client, ref, projectId) {
|
|
17038
|
+
const input = ref.trim();
|
|
17039
|
+
const normalizedIdRef = input.toLowerCase();
|
|
17040
|
+
if (UUID_RE.test(input) && !projectId)
|
|
17041
|
+
return normalizedIdRef;
|
|
17042
|
+
const lists = await cloudListTaskLists(client, projectId);
|
|
17043
|
+
const exactIds = lists.filter((list) => list.id.toLowerCase() === normalizedIdRef);
|
|
17044
|
+
if (exactIds.length === 1)
|
|
17045
|
+
return exactIds[0].id;
|
|
17046
|
+
if (exactIds.length > 1) {
|
|
17047
|
+
throw new Error(`Task list reference is ambiguous: "${input}"`);
|
|
17048
|
+
}
|
|
17049
|
+
const slugs = lists.filter((list) => list.slug === input);
|
|
17050
|
+
if (slugs.length === 1)
|
|
17051
|
+
return slugs[0].id;
|
|
17052
|
+
if (slugs.length > 1) {
|
|
17053
|
+
throw new Error(`Task list reference is ambiguous: "${input}"`);
|
|
17054
|
+
}
|
|
17055
|
+
const prefixes = lists.filter((list) => list.id.toLowerCase().startsWith(normalizedIdRef));
|
|
17056
|
+
if (prefixes.length === 1)
|
|
17057
|
+
return prefixes[0].id;
|
|
17058
|
+
if (prefixes.length > 1) {
|
|
17059
|
+
throw new Error(`Task list reference is ambiguous: "${input}"`);
|
|
17060
|
+
}
|
|
17061
|
+
throw new Error(`Task list not found: "${input}"`);
|
|
17062
|
+
}
|
|
17063
|
+
var UUID_RE, CLOUD_MODES, VALID_STORAGE_MODES, completionCapabilityCache;
|
|
16988
17064
|
var init_cloud_router = __esm(() => {
|
|
16989
17065
|
init_redaction();
|
|
17066
|
+
UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
16990
17067
|
CLOUD_MODES = new Set(["self_hosted", "cloud", "remote", "hybrid"]);
|
|
16991
17068
|
VALID_STORAGE_MODES = new Set(["local", ...CLOUD_MODES]);
|
|
16992
17069
|
completionCapabilityCache = new Map;
|
|
@@ -17273,6 +17350,17 @@ ${task.description}` : null
|
|
|
17273
17350
|
patch.estimated_minutes = estimate;
|
|
17274
17351
|
if (deadline !== undefined)
|
|
17275
17352
|
patch.due_at = deadline;
|
|
17353
|
+
if (typeof patch.project_id === "string" && patch.project_id) {
|
|
17354
|
+
patch.project_id = await cloudResolveProjectRef(cloud, patch.project_id);
|
|
17355
|
+
}
|
|
17356
|
+
if (typeof patch.task_list_id === "string" && patch.task_list_id) {
|
|
17357
|
+
let scope = typeof patch.project_id === "string" ? patch.project_id : undefined;
|
|
17358
|
+
if (!scope) {
|
|
17359
|
+
const current = await cloudGetTask(cloud, task_id2);
|
|
17360
|
+
scope = current?.project_id ?? undefined;
|
|
17361
|
+
}
|
|
17362
|
+
patch.task_list_id = await cloudResolveTaskListRef(cloud, patch.task_list_id, scope);
|
|
17363
|
+
}
|
|
17276
17364
|
if (version2 !== undefined)
|
|
17277
17365
|
patch.version = version2;
|
|
17278
17366
|
const updated = await cloudUpdateTask(cloud, task_id2, patch);
|
|
@@ -23286,6 +23374,63 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
23286
23374
|
}
|
|
23287
23375
|
});
|
|
23288
23376
|
}
|
|
23377
|
+
if (shouldRegisterTool("move_task")) {
|
|
23378
|
+
server.tool("move_task", "Re-parent a task to another project and/or task list, preserving its id and history. " + "A task list is project-scoped, so moving to a new project detaches the old list unless to_list is given.", {
|
|
23379
|
+
task_id: exports_external.string().describe("Task ID"),
|
|
23380
|
+
to_project: exports_external.string().optional().describe("Destination project ID, slug, or path"),
|
|
23381
|
+
to_list: exports_external.string().optional().describe("Destination task list (UUID, or slug resolved in the destination project)"),
|
|
23382
|
+
clear_list: exports_external.boolean().optional().describe("Detach from its task list (set task_list_id to null)"),
|
|
23383
|
+
version: exports_external.number().optional().describe("Expected version for optimistic locking")
|
|
23384
|
+
}, async ({ task_id, to_project, to_list, clear_list, version }) => {
|
|
23385
|
+
try {
|
|
23386
|
+
if (!to_project && !to_list && !clear_list) {
|
|
23387
|
+
throw new Error("Nothing to move: pass to_project, to_list, or clear_list.");
|
|
23388
|
+
}
|
|
23389
|
+
if (to_list && clear_list) {
|
|
23390
|
+
throw new Error("Use either to_list or clear_list, not both.");
|
|
23391
|
+
}
|
|
23392
|
+
const cloud = getTodosCloudClient();
|
|
23393
|
+
if (cloud) {
|
|
23394
|
+
const current2 = await cloudGetTask(cloud, task_id);
|
|
23395
|
+
if (!current2)
|
|
23396
|
+
throw new Error(`Task not found: ${task_id}`);
|
|
23397
|
+
const targetProjectId2 = to_project ? await cloudResolveProjectRef(cloud, to_project) : undefined;
|
|
23398
|
+
const scope = targetProjectId2 ?? current2.project_id ?? undefined;
|
|
23399
|
+
const patch = {};
|
|
23400
|
+
if (targetProjectId2 !== undefined)
|
|
23401
|
+
patch.project_id = targetProjectId2;
|
|
23402
|
+
if (to_list)
|
|
23403
|
+
patch.task_list_id = await cloudResolveTaskListRef(cloud, to_list, scope);
|
|
23404
|
+
else if (clear_list)
|
|
23405
|
+
patch.task_list_id = null;
|
|
23406
|
+
else if (targetProjectId2 && targetProjectId2 !== current2.project_id)
|
|
23407
|
+
patch.task_list_id = null;
|
|
23408
|
+
if (version !== undefined)
|
|
23409
|
+
patch.version = version;
|
|
23410
|
+
const task2 = await cloudUpdateTask(cloud, task_id, patch);
|
|
23411
|
+
return { content: [{ type: "text", text: formatTask(task2) }] };
|
|
23412
|
+
}
|
|
23413
|
+
const resolvedId = resolveId(task_id);
|
|
23414
|
+
const current = getTask(resolvedId);
|
|
23415
|
+
if (!current)
|
|
23416
|
+
throw new Error(`Task not found: ${task_id}`);
|
|
23417
|
+
const targetProjectId = to_project ? resolveId(to_project, "projects") : undefined;
|
|
23418
|
+
const updates = {};
|
|
23419
|
+
if (targetProjectId !== undefined)
|
|
23420
|
+
updates.project_id = targetProjectId;
|
|
23421
|
+
if (to_list)
|
|
23422
|
+
updates.task_list_id = resolveId(to_list, "task_lists");
|
|
23423
|
+
else if (clear_list)
|
|
23424
|
+
updates.task_list_id = null;
|
|
23425
|
+
else if (targetProjectId && targetProjectId !== current.project_id)
|
|
23426
|
+
updates.task_list_id = null;
|
|
23427
|
+
const task = updateWithOptionalVersion(resolvedId, updates, version);
|
|
23428
|
+
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
23429
|
+
} catch (e) {
|
|
23430
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
23431
|
+
}
|
|
23432
|
+
});
|
|
23433
|
+
}
|
|
23289
23434
|
if (shouldRegisterTool("reschedule_task")) {
|
|
23290
23435
|
server.tool("reschedule_task", "Update a task's deadline.", {
|
|
23291
23436
|
task_id: exports_external.string().describe("Task ID"),
|
|
@@ -27608,6 +27753,7 @@ function registerTaskMetaTools(server, ctx) {
|
|
|
27608
27753
|
complete_task: "complete_task \u2014 Mark task completed. Params: task_id, confidence, completed_at, version",
|
|
27609
27754
|
cancel_task: "cancel_task \u2014 Cancel a task. Params: task_id, version",
|
|
27610
27755
|
reassign_task: "reassign_task \u2014 Change task assignee. Params: task_id, new_assignee, version",
|
|
27756
|
+
move_task: "move_task \u2014 Re-parent a task to another project and/or task list (keeps its id and history). Params: task_id (required), to_project, to_list, clear_list, version",
|
|
27611
27757
|
reschedule_task: "reschedule_task \u2014 Update deadline. Params: task_id, deadline, version",
|
|
27612
27758
|
prioritize_task: "prioritize_task \u2014 Set priority. Params: task_id, priority, version",
|
|
27613
27759
|
search_tasks: "search_tasks \u2014 Full-text search. Params: query, project_id, status, limit",
|
|
@@ -30647,7 +30793,7 @@ var package_default;
|
|
|
30647
30793
|
var init_package = __esm(() => {
|
|
30648
30794
|
package_default = {
|
|
30649
30795
|
name: "@hasna/todos",
|
|
30650
|
-
version: "0.11.
|
|
30796
|
+
version: "0.11.95",
|
|
30651
30797
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
30652
30798
|
type: "module",
|
|
30653
30799
|
main: "dist/index.js",
|
|
@@ -39991,6 +40137,7 @@ function exportSqliteTodosStorageSnapshot(db) {
|
|
|
39991
40137
|
agents: listAgents({ include_archived: true }, d),
|
|
39992
40138
|
taskLists: listTaskLists(undefined, d),
|
|
39993
40139
|
templates: listTemplates(d),
|
|
40140
|
+
templateTasks: listTemplates(d).flatMap((template) => getTemplateTasks(template.id, d)),
|
|
39994
40141
|
auditHistory: getRecentActivity(Number.MAX_SAFE_INTEGER, d),
|
|
39995
40142
|
tombstones: listStorageTombstones(d)
|
|
39996
40143
|
};
|
|
@@ -40040,6 +40187,7 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
40040
40187
|
applyRows("task_lists", "task_lists", TASK_LIST_COLUMNS, snapshot.taskLists, "updated_at");
|
|
40041
40188
|
applyRows("plans", "plans", PLAN_COLUMNS, snapshot.plans, "updated_at");
|
|
40042
40189
|
applyRows("templates", "task_templates", TEMPLATE_COLUMNS, snapshot.templates);
|
|
40190
|
+
applyRows("template_tasks", "template_tasks", TEMPLATE_TASK_COLUMNS, snapshot.templateTasks ?? []);
|
|
40043
40191
|
applyRows("tasks", "tasks", TASK_COLUMNS, sortedTasks2(snapshot.tasks), "updated_at", (row, changed) => {
|
|
40044
40192
|
if (changed && Array.isArray(row["tags"]) && typeof row["id"] === "string") {
|
|
40045
40193
|
replaceTaskTags(row["id"], row["tags"].filter((tag) => typeof tag === "string"), d);
|
|
@@ -40144,6 +40292,8 @@ function tableForTombstone(objectType2) {
|
|
|
40144
40292
|
return "task_lists";
|
|
40145
40293
|
if (objectType2 === "templates")
|
|
40146
40294
|
return "task_templates";
|
|
40295
|
+
if (objectType2 === "template_tasks")
|
|
40296
|
+
return "template_tasks";
|
|
40147
40297
|
return "task_history";
|
|
40148
40298
|
}
|
|
40149
40299
|
function listRows(db, table, columns) {
|
|
@@ -40180,7 +40330,7 @@ function clockColumnsForTable(table) {
|
|
|
40180
40330
|
return ["created_at"];
|
|
40181
40331
|
return ["updated_at", "created_at"];
|
|
40182
40332
|
}
|
|
40183
|
-
var PROJECT_COLUMNS, PROJECT_MACHINE_PATH_COLUMNS, TASK_LIST_COLUMNS, PLAN_COLUMNS, AGENT_COLUMNS, TEMPLATE_COLUMNS, TASK_COLUMNS, AUDIT_COLUMNS, JSON_COLUMNS, BOOLEAN_COLUMNS;
|
|
40333
|
+
var PROJECT_COLUMNS, PROJECT_MACHINE_PATH_COLUMNS, TASK_LIST_COLUMNS, PLAN_COLUMNS, AGENT_COLUMNS, TEMPLATE_COLUMNS, TEMPLATE_TASK_COLUMNS, TASK_COLUMNS, AUDIT_COLUMNS, JSON_COLUMNS, BOOLEAN_COLUMNS;
|
|
40184
40334
|
var init_sqlite_snapshot = __esm(() => {
|
|
40185
40335
|
init_database();
|
|
40186
40336
|
init_agents();
|
|
@@ -40274,6 +40424,21 @@ var init_sqlite_snapshot = __esm(() => {
|
|
|
40274
40424
|
"machine_id",
|
|
40275
40425
|
"synced_at"
|
|
40276
40426
|
];
|
|
40427
|
+
TEMPLATE_TASK_COLUMNS = [
|
|
40428
|
+
"id",
|
|
40429
|
+
"template_id",
|
|
40430
|
+
"position",
|
|
40431
|
+
"title_pattern",
|
|
40432
|
+
"description",
|
|
40433
|
+
"priority",
|
|
40434
|
+
"tags",
|
|
40435
|
+
"task_type",
|
|
40436
|
+
"condition",
|
|
40437
|
+
"include_template_id",
|
|
40438
|
+
"depends_on_positions",
|
|
40439
|
+
"metadata",
|
|
40440
|
+
"created_at"
|
|
40441
|
+
];
|
|
40277
40442
|
TASK_COLUMNS = [
|
|
40278
40443
|
"id",
|
|
40279
40444
|
"short_id",
|
|
@@ -40342,7 +40507,7 @@ var init_sqlite_snapshot = __esm(() => {
|
|
|
40342
40507
|
"created_at",
|
|
40343
40508
|
"machine_id"
|
|
40344
40509
|
];
|
|
40345
|
-
JSON_COLUMNS = new Set(["tags", "metadata", "permissions", "capabilities", "variables"]);
|
|
40510
|
+
JSON_COLUMNS = new Set(["tags", "metadata", "permissions", "capabilities", "variables", "depends_on_positions"]);
|
|
40346
40511
|
BOOLEAN_COLUMNS = new Set(["requires_approval"]);
|
|
40347
40512
|
});
|
|
40348
40513
|
|
|
@@ -40732,6 +40897,7 @@ function snapshotEntries(snapshot) {
|
|
|
40732
40897
|
...snapshot.agents.map((payload) => entry("agents", payload, snapshot.exportedAt)),
|
|
40733
40898
|
...snapshot.taskLists.map((payload) => entry("task_lists", payload, snapshot.exportedAt)),
|
|
40734
40899
|
...snapshot.templates.map((payload) => entry("templates", payload, snapshot.exportedAt)),
|
|
40900
|
+
...(snapshot.templateTasks ?? []).map((payload) => entry("template_tasks", payload, snapshot.exportedAt)),
|
|
40735
40901
|
...snapshot.auditHistory.map((payload) => entry("audit_history", payload, snapshot.exportedAt)),
|
|
40736
40902
|
...(snapshot.tombstones ?? []).map((tombstone) => ({
|
|
40737
40903
|
type: tombstone.object_type,
|
|
@@ -40783,6 +40949,7 @@ function rowsToSnapshot(rows) {
|
|
|
40783
40949
|
agents: [],
|
|
40784
40950
|
taskLists: [],
|
|
40785
40951
|
templates: [],
|
|
40952
|
+
templateTasks: [],
|
|
40786
40953
|
auditHistory: [],
|
|
40787
40954
|
tombstones: []
|
|
40788
40955
|
};
|
|
@@ -40817,6 +40984,8 @@ function rowsToSnapshot(rows) {
|
|
|
40817
40984
|
snapshot.taskLists.push(payload);
|
|
40818
40985
|
else if (row.object_type === "templates")
|
|
40819
40986
|
snapshot.templates.push(payload);
|
|
40987
|
+
else if (row.object_type === "template_tasks")
|
|
40988
|
+
snapshot.templateTasks.push(payload);
|
|
40820
40989
|
else if (row.object_type === "audit_history")
|
|
40821
40990
|
snapshot.auditHistory.push(payload);
|
|
40822
40991
|
}
|
|
@@ -41034,6 +41203,9 @@ class TodosShadowOutbox {
|
|
|
41034
41203
|
case "templates":
|
|
41035
41204
|
snapshot.templates.push(record);
|
|
41036
41205
|
break;
|
|
41206
|
+
case "template_tasks":
|
|
41207
|
+
snapshot.templateTasks.push(record);
|
|
41208
|
+
break;
|
|
41037
41209
|
case "audit_history":
|
|
41038
41210
|
snapshot.auditHistory.push(record);
|
|
41039
41211
|
break;
|
|
@@ -41100,6 +41272,7 @@ function emptySnapshot() {
|
|
|
41100
41272
|
agents: [],
|
|
41101
41273
|
taskLists: [],
|
|
41102
41274
|
templates: [],
|
|
41275
|
+
templateTasks: [],
|
|
41103
41276
|
auditHistory: [],
|
|
41104
41277
|
tombstones: []
|
|
41105
41278
|
};
|
|
@@ -42299,10 +42472,13 @@ function createPostgresTodosStorageAdapter(options) {
|
|
|
42299
42472
|
get: (id) => store.get("templates", id),
|
|
42300
42473
|
list: async () => (await store.list("templates")).sort((a, b) => a.name.localeCompare(b.name)),
|
|
42301
42474
|
update: (id, input) => updateTemplate2(id, input, store),
|
|
42302
|
-
delete: (id, context) =>
|
|
42475
|
+
delete: (id, context) => deleteTemplate2(id, store, context),
|
|
42303
42476
|
getWithTasks: async (id) => {
|
|
42304
42477
|
const template = await store.get("templates", id);
|
|
42305
|
-
|
|
42478
|
+
if (!template)
|
|
42479
|
+
return null;
|
|
42480
|
+
const tasks = (await store.list("template_tasks")).filter((task2) => task2.template_id === id).sort((left, right) => left.position - right.position || left.id.localeCompare(right.id));
|
|
42481
|
+
return { ...template, tasks };
|
|
42306
42482
|
}
|
|
42307
42483
|
},
|
|
42308
42484
|
audit: {
|
|
@@ -42590,6 +42766,57 @@ class PostgresJsonRecordStore {
|
|
|
42590
42766
|
}
|
|
42591
42767
|
return value;
|
|
42592
42768
|
}
|
|
42769
|
+
async createTemplateWithTasks(template, tasks, context = {}) {
|
|
42770
|
+
await this.ensureSchema();
|
|
42771
|
+
const records = [
|
|
42772
|
+
{ object_type: "templates", object_id: template.id, payload: template, updated_at: template.created_at, version: template.version },
|
|
42773
|
+
...tasks.map((task2) => ({ object_type: "template_tasks", object_id: task2.id, payload: task2, updated_at: task2.created_at, version: 1 }))
|
|
42774
|
+
];
|
|
42775
|
+
const result = await this.options.client.query(`/* todos:create-template-with-tasks-atomic */ WITH input AS (
|
|
42776
|
+
SELECT value->>'object_type' AS object_type,
|
|
42777
|
+
value->>'object_id' AS object_id,
|
|
42778
|
+
value->'payload' AS payload,
|
|
42779
|
+
value->>'updated_at' AS updated_at,
|
|
42780
|
+
COALESCE((value->>'version')::integer, 1) AS version
|
|
42781
|
+
FROM jsonb_array_elements($2::jsonb) AS value
|
|
42782
|
+
) INSERT INTO ${this.tableName} (
|
|
42783
|
+
service, object_type, object_id, payload, updated_at,
|
|
42784
|
+
deleted_at, source_machine_id, version
|
|
42785
|
+
) SELECT $1, object_type, object_id, payload, updated_at::timestamptz,
|
|
42786
|
+
NULL, $3, version
|
|
42787
|
+
FROM input
|
|
42788
|
+
ON CONFLICT (service, object_type, object_id) DO UPDATE SET
|
|
42789
|
+
payload = EXCLUDED.payload,
|
|
42790
|
+
updated_at = EXCLUDED.updated_at,
|
|
42791
|
+
deleted_at = NULL,
|
|
42792
|
+
source_machine_id = EXCLUDED.source_machine_id,
|
|
42793
|
+
version = EXCLUDED.version
|
|
42794
|
+
WHERE ${this.tableName}.updated_at IS NULL
|
|
42795
|
+
OR ${this.tableName}.updated_at < EXCLUDED.updated_at
|
|
42796
|
+
OR (${this.tableName}.updated_at = EXCLUDED.updated_at
|
|
42797
|
+
AND COALESCE(${this.tableName}.version, 0) <= COALESCE(EXCLUDED.version, 0))
|
|
42798
|
+
RETURNING object_type, object_id`, [this.service, jsonbParam(records), this.machineId(context)]);
|
|
42799
|
+
if (result.rows.length !== records.length) {
|
|
42800
|
+
throw new Error("Template checklist write was rejected before completion; no partial template was committed");
|
|
42801
|
+
}
|
|
42802
|
+
}
|
|
42803
|
+
async deleteTemplateWithTasks(id, context = {}) {
|
|
42804
|
+
await this.ensureSchema();
|
|
42805
|
+
const timestamp3 = new Date().toISOString();
|
|
42806
|
+
const result = await this.options.client.query(`/* todos:delete-template-with-tasks-atomic */ WITH target AS (
|
|
42807
|
+
SELECT 1 FROM ${this.tableName}
|
|
42808
|
+
WHERE service = $1 AND object_type = 'templates' AND object_id = $2 AND deleted_at IS NULL
|
|
42809
|
+
) UPDATE ${this.tableName} AS record SET
|
|
42810
|
+
deleted_at = $3::timestamptz,
|
|
42811
|
+
updated_at = $3::timestamptz,
|
|
42812
|
+
source_machine_id = COALESCE($4, record.source_machine_id),
|
|
42813
|
+
version = COALESCE(record.version, 0) + 1
|
|
42814
|
+
WHERE record.service = $1 AND record.deleted_at IS NULL AND EXISTS (SELECT 1 FROM target)
|
|
42815
|
+
AND (record.object_type = 'templates' AND record.object_id = $2
|
|
42816
|
+
OR record.object_type = 'template_tasks' AND record.payload->>'template_id' = $2)
|
|
42817
|
+
RETURNING record.object_type`, [this.service, id, timestamp3, this.machineId(context)]);
|
|
42818
|
+
return result.rows.some((row) => row.object_type === "templates");
|
|
42819
|
+
}
|
|
42593
42820
|
async completeTask(id, agentId, options) {
|
|
42594
42821
|
await this.ensureSchema();
|
|
42595
42822
|
const operationTimestamp = new Date().toISOString();
|
|
@@ -42930,7 +43157,7 @@ async function updateTask2(id, input, store) {
|
|
|
42930
43157
|
tags: input.tags ?? existing.tags,
|
|
42931
43158
|
metadata: input.metadata ?? existing.metadata,
|
|
42932
43159
|
requires_approval: input.requires_approval ?? existing.requires_approval,
|
|
42933
|
-
task_list_id: input.task_list_id
|
|
43160
|
+
task_list_id: input.task_list_id !== undefined ? input.task_list_id : existing.task_list_id
|
|
42934
43161
|
};
|
|
42935
43162
|
await store.upsert("tasks", task2);
|
|
42936
43163
|
return task2;
|
|
@@ -43352,7 +43579,7 @@ async function updateTaskList2(id, input, store) {
|
|
|
43352
43579
|
}
|
|
43353
43580
|
async function createTemplate2(input, store, context) {
|
|
43354
43581
|
const timestamp3 = new Date().toISOString();
|
|
43355
|
-
|
|
43582
|
+
const template = {
|
|
43356
43583
|
id: randomUUID3(),
|
|
43357
43584
|
name: input.name,
|
|
43358
43585
|
title_pattern: input.title_pattern,
|
|
@@ -43367,7 +43594,30 @@ async function createTemplate2(input, store, context) {
|
|
|
43367
43594
|
created_at: timestamp3,
|
|
43368
43595
|
machine_id: store.machineId(context),
|
|
43369
43596
|
synced_at: null
|
|
43370
|
-
}
|
|
43597
|
+
};
|
|
43598
|
+
const tasks = buildTemplateTasks(template.id, input.tasks ?? [], timestamp3);
|
|
43599
|
+
await store.createTemplateWithTasks(template, tasks, context);
|
|
43600
|
+
return template;
|
|
43601
|
+
}
|
|
43602
|
+
function buildTemplateTasks(templateId, inputs, timestamp3) {
|
|
43603
|
+
return inputs.map((input, position) => ({
|
|
43604
|
+
id: randomUUID3(),
|
|
43605
|
+
template_id: templateId,
|
|
43606
|
+
position,
|
|
43607
|
+
title_pattern: input.title_pattern,
|
|
43608
|
+
description: input.description ?? null,
|
|
43609
|
+
priority: input.priority ?? "medium",
|
|
43610
|
+
tags: input.tags ?? [],
|
|
43611
|
+
task_type: input.task_type ?? null,
|
|
43612
|
+
condition: input.condition ?? null,
|
|
43613
|
+
include_template_id: input.include_template_id ?? null,
|
|
43614
|
+
depends_on_positions: input.depends_on ?? [],
|
|
43615
|
+
metadata: input.metadata ?? {},
|
|
43616
|
+
created_at: timestamp3
|
|
43617
|
+
}));
|
|
43618
|
+
}
|
|
43619
|
+
async function deleteTemplate2(id, store, context) {
|
|
43620
|
+
return store.deleteTemplateWithTasks(id, context);
|
|
43371
43621
|
}
|
|
43372
43622
|
async function updateTemplate2(id, input, store) {
|
|
43373
43623
|
const template = await store.get("templates", id);
|
|
@@ -43423,6 +43673,7 @@ async function exportSnapshot(store) {
|
|
|
43423
43673
|
agents: await store.list("agents"),
|
|
43424
43674
|
taskLists: await store.list("task_lists"),
|
|
43425
43675
|
templates: await store.list("templates"),
|
|
43676
|
+
templateTasks: await store.list("template_tasks"),
|
|
43426
43677
|
auditHistory: await store.list("audit_history"),
|
|
43427
43678
|
tombstones: await store.listTombstones()
|
|
43428
43679
|
};
|
|
@@ -43447,6 +43698,7 @@ async function importSnapshot(snapshot, store, context) {
|
|
|
43447
43698
|
...snapshot.agents.map((row) => ["agents", row]),
|
|
43448
43699
|
...snapshot.taskLists.map((row) => ["task_lists", row]),
|
|
43449
43700
|
...snapshot.templates.map((row) => ["templates", row]),
|
|
43701
|
+
...(snapshot.templateTasks ?? []).map((row) => ["template_tasks", row]),
|
|
43450
43702
|
...snapshot.auditHistory.map((row) => ["audit_history", row])
|
|
43451
43703
|
];
|
|
43452
43704
|
for (const [type, row] of entries) {
|
|
@@ -43853,12 +44105,16 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
43853
44105
|
TaskList: taskListSchema,
|
|
43854
44106
|
TaskComment: taskCommentSchema,
|
|
43855
44107
|
Plan: planSchema,
|
|
44108
|
+
Template: templateSchema,
|
|
44109
|
+
TemplateTask: templateTaskSchema,
|
|
44110
|
+
TemplateVariable: templateVariableSchema,
|
|
44111
|
+
CreateTemplateTaskInput: createTemplateTaskInputSchema,
|
|
43856
44112
|
CreateTaskInput: {
|
|
43857
44113
|
type: "object",
|
|
43858
44114
|
required: ["title"],
|
|
43859
44115
|
properties: {
|
|
43860
44116
|
title: { type: "string" },
|
|
43861
|
-
description: { type: "string" },
|
|
44117
|
+
description: { type: "string", nullable: true },
|
|
43862
44118
|
status: { type: "string" },
|
|
43863
44119
|
priority: { type: "string" },
|
|
43864
44120
|
project_id: { type: "string" },
|
|
@@ -43875,6 +44131,8 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
43875
44131
|
status: { type: "string" },
|
|
43876
44132
|
priority: { type: "string" },
|
|
43877
44133
|
assigned_to: { type: "string" },
|
|
44134
|
+
project_id: { type: "string", nullable: true },
|
|
44135
|
+
task_list_id: { type: "string", nullable: true },
|
|
43878
44136
|
version: { type: "number" }
|
|
43879
44137
|
}
|
|
43880
44138
|
},
|
|
@@ -43991,6 +44249,39 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
43991
44249
|
agent_id: { type: "string", minLength: 1 },
|
|
43992
44250
|
status: { type: "string", enum: ["active", "completed", "archived"] }
|
|
43993
44251
|
}
|
|
44252
|
+
},
|
|
44253
|
+
CreateTemplateInput: {
|
|
44254
|
+
type: "object",
|
|
44255
|
+
additionalProperties: false,
|
|
44256
|
+
required: ["name", "title_pattern"],
|
|
44257
|
+
properties: {
|
|
44258
|
+
name: { type: "string", minLength: 1 },
|
|
44259
|
+
title_pattern: { type: "string", minLength: 1 },
|
|
44260
|
+
description: { type: "string", nullable: true },
|
|
44261
|
+
priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
44262
|
+
tags: { type: "array", items: { type: "string", minLength: 1 } },
|
|
44263
|
+
variables: { type: "array", items: { $ref: "#/components/schemas/TemplateVariable" } },
|
|
44264
|
+
project_id: { type: "string", minLength: 1, nullable: true },
|
|
44265
|
+
plan_id: { type: "string", minLength: 1, nullable: true },
|
|
44266
|
+
metadata: { type: "object", additionalProperties: true },
|
|
44267
|
+
tasks: { type: "array", items: { $ref: "#/components/schemas/CreateTemplateTaskInput" } }
|
|
44268
|
+
}
|
|
44269
|
+
},
|
|
44270
|
+
UpdateTemplateInput: {
|
|
44271
|
+
type: "object",
|
|
44272
|
+
additionalProperties: false,
|
|
44273
|
+
minProperties: 1,
|
|
44274
|
+
properties: {
|
|
44275
|
+
name: { type: "string", minLength: 1 },
|
|
44276
|
+
title_pattern: { type: "string", minLength: 1 },
|
|
44277
|
+
description: { type: "string", nullable: true },
|
|
44278
|
+
priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
44279
|
+
tags: { type: "array", items: { type: "string", minLength: 1 } },
|
|
44280
|
+
variables: { type: "array", items: { type: "object" } },
|
|
44281
|
+
project_id: { type: "string", nullable: true },
|
|
44282
|
+
plan_id: { type: "string", nullable: true },
|
|
44283
|
+
metadata: { type: "object", additionalProperties: true }
|
|
44284
|
+
}
|
|
43994
44285
|
}
|
|
43995
44286
|
}
|
|
43996
44287
|
},
|
|
@@ -44293,6 +44584,41 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
44293
44584
|
responses: { "200": { content: { "application/json": { schema: { type: "object", properties: { deleted: { type: "boolean" }, id: { type: "string" } } } } } } }
|
|
44294
44585
|
}
|
|
44295
44586
|
},
|
|
44587
|
+
"/v1/templates": {
|
|
44588
|
+
get: {
|
|
44589
|
+
operationId: "listTemplates",
|
|
44590
|
+
summary: "List reusable task templates",
|
|
44591
|
+
parameters: [{ name: "project_id", in: "query", schema: { type: "string" } }],
|
|
44592
|
+
responses: { "200": { content: { "application/json": { schema: { type: "object", properties: { templates: { type: "array", items: { $ref: "#/components/schemas/Template" } }, count: { type: "number" } } } } } } }
|
|
44593
|
+
},
|
|
44594
|
+
post: {
|
|
44595
|
+
operationId: "createTemplate",
|
|
44596
|
+
summary: "Create a reusable task template",
|
|
44597
|
+
requestBody: { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/CreateTemplateInput" } } } },
|
|
44598
|
+
responses: { "201": { content: { "application/json": { schema: { type: "object", properties: { template: { $ref: "#/components/schemas/Template" } } } } } } }
|
|
44599
|
+
}
|
|
44600
|
+
},
|
|
44601
|
+
"/v1/templates/{id}": {
|
|
44602
|
+
get: {
|
|
44603
|
+
operationId: "getTemplate",
|
|
44604
|
+
summary: "Get one reusable task template with its checklist steps",
|
|
44605
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
44606
|
+
responses: { "200": { content: { "application/json": { schema: { type: "object", properties: { template: { $ref: "#/components/schemas/Template" } } } } } } }
|
|
44607
|
+
},
|
|
44608
|
+
patch: {
|
|
44609
|
+
operationId: "updateTemplate",
|
|
44610
|
+
summary: "Update reusable template metadata and defaults",
|
|
44611
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
44612
|
+
requestBody: { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/UpdateTemplateInput" } } } },
|
|
44613
|
+
responses: { "200": { content: { "application/json": { schema: { type: "object", properties: { template: { $ref: "#/components/schemas/Template" } } } } } } }
|
|
44614
|
+
},
|
|
44615
|
+
delete: {
|
|
44616
|
+
operationId: "deleteTemplate",
|
|
44617
|
+
summary: "Delete a reusable task template and its checklist steps",
|
|
44618
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
44619
|
+
responses: { "200": { content: { "application/json": { schema: { type: "object", properties: { deleted: { type: "boolean" }, id: { type: "string" } } } } } } }
|
|
44620
|
+
}
|
|
44621
|
+
},
|
|
44296
44622
|
"/v1/task-lists": {
|
|
44297
44623
|
get: {
|
|
44298
44624
|
operationId: "listTaskLists",
|
|
@@ -44370,6 +44696,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
44370
44696
|
agents: { type: "array", items: { type: "object" } },
|
|
44371
44697
|
taskLists: { type: "array", items: { type: "object" } },
|
|
44372
44698
|
templates: { type: "array", items: { type: "object" } },
|
|
44699
|
+
templateTasks: { type: "array", items: { $ref: "#/components/schemas/TemplateTask" } },
|
|
44373
44700
|
auditHistory: { type: "array", items: { type: "object" } },
|
|
44374
44701
|
tombstones: { type: "array", items: { type: "object" } }
|
|
44375
44702
|
}
|
|
@@ -44406,7 +44733,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
44406
44733
|
}
|
|
44407
44734
|
};
|
|
44408
44735
|
}
|
|
44409
|
-
var taskSchema, projectSchema, taskListSchema, taskCommentSchema, planSchema;
|
|
44736
|
+
var taskSchema, projectSchema, taskListSchema, taskCommentSchema, planSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema;
|
|
44410
44737
|
var init_openapi = __esm(() => {
|
|
44411
44738
|
init_package_version();
|
|
44412
44739
|
taskSchema = {
|
|
@@ -44483,6 +44810,72 @@ var init_openapi = __esm(() => {
|
|
|
44483
44810
|
updated_at: { type: "string", format: "date-time" }
|
|
44484
44811
|
}
|
|
44485
44812
|
};
|
|
44813
|
+
templateTaskSchema = {
|
|
44814
|
+
type: "object",
|
|
44815
|
+
required: ["id", "template_id", "position", "title_pattern", "priority", "tags", "depends_on_positions", "metadata", "created_at"],
|
|
44816
|
+
properties: {
|
|
44817
|
+
id: { type: "string" },
|
|
44818
|
+
template_id: { type: "string" },
|
|
44819
|
+
position: { type: "integer", minimum: 0 },
|
|
44820
|
+
title_pattern: { type: "string" },
|
|
44821
|
+
description: { type: "string", nullable: true },
|
|
44822
|
+
priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
44823
|
+
tags: { type: "array", items: { type: "string" } },
|
|
44824
|
+
task_type: { type: "string", nullable: true },
|
|
44825
|
+
condition: { type: "string", nullable: true },
|
|
44826
|
+
include_template_id: { type: "string", nullable: true },
|
|
44827
|
+
depends_on_positions: { type: "array", items: { type: "integer", minimum: 0 } },
|
|
44828
|
+
metadata: { type: "object", additionalProperties: true },
|
|
44829
|
+
created_at: { type: "string", format: "date-time" }
|
|
44830
|
+
}
|
|
44831
|
+
};
|
|
44832
|
+
templateSchema = {
|
|
44833
|
+
type: "object",
|
|
44834
|
+
required: ["id", "name", "title_pattern", "priority", "tags", "variables", "version", "metadata", "created_at"],
|
|
44835
|
+
properties: {
|
|
44836
|
+
id: { type: "string" },
|
|
44837
|
+
name: { type: "string" },
|
|
44838
|
+
title_pattern: { type: "string" },
|
|
44839
|
+
description: { type: "string", nullable: true },
|
|
44840
|
+
priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
44841
|
+
tags: { type: "array", items: { type: "string" } },
|
|
44842
|
+
variables: { type: "array", items: { type: "object", properties: { name: { type: "string" }, required: { type: "boolean" }, default: { type: "string" }, description: { type: "string" } } } },
|
|
44843
|
+
version: { type: "integer", minimum: 1 },
|
|
44844
|
+
project_id: { type: "string", nullable: true },
|
|
44845
|
+
plan_id: { type: "string", nullable: true },
|
|
44846
|
+
metadata: { type: "object", additionalProperties: true },
|
|
44847
|
+
created_at: { type: "string", format: "date-time" },
|
|
44848
|
+
tasks: { type: "array", items: { $ref: "#/components/schemas/TemplateTask" } }
|
|
44849
|
+
}
|
|
44850
|
+
};
|
|
44851
|
+
templateVariableSchema = {
|
|
44852
|
+
type: "object",
|
|
44853
|
+
required: ["name", "required"],
|
|
44854
|
+
properties: {
|
|
44855
|
+
name: { type: "string" },
|
|
44856
|
+
required: { type: "boolean" },
|
|
44857
|
+
default: { type: "string" },
|
|
44858
|
+
description: { type: "string" }
|
|
44859
|
+
}
|
|
44860
|
+
};
|
|
44861
|
+
createTemplateTaskInputSchema = {
|
|
44862
|
+
type: "object",
|
|
44863
|
+
additionalProperties: false,
|
|
44864
|
+
required: ["title_pattern"],
|
|
44865
|
+
properties: {
|
|
44866
|
+
position: { type: "integer", minimum: 0 },
|
|
44867
|
+
title_pattern: { type: "string", minLength: 1 },
|
|
44868
|
+
description: { type: "string", nullable: true },
|
|
44869
|
+
priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
44870
|
+
tags: { type: "array", items: { type: "string", minLength: 1 } },
|
|
44871
|
+
task_type: { type: "string", nullable: true },
|
|
44872
|
+
condition: { type: "string", nullable: true },
|
|
44873
|
+
include_template_id: { type: "string", nullable: true },
|
|
44874
|
+
depends_on: { type: "array", items: { type: "integer", minimum: 0 } },
|
|
44875
|
+
depends_on_positions: { type: "array", items: { type: "integer", minimum: 0 } },
|
|
44876
|
+
metadata: { type: "object", additionalProperties: true }
|
|
44877
|
+
}
|
|
44878
|
+
};
|
|
44486
44879
|
});
|
|
44487
44880
|
|
|
44488
44881
|
// src/server/v1.ts
|
|
@@ -44622,6 +45015,122 @@ function validatePlanCreate(value) {
|
|
|
44622
45015
|
}
|
|
44623
45016
|
};
|
|
44624
45017
|
}
|
|
45018
|
+
function validateTemplateTask(value) {
|
|
45019
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
45020
|
+
return null;
|
|
45021
|
+
const body = value;
|
|
45022
|
+
const allowed = new Set(["position", "title_pattern", "description", "priority", "tags", "task_type", "condition", "include_template_id", "depends_on", "depends_on_positions", "metadata"]);
|
|
45023
|
+
if (Object.keys(body).some((key) => !allowed.has(key)))
|
|
45024
|
+
return null;
|
|
45025
|
+
if (typeof body.title_pattern !== "string" || !body.title_pattern.trim())
|
|
45026
|
+
return null;
|
|
45027
|
+
if (body.position !== undefined && (typeof body.position !== "number" || !Number.isSafeInteger(body.position) || body.position < 0))
|
|
45028
|
+
return null;
|
|
45029
|
+
if (body.description !== undefined && body.description !== null && typeof body.description !== "string")
|
|
45030
|
+
return null;
|
|
45031
|
+
if (body.priority !== undefined && (typeof body.priority !== "string" || !["low", "medium", "high", "critical"].includes(body.priority)))
|
|
45032
|
+
return null;
|
|
45033
|
+
if (body.tags !== undefined && (!Array.isArray(body.tags) || body.tags.some((tag) => typeof tag !== "string" || !tag.trim())))
|
|
45034
|
+
return null;
|
|
45035
|
+
for (const field of ["task_type", "condition", "include_template_id"]) {
|
|
45036
|
+
if (body[field] !== undefined && body[field] !== null && (typeof body[field] !== "string" || !body[field].trim()))
|
|
45037
|
+
return null;
|
|
45038
|
+
}
|
|
45039
|
+
if (body.depends_on !== undefined && body.depends_on_positions !== undefined)
|
|
45040
|
+
return null;
|
|
45041
|
+
const dependencies = body.depends_on ?? body.depends_on_positions;
|
|
45042
|
+
if (dependencies !== undefined && (!Array.isArray(dependencies) || dependencies.some((position) => !Number.isSafeInteger(position) || position < 0)))
|
|
45043
|
+
return null;
|
|
45044
|
+
if (body.metadata !== undefined && (!body.metadata || typeof body.metadata !== "object" || Array.isArray(body.metadata)))
|
|
45045
|
+
return null;
|
|
45046
|
+
return {
|
|
45047
|
+
title_pattern: body.title_pattern,
|
|
45048
|
+
...typeof body.description === "string" ? { description: body.description } : {},
|
|
45049
|
+
...typeof body.priority === "string" ? { priority: body.priority } : {},
|
|
45050
|
+
...Array.isArray(body.tags) ? { tags: body.tags } : {},
|
|
45051
|
+
...typeof body.task_type === "string" ? { task_type: body.task_type } : {},
|
|
45052
|
+
...typeof body.condition === "string" ? { condition: body.condition } : {},
|
|
45053
|
+
...typeof body.include_template_id === "string" ? { include_template_id: body.include_template_id } : {},
|
|
45054
|
+
...Array.isArray(dependencies) ? { depends_on: dependencies } : {},
|
|
45055
|
+
...body.metadata && typeof body.metadata === "object" && !Array.isArray(body.metadata) ? { metadata: body.metadata } : {}
|
|
45056
|
+
};
|
|
45057
|
+
}
|
|
45058
|
+
function validateTemplateCreate(value) {
|
|
45059
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
45060
|
+
return { ok: false, message: "template body must be an object" };
|
|
45061
|
+
const body = value;
|
|
45062
|
+
const allowed = new Set(["name", "title_pattern", "description", "priority", "tags", "variables", "project_id", "plan_id", "metadata", "tasks"]);
|
|
45063
|
+
const unknown = Object.keys(body).find((key) => !allowed.has(key));
|
|
45064
|
+
if (unknown)
|
|
45065
|
+
return { ok: false, message: `unknown template field: ${unknown}` };
|
|
45066
|
+
if (typeof body.name !== "string" || !body.name.trim())
|
|
45067
|
+
return { ok: false, message: "name must be a non-empty string" };
|
|
45068
|
+
if (typeof body.title_pattern !== "string" || !body.title_pattern.trim())
|
|
45069
|
+
return { ok: false, message: "title_pattern must be a non-empty string" };
|
|
45070
|
+
if (body.description !== undefined && body.description !== null && typeof body.description !== "string")
|
|
45071
|
+
return { ok: false, message: "description must be a string or null" };
|
|
45072
|
+
if (body.priority !== undefined && (typeof body.priority !== "string" || !["low", "medium", "high", "critical"].includes(body.priority)))
|
|
45073
|
+
return { ok: false, message: "priority must be low, medium, high, or critical" };
|
|
45074
|
+
if (body.tags !== undefined && (!Array.isArray(body.tags) || body.tags.some((tag) => typeof tag !== "string" || !tag.trim())))
|
|
45075
|
+
return { ok: false, message: "tags must be an array of non-empty strings" };
|
|
45076
|
+
if (body.variables !== undefined && (!Array.isArray(body.variables) || body.variables.some((variable) => !variable || typeof variable !== "object" || Array.isArray(variable) || typeof variable.name !== "string" || !variable.name || typeof variable.required !== "boolean" || variable.default !== undefined && typeof variable.default !== "string" || variable.description !== undefined && typeof variable.description !== "string"))) {
|
|
45077
|
+
return { ok: false, message: "variables must be valid template variable objects" };
|
|
45078
|
+
}
|
|
45079
|
+
for (const field of ["project_id", "plan_id"]) {
|
|
45080
|
+
if (body[field] !== undefined && body[field] !== null && (typeof body[field] !== "string" || !body[field].trim()))
|
|
45081
|
+
return { ok: false, message: `${field} must be a non-empty string or null` };
|
|
45082
|
+
}
|
|
45083
|
+
if (body.metadata !== undefined && (!body.metadata || typeof body.metadata !== "object" || Array.isArray(body.metadata)))
|
|
45084
|
+
return { ok: false, message: "metadata must be an object" };
|
|
45085
|
+
const tasks = body.tasks === undefined ? [] : Array.isArray(body.tasks) ? body.tasks.map(validateTemplateTask) : null;
|
|
45086
|
+
if (tasks === null || tasks.some((task2) => task2 === null))
|
|
45087
|
+
return { ok: false, message: "tasks must be valid template task objects" };
|
|
45088
|
+
const taskInputs = tasks;
|
|
45089
|
+
for (const [position, task2] of taskInputs.entries()) {
|
|
45090
|
+
if ((task2.depends_on ?? []).some((dependency) => dependency >= position)) {
|
|
45091
|
+
return { ok: false, message: "template task dependencies must reference earlier task positions" };
|
|
45092
|
+
}
|
|
45093
|
+
}
|
|
45094
|
+
return {
|
|
45095
|
+
ok: true,
|
|
45096
|
+
input: {
|
|
45097
|
+
name: body.name,
|
|
45098
|
+
title_pattern: body.title_pattern,
|
|
45099
|
+
...typeof body.description === "string" ? { description: body.description } : {},
|
|
45100
|
+
...typeof body.priority === "string" ? { priority: body.priority } : {},
|
|
45101
|
+
...Array.isArray(body.tags) ? { tags: body.tags } : {},
|
|
45102
|
+
...Array.isArray(body.variables) ? { variables: body.variables } : {},
|
|
45103
|
+
...typeof body.project_id === "string" ? { project_id: body.project_id } : {},
|
|
45104
|
+
...typeof body.plan_id === "string" ? { plan_id: body.plan_id } : {},
|
|
45105
|
+
...body.metadata && typeof body.metadata === "object" && !Array.isArray(body.metadata) ? { metadata: body.metadata } : {},
|
|
45106
|
+
tasks: taskInputs
|
|
45107
|
+
}
|
|
45108
|
+
};
|
|
45109
|
+
}
|
|
45110
|
+
function validateTemplatePatch(value) {
|
|
45111
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
45112
|
+
return { ok: false, message: "template patch must be an object" };
|
|
45113
|
+
const body = value;
|
|
45114
|
+
const allowed = new Set(["name", "title_pattern", "description", "priority", "tags", "variables", "project_id", "plan_id", "metadata"]);
|
|
45115
|
+
const unknown = Object.keys(body).find((key) => !allowed.has(key));
|
|
45116
|
+
if (unknown)
|
|
45117
|
+
return { ok: false, message: `unknown template field: ${unknown}` };
|
|
45118
|
+
if (Object.keys(body).length === 0)
|
|
45119
|
+
return { ok: false, message: "template patch must not be empty" };
|
|
45120
|
+
const templateLike = { name: body.name ?? "template", title_pattern: body.title_pattern ?? "template", ...body };
|
|
45121
|
+
const validated = validateTemplateCreate(templateLike);
|
|
45122
|
+
if (!validated.ok)
|
|
45123
|
+
return validated;
|
|
45124
|
+
const { name: _name, title_pattern: _title, tasks: _tasks, ...patch } = validated.input;
|
|
45125
|
+
return { ok: true, patch: {
|
|
45126
|
+
...body.name !== undefined ? { name: validated.input.name } : {},
|
|
45127
|
+
...body.title_pattern !== undefined ? { title_pattern: validated.input.title_pattern } : {},
|
|
45128
|
+
...patch,
|
|
45129
|
+
...body.description === null ? { description: null } : {},
|
|
45130
|
+
...body.project_id === null ? { project_id: null } : {},
|
|
45131
|
+
...body.plan_id === null ? { plan_id: null } : {}
|
|
45132
|
+
} };
|
|
45133
|
+
}
|
|
44625
45134
|
async function readJson(req) {
|
|
44626
45135
|
try {
|
|
44627
45136
|
const text2 = await req.text();
|
|
@@ -44682,12 +45191,13 @@ function normalizeImportSnapshot(raw) {
|
|
|
44682
45191
|
agents: arr(body["agents"]),
|
|
44683
45192
|
taskLists: arr(body["taskLists"]),
|
|
44684
45193
|
templates: arr(body["templates"]),
|
|
45194
|
+
templateTasks: arr(body["templateTasks"]),
|
|
44685
45195
|
auditHistory: arr(body["auditHistory"]),
|
|
44686
45196
|
tombstones: arr(body["tombstones"])
|
|
44687
45197
|
};
|
|
44688
45198
|
}
|
|
44689
45199
|
function countSnapshotRecords(s) {
|
|
44690
|
-
return s.tasks.length + s.projects.length + (s.projectMachinePaths?.length ?? 0) + s.plans.length + s.agents.length + s.taskLists.length + s.templates.length + s.auditHistory.length + (s.tombstones?.length ?? 0);
|
|
45200
|
+
return s.tasks.length + s.projects.length + (s.projectMachinePaths?.length ?? 0) + s.plans.length + s.agents.length + s.taskLists.length + s.templates.length + s.templateTasks.length + s.auditHistory.length + (s.tombstones?.length ?? 0);
|
|
44691
45201
|
}
|
|
44692
45202
|
async function handleV1Request(req, url, dependencies = {}) {
|
|
44693
45203
|
const path = url.pathname;
|
|
@@ -45257,6 +45767,40 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
45257
45767
|
if (id)
|
|
45258
45768
|
return error(405, `method ${method} not allowed on /v1/plans/:id`);
|
|
45259
45769
|
}
|
|
45770
|
+
if (resource === "templates") {
|
|
45771
|
+
if (!id && method === "GET") {
|
|
45772
|
+
const projectId = url.searchParams.get("project_id");
|
|
45773
|
+
const templates = (await store.templates.list()).filter((template) => projectId === null || template.project_id === projectId);
|
|
45774
|
+
return json2({ templates, count: templates.length });
|
|
45775
|
+
}
|
|
45776
|
+
if (!id && method === "POST") {
|
|
45777
|
+
const body = await readJson(req);
|
|
45778
|
+
const validated = validateTemplateCreate(body);
|
|
45779
|
+
if (!validated.ok)
|
|
45780
|
+
return error(400, validated.message);
|
|
45781
|
+
const template = await store.templates.create(validated.input, contextFromPrincipal(principal));
|
|
45782
|
+
return json2({ template: await store.templates.getWithTasks(template.id) }, 201);
|
|
45783
|
+
}
|
|
45784
|
+
if (!id)
|
|
45785
|
+
return error(405, `method ${method} not allowed on /v1/templates`);
|
|
45786
|
+
if (method === "GET") {
|
|
45787
|
+
const template = await store.templates.getWithTasks(id);
|
|
45788
|
+
return template ? json2({ template }) : error(404, "template not found");
|
|
45789
|
+
}
|
|
45790
|
+
if (method === "PATCH" || method === "PUT") {
|
|
45791
|
+
const body = await readJson(req);
|
|
45792
|
+
const validated = validateTemplatePatch(body);
|
|
45793
|
+
if (!validated.ok)
|
|
45794
|
+
return error(400, validated.message);
|
|
45795
|
+
const template = await store.templates.update(id, validated.patch, contextFromPrincipal(principal));
|
|
45796
|
+
return template ? json2({ template: await store.templates.getWithTasks(id) }) : error(404, "template not found");
|
|
45797
|
+
}
|
|
45798
|
+
if (method === "DELETE") {
|
|
45799
|
+
const deleted = await store.templates.delete(id, contextFromPrincipal(principal));
|
|
45800
|
+
return deleted ? json2({ deleted: true, id }) : error(404, "template not found");
|
|
45801
|
+
}
|
|
45802
|
+
return error(405, `method ${method} not allowed on /v1/templates/:id`);
|
|
45803
|
+
}
|
|
45260
45804
|
if (resource === "agents") {
|
|
45261
45805
|
if (!id && method === "GET") {
|
|
45262
45806
|
const agents = await store.agents.list();
|