@mulmoclaude/todo-plugin 0.1.0

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.
Files changed (42) hide show
  1. package/dist/Preview.vue.d.ts +8 -0
  2. package/dist/View.vue.d.ts +12 -0
  3. package/dist/composables/index.d.ts +2 -0
  4. package/dist/composables/useTodos.d.ts +26 -0
  5. package/dist/composables.js +101 -0
  6. package/dist/composables.js.map +1 -0
  7. package/dist/definition-mymex4HE.js +55 -0
  8. package/dist/definition-mymex4HE.js.map +1 -0
  9. package/dist/definition.d.ts +43 -0
  10. package/dist/handlers/columns.d.ts +31 -0
  11. package/dist/handlers/items.d.ts +36 -0
  12. package/dist/handlers/llm.d.ts +30 -0
  13. package/dist/handlers/priority-notifier.d.ts +55 -0
  14. package/dist/index.d.ts +71 -0
  15. package/dist/index.js +1223 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/internal/utils.d.ts +4 -0
  18. package/dist/io.d.ts +6 -0
  19. package/dist/labels-C4z7FMoE.js +85 -0
  20. package/dist/labels-C4z7FMoE.js.map +1 -0
  21. package/dist/labels.d.ts +15 -0
  22. package/dist/lang/de.d.ts +25 -0
  23. package/dist/lang/en.d.ts +25 -0
  24. package/dist/lang/es.d.ts +25 -0
  25. package/dist/lang/fr.d.ts +25 -0
  26. package/dist/lang/index.d.ts +28 -0
  27. package/dist/lang/ja.d.ts +25 -0
  28. package/dist/lang/ko.d.ts +25 -0
  29. package/dist/lang/pt-BR.d.ts +25 -0
  30. package/dist/lang/zh.d.ts +25 -0
  31. package/dist/lang-D72AIF9U.js +215 -0
  32. package/dist/lang-D72AIF9U.js.map +1 -0
  33. package/dist/priority.d.ts +10 -0
  34. package/dist/shared.d.ts +13 -0
  35. package/dist/shared.js +89 -0
  36. package/dist/shared.js.map +1 -0
  37. package/dist/types.d.ts +22 -0
  38. package/dist/viewModes.d.ts +11 -0
  39. package/dist/vue.d.ts +59 -0
  40. package/dist/vue.js +422 -0
  41. package/dist/vue.js.map +1 -0
  42. package/package.json +48 -0
