@hasna/todos 0.11.18 → 0.11.20
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 +392 -21
- package/dist/index.js +161 -9
- package/dist/mcp/index.js +257 -18
- package/dist/server/index.js +159 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -176,6 +176,18 @@ function ensureSchema(db) {
|
|
|
176
176
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
177
177
|
UNIQUE(source_id, source_type, target_id, target_type, relation_type)
|
|
178
178
|
)`);
|
|
179
|
+
ensureTable("project_machine_paths", `
|
|
180
|
+
CREATE TABLE project_machine_paths (
|
|
181
|
+
id TEXT PRIMARY KEY,
|
|
182
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
183
|
+
machine_id TEXT NOT NULL,
|
|
184
|
+
path TEXT NOT NULL,
|
|
185
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
186
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
187
|
+
UNIQUE(project_id, machine_id)
|
|
188
|
+
)`);
|
|
189
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_project ON project_machine_paths(project_id)");
|
|
190
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_machine ON project_machine_paths(machine_id)");
|
|
179
191
|
ensureTable("machines", `
|
|
180
192
|
CREATE TABLE machines (
|
|
181
193
|
id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, hostname TEXT, platform TEXT,
|
|
@@ -211,6 +223,7 @@ function ensureSchema(db) {
|
|
|
211
223
|
ensureColumn("tasks", "max_retries", "INTEGER DEFAULT 3");
|
|
212
224
|
ensureColumn("tasks", "retry_after", "TEXT");
|
|
213
225
|
ensureColumn("tasks", "sla_minutes", "INTEGER");
|
|
226
|
+
ensureColumn("tasks", "archived_at", "TEXT");
|
|
214
227
|
ensureColumn("agents", "role", "TEXT DEFAULT 'agent'");
|
|
215
228
|
ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
|
|
216
229
|
ensureColumn("agents", "reports_to", "TEXT");
|
|
@@ -1049,6 +1062,25 @@ var init_schema = __esm(() => {
|
|
|
1049
1062
|
CREATE INDEX IF NOT EXISTS idx_agents_machine ON agents(machine_id);
|
|
1050
1063
|
|
|
1051
1064
|
INSERT OR IGNORE INTO _migrations (id) VALUES (41);
|
|
1065
|
+
`,
|
|
1066
|
+
`
|
|
1067
|
+
CREATE TABLE IF NOT EXISTS project_machine_paths (
|
|
1068
|
+
id TEXT PRIMARY KEY,
|
|
1069
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
1070
|
+
machine_id TEXT NOT NULL,
|
|
1071
|
+
path TEXT NOT NULL,
|
|
1072
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1073
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1074
|
+
UNIQUE(project_id, machine_id)
|
|
1075
|
+
);
|
|
1076
|
+
CREATE INDEX IF NOT EXISTS idx_project_machine_paths_project ON project_machine_paths(project_id);
|
|
1077
|
+
CREATE INDEX IF NOT EXISTS idx_project_machine_paths_machine ON project_machine_paths(machine_id);
|
|
1078
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (42);
|
|
1079
|
+
`,
|
|
1080
|
+
`
|
|
1081
|
+
ALTER TABLE tasks ADD COLUMN archived_at TEXT;
|
|
1082
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_archived ON tasks(archived_at) WHERE archived_at IS NOT NULL;
|
|
1083
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (43);
|
|
1052
1084
|
`
|
|
1053
1085
|
];
|
|
1054
1086
|
});
|
|
@@ -1750,6 +1782,7 @@ init_database();
|
|
|
1750
1782
|
// src/db/projects.ts
|
|
1751
1783
|
init_types();
|
|
1752
1784
|
init_database();
|
|
1785
|
+
init_machines();
|
|
1753
1786
|
function slugify(name) {
|
|
1754
1787
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
1755
1788
|
}
|
|
@@ -1790,8 +1823,15 @@ function getProject(id, db) {
|
|
|
1790
1823
|
}
|
|
1791
1824
|
function getProjectByPath(path, db) {
|
|
1792
1825
|
const d = db || getDatabase();
|
|
1793
|
-
|
|
1794
|
-
|
|
1826
|
+
try {
|
|
1827
|
+
const machineId = getMachineId(d);
|
|
1828
|
+
const machineRow = d.query(`SELECT p.* FROM projects p
|
|
1829
|
+
JOIN project_machine_paths pmp ON pmp.project_id = p.id
|
|
1830
|
+
WHERE pmp.machine_id = ? AND pmp.path = ?`).get(machineId, path);
|
|
1831
|
+
if (machineRow)
|
|
1832
|
+
return machineRow;
|
|
1833
|
+
} catch {}
|
|
1834
|
+
return d.query("SELECT * FROM projects WHERE path = ?").get(path);
|
|
1795
1835
|
}
|
|
1796
1836
|
function listProjects(db) {
|
|
1797
1837
|
const d = db || getDatabase();
|
|
@@ -1816,10 +1856,40 @@ function updateProject(id, input, db) {
|
|
|
1816
1856
|
sets.push("task_list_id = ?");
|
|
1817
1857
|
params.push(input.task_list_id);
|
|
1818
1858
|
}
|
|
1859
|
+
if (input.path !== undefined) {
|
|
1860
|
+
sets.push("path = ?");
|
|
1861
|
+
params.push(input.path);
|
|
1862
|
+
}
|
|
1819
1863
|
params.push(id);
|
|
1820
1864
|
d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
1821
1865
|
return getProject(id, d);
|
|
1822
1866
|
}
|
|
1867
|
+
function renameProject(id, input, db) {
|
|
1868
|
+
const d = db || getDatabase();
|
|
1869
|
+
const project = getProject(id, d);
|
|
1870
|
+
if (!project)
|
|
1871
|
+
throw new ProjectNotFoundError(id);
|
|
1872
|
+
let taskListsUpdated = 0;
|
|
1873
|
+
const ts = now();
|
|
1874
|
+
if (input.new_slug !== undefined) {
|
|
1875
|
+
const normalised = input.new_slug.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "");
|
|
1876
|
+
if (!normalised)
|
|
1877
|
+
throw new Error("Invalid slug \u2014 must be non-empty kebab-case");
|
|
1878
|
+
const conflict = d.query("SELECT id FROM projects WHERE task_list_id = ? AND id != ?").get(normalised, id);
|
|
1879
|
+
if (conflict)
|
|
1880
|
+
throw new Error(`Slug "${normalised}" is already used by another project`);
|
|
1881
|
+
const oldSlug = project.task_list_id;
|
|
1882
|
+
d.run("UPDATE projects SET task_list_id = ?, updated_at = ? WHERE id = ?", [normalised, ts, id]);
|
|
1883
|
+
if (oldSlug) {
|
|
1884
|
+
const result = d.run("UPDATE task_lists SET slug = ?, name = COALESCE(?, name), updated_at = ? WHERE project_id = ? AND slug = ?", [normalised, input.name ?? null, ts, id, oldSlug]);
|
|
1885
|
+
taskListsUpdated = result.changes;
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
if (input.name !== undefined) {
|
|
1889
|
+
d.run("UPDATE projects SET name = ?, updated_at = ? WHERE id = ?", [input.name, ts, id]);
|
|
1890
|
+
}
|
|
1891
|
+
return { project: getProject(id, d), task_lists_updated: taskListsUpdated };
|
|
1892
|
+
}
|
|
1823
1893
|
function deleteProject(id, db) {
|
|
1824
1894
|
const d = db || getDatabase();
|
|
1825
1895
|
const result = d.run("DELETE FROM projects WHERE id = ?", [id]);
|
|
@@ -1886,9 +1956,48 @@ function ensureProject(name, path, db) {
|
|
|
1886
1956
|
d.run("UPDATE projects SET task_prefix = ?, updated_at = ? WHERE id = ?", [prefix, now(), existing.id]);
|
|
1887
1957
|
return getProject(existing.id, d);
|
|
1888
1958
|
}
|
|
1959
|
+
setMachineLocalPath(existing.id, path, d);
|
|
1889
1960
|
return existing;
|
|
1890
1961
|
}
|
|
1891
|
-
|
|
1962
|
+
const project = createProject({ name, path }, d);
|
|
1963
|
+
setMachineLocalPath(project.id, path, d);
|
|
1964
|
+
return project;
|
|
1965
|
+
}
|
|
1966
|
+
function setMachineLocalPath(projectId, path, db) {
|
|
1967
|
+
const d = db || getDatabase();
|
|
1968
|
+
const machineId = getMachineId(d);
|
|
1969
|
+
const ts = now();
|
|
1970
|
+
const existing = d.query("SELECT * FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(projectId, machineId);
|
|
1971
|
+
if (existing) {
|
|
1972
|
+
if (existing.path !== path) {
|
|
1973
|
+
d.run("UPDATE project_machine_paths SET path = ?, updated_at = ? WHERE id = ?", [path, ts, existing.id]);
|
|
1974
|
+
}
|
|
1975
|
+
return d.query("SELECT * FROM project_machine_paths WHERE id = ?").get(existing.id);
|
|
1976
|
+
}
|
|
1977
|
+
const id = uuid();
|
|
1978
|
+
d.run("INSERT INTO project_machine_paths (id, project_id, machine_id, path, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)", [id, projectId, machineId, path, ts, ts]);
|
|
1979
|
+
return d.query("SELECT * FROM project_machine_paths WHERE id = ?").get(id);
|
|
1980
|
+
}
|
|
1981
|
+
function getMachineLocalPath(projectId, db) {
|
|
1982
|
+
const d = db || getDatabase();
|
|
1983
|
+
try {
|
|
1984
|
+
const machineId = getMachineId(d);
|
|
1985
|
+
const row = d.query("SELECT path FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(projectId, machineId);
|
|
1986
|
+
if (row)
|
|
1987
|
+
return row.path;
|
|
1988
|
+
} catch {}
|
|
1989
|
+
const project = getProject(projectId, d);
|
|
1990
|
+
return project?.path ?? null;
|
|
1991
|
+
}
|
|
1992
|
+
function listMachineLocalPaths(projectId, db) {
|
|
1993
|
+
const d = db || getDatabase();
|
|
1994
|
+
return d.query("SELECT * FROM project_machine_paths WHERE project_id = ? ORDER BY machine_id").all(projectId);
|
|
1995
|
+
}
|
|
1996
|
+
function removeMachineLocalPath(projectId, machineId, db) {
|
|
1997
|
+
const d = db || getDatabase();
|
|
1998
|
+
const mid = machineId ?? getMachineId(d);
|
|
1999
|
+
const result = d.run("DELETE FROM project_machine_paths WHERE project_id = ? AND machine_id = ?", [projectId, mid]);
|
|
2000
|
+
return result.changes > 0;
|
|
1892
2001
|
}
|
|
1893
2002
|
|
|
1894
2003
|
// src/lib/completion-guard.ts
|
|
@@ -2980,6 +3089,9 @@ function listTasks(filter = {}, db) {
|
|
|
2980
3089
|
params.push(decoded.p, decoded.p, decoded.c, decoded.p, decoded.c, decoded.i);
|
|
2981
3090
|
} catch {}
|
|
2982
3091
|
}
|
|
3092
|
+
if (!filter.include_archived) {
|
|
3093
|
+
conditions.push("archived_at IS NULL");
|
|
3094
|
+
}
|
|
2983
3095
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
2984
3096
|
let limitClause = "";
|
|
2985
3097
|
if (filter.limit) {
|
|
@@ -3893,6 +4005,35 @@ function bulkUpdateTasks(taskIds, updates, db) {
|
|
|
3893
4005
|
tx();
|
|
3894
4006
|
return { updated, failed };
|
|
3895
4007
|
}
|
|
4008
|
+
function archiveTasks(options, db) {
|
|
4009
|
+
const d = db || getDatabase();
|
|
4010
|
+
const conditions = ["archived_at IS NULL"];
|
|
4011
|
+
const params = [];
|
|
4012
|
+
const statuses = options.status ?? ["completed", "failed", "cancelled"];
|
|
4013
|
+
conditions.push(`status IN (${statuses.map(() => "?").join(",")})`);
|
|
4014
|
+
params.push(...statuses);
|
|
4015
|
+
if (options.project_id) {
|
|
4016
|
+
conditions.push("project_id = ?");
|
|
4017
|
+
params.push(options.project_id);
|
|
4018
|
+
}
|
|
4019
|
+
if (options.task_list_id) {
|
|
4020
|
+
conditions.push("task_list_id = ?");
|
|
4021
|
+
params.push(options.task_list_id);
|
|
4022
|
+
}
|
|
4023
|
+
if (options.older_than_days !== undefined) {
|
|
4024
|
+
const cutoff = new Date(Date.now() - options.older_than_days * 86400000).toISOString();
|
|
4025
|
+
conditions.push("updated_at < ?");
|
|
4026
|
+
params.push(cutoff);
|
|
4027
|
+
}
|
|
4028
|
+
const ts = now();
|
|
4029
|
+
const result = d.run(`UPDATE tasks SET archived_at = ? WHERE ${conditions.join(" AND ")}`, [ts, ...params]);
|
|
4030
|
+
return { archived: result.changes };
|
|
4031
|
+
}
|
|
4032
|
+
function unarchiveTask(id, db) {
|
|
4033
|
+
const d = db || getDatabase();
|
|
4034
|
+
d.run("UPDATE tasks SET archived_at = NULL WHERE id = ?", [id]);
|
|
4035
|
+
return getTask(id, d);
|
|
4036
|
+
}
|
|
3896
4037
|
function getOverdueTasks(projectId, db) {
|
|
3897
4038
|
const d = db || getDatabase();
|
|
3898
4039
|
const nowStr = new Date().toISOString();
|
|
@@ -5402,21 +5543,25 @@ function escapeFtsQuery(q) {
|
|
|
5402
5543
|
return q.replace(/["*^()]/g, " ").trim().split(/\s+/).filter(Boolean).map((token) => `"${token}"*`).join(" ");
|
|
5403
5544
|
}
|
|
5404
5545
|
function searchTasks(options, projectId, taskListId, db) {
|
|
5405
|
-
const opts = typeof options === "string" ? { query: options, project_id: projectId, task_list_id: taskListId } : options;
|
|
5546
|
+
const opts = typeof options === "string" ? { query: options || undefined, project_id: projectId, task_list_id: taskListId } : options;
|
|
5406
5547
|
const d = db || getDatabase();
|
|
5407
5548
|
clearExpiredLocks(d);
|
|
5408
5549
|
const params = [];
|
|
5409
5550
|
let sql;
|
|
5410
|
-
|
|
5411
|
-
|
|
5551
|
+
const raw = opts.query?.trim() ?? "";
|
|
5552
|
+
const q = raw === "*" ? "" : raw;
|
|
5553
|
+
if (hasFts(d) && q) {
|
|
5554
|
+
const ftsQuery = escapeFtsQuery(q);
|
|
5412
5555
|
sql = `SELECT t.* FROM tasks t
|
|
5413
5556
|
INNER JOIN tasks_fts fts ON fts.rowid = t.rowid
|
|
5414
5557
|
WHERE tasks_fts MATCH ?`;
|
|
5415
5558
|
params.push(ftsQuery);
|
|
5416
|
-
} else {
|
|
5417
|
-
const pattern = `%${
|
|
5559
|
+
} else if (q) {
|
|
5560
|
+
const pattern = `%${q}%`;
|
|
5418
5561
|
sql = `SELECT * FROM tasks t WHERE (t.title LIKE ? OR t.description LIKE ? OR EXISTS (SELECT 1 FROM task_tags WHERE task_tags.task_id = t.id AND tag LIKE ?))`;
|
|
5419
5562
|
params.push(pattern, pattern, pattern);
|
|
5563
|
+
} else {
|
|
5564
|
+
sql = `SELECT * FROM tasks t WHERE 1=1`;
|
|
5420
5565
|
}
|
|
5421
5566
|
if (opts.project_id) {
|
|
5422
5567
|
sql += " AND t.project_id = ?";
|
|
@@ -5470,7 +5615,7 @@ function searchTasks(options, projectId, taskListId, db) {
|
|
|
5470
5615
|
} else if (opts.is_blocked === false) {
|
|
5471
5616
|
sql += " AND t.id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status != 'completed')";
|
|
5472
5617
|
}
|
|
5473
|
-
if (hasFts(d) &&
|
|
5618
|
+
if (hasFts(d) && q) {
|
|
5474
5619
|
sql += ` ORDER BY bm25(tasks_fts),
|
|
5475
5620
|
CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
5476
5621
|
t.created_at DESC`;
|
|
@@ -16940,6 +17085,7 @@ export {
|
|
|
16940
17085
|
updateAgentActivity,
|
|
16941
17086
|
updateAgent,
|
|
16942
17087
|
unlockTask,
|
|
17088
|
+
unarchiveTask,
|
|
16943
17089
|
unarchiveAgent,
|
|
16944
17090
|
tasksFromTemplate,
|
|
16945
17091
|
taskFromTemplate,
|
|
@@ -16952,6 +17098,7 @@ export {
|
|
|
16952
17098
|
slugify,
|
|
16953
17099
|
setTaskStatus,
|
|
16954
17100
|
setTaskPriority,
|
|
17101
|
+
setMachineLocalPath,
|
|
16955
17102
|
setBudget,
|
|
16956
17103
|
setActiveModel,
|
|
16957
17104
|
sendToTmux,
|
|
@@ -16963,10 +17110,12 @@ export {
|
|
|
16963
17110
|
resolvePartialId,
|
|
16964
17111
|
resetMachineId,
|
|
16965
17112
|
resetDatabase,
|
|
17113
|
+
renameProject,
|
|
16966
17114
|
removeTaskRelationshipByPair,
|
|
16967
17115
|
removeTaskRelationship,
|
|
16968
17116
|
removeTaskFile,
|
|
16969
17117
|
removeProjectSource,
|
|
17118
|
+
removeMachineLocalPath,
|
|
16970
17119
|
removeKgEdges,
|
|
16971
17120
|
removeDependency,
|
|
16972
17121
|
removeChecklistItem,
|
|
@@ -17003,6 +17152,7 @@ export {
|
|
|
17003
17152
|
listPlans,
|
|
17004
17153
|
listOrgs,
|
|
17005
17154
|
listMachines,
|
|
17155
|
+
listMachineLocalPaths,
|
|
17006
17156
|
listHandoffs,
|
|
17007
17157
|
listDispatches,
|
|
17008
17158
|
listDispatchLogs,
|
|
@@ -17051,6 +17201,7 @@ export {
|
|
|
17051
17201
|
getOrg,
|
|
17052
17202
|
getOrCreateLocalMachine,
|
|
17053
17203
|
getNextTask,
|
|
17204
|
+
getMachineLocalPath,
|
|
17054
17205
|
getMachineId,
|
|
17055
17206
|
getMachineByName,
|
|
17056
17207
|
getMachine,
|
|
@@ -17139,6 +17290,7 @@ export {
|
|
|
17139
17290
|
bulkAddTaskFiles,
|
|
17140
17291
|
autoReleaseStaleAgents,
|
|
17141
17292
|
autoDetectFileRelationships,
|
|
17293
|
+
archiveTasks,
|
|
17142
17294
|
archiveAgent,
|
|
17143
17295
|
applyPgMigrations,
|
|
17144
17296
|
addTemplateTasks,
|