@hasna/todos 0.11.57 → 0.11.58
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 +202 -98
- package/dist/contracts.js +201 -17
- package/dist/index.js +201 -98
- package/dist/lib/shared-events.d.ts.map +1 -1
- package/dist/mcp/index.js +202 -98
- package/dist/registry.js +201 -17
- package/dist/release-provenance.json +3 -3
- package/dist/server/index.js +202 -98
- package/dist/storage.js +199 -96
- package/package.json +2 -2
package/dist/storage.js
CHANGED
|
@@ -4418,7 +4418,7 @@ async function testLocalEventHook(name, input) {
|
|
|
4418
4418
|
return emitLocalEventHooks({ ...input, hooks: [hook] });
|
|
4419
4419
|
}
|
|
4420
4420
|
|
|
4421
|
-
// node_modules/.bun/@hasna+events@0.1.
|
|
4421
|
+
// node_modules/.bun/@hasna+events@0.1.9/node_modules/@hasna/events/dist/index.js
|
|
4422
4422
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
4423
4423
|
import { existsSync as existsSync5 } from "fs";
|
|
4424
4424
|
import { homedir } from "os";
|
|
@@ -4435,17 +4435,30 @@ function getPathValue(input, path) {
|
|
|
4435
4435
|
return;
|
|
4436
4436
|
}, input);
|
|
4437
4437
|
}
|
|
4438
|
-
function wildcardToRegExp(pattern) {
|
|
4439
|
-
|
|
4440
|
-
|
|
4438
|
+
function wildcardToRegExp(pattern, options = {}) {
|
|
4439
|
+
let body = "";
|
|
4440
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
4441
|
+
const char = pattern[index];
|
|
4442
|
+
if (char === "*") {
|
|
4443
|
+
if (pattern[index + 1] === "*") {
|
|
4444
|
+
body += ".*";
|
|
4445
|
+
index += 1;
|
|
4446
|
+
} else {
|
|
4447
|
+
body += options.segmentSafe ? "[^/]*" : ".*";
|
|
4448
|
+
}
|
|
4449
|
+
} else {
|
|
4450
|
+
body += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
4451
|
+
}
|
|
4452
|
+
}
|
|
4453
|
+
return new RegExp(`^${body}$`);
|
|
4441
4454
|
}
|
|
4442
|
-
function matchString(value, matcher) {
|
|
4455
|
+
function matchString(value, matcher, options = {}) {
|
|
4443
4456
|
if (matcher === undefined)
|
|
4444
4457
|
return true;
|
|
4445
4458
|
if (value === undefined)
|
|
4446
4459
|
return false;
|
|
4447
4460
|
const matchers = Array.isArray(matcher) ? matcher : [matcher];
|
|
4448
|
-
return matchers.some((item) => wildcardToRegExp(item).test(value));
|
|
4461
|
+
return matchers.some((item) => wildcardToRegExp(item, options).test(value));
|
|
4449
4462
|
}
|
|
4450
4463
|
function matchRecord(input, matcher) {
|
|
4451
4464
|
if (!matcher)
|
|
@@ -4453,7 +4466,9 @@ function matchRecord(input, matcher) {
|
|
|
4453
4466
|
return Object.entries(matcher).every(([path, expected]) => {
|
|
4454
4467
|
const actual = getPathValue(input, path);
|
|
4455
4468
|
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
4456
|
-
return matchString(actual === undefined ? undefined : String(actual), expected
|
|
4469
|
+
return matchString(actual === undefined ? undefined : String(actual), expected, {
|
|
4470
|
+
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
4471
|
+
});
|
|
4457
4472
|
}
|
|
4458
4473
|
return actual === expected;
|
|
4459
4474
|
});
|
|
@@ -4473,7 +4488,6 @@ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
|
4473
4488
|
function getEventsDataDir(override) {
|
|
4474
4489
|
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join4(homedir(), ".hasna", "events");
|
|
4475
4490
|
}
|
|
4476
|
-
|
|
4477
4491
|
class JsonEventsStore {
|
|
4478
4492
|
dataDir;
|
|
4479
4493
|
channelsPath;
|
|
@@ -4810,7 +4824,7 @@ class EventsClient {
|
|
|
4810
4824
|
}
|
|
4811
4825
|
return deliveries;
|
|
4812
4826
|
}
|
|
4813
|
-
async
|
|
4827
|
+
async matchChannel(id, input = {}) {
|
|
4814
4828
|
const channel = await this.store.getChannel(id);
|
|
4815
4829
|
if (!channel)
|
|
4816
4830
|
throw new Error(`Channel not found: ${id}`);
|
|
@@ -4827,6 +4841,34 @@ class EventsClient {
|
|
|
4827
4841
|
time: input.time,
|
|
4828
4842
|
id: input.id
|
|
4829
4843
|
});
|
|
4844
|
+
const matched = channelMatchesEvent(channel, event);
|
|
4845
|
+
return {
|
|
4846
|
+
channelId: channel.id,
|
|
4847
|
+
matched,
|
|
4848
|
+
event,
|
|
4849
|
+
filters: channel.filters,
|
|
4850
|
+
reason: matched ? undefined : channel.enabled ? "event did not match channel filters" : "channel is disabled"
|
|
4851
|
+
};
|
|
4852
|
+
}
|
|
4853
|
+
async testChannel(id, input = {}, options = {}) {
|
|
4854
|
+
const channel = await this.store.getChannel(id);
|
|
4855
|
+
if (!channel)
|
|
4856
|
+
throw new Error(`Channel not found: ${id}`);
|
|
4857
|
+
const match = await this.matchChannel(id, input);
|
|
4858
|
+
const event = match.event;
|
|
4859
|
+
if (options.honorFilters && !match.matched) {
|
|
4860
|
+
const timestamp = new Date().toISOString();
|
|
4861
|
+
const result2 = createDeliveryResult(event, channel, [{
|
|
4862
|
+
attempt: 1,
|
|
4863
|
+
status: "skipped",
|
|
4864
|
+
startedAt: timestamp,
|
|
4865
|
+
completedAt: timestamp,
|
|
4866
|
+
error: match.reason
|
|
4867
|
+
}]);
|
|
4868
|
+
result2.metadata = { reason: "filter_mismatch" };
|
|
4869
|
+
await this.store.appendDelivery(result2);
|
|
4870
|
+
return result2;
|
|
4871
|
+
}
|
|
4830
4872
|
const eventForChannel = await this.applyRedaction(event, channel);
|
|
4831
4873
|
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
4832
4874
|
await this.store.appendDelivery(result);
|
|
@@ -4926,6 +4968,90 @@ function normalizeRetryPolicy(policy) {
|
|
|
4926
4968
|
};
|
|
4927
4969
|
}
|
|
4928
4970
|
|
|
4971
|
+
// src/lib/shared-events.ts
|
|
4972
|
+
init_database();
|
|
4973
|
+
|
|
4974
|
+
// src/db/task-lists.ts
|
|
4975
|
+
init_types();
|
|
4976
|
+
init_database();
|
|
4977
|
+
function rowToTaskList(row) {
|
|
4978
|
+
return {
|
|
4979
|
+
...row,
|
|
4980
|
+
metadata: JSON.parse(row.metadata || "{}")
|
|
4981
|
+
};
|
|
4982
|
+
}
|
|
4983
|
+
function createTaskList(input, db) {
|
|
4984
|
+
const d = db || getDatabase();
|
|
4985
|
+
const id = uuid();
|
|
4986
|
+
const timestamp = now();
|
|
4987
|
+
const slug = input.slug || slugify(input.name);
|
|
4988
|
+
if (!input.project_id) {
|
|
4989
|
+
const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
|
|
4990
|
+
if (existing) {
|
|
4991
|
+
throw new Error(`Standalone task list with slug "${slug}" already exists`);
|
|
4992
|
+
}
|
|
4993
|
+
}
|
|
4994
|
+
d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
|
|
4995
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
|
|
4996
|
+
return getTaskList(id, d);
|
|
4997
|
+
}
|
|
4998
|
+
function getTaskList(id, db) {
|
|
4999
|
+
const d = db || getDatabase();
|
|
5000
|
+
const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
|
|
5001
|
+
return row ? rowToTaskList(row) : null;
|
|
5002
|
+
}
|
|
5003
|
+
function getTaskListBySlug(slug, projectId, db) {
|
|
5004
|
+
const d = db || getDatabase();
|
|
5005
|
+
let row;
|
|
5006
|
+
if (projectId) {
|
|
5007
|
+
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id = ?").get(slug, projectId);
|
|
5008
|
+
} else {
|
|
5009
|
+
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id IS NULL").get(slug);
|
|
5010
|
+
}
|
|
5011
|
+
return row ? rowToTaskList(row) : null;
|
|
5012
|
+
}
|
|
5013
|
+
function listTaskLists(projectId, db) {
|
|
5014
|
+
const d = db || getDatabase();
|
|
5015
|
+
if (projectId) {
|
|
5016
|
+
return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList);
|
|
5017
|
+
}
|
|
5018
|
+
return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList);
|
|
5019
|
+
}
|
|
5020
|
+
function updateTaskList(id, input, db) {
|
|
5021
|
+
const d = db || getDatabase();
|
|
5022
|
+
const existing = getTaskList(id, d);
|
|
5023
|
+
if (!existing)
|
|
5024
|
+
throw new TaskListNotFoundError(id);
|
|
5025
|
+
const sets = ["updated_at = ?"];
|
|
5026
|
+
const params = [now()];
|
|
5027
|
+
if (input.name !== undefined) {
|
|
5028
|
+
sets.push("name = ?");
|
|
5029
|
+
params.push(input.name);
|
|
5030
|
+
}
|
|
5031
|
+
if (input.description !== undefined) {
|
|
5032
|
+
sets.push("description = ?");
|
|
5033
|
+
params.push(input.description);
|
|
5034
|
+
}
|
|
5035
|
+
if (input.metadata !== undefined) {
|
|
5036
|
+
sets.push("metadata = ?");
|
|
5037
|
+
params.push(JSON.stringify(input.metadata));
|
|
5038
|
+
}
|
|
5039
|
+
params.push(id);
|
|
5040
|
+
d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
5041
|
+
return getTaskList(id, d);
|
|
5042
|
+
}
|
|
5043
|
+
function deleteTaskList(id, db) {
|
|
5044
|
+
const d = db || getDatabase();
|
|
5045
|
+
return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
|
|
5046
|
+
}
|
|
5047
|
+
function ensureTaskList(name, slug, projectId, db) {
|
|
5048
|
+
const d = db || getDatabase();
|
|
5049
|
+
const existing = getTaskListBySlug(slug, projectId, d);
|
|
5050
|
+
if (existing)
|
|
5051
|
+
return existing;
|
|
5052
|
+
return createTaskList({ name, slug, project_id: projectId }, d);
|
|
5053
|
+
}
|
|
5054
|
+
|
|
4929
5055
|
// src/lib/shared-events.ts
|
|
4930
5056
|
var SOURCE = "todos";
|
|
4931
5057
|
function taskEventData(task, extra = {}) {
|
|
@@ -4956,6 +5082,69 @@ function taskEventData(task, extra = {}) {
|
|
|
4956
5082
|
...extra
|
|
4957
5083
|
};
|
|
4958
5084
|
}
|
|
5085
|
+
function taskEventMetadata(task) {
|
|
5086
|
+
const metadata = {
|
|
5087
|
+
package: "@hasna/todos",
|
|
5088
|
+
todos_event_schema_version: 1,
|
|
5089
|
+
task_id: task.id,
|
|
5090
|
+
task_short_id: task.short_id,
|
|
5091
|
+
project_id: task.project_id,
|
|
5092
|
+
task_list_id: task.task_list_id,
|
|
5093
|
+
working_dir: task.working_dir
|
|
5094
|
+
};
|
|
5095
|
+
try {
|
|
5096
|
+
const project = task.project_id ? getProject(task.project_id) : null;
|
|
5097
|
+
const projectPath = project ? readMachineLocalPath(project) ?? project.path : task.working_dir;
|
|
5098
|
+
if (project) {
|
|
5099
|
+
metadata.project_id = project.id;
|
|
5100
|
+
metadata.project_name = project.name;
|
|
5101
|
+
metadata.project_path = projectPath;
|
|
5102
|
+
metadata.project_canonical_path = project.path;
|
|
5103
|
+
metadata.project_default_task_list_slug = project.task_list_id;
|
|
5104
|
+
metadata.root_project_id = inferRootProjectId(project);
|
|
5105
|
+
} else if (projectPath) {
|
|
5106
|
+
metadata.project_path = projectPath;
|
|
5107
|
+
metadata.project_canonical_path = projectPath;
|
|
5108
|
+
}
|
|
5109
|
+
if (projectPath) {
|
|
5110
|
+
metadata.project_kind = classifyProjectKind(projectPath);
|
|
5111
|
+
metadata.project_is_worktree = isWorktreePath(projectPath);
|
|
5112
|
+
if (typeof task.metadata.route_enabled === "boolean") {
|
|
5113
|
+
metadata.route_enabled = task.metadata.route_enabled;
|
|
5114
|
+
}
|
|
5115
|
+
metadata.working_dir = task.working_dir ?? projectPath;
|
|
5116
|
+
}
|
|
5117
|
+
const taskList = task.task_list_id ? getTaskList(task.task_list_id) ?? (project ? getTaskListBySlug(task.task_list_id, project.id) : null) : project?.task_list_id ? getTaskListBySlug(project.task_list_id, project.id) : null;
|
|
5118
|
+
if (taskList) {
|
|
5119
|
+
metadata.task_list_id = taskList.id;
|
|
5120
|
+
metadata.task_list_slug = taskList.slug;
|
|
5121
|
+
metadata.task_list_name = taskList.name;
|
|
5122
|
+
metadata.task_list_project_id = taskList.project_id;
|
|
5123
|
+
metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
|
|
5124
|
+
}
|
|
5125
|
+
} catch {}
|
|
5126
|
+
return metadata;
|
|
5127
|
+
}
|
|
5128
|
+
function classifyProjectKind(path) {
|
|
5129
|
+
return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
|
|
5130
|
+
}
|
|
5131
|
+
function isWorktreePath(path) {
|
|
5132
|
+
return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
|
|
5133
|
+
}
|
|
5134
|
+
function inferRootProjectId(project) {
|
|
5135
|
+
return isWorktreePath(project.path) ? null : project.id;
|
|
5136
|
+
}
|
|
5137
|
+
function readMachineLocalPath(project) {
|
|
5138
|
+
const machineId = process.env["TODOS_MACHINE_ID"];
|
|
5139
|
+
if (!machineId)
|
|
5140
|
+
return null;
|
|
5141
|
+
try {
|
|
5142
|
+
const row = getDatabase().query("SELECT path FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(project.id, machineId);
|
|
5143
|
+
return row?.path ?? null;
|
|
5144
|
+
} catch {
|
|
5145
|
+
return null;
|
|
5146
|
+
}
|
|
5147
|
+
}
|
|
4959
5148
|
async function emitSharedTaskEvent(input) {
|
|
4960
5149
|
const data = taskEventData(input.task, input.data);
|
|
4961
5150
|
await new EventsClient().emit({
|
|
@@ -4966,12 +5155,7 @@ async function emitSharedTaskEvent(input) {
|
|
|
4966
5155
|
message: input.message ?? `${input.type}: ${input.task.title}`,
|
|
4967
5156
|
data,
|
|
4968
5157
|
dedupeKey: input.dedupeKey ?? `${input.type}:${input.task.id}:${input.task.version}`,
|
|
4969
|
-
metadata:
|
|
4970
|
-
package: "@hasna/todos",
|
|
4971
|
-
task_id: input.task.id,
|
|
4972
|
-
project_id: input.task.project_id,
|
|
4973
|
-
task_list_id: input.task.task_list_id
|
|
4974
|
-
}
|
|
5158
|
+
metadata: taskEventMetadata(input.task)
|
|
4975
5159
|
}, { deliver: true, dedupe: true });
|
|
4976
5160
|
}
|
|
4977
5161
|
function emitSharedTaskEventQuiet(input) {
|
|
@@ -9741,87 +9925,6 @@ function getCapableAgents(capabilities, opts, db) {
|
|
|
9741
9925
|
return opts?.limit ? scored.slice(0, opts.limit) : scored;
|
|
9742
9926
|
}
|
|
9743
9927
|
|
|
9744
|
-
// src/db/task-lists.ts
|
|
9745
|
-
init_types();
|
|
9746
|
-
init_database();
|
|
9747
|
-
function rowToTaskList(row) {
|
|
9748
|
-
return {
|
|
9749
|
-
...row,
|
|
9750
|
-
metadata: JSON.parse(row.metadata || "{}")
|
|
9751
|
-
};
|
|
9752
|
-
}
|
|
9753
|
-
function createTaskList(input, db) {
|
|
9754
|
-
const d = db || getDatabase();
|
|
9755
|
-
const id = uuid();
|
|
9756
|
-
const timestamp = now();
|
|
9757
|
-
const slug = input.slug || slugify(input.name);
|
|
9758
|
-
if (!input.project_id) {
|
|
9759
|
-
const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
|
|
9760
|
-
if (existing) {
|
|
9761
|
-
throw new Error(`Standalone task list with slug "${slug}" already exists`);
|
|
9762
|
-
}
|
|
9763
|
-
}
|
|
9764
|
-
d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
|
|
9765
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
|
|
9766
|
-
return getTaskList(id, d);
|
|
9767
|
-
}
|
|
9768
|
-
function getTaskList(id, db) {
|
|
9769
|
-
const d = db || getDatabase();
|
|
9770
|
-
const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
|
|
9771
|
-
return row ? rowToTaskList(row) : null;
|
|
9772
|
-
}
|
|
9773
|
-
function getTaskListBySlug(slug, projectId, db) {
|
|
9774
|
-
const d = db || getDatabase();
|
|
9775
|
-
let row;
|
|
9776
|
-
if (projectId) {
|
|
9777
|
-
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id = ?").get(slug, projectId);
|
|
9778
|
-
} else {
|
|
9779
|
-
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id IS NULL").get(slug);
|
|
9780
|
-
}
|
|
9781
|
-
return row ? rowToTaskList(row) : null;
|
|
9782
|
-
}
|
|
9783
|
-
function listTaskLists(projectId, db) {
|
|
9784
|
-
const d = db || getDatabase();
|
|
9785
|
-
if (projectId) {
|
|
9786
|
-
return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList);
|
|
9787
|
-
}
|
|
9788
|
-
return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList);
|
|
9789
|
-
}
|
|
9790
|
-
function updateTaskList(id, input, db) {
|
|
9791
|
-
const d = db || getDatabase();
|
|
9792
|
-
const existing = getTaskList(id, d);
|
|
9793
|
-
if (!existing)
|
|
9794
|
-
throw new TaskListNotFoundError(id);
|
|
9795
|
-
const sets = ["updated_at = ?"];
|
|
9796
|
-
const params = [now()];
|
|
9797
|
-
if (input.name !== undefined) {
|
|
9798
|
-
sets.push("name = ?");
|
|
9799
|
-
params.push(input.name);
|
|
9800
|
-
}
|
|
9801
|
-
if (input.description !== undefined) {
|
|
9802
|
-
sets.push("description = ?");
|
|
9803
|
-
params.push(input.description);
|
|
9804
|
-
}
|
|
9805
|
-
if (input.metadata !== undefined) {
|
|
9806
|
-
sets.push("metadata = ?");
|
|
9807
|
-
params.push(JSON.stringify(input.metadata));
|
|
9808
|
-
}
|
|
9809
|
-
params.push(id);
|
|
9810
|
-
d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
9811
|
-
return getTaskList(id, d);
|
|
9812
|
-
}
|
|
9813
|
-
function deleteTaskList(id, db) {
|
|
9814
|
-
const d = db || getDatabase();
|
|
9815
|
-
return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
|
|
9816
|
-
}
|
|
9817
|
-
function ensureTaskList(name, slug, projectId, db) {
|
|
9818
|
-
const d = db || getDatabase();
|
|
9819
|
-
const existing = getTaskListBySlug(slug, projectId, d);
|
|
9820
|
-
if (existing)
|
|
9821
|
-
return existing;
|
|
9822
|
-
return createTaskList({ name, slug, project_id: projectId }, d);
|
|
9823
|
-
}
|
|
9824
|
-
|
|
9825
9928
|
// src/storage/local-sqlite.ts
|
|
9826
9929
|
init_database();
|
|
9827
9930
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/todos",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.58",
|
|
4
4
|
"description": "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"author": "Andrei Hasna <andrei@hasna.com>",
|
|
90
90
|
"license": "Apache-2.0",
|
|
91
91
|
"dependencies": {
|
|
92
|
-
"@hasna/events": "^0.1.
|
|
92
|
+
"@hasna/events": "^0.1.9",
|
|
93
93
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
94
94
|
"chalk": "^5.4.1",
|
|
95
95
|
"commander": "^13.1.0",
|