package/dist/index.js ADDED
@@ -0,0 +1,1223 @@
1
+ import { t as TOOL_DEFINITION } from "./definition-mymex4HE.js";
2
+ import { a as listLabelsWithCount, c as subtractLabels, o as mergeLabels, r as filterByLabels } from "./labels-C4z7FMoE.js";
3
+ //#region ../../../node_modules/gui-chat-protocol/dist/index.js
4
+ function definePlugin(setup) {
5
+ return setup;
6
+ }
7
+ //#endregion
8
+ //#region src/internal/utils.ts
9
+ function makeId(prefix) {
10
+ return `${prefix}_${globalThis.crypto.randomUUID()}`;
11
+ }
12
+ function isRecord(value) {
13
+ return typeof value === "object" && value !== null && !Array.isArray(value);
14
+ }
15
+ var MAX_SLUG_LEN = 60;
16
+ function slugify$1(label, fallback) {
17
+ const normalised = label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, MAX_SLUG_LEN);
18
+ return normalised.length > 0 ? normalised : fallback;
19
+ }
20
+ function disambiguateSlug(base, existingIds) {
21
+ if (!existingIds.has(base)) return base;
22
+ let n = 2;
23
+ while (existingIds.has(`${base}-${n}`)) n += 1;
24
+ return `${base}-${n}`;
25
+ }
26
+ //#endregion
27
+ //#region src/handlers/columns.ts
28
+ var DEFAULT_COLUMNS = [
29
+ {
30
+ id: "backlog",
31
+ label: "Backlog"
32
+ },
33
+ {
34
+ id: "todo",
35
+ label: "Todo"
36
+ },
37
+ {
38
+ id: "in-progress",
39
+ label: "In Progress"
40
+ },
41
+ {
42
+ id: "done",
43
+ label: "Done",
44
+ isDone: true
45
+ }
46
+ ];
47
+ function slugify(label) {
48
+ return slugify$1(label, "column");
49
+ }
50
+ function uniqueId(base, existingIds) {
51
+ return disambiguateSlug(base, existingIds);
52
+ }
53
+ function findColumn(columns, columnId) {
54
+ return columns.find((column) => column.id === columnId);
55
+ }
56
+ function ensureColumnsValid(columns) {
57
+ if (columns.length === 0) return [...DEFAULT_COLUMNS];
58
+ const seen = /* @__PURE__ */ new Set();
59
+ for (const column of columns) {
60
+ if (seen.has(column.id)) return [...DEFAULT_COLUMNS];
61
+ seen.add(column.id);
62
+ }
63
+ const doneCount = columns.filter((column) => column.isDone).length;
64
+ if (doneCount === 0) return columns.map((column, i) => i === columns.length - 1 ? {
65
+ ...column,
66
+ isDone: true
67
+ } : column);
68
+ if (doneCount > 1) {
69
+ let kept = false;
70
+ return columns.map((column) => {
71
+ if (!column.isDone) return column;
72
+ if (kept) return {
73
+ id: column.id,
74
+ label: column.label
75
+ };
76
+ kept = true;
77
+ return column;
78
+ });
79
+ }
80
+ return columns;
81
+ }
82
+ function normalizeColumns(raw) {
83
+ if (!Array.isArray(raw)) return [...DEFAULT_COLUMNS];
84
+ const cleaned = [];
85
+ for (const entry of raw) {
86
+ if (!isRecord(entry)) continue;
87
+ if (typeof entry["id"] !== "string" || typeof entry["label"] !== "string") continue;
88
+ const col = {
89
+ id: entry["id"],
90
+ label: entry["label"]
91
+ };
92
+ if (entry["isDone"] === true) col.isDone = true;
93
+ cleaned.push(col);
94
+ }
95
+ return ensureColumnsValid(cleaned);
96
+ }
97
+ function doneColumnId(columns) {
98
+ const done = columns.find((column) => column.isDone);
99
+ if (done) return done.id;
100
+ const last = columns[columns.length - 1];
101
+ if (!last) throw new Error("doneColumnId: empty columns array (normalizeColumns invariant violated)");
102
+ return last.id;
103
+ }
104
+ function defaultStatusId(columns) {
105
+ const open = columns.find((column) => !column.isDone);
106
+ return open ? open.id : doneColumnId(columns);
107
+ }
108
+ function resyncDoneMembership(items, newDoneId) {
109
+ let changed = false;
110
+ return {
111
+ items: items.map((item) => {
112
+ const shouldBeDone = item.status === newDoneId;
113
+ if (item.completed === shouldBeDone) return item;
114
+ changed = true;
115
+ return {
116
+ ...item,
117
+ completed: shouldBeDone
118
+ };
119
+ }),
120
+ changed
121
+ };
122
+ }
123
+ var ORDER_STEP$1 = 1e3;
124
+ function rebuildColumnOrder(items, columnId) {
125
+ const inColumn = items.filter((item) => item.status === columnId).sort((left, right) => (left.order ?? 0) - (right.order ?? 0));
126
+ const newOrders = /* @__PURE__ */ new Map();
127
+ inColumn.forEach((item, index) => newOrders.set(item.id, (index + 1) * ORDER_STEP$1));
128
+ return items.map((item) => {
129
+ const newOrder = newOrders.get(item.id);
130
+ if (newOrder === void 0) return item;
131
+ return {
132
+ ...item,
133
+ order: newOrder
134
+ };
135
+ });
136
+ }
137
+ function handleAddColumn(columns, items, input) {
138
+ if (!input.label || input.label.trim().length === 0) return {
139
+ kind: "error",
140
+ status: 400,
141
+ error: "label required"
142
+ };
143
+ const columnId = uniqueId(slugify(input.label), new Set(columns.map((column) => column.id)));
144
+ const columnToAdd = {
145
+ id: columnId,
146
+ label: input.label.trim()
147
+ };
148
+ if (input.isDone === true) columnToAdd.isDone = true;
149
+ if (input.isDone === true) {
150
+ const nextColumns = [...columns.map((column) => ({
151
+ ...column,
152
+ isDone: false
153
+ })), columnToAdd];
154
+ const { items: nextItems, changed } = resyncDoneMembership(items, columnId);
155
+ return {
156
+ kind: "success",
157
+ columns: nextColumns,
158
+ ...changed ? { items: nextItems } : {}
159
+ };
160
+ }
161
+ return {
162
+ kind: "success",
163
+ columns: [...columns, columnToAdd]
164
+ };
165
+ }
166
+ function handlePatchColumn(columns, columnId, input, items) {
167
+ const target = findColumn(columns, columnId);
168
+ if (!target) return {
169
+ kind: "error",
170
+ status: 404,
171
+ error: `column not found: ${columnId}`
172
+ };
173
+ const patched = {
174
+ id: target.id,
175
+ label: target.label
176
+ };
177
+ if (target.isDone) patched.isDone = true;
178
+ if (typeof input.label === "string" && input.label.trim().length > 0) patched.label = input.label.trim();
179
+ let nextColumns = columns.map((column) => column.id === columnId ? patched : column);
180
+ let itemsChanged = false;
181
+ let nextItems = items;
182
+ if (input.isDone === true && !target.isDone) {
183
+ nextColumns = nextColumns.map((column) => column.id === columnId ? {
184
+ ...column,
185
+ isDone: true
186
+ } : {
187
+ id: column.id,
188
+ label: column.label
189
+ });
190
+ const synced = resyncDoneMembership(items, columnId);
191
+ nextItems = synced.items;
192
+ itemsChanged = synced.changed;
193
+ } else if (input.isDone === false && target.isDone) return {
194
+ kind: "error",
195
+ status: 400,
196
+ error: "at least one column must be marked as done"
197
+ };
198
+ return {
199
+ kind: "success",
200
+ columns: nextColumns,
201
+ ...itemsChanged ? { items: nextItems } : {}
202
+ };
203
+ }
204
+ function handleDeleteColumn(columns, columnId, items) {
205
+ if (columns.length <= 1) return {
206
+ kind: "error",
207
+ status: 400,
208
+ error: "cannot delete the last remaining column"
209
+ };
210
+ const target = findColumn(columns, columnId);
211
+ if (!target) return {
212
+ kind: "error",
213
+ status: 404,
214
+ error: `column not found: ${columnId}`
215
+ };
216
+ const remaining = columns.filter((column) => column.id !== columnId);
217
+ let nextColumns = remaining;
218
+ if (target.isDone) nextColumns = remaining.map((column, i) => i === remaining.length - 1 ? {
219
+ ...column,
220
+ isDone: true
221
+ } : column);
222
+ const newDoneId = doneColumnId(nextColumns);
223
+ const refugeId = target.isDone ? newDoneId : defaultStatusId(nextColumns);
224
+ let itemsChanged = false;
225
+ let nextItems = items.map((item) => {
226
+ if (item.status !== columnId) return item;
227
+ itemsChanged = true;
228
+ return {
229
+ ...item,
230
+ status: refugeId
231
+ };
232
+ });
233
+ if (itemsChanged) nextItems = rebuildColumnOrder(nextItems, refugeId);
234
+ if (target.isDone) {
235
+ const synced = resyncDoneMembership(nextItems, newDoneId);
236
+ nextItems = synced.items;
237
+ itemsChanged = itemsChanged || synced.changed;
238
+ }
239
+ return {
240
+ kind: "success",
241
+ columns: nextColumns,
242
+ ...itemsChanged ? { items: nextItems } : {}
243
+ };
244
+ }
245
+ function handleReorderColumns(columns, ids) {
246
+ if (!Array.isArray(ids)) return {
247
+ kind: "error",
248
+ status: 400,
249
+ error: "ids array required"
250
+ };
251
+ if (ids.length !== columns.length) return {
252
+ kind: "error",
253
+ status: 400,
254
+ error: "ids must contain every existing column id exactly once"
255
+ };
256
+ const known = new Set(columns.map((column) => column.id));
257
+ const seen = /* @__PURE__ */ new Set();
258
+ for (const columnId of ids) {
259
+ if (!known.has(columnId) || seen.has(columnId)) return {
260
+ kind: "error",
261
+ status: 400,
262
+ error: "ids must contain every existing column id exactly once"
263
+ };
264
+ seen.add(columnId);
265
+ }
266
+ const byId = new Map(columns.map((column) => [column.id, column]));
267
+ return {
268
+ kind: "success",
269
+ columns: ids.map((columnId) => {
270
+ const column = byId.get(columnId);
271
+ if (!column) throw new Error(`reorderColumns: missing column for id "${columnId}" (set-equality check above missed it)`);
272
+ return column;
273
+ })
274
+ };
275
+ }
276
+ //#endregion
277
+ //#region src/handlers/items.ts
278
+ var ORDER_STEP = 1e3;
279
+ var PRIORITIES = [
280
+ "low",
281
+ "medium",
282
+ "high",
283
+ "urgent"
284
+ ];
285
+ function migrateItems(rawItems, columns) {
286
+ const doneId = doneColumnId(columns);
287
+ const openId = defaultStatusId(columns);
288
+ const validStatusIds = new Set(columns.map((column) => column.id));
289
+ const withStatus = rawItems.map((item) => {
290
+ if (typeof item.status === "string" && validStatusIds.has(item.status)) return item;
291
+ const status = item.completed ? doneId : openId;
292
+ return {
293
+ ...item,
294
+ status
295
+ };
296
+ });
297
+ const byStatus = /* @__PURE__ */ new Map();
298
+ for (const item of withStatus) {
299
+ const key = item.status ?? openId;
300
+ let bucket = byStatus.get(key);
301
+ if (!bucket) {
302
+ bucket = [];
303
+ byStatus.set(key, bucket);
304
+ }
305
+ bucket.push(item);
306
+ }
307
+ const orderById = /* @__PURE__ */ new Map();
308
+ for (const [, group] of byStatus) {
309
+ const missing = group.filter((item) => typeof item.order !== "number");
310
+ if (missing.length === 0) continue;
311
+ const existingMax = group.filter((item) => typeof item.order === "number").reduce((acc, item) => Math.max(acc, item.order ?? 0), 0);
312
+ [...missing].sort((left, right) => left.createdAt - right.createdAt).forEach((item, i) => {
313
+ orderById.set(item.id, existingMax + (i + 1) * ORDER_STEP);
314
+ });
315
+ }
316
+ return withStatus.map((item) => {
317
+ const next = orderById.get(item.id);
318
+ if (next === void 0) return item;
319
+ return {
320
+ ...item,
321
+ order: next
322
+ };
323
+ });
324
+ }
325
+ function isPriority(value) {
326
+ return typeof value === "string" && PRIORITIES.includes(value);
327
+ }
328
+ function isDueDate(value) {
329
+ return typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value);
330
+ }
331
+ function nextOrder(items, statusId) {
332
+ const inColumn = items.filter((item) => item.status === statusId).map((item) => item.order ?? 0);
333
+ if (inColumn.length === 0) return ORDER_STEP;
334
+ return Math.max(...inColumn) + ORDER_STEP;
335
+ }
336
+ function resolveStatus(input, columns) {
337
+ if (input.status === void 0 || input.status === "") return {
338
+ kind: "ok",
339
+ status: defaultStatusId(columns)
340
+ };
341
+ if (new Set(columns.map((column) => column.id)).has(input.status)) return {
342
+ kind: "ok",
343
+ status: input.status
344
+ };
345
+ return {
346
+ kind: "error",
347
+ status: 400,
348
+ error: `unknown status: ${input.status}`
349
+ };
350
+ }
351
+ function applyOptionalFields(item, input) {
352
+ if (input.priority !== void 0 && input.priority !== "") {
353
+ if (!isPriority(input.priority)) return {
354
+ kind: "error",
355
+ status: 400,
356
+ error: "invalid priority"
357
+ };
358
+ item.priority = input.priority;
359
+ }
360
+ if (input.dueDate !== void 0 && input.dueDate !== "") {
361
+ if (!isDueDate(input.dueDate)) return {
362
+ kind: "error",
363
+ status: 400,
364
+ error: "dueDate must be YYYY-MM-DD"
365
+ };
366
+ item.dueDate = input.dueDate;
367
+ }
368
+ return null;
369
+ }
370
+ function handleCreate(items, columns, input) {
371
+ if (!input.text || input.text.trim().length === 0) return {
372
+ kind: "error",
373
+ status: 400,
374
+ error: "text required"
375
+ };
376
+ const resolved = resolveStatus(input, columns);
377
+ if (resolved.kind === "error") return resolved;
378
+ const { status } = resolved;
379
+ const item = {
380
+ id: makeId("todo"),
381
+ text: input.text.trim(),
382
+ completed: status === doneColumnId(columns),
383
+ createdAt: Date.now(),
384
+ status,
385
+ order: nextOrder(items, status)
386
+ };
387
+ if (input.note !== void 0 && input.note !== "") item.note = input.note;
388
+ const normalizedLabels = mergeLabels([], input.labels ?? []);
389
+ if (normalizedLabels.length > 0) item.labels = normalizedLabels;
390
+ const fieldError = applyOptionalFields(item, input);
391
+ if (fieldError) return fieldError;
392
+ return {
393
+ kind: "success",
394
+ items: [...items, item],
395
+ item
396
+ };
397
+ }
398
+ function applyTextPatch(updated, input) {
399
+ if (typeof input.text !== "string") return null;
400
+ if (input.text.trim().length === 0) return {
401
+ kind: "error",
402
+ status: 400,
403
+ error: "text cannot be empty"
404
+ };
405
+ updated.text = input.text.trim();
406
+ return null;
407
+ }
408
+ function applyNotePatch(updated, input) {
409
+ if (input.note === null || input.note === "") {
410
+ delete updated.note;
411
+ return null;
412
+ }
413
+ if (typeof input.note === "string") updated.note = input.note;
414
+ return null;
415
+ }
416
+ function applyLabelsPatch(updated, input) {
417
+ if (!Array.isArray(input.labels)) return null;
418
+ const merged = mergeLabels([], input.labels);
419
+ if (merged.length > 0) updated.labels = merged;
420
+ else delete updated.labels;
421
+ return null;
422
+ }
423
+ function applyPriorityPatch(updated, input) {
424
+ if (input.priority === null || input.priority === "") {
425
+ delete updated.priority;
426
+ return null;
427
+ }
428
+ if (input.priority === void 0) return null;
429
+ if (!isPriority(input.priority)) return {
430
+ kind: "error",
431
+ status: 400,
432
+ error: "invalid priority"
433
+ };
434
+ updated.priority = input.priority;
435
+ return null;
436
+ }
437
+ function applyDueDatePatch(updated, input) {
438
+ if (input.dueDate === null || input.dueDate === "") {
439
+ delete updated.dueDate;
440
+ return null;
441
+ }
442
+ if (input.dueDate === void 0) return null;
443
+ if (!isDueDate(input.dueDate)) return {
444
+ kind: "error",
445
+ status: 400,
446
+ error: "dueDate must be YYYY-MM-DD"
447
+ };
448
+ updated.dueDate = input.dueDate;
449
+ return null;
450
+ }
451
+ function applyStatusPatch(updated, target, items, columns, input) {
452
+ if (typeof input.status !== "string" || input.status === target.status) return null;
453
+ if (!new Set(columns.map((column) => column.id)).has(input.status)) return {
454
+ kind: "error",
455
+ status: 400,
456
+ error: `unknown status: ${input.status}`
457
+ };
458
+ updated.status = input.status;
459
+ updated.order = nextOrder(items, input.status);
460
+ updated.completed = input.status === doneColumnId(columns);
461
+ return null;
462
+ }
463
+ function applyCompletedPatch(updated, items, columns, input) {
464
+ if (typeof input.completed !== "boolean") return null;
465
+ if (input.completed === updated.completed) return null;
466
+ updated.completed = input.completed;
467
+ const targetStatus = input.completed ? doneColumnId(columns) : defaultStatusId(columns);
468
+ if (targetStatus !== updated.status) {
469
+ updated.status = targetStatus;
470
+ updated.order = nextOrder(items, targetStatus);
471
+ }
472
+ return null;
473
+ }
474
+ function handlePatch(items, columns, itemId, input) {
475
+ const target = items.find((item) => item.id === itemId);
476
+ if (!target) return {
477
+ kind: "error",
478
+ status: 404,
479
+ error: `item not found: ${itemId}`
480
+ };
481
+ const updated = { ...target };
482
+ const steps = [
483
+ () => applyTextPatch(updated, input),
484
+ () => applyNotePatch(updated, input),
485
+ () => applyLabelsPatch(updated, input),
486
+ () => applyPriorityPatch(updated, input),
487
+ () => applyDueDatePatch(updated, input),
488
+ () => applyStatusPatch(updated, target, items, columns, input),
489
+ () => applyCompletedPatch(updated, items, columns, input)
490
+ ];
491
+ for (const step of steps) {
492
+ const err = step();
493
+ if (err) return err;
494
+ }
495
+ return {
496
+ kind: "success",
497
+ items: items.map((item) => item.id === itemId ? updated : item),
498
+ item: updated
499
+ };
500
+ }
501
+ function handleMove(items, columns, itemId, input) {
502
+ const target = items.find((item) => item.id === itemId);
503
+ if (!target) return {
504
+ kind: "error",
505
+ status: 404,
506
+ error: `item not found: ${itemId}`
507
+ };
508
+ const validStatusIds = new Set(columns.map((column) => column.id));
509
+ const newStatus = input.status ?? target.status ?? defaultStatusId(columns);
510
+ if (!validStatusIds.has(newStatus)) return {
511
+ kind: "error",
512
+ status: 400,
513
+ error: `unknown status: ${newStatus}`
514
+ };
515
+ const isDone = newStatus === doneColumnId(columns);
516
+ const updatedSelf = {
517
+ ...target,
518
+ status: newStatus,
519
+ completed: isDone
520
+ };
521
+ const others = items.filter((item) => item.id !== itemId && item.status === newStatus).sort((left, right) => (left.order ?? 0) - (right.order ?? 0));
522
+ const insertAt = clampPosition(input.position, others.length);
523
+ const reordered = [...others];
524
+ reordered.splice(insertAt, 0, updatedSelf);
525
+ const reorderedById = /* @__PURE__ */ new Map();
526
+ reordered.forEach((item, i) => reorderedById.set(item.id, (i + 1) * ORDER_STEP));
527
+ const nextItems = items.map((item) => {
528
+ const newOrder = reorderedById.get(item.id);
529
+ if (item.id === itemId) return {
530
+ ...updatedSelf,
531
+ order: newOrder ?? updatedSelf.order ?? ORDER_STEP
532
+ };
533
+ if (newOrder !== void 0) return {
534
+ ...item,
535
+ order: newOrder
536
+ };
537
+ return item;
538
+ });
539
+ const finalSelf = nextItems.find((item) => item.id === itemId);
540
+ if (!finalSelf) throw new Error(`reorder result missing item ${itemId}`);
541
+ return {
542
+ kind: "success",
543
+ items: nextItems,
544
+ item: finalSelf
545
+ };
546
+ }
547
+ function clampPosition(raw, max) {
548
+ if (typeof raw !== "number" || !Number.isFinite(raw)) return max;
549
+ if (raw < 0) return 0;
550
+ if (raw > max) return max;
551
+ return Math.floor(raw);
552
+ }
553
+ function handleDeleteItem(items, itemId) {
554
+ if (!items.find((item) => item.id === itemId)) return {
555
+ kind: "error",
556
+ status: 404,
557
+ error: `item not found: ${itemId}`
558
+ };
559
+ return {
560
+ kind: "success",
561
+ items: items.filter((item) => item.id !== itemId)
562
+ };
563
+ }
564
+ //#endregion
565
+ //#region src/io.ts
566
+ var TODOS_FILE = "todos.json";
567
+ var COLUMNS_FILE = "columns.json";
568
+ async function readJson(files, rel) {
569
+ if (!await files.exists(rel)) return void 0;
570
+ try {
571
+ return JSON.parse(await files.read(rel));
572
+ } catch {
573
+ return;
574
+ }
575
+ }
576
+ function isTodoItem(value) {
577
+ if (!value || typeof value !== "object") return false;
578
+ const obj = value;
579
+ return typeof obj.id === "string" && typeof obj.text === "string" && typeof obj.completed === "boolean" && typeof obj.createdAt === "number";
580
+ }
581
+ async function loadColumns(files) {
582
+ return normalizeColumns(await readJson(files, COLUMNS_FILE) ?? DEFAULT_COLUMNS);
583
+ }
584
+ async function saveColumns(files, columns) {
585
+ await files.write(COLUMNS_FILE, JSON.stringify(columns, null, 2));
586
+ }
587
+ async function loadTodos(files) {
588
+ const raw = await readJson(files, TODOS_FILE);
589
+ return migrateItems(Array.isArray(raw) ? raw.filter(isTodoItem) : [], await loadColumns(files));
590
+ }
591
+ async function saveTodos(files, items) {
592
+ await files.write(TODOS_FILE, JSON.stringify(items, null, 2));
593
+ }
594
+ //#endregion
595
+ //#region src/handlers/llm.ts
596
+ function findTodoByText(items, text) {
597
+ const needle = text.toLowerCase();
598
+ return items.find((i) => i.text.toLowerCase().includes(needle));
599
+ }
600
+ function handleShow(items, input) {
601
+ const filterLabels = input.filterLabels ?? [];
602
+ const filtered = filterByLabels(items, filterLabels);
603
+ return {
604
+ kind: "success",
605
+ items: filtered,
606
+ message: filterLabels.length > 0 ? `Showing ${filtered.length} of ${items.length} todo item(s) filtered by: ${filterLabels.join(", ")}` : `Showing ${items.length} todo item(s)`,
607
+ jsonData: { items: filtered.map((i) => ({
608
+ text: i.text,
609
+ completed: i.completed,
610
+ ...i.labels && i.labels.length > 0 && { labels: i.labels }
611
+ })) }
612
+ };
613
+ }
614
+ function handleAdd(items, input) {
615
+ if (!input.text) return {
616
+ kind: "error",
617
+ status: 400,
618
+ error: "text required"
619
+ };
620
+ const normalizedLabels = mergeLabels([], input.labels ?? []);
621
+ const item = {
622
+ id: makeId("todo"),
623
+ text: input.text,
624
+ ...input.note !== void 0 && { note: input.note },
625
+ ...normalizedLabels.length > 0 && { labels: normalizedLabels },
626
+ completed: false,
627
+ createdAt: Date.now()
628
+ };
629
+ return {
630
+ kind: "success",
631
+ items: [...items, item],
632
+ message: normalizedLabels.length > 0 ? `Added: "${input.text}" [${normalizedLabels.join(", ")}]` : `Added: "${input.text}"`,
633
+ jsonData: {
634
+ added: input.text,
635
+ labels: normalizedLabels
636
+ }
637
+ };
638
+ }
639
+ function handleDelete(items, input) {
640
+ if (!input.text) return {
641
+ kind: "error",
642
+ status: 400,
643
+ error: "text required"
644
+ };
645
+ const needle = input.text.toLowerCase();
646
+ const next = items.filter((i) => !i.text.toLowerCase().includes(needle));
647
+ return {
648
+ kind: "success",
649
+ items: next,
650
+ message: next.length < items.length ? `Deleted: "${input.text}"` : `Item not found: "${input.text}"`,
651
+ jsonData: { deleted: input.text }
652
+ };
653
+ }
654
+ function handleUpdate(items, input) {
655
+ if (!input.text || !input.newText) return {
656
+ kind: "error",
657
+ status: 400,
658
+ error: "text and newText required"
659
+ };
660
+ const target = findTodoByText(items, input.text);
661
+ if (!target) return {
662
+ kind: "success",
663
+ items,
664
+ message: `Item not found: "${input.text}"`,
665
+ jsonData: {}
666
+ };
667
+ const oldText = target.text;
668
+ const updated = {
669
+ ...target,
670
+ text: input.newText,
671
+ note: input.note !== void 0 ? input.note || void 0 : target.note
672
+ };
673
+ return {
674
+ kind: "success",
675
+ items: items.map((i) => i.id === target.id ? updated : i),
676
+ message: `Updated: "${oldText}" → "${input.newText}"`,
677
+ jsonData: {
678
+ oldText,
679
+ newText: input.newText
680
+ }
681
+ };
682
+ }
683
+ function setCompleted(items, input, completed, verb, jsonKey) {
684
+ if (!input.text) return {
685
+ kind: "error",
686
+ status: 400,
687
+ error: "text required"
688
+ };
689
+ const target = findTodoByText(items, input.text);
690
+ if (!target) return {
691
+ kind: "success",
692
+ items,
693
+ message: `Item not found: "${input.text}"`,
694
+ jsonData: {}
695
+ };
696
+ const updated = {
697
+ ...target,
698
+ completed
699
+ };
700
+ return {
701
+ kind: "success",
702
+ items: items.map((i) => i.id === target.id ? updated : i),
703
+ message: `${verb}: "${target.text}"`,
704
+ jsonData: { [jsonKey]: target.text }
705
+ };
706
+ }
707
+ function handleCheck(items, input) {
708
+ return setCompleted(items, input, true, "Checked", "checkedItem");
709
+ }
710
+ function handleUncheck(items, input) {
711
+ return setCompleted(items, input, false, "Unchecked", "uncheckedItem");
712
+ }
713
+ function handleClearCompleted(items) {
714
+ const count = items.filter((i) => i.completed).length;
715
+ return {
716
+ kind: "success",
717
+ items: items.filter((i) => !i.completed),
718
+ message: `Cleared ${count} completed item(s)`,
719
+ jsonData: { clearedCount: count }
720
+ };
721
+ }
722
+ function handleAddLabel(items, input) {
723
+ if (!input.text || !input.labels || input.labels.length === 0) return {
724
+ kind: "error",
725
+ status: 400,
726
+ error: "text and a non-empty labels array required"
727
+ };
728
+ const target = findTodoByText(items, input.text);
729
+ if (!target) return {
730
+ kind: "success",
731
+ items,
732
+ message: `Item not found: "${input.text}"`,
733
+ jsonData: { notFound: input.text }
734
+ };
735
+ const merged = mergeLabels(target.labels ?? [], input.labels);
736
+ const updated = {
737
+ ...target,
738
+ labels: merged
739
+ };
740
+ return {
741
+ kind: "success",
742
+ items: items.map((i) => i.id === target.id ? updated : i),
743
+ message: `Labels on "${target.text}": ${merged.join(", ")}`,
744
+ jsonData: {
745
+ item: target.text,
746
+ labels: merged
747
+ }
748
+ };
749
+ }
750
+ function handleRemoveLabel(items, input) {
751
+ if (!input.text || !input.labels || input.labels.length === 0) return {
752
+ kind: "error",
753
+ status: 400,
754
+ error: "text and a non-empty labels array required"
755
+ };
756
+ const target = findTodoByText(items, input.text);
757
+ if (!target) return {
758
+ kind: "success",
759
+ items,
760
+ message: `Item not found: "${input.text}"`,
761
+ jsonData: { notFound: input.text }
762
+ };
763
+ const remaining = subtractLabels(target.labels ?? [], input.labels);
764
+ const updated = { ...target };
765
+ if (remaining.length > 0) updated.labels = remaining;
766
+ else delete updated.labels;
767
+ return {
768
+ kind: "success",
769
+ items: items.map((i) => i.id === target.id ? updated : i),
770
+ message: remaining.length > 0 ? `Labels on "${target.text}": ${remaining.join(", ")}` : `"${target.text}" now has no labels`,
771
+ jsonData: {
772
+ item: target.text,
773
+ labels: remaining
774
+ }
775
+ };
776
+ }
777
+ function handleListLabels(items) {
778
+ const inventory = listLabelsWithCount(items);
779
+ const summary = inventory.map((entry) => `${entry.label} (${entry.count})`).join(", ");
780
+ return {
781
+ kind: "success",
782
+ items,
783
+ message: inventory.length === 0 ? "No labels in use" : `${inventory.length} label(s) in use: ${summary}`,
784
+ jsonData: { labels: inventory }
785
+ };
786
+ }
787
+ var HANDLERS = {
788
+ show: handleShow,
789
+ add: handleAdd,
790
+ delete: handleDelete,
791
+ update: handleUpdate,
792
+ check: handleCheck,
793
+ uncheck: handleUncheck,
794
+ clear_completed: handleClearCompleted,
795
+ add_label: handleAddLabel,
796
+ remove_label: handleRemoveLabel,
797
+ list_labels: handleListLabels
798
+ };
799
+ function dispatchTodos(action, items, input) {
800
+ const handler = HANDLERS[action];
801
+ if (!handler) return {
802
+ kind: "error",
803
+ status: 400,
804
+ error: `Unknown action: ${action}`
805
+ };
806
+ return handler(items, input);
807
+ }
808
+ //#endregion
809
+ //#region src/handlers/priority-notifier.ts
810
+ var PLUGIN_DATA_KIND = "todo-priority";
811
+ var NAVIGATE_TARGET = "/todos";
812
+ var TITLE_MAX = 60;
813
+ var BODY_MAX = 4e3;
814
+ var TICKETS_FILE = "urgent-tickets.json";
815
+ function isTicket(value) {
816
+ if (!value || typeof value !== "object") return false;
817
+ const t = value;
818
+ if (typeof t["todoId"] !== "string") return false;
819
+ if (typeof t["notificationId"] !== "string") return false;
820
+ if (t["priority"] !== "urgent" && t["priority"] !== "high") return false;
821
+ if (t["title"] !== void 0 && typeof t["title"] !== "string") return false;
822
+ if (t["body"] !== void 0 && typeof t["body"] !== "string") return false;
823
+ return true;
824
+ }
825
+ async function loadTickets(files, log) {
826
+ if (!await files.exists(TICKETS_FILE)) return { tickets: {} };
827
+ let raw;
828
+ try {
829
+ raw = JSON.parse(await files.read(TICKETS_FILE));
830
+ } catch (err) {
831
+ log?.warn("priority reconcile: tickets file unparseable; treating as empty", {
832
+ file: TICKETS_FILE,
833
+ error: String(err)
834
+ });
835
+ return { tickets: {} };
836
+ }
837
+ if (!raw || typeof raw !== "object" || !("tickets" in raw) || typeof raw.tickets !== "object" || raw.tickets === null) {
838
+ log?.warn("priority reconcile: tickets file has unexpected shape; treating as empty", { file: TICKETS_FILE });
839
+ return { tickets: {} };
840
+ }
841
+ const rawTickets = raw.tickets;
842
+ const out = {};
843
+ for (const [key, value] of Object.entries(rawTickets)) {
844
+ if (!isTicket(value)) continue;
845
+ if (value.todoId !== key) continue;
846
+ out[key] = value;
847
+ }
848
+ return { tickets: out };
849
+ }
850
+ async function saveTickets(files, file) {
851
+ await files.write(TICKETS_FILE, JSON.stringify(file, null, 2));
852
+ }
853
+ function isNotifiablePriority(priority) {
854
+ return priority === "urgent" || priority === "high";
855
+ }
856
+ function severityFor(priority) {
857
+ return priority === "urgent" ? "urgent" : "nudge";
858
+ }
859
+ function truncate(text, max) {
860
+ return text.length <= max ? text : `${text.slice(0, max - 1)}…`;
861
+ }
862
+ function buildTitle(item) {
863
+ return truncate(item.text, TITLE_MAX);
864
+ }
865
+ function buildBody(item) {
866
+ const note = item.note?.trim();
867
+ let body = "";
868
+ if (note) body = note;
869
+ else if (item.dueDate) body = `Due ${item.dueDate}`;
870
+ return body.length <= BODY_MAX ? body : `${body.slice(0, BODY_MAX - 1)}…`;
871
+ }
872
+ async function safeClear(notifier, notificationId, todoId, log) {
873
+ try {
874
+ await notifier.clear(notificationId);
875
+ return true;
876
+ } catch (err) {
877
+ log?.warn("priority reconcile: clear failed", {
878
+ notificationId,
879
+ todoId,
880
+ error: String(err)
881
+ });
882
+ return false;
883
+ }
884
+ }
885
+ async function safeUpdate(notifier, notificationId, patch, todoId, log) {
886
+ try {
887
+ await notifier.update(notificationId, patch);
888
+ return true;
889
+ } catch (err) {
890
+ log?.warn("priority reconcile: update failed", {
891
+ notificationId,
892
+ todoId,
893
+ error: String(err)
894
+ });
895
+ return false;
896
+ }
897
+ }
898
+ async function safePublish(notifier, item, priority, log) {
899
+ try {
900
+ const { id } = await notifier.publish({
901
+ severity: severityFor(priority),
902
+ lifecycle: "action",
903
+ title: buildTitle(item),
904
+ body: buildBody(item),
905
+ navigateTarget: NAVIGATE_TARGET,
906
+ pluginData: {
907
+ kind: PLUGIN_DATA_KIND,
908
+ todoId: item.id,
909
+ priority
910
+ }
911
+ });
912
+ return id;
913
+ } catch (err) {
914
+ log?.warn("priority reconcile: publish failed", {
915
+ todoId: item.id,
916
+ error: String(err)
917
+ });
918
+ return null;
919
+ }
920
+ }
921
+ /** Reconcile the plugin's tickets and the host's bell entries with
922
+ * the current item list. After this resolves:
923
+ *
924
+ * - every notifiable item has a ticket and a live bell, with the
925
+ * bell's severity / title / body matching the item's current
926
+ * state;
927
+ * - no ticket references a non-notifiable item.
928
+ *
929
+ * Idempotent and tolerant of partial state. Drift is detected per-
930
+ * ticket against the title / body / priority stored at last publish
931
+ * or update; an item rename flows through `notifier.update` rather
932
+ * than clear-then-publish, preserving the notificationId. */
933
+ async function reconcilePriorityNotifications(items, notifier, files, log) {
934
+ const ticketsFile = await loadTickets(files, log);
935
+ const itemsById = new Map(items.map((item) => [item.id, item]));
936
+ let dirty = false;
937
+ for (const [todoId, ticket] of Object.entries(ticketsFile.tickets)) {
938
+ const item = itemsById.get(todoId);
939
+ if (!(item !== void 0 && !item.completed && isNotifiablePriority(item.priority))) {
940
+ if (await safeClear(notifier, ticket.notificationId, todoId, log)) {
941
+ delete ticketsFile.tickets[todoId];
942
+ dirty = true;
943
+ }
944
+ continue;
945
+ }
946
+ if (!(await notifier.get(ticket.notificationId) !== void 0)) {
947
+ log?.warn("priority reconcile: ghost ticket dropped; Phase 2 will re-publish", {
948
+ notificationId: ticket.notificationId,
949
+ todoId
950
+ });
951
+ delete ticketsFile.tickets[todoId];
952
+ dirty = true;
953
+ continue;
954
+ }
955
+ const currentPriority = item.priority;
956
+ const desiredTitle = buildTitle(item);
957
+ const desiredBody = buildBody(item);
958
+ const priorityDrift = currentPriority !== ticket.priority;
959
+ const titleDrift = ticket.title !== desiredTitle;
960
+ const bodyDrift = ticket.body !== desiredBody;
961
+ if (!priorityDrift && !titleDrift && !bodyDrift) continue;
962
+ if (await safeUpdate(notifier, ticket.notificationId, {
963
+ ...priorityDrift ? { severity: severityFor(currentPriority) } : {},
964
+ ...titleDrift ? { title: desiredTitle } : {},
965
+ ...bodyDrift ? { body: desiredBody } : {},
966
+ ...priorityDrift ? { pluginData: {
967
+ kind: PLUGIN_DATA_KIND,
968
+ todoId,
969
+ priority: currentPriority
970
+ } } : {}
971
+ }, todoId, log)) {
972
+ ticketsFile.tickets[todoId] = {
973
+ todoId,
974
+ notificationId: ticket.notificationId,
975
+ priority: currentPriority,
976
+ title: desiredTitle,
977
+ body: desiredBody
978
+ };
979
+ dirty = true;
980
+ }
981
+ }
982
+ for (const item of items) {
983
+ if (item.completed || !isNotifiablePriority(item.priority)) continue;
984
+ if (ticketsFile.tickets[item.id]) continue;
985
+ const newId = await safePublish(notifier, item, item.priority, log);
986
+ if (newId === null) continue;
987
+ ticketsFile.tickets[item.id] = {
988
+ todoId: item.id,
989
+ notificationId: newId,
990
+ priority: item.priority,
991
+ title: buildTitle(item),
992
+ body: buildBody(item)
993
+ };
994
+ dirty = true;
995
+ }
996
+ if (dirty) try {
997
+ await saveTickets(files, ticketsFile);
998
+ } catch (err) {
999
+ log?.warn("priority reconcile: tickets save failed", { error: String(err) });
1000
+ }
1001
+ }
1002
+ //#endregion
1003
+ //#region src/index.ts
1004
+ var READ_ONLY_ACTIONS = new Set(["show", "list_labels"]);
1005
+ function isLlmArgs(value) {
1006
+ return typeof value === "object" && value !== null && "action" in value && typeof value.action === "string";
1007
+ }
1008
+ function isUiArgs(value) {
1009
+ return typeof value === "object" && value !== null && "kind" in value && typeof value.kind === "string";
1010
+ }
1011
+ var src_default = definePlugin((runtime) => {
1012
+ const { pubsub, files, log } = runtime;
1013
+ const { notifier } = runtime;
1014
+ async function reconcileNotifications(items) {
1015
+ await reconcilePriorityNotifications(items, notifier, files.data, log);
1016
+ }
1017
+ const bootReconcile = (async () => {
1018
+ try {
1019
+ await reconcileNotifications(await loadTodos(files.data));
1020
+ } catch (err) {
1021
+ log.warn("boot reconcile failed", { error: String(err) });
1022
+ }
1023
+ })();
1024
+ async function handleLlm(args) {
1025
+ await bootReconcile;
1026
+ const { action, ...input } = args;
1027
+ log.info("dispatch llm", { action });
1028
+ const result = dispatchTodos(action, await loadTodos(files.data), input);
1029
+ if (result.kind === "error") {
1030
+ log.warn("dispatch llm error", {
1031
+ action,
1032
+ error: result.error
1033
+ });
1034
+ return {
1035
+ error: result.error,
1036
+ status: result.status
1037
+ };
1038
+ }
1039
+ if (!READ_ONLY_ACTIONS.has(action)) {
1040
+ await saveTodos(files.data, result.items);
1041
+ await reconcileNotifications(result.items);
1042
+ pubsub.publish("changed", {
1043
+ reason: "llm-action",
1044
+ action
1045
+ });
1046
+ }
1047
+ return {
1048
+ data: { items: result.items },
1049
+ message: result.message,
1050
+ jsonData: result.jsonData,
1051
+ instructions: "Display the updated todo list to the user."
1052
+ };
1053
+ }
1054
+ async function handleUi(args) {
1055
+ await bootReconcile;
1056
+ log.info("dispatch ui", { kind: args.kind });
1057
+ if (args.kind === "listAll") {
1058
+ const [items, columns] = await Promise.all([loadTodos(files.data), loadColumns(files.data)]);
1059
+ return { data: {
1060
+ items,
1061
+ columns
1062
+ } };
1063
+ }
1064
+ const [items, columns] = await Promise.all([loadTodos(files.data), loadColumns(files.data)]);
1065
+ if (args.kind === "itemCreate") {
1066
+ const result = handleCreate(items, columns, args);
1067
+ if (result.kind === "error") return {
1068
+ error: result.error,
1069
+ status: result.status
1070
+ };
1071
+ await saveTodos(files.data, result.items);
1072
+ await reconcileNotifications(result.items);
1073
+ pubsub.publish("changed", { reason: "item-create" });
1074
+ return {
1075
+ data: {
1076
+ items: result.items,
1077
+ columns
1078
+ },
1079
+ ...result.item && { item: result.item }
1080
+ };
1081
+ }
1082
+ if (args.kind === "itemPatch") {
1083
+ const result = handlePatch(items, columns, args.id, args);
1084
+ if (result.kind === "error") return {
1085
+ error: result.error,
1086
+ status: result.status
1087
+ };
1088
+ await saveTodos(files.data, result.items);
1089
+ await reconcileNotifications(result.items);
1090
+ pubsub.publish("changed", {
1091
+ reason: "item-patch",
1092
+ id: args.id
1093
+ });
1094
+ return {
1095
+ data: {
1096
+ items: result.items,
1097
+ columns
1098
+ },
1099
+ ...result.item && { item: result.item }
1100
+ };
1101
+ }
1102
+ if (args.kind === "itemMove") {
1103
+ const result = handleMove(items, columns, args.id, args);
1104
+ if (result.kind === "error") return {
1105
+ error: result.error,
1106
+ status: result.status
1107
+ };
1108
+ await saveTodos(files.data, result.items);
1109
+ await reconcileNotifications(result.items);
1110
+ pubsub.publish("changed", {
1111
+ reason: "item-move",
1112
+ id: args.id
1113
+ });
1114
+ return {
1115
+ data: {
1116
+ items: result.items,
1117
+ columns
1118
+ },
1119
+ ...result.item && { item: result.item }
1120
+ };
1121
+ }
1122
+ if (args.kind === "itemDelete") {
1123
+ const result = handleDeleteItem(items, args.id);
1124
+ if (result.kind === "error") return {
1125
+ error: result.error,
1126
+ status: result.status
1127
+ };
1128
+ await saveTodos(files.data, result.items);
1129
+ await reconcileNotifications(result.items);
1130
+ pubsub.publish("changed", {
1131
+ reason: "item-delete",
1132
+ id: args.id
1133
+ });
1134
+ return { data: {
1135
+ items: result.items,
1136
+ columns
1137
+ } };
1138
+ }
1139
+ if (args.kind === "columnsAdd") {
1140
+ const result = handleAddColumn(columns, items, args);
1141
+ if (result.kind === "error") return {
1142
+ error: result.error,
1143
+ status: result.status
1144
+ };
1145
+ await saveColumns(files.data, result.columns);
1146
+ if (result.items) await saveTodos(files.data, result.items);
1147
+ if (result.items) await reconcileNotifications(result.items);
1148
+ pubsub.publish("changed", { reason: "column-add" });
1149
+ return { data: {
1150
+ items: result.items ?? items,
1151
+ columns: result.columns
1152
+ } };
1153
+ }
1154
+ if (args.kind === "columnPatch") {
1155
+ const result = handlePatchColumn(columns, args.id, args, items);
1156
+ if (result.kind === "error") return {
1157
+ error: result.error,
1158
+ status: result.status
1159
+ };
1160
+ await saveColumns(files.data, result.columns);
1161
+ if (result.items) await saveTodos(files.data, result.items);
1162
+ if (result.items) await reconcileNotifications(result.items);
1163
+ pubsub.publish("changed", {
1164
+ reason: "column-patch",
1165
+ id: args.id
1166
+ });
1167
+ return { data: {
1168
+ items: result.items ?? items,
1169
+ columns: result.columns
1170
+ } };
1171
+ }
1172
+ if (args.kind === "columnDelete") {
1173
+ const result = handleDeleteColumn(columns, args.id, items);
1174
+ if (result.kind === "error") return {
1175
+ error: result.error,
1176
+ status: result.status
1177
+ };
1178
+ await saveColumns(files.data, result.columns);
1179
+ if (result.items) await saveTodos(files.data, result.items);
1180
+ if (result.items) await reconcileNotifications(result.items);
1181
+ pubsub.publish("changed", {
1182
+ reason: "column-delete",
1183
+ id: args.id
1184
+ });
1185
+ return { data: {
1186
+ items: result.items ?? items,
1187
+ columns: result.columns
1188
+ } };
1189
+ }
1190
+ if (args.kind === "columnsOrder") {
1191
+ const result = handleReorderColumns(columns, args.ids);
1192
+ if (result.kind === "error") return {
1193
+ error: result.error,
1194
+ status: result.status
1195
+ };
1196
+ await saveColumns(files.data, result.columns);
1197
+ pubsub.publish("changed", { reason: "columns-order" });
1198
+ return { data: {
1199
+ items,
1200
+ columns: result.columns
1201
+ } };
1202
+ }
1203
+ return {
1204
+ error: `unknown kind: ${JSON.stringify(args)}`,
1205
+ status: 400
1206
+ };
1207
+ }
1208
+ return {
1209
+ TOOL_DEFINITION,
1210
+ async manageTodoList(rawArgs) {
1211
+ if (isLlmArgs(rawArgs)) return handleLlm(rawArgs);
1212
+ if (isUiArgs(rawArgs)) return handleUi(rawArgs);
1213
+ return {
1214
+ error: "unknown args shape — expected { action: ... } or { kind: ... }",
1215
+ status: 400
1216
+ };
1217
+ }
1218
+ };
1219
+ });
1220
+ //#endregion
1221
+ export { TOOL_DEFINITION, src_default as default };
1222
+
1223
+ //# sourceMappingURL=index.js.map