things-api 0.14.0 → 0.16.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.
- package/README.md +4 -4
- package/dist/audit/schema.d.ts +8 -0
- package/dist/audit/schema.js.map +1 -1
- package/dist/cli/commands/area.js +1 -1
- package/dist/cli/commands/install-skill.js +9 -2
- package/dist/cli/commands/install-skill.js.map +1 -1
- package/dist/cli/commands/project.js +1 -1
- package/dist/cli/commands/project.js.map +1 -1
- package/dist/cli/commands/reads.js +73 -4
- package/dist/cli/commands/reads.js.map +1 -1
- package/dist/cli/commands/repeat-flags.d.ts +11 -1
- package/dist/cli/commands/repeat-flags.js +23 -0
- package/dist/cli/commands/repeat-flags.js.map +1 -1
- package/dist/cli/commands/todo.js +50 -3
- package/dist/cli/commands/todo.js.map +1 -1
- package/dist/cli/commands/writes.js +202 -61
- package/dist/cli/commands/writes.js.map +1 -1
- package/dist/cli/glyphs.js +9 -10
- package/dist/cli/glyphs.js.map +1 -1
- package/dist/cli/help.js +18 -13
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/read-driver.d.ts +9 -3
- package/dist/cli/read-driver.js +3 -1
- package/dist/cli/read-driver.js.map +1 -1
- package/dist/cli/render.d.ts +33 -2
- package/dist/cli/render.js +60 -9
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/skill.d.ts +24 -0
- package/dist/cli/skill.js +40 -0
- package/dist/cli/skill.js.map +1 -1
- package/dist/client.d.ts +88 -15
- package/dist/client.js +31 -5
- package/dist/client.js.map +1 -1
- package/dist/contracts.d.ts +13 -2
- package/dist/contracts.js +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +187 -36
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/entities.d.ts +97 -40
- package/dist/model/entities.js +0 -4
- package/dist/model/entities.js.map +1 -1
- package/dist/model/mappers.js +24 -43
- package/dist/model/mappers.js.map +1 -1
- package/dist/read/area-view.js +6 -6
- package/dist/read/area-view.js.map +1 -1
- package/dist/read/detail.js +51 -3
- package/dist/read/detail.js.map +1 -1
- package/dist/read/filter-contract.d.ts +1 -1
- package/dist/read/filter-contract.js +3 -0
- package/dist/read/filter-contract.js.map +1 -1
- package/dist/read/log-boundary.d.ts +46 -2
- package/dist/read/log-boundary.js +43 -2
- package/dist/read/log-boundary.js.map +1 -1
- package/dist/read/predicates.d.ts +13 -0
- package/dist/read/predicates.js +13 -0
- package/dist/read/predicates.js.map +1 -1
- package/dist/read/project-view.js +5 -5
- package/dist/read/project-view.js.map +1 -1
- package/dist/read/queries.d.ts +12 -0
- package/dist/read/queries.js +14 -0
- package/dist/read/queries.js.map +1 -1
- package/dist/read/search-rank.js +2 -2
- package/dist/read/search-rank.js.map +1 -1
- package/dist/read/shape.d.ts +30 -26
- package/dist/read/shape.js +130 -101
- package/dist/read/shape.js.map +1 -1
- package/dist/read/snapshot.js +2 -2
- package/dist/read/snapshot.js.map +1 -1
- package/dist/read/stage.d.ts +36 -18
- package/dist/read/stage.js +30 -13
- package/dist/read/stage.js.map +1 -1
- package/dist/read/views.d.ts +62 -0
- package/dist/read/views.js +134 -14
- package/dist/read/views.js.map +1 -1
- package/dist/surface-copy.d.ts +2 -2
- package/dist/surface-copy.js +2 -2
- package/dist/write/batch.d.ts +28 -3
- package/dist/write/batch.js +162 -46
- package/dist/write/batch.js.map +1 -1
- package/dist/write/clear-reminder.js +3 -1
- package/dist/write/clear-reminder.js.map +1 -1
- package/dist/write/clone.d.ts +4 -0
- package/dist/write/clone.js +531 -0
- package/dist/write/clone.js.map +1 -0
- package/dist/write/commands.js +196 -29
- package/dist/write/commands.js.map +1 -1
- package/dist/write/guards.d.ts +1 -1
- package/dist/write/guards.js +44 -12
- package/dist/write/guards.js.map +1 -1
- package/dist/write/make-repeating-project.d.ts +2 -3
- package/dist/write/make-repeating-project.js +2 -73
- package/dist/write/make-repeating-project.js.map +1 -1
- package/dist/write/move.js +53 -1
- package/dist/write/move.js.map +1 -1
- package/dist/write/operations.d.ts +134 -11
- package/dist/write/operations.js +5 -1
- package/dist/write/operations.js.map +1 -1
- package/dist/write/pipeline.d.ts +38 -0
- package/dist/write/pipeline.js +71 -6
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/pre-state.d.ts +41 -0
- package/dist/write/pre-state.js +71 -8
- package/dist/write/pre-state.js.map +1 -1
- package/dist/write/preserve-modified.d.ts +55 -0
- package/dist/write/preserve-modified.js +106 -0
- package/dist/write/preserve-modified.js.map +1 -0
- package/dist/write/promote-clone.d.ts +32 -0
- package/dist/write/promote-clone.js +620 -0
- package/dist/write/promote-clone.js.map +1 -0
- package/dist/write/reorder.js +49 -2
- package/dist/write/reorder.js.map +1 -1
- package/dist/write/resolution-timestamps.js +29 -7
- package/dist/write/resolution-timestamps.js.map +1 -1
- package/dist/write/reversibility.js +32 -14
- package/dist/write/reversibility.js.map +1 -1
- package/dist/write/scope-guard.js +2 -0
- package/dist/write/scope-guard.js.map +1 -1
- package/dist/write/undo.d.ts +12 -2
- package/dist/write/undo.js +145 -22
- package/dist/write/undo.js.map +1 -1
- package/dist/write/vectors/applescript.js +9 -0
- package/dist/write/vectors/applescript.js.map +1 -1
- package/dist/write/vectors/simulator.js +242 -10
- package/dist/write/vectors/simulator.js.map +1 -1
- package/dist/write/vectors/ui-certification.d.ts +1 -1
- package/dist/write/vectors/ui-certification.js +10 -3
- package/dist/write/vectors/ui-certification.js.map +1 -1
- package/dist/write/verify/delta.d.ts +55 -10
- package/dist/write/verify/delta.js +73 -10
- package/dist/write/verify/delta.js.map +1 -1
- package/package.json +1 -1
- package/schema/envelope.schema.json +25 -0
- package/skills/things-cli/SKILL.md +10 -7
- package/skills/things-cli/references/banner.md +1 -1
- package/skills/things-cli/references/contracts.md +17 -9
- package/skills/things-cli/references/{model.md → data-model.md} +5 -2
- package/skills/things-cli/references/ordering.md +2 -0
package/dist/mcp/server.js
CHANGED
|
@@ -327,6 +327,17 @@ const MCP_UNTAGGED_CONFLICT = "untagged does not combine with tag/exact_tag";
|
|
|
327
327
|
const dryRunShape = {
|
|
328
328
|
dry_run: z.boolean().optional().describe("Preview the planned change without applying anything"),
|
|
329
329
|
};
|
|
330
|
+
const preserveModifiedShape = {
|
|
331
|
+
preserve_modified: z
|
|
332
|
+
.boolean()
|
|
333
|
+
.optional()
|
|
334
|
+
.describe("Keep this change off the modification-date timeline: capture each pre-existing edited " +
|
|
335
|
+
"item's modification date and restore it (to the whole second) after the change, so a " +
|
|
336
|
+
"changes/watch query keyed on it does not surface the edit. A no-op on a pure create. " +
|
|
337
|
+
"Safe with Things Cloud: the restored date syncs to your other devices and stays put, so " +
|
|
338
|
+
"the item stays off the timeline everywhere — unless another device edits the same item " +
|
|
339
|
+
"at nearly the same time, which re-dates it."),
|
|
340
|
+
};
|
|
330
341
|
/**
|
|
331
342
|
* The per-call idempotency key for a single write tool — the analogue of a batch
|
|
332
343
|
* line's op_id. A resubmission carrying the same key is recognized as already
|
|
@@ -507,6 +518,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
507
518
|
...(args.allow_non_empty_area === true && { allowNonEmptyArea: true }),
|
|
508
519
|
...(args.dangerously_drive_gui === true && { dangerouslyDriveGui: true }),
|
|
509
520
|
...(args.create_tags === true && { createTags: true }),
|
|
521
|
+
...(args.preserve_modified === true && { preserveModified: true }),
|
|
510
522
|
...(args.tz !== undefined && { zone: args.tz }),
|
|
511
523
|
...(args.op_id !== undefined && { opId: assertOpId(args.op_id) }),
|
|
512
524
|
});
|
|
@@ -600,7 +612,10 @@ export function createThingsMcpServer(options = {}) {
|
|
|
600
612
|
description: "Read a Things list as the app presents it: today (two children buckets — " +
|
|
601
613
|
"children.today and children.evening, evening expires daily; the whole-view " +
|
|
602
614
|
"count due/overdue vs. other rides the result's second block), inbox, anytime, " +
|
|
603
|
-
"upcoming, someday, logbook, or
|
|
615
|
+
"upcoming, someday, logbook, trash, or deadlines (a flat items list of everything " +
|
|
616
|
+
"carrying a deadline — to-dos and projects — in deadline order, most-overdue first; " +
|
|
617
|
+
"repeating items appear at their next occurrence's projected deadline; scope with " +
|
|
618
|
+
"today/overdue/project/area/tag). For upcoming, " +
|
|
604
619
|
"horizon > 1 also includes future occurrences of repeating items (up to 10 each). " +
|
|
605
620
|
"anytime/someday return sections in canonical order (area + items; null area = the " +
|
|
606
621
|
"top-level block); children of someday/future-scheduled projects are excluded " +
|
|
@@ -615,13 +630,30 @@ export function createThingsMcpServer(options = {}) {
|
|
|
615
630
|
" " +
|
|
616
631
|
REF_RULE_NOTE,
|
|
617
632
|
inputSchema: {
|
|
618
|
-
view: z.enum([
|
|
633
|
+
view: z.enum([
|
|
634
|
+
"today",
|
|
635
|
+
"inbox",
|
|
636
|
+
"anytime",
|
|
637
|
+
"upcoming",
|
|
638
|
+
"someday",
|
|
639
|
+
"logbook",
|
|
640
|
+
"trash",
|
|
641
|
+
"deadlines",
|
|
642
|
+
]),
|
|
619
643
|
...tagFilterShape,
|
|
620
644
|
...tzShape,
|
|
621
645
|
area: z
|
|
622
646
|
.string()
|
|
623
647
|
.optional()
|
|
624
|
-
.describe(`today/anytime/someday/upcoming/logbook only: ${AREA_FILTER_DESC}`),
|
|
648
|
+
.describe(`today/anytime/someday/upcoming/logbook/deadlines only: ${AREA_FILTER_DESC}`),
|
|
649
|
+
today: z
|
|
650
|
+
.boolean()
|
|
651
|
+
.optional()
|
|
652
|
+
.describe("deadlines only: keep only current Today members (This Evening included)"),
|
|
653
|
+
project: z
|
|
654
|
+
.string()
|
|
655
|
+
.optional()
|
|
656
|
+
.describe("deadlines only: restrict to one project's children (uuid or unique name)"),
|
|
625
657
|
evening: z.boolean().optional().describe("today only: show only the This Evening section"),
|
|
626
658
|
show_active_project_items: z
|
|
627
659
|
.union([z.boolean(), z.number().int().min(1)])
|
|
@@ -679,7 +711,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
679
711
|
// (the current-work views), matching FILTER_CONTRACT.
|
|
680
712
|
const validated = validateViewArgs(args.view, { ...tagPresence(args), overdue: args.overdue }, {
|
|
681
713
|
untaggedConflict: MCP_UNTAGGED_CONFLICT,
|
|
682
|
-
overdueRejected: `overdue applies to today/inbox/anytime/someday, not ${args.view}`,
|
|
714
|
+
overdueRejected: `overdue applies to today/inbox/anytime/someday/deadlines, not ${args.view}`,
|
|
683
715
|
overdueStatusWiden: "",
|
|
684
716
|
});
|
|
685
717
|
if (!validated.ok)
|
|
@@ -701,6 +733,14 @@ export function createThingsMcpServer(options = {}) {
|
|
|
701
733
|
if (args.view !== "today" && args.evening === true) {
|
|
702
734
|
return usage(`evening applies only to today, not ${args.view}`);
|
|
703
735
|
}
|
|
736
|
+
// The two deadlines-only scopes. `area`/`tag`/`overdue`/`limit` are
|
|
737
|
+
// shared with the other flat views; `today`/`project` are not.
|
|
738
|
+
if (args.view !== "deadlines" && args.today === true) {
|
|
739
|
+
return usage(`today applies only to deadlines, not ${args.view}`);
|
|
740
|
+
}
|
|
741
|
+
if (args.view !== "deadlines" && args.project !== undefined) {
|
|
742
|
+
return usage(`project applies only to deadlines, not ${args.view}`);
|
|
743
|
+
}
|
|
704
744
|
// The `area` filter applies to the area-carrying views only; inbox
|
|
705
745
|
// (area-less captures) and trash have no area to scope by.
|
|
706
746
|
if ((args.view === "inbox" || args.view === "trash") && args.area !== undefined) {
|
|
@@ -786,19 +826,34 @@ export function createThingsMcpServer(options = {}) {
|
|
|
786
826
|
return groupedResult(withSectionTotals(shapeReadPayload("someday", view, full, c.refPromoter()), sectionTotals), truncation);
|
|
787
827
|
}
|
|
788
828
|
case "logbook": {
|
|
789
|
-
const { items, truncation, filter: fm, } = c.read.logbook({
|
|
829
|
+
const { items, truncation, logging, filter: fm, } = c.read.logbook({
|
|
790
830
|
...filter,
|
|
791
831
|
...zone,
|
|
792
832
|
...area,
|
|
793
833
|
limit,
|
|
794
834
|
});
|
|
795
835
|
filterMeta = fm;
|
|
796
|
-
|
|
836
|
+
// The log-move cadence fact rides the metadata block (the CLI
|
|
837
|
+
// meta.logging analog, exactly as today's counts do).
|
|
838
|
+
return truncatedResult(shapeReadPayload("logbook", items, full, c.refPromoter()), truncation, { logging });
|
|
797
839
|
}
|
|
798
840
|
case "trash": {
|
|
799
841
|
const { items, truncation } = c.read.trash({ ...zone, limit });
|
|
800
842
|
return truncatedResult(shapeReadPayload("trash", items, full, c.refPromoter()), truncation);
|
|
801
843
|
}
|
|
844
|
+
case "deadlines": {
|
|
845
|
+
// `project`/`area` scope inside the view (SQL), not the shared
|
|
846
|
+
// area post-filter — so no `meta.filter` here (parity with search).
|
|
847
|
+
const { items, truncation } = c.read.deadlines({
|
|
848
|
+
...filter,
|
|
849
|
+
...zone,
|
|
850
|
+
...(args.today === true && { todayOnly: true }),
|
|
851
|
+
...(args.project !== undefined && { project: args.project }),
|
|
852
|
+
...(args.area !== undefined && { area: args.area }),
|
|
853
|
+
limit,
|
|
854
|
+
});
|
|
855
|
+
return truncatedResult(shapeReadPayload("deadlines", items, full, c.refPromoter()), truncation);
|
|
856
|
+
}
|
|
802
857
|
}
|
|
803
858
|
}, args.tz, () => (filterMeta !== undefined ? { filter: filterMeta } : undefined), () => looseAreaWarnings(getClient(), args.area));
|
|
804
859
|
});
|
|
@@ -1094,6 +1149,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1094
1149
|
...createTagsShape,
|
|
1095
1150
|
...tzShape,
|
|
1096
1151
|
...dryRunShape,
|
|
1152
|
+
...preserveModifiedShape,
|
|
1097
1153
|
...opIdShape,
|
|
1098
1154
|
},
|
|
1099
1155
|
annotations: NON_DESTRUCTIVE,
|
|
@@ -1171,6 +1227,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1171
1227
|
...createTagsShape,
|
|
1172
1228
|
...tzShape,
|
|
1173
1229
|
...dryRunShape,
|
|
1230
|
+
...preserveModifiedShape,
|
|
1174
1231
|
...opIdShape,
|
|
1175
1232
|
},
|
|
1176
1233
|
annotations: NON_DESTRUCTIVE,
|
|
@@ -1286,6 +1343,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1286
1343
|
.describe(`status completed or canceled: the completion timestamp (${RESOLUTION_DATE_FORMAT})`),
|
|
1287
1344
|
...tzShape,
|
|
1288
1345
|
...dryRunShape,
|
|
1346
|
+
...preserveModifiedShape,
|
|
1289
1347
|
...opIdShape,
|
|
1290
1348
|
},
|
|
1291
1349
|
annotations: NON_DESTRUCTIVE,
|
|
@@ -1380,6 +1438,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1380
1438
|
.optional()
|
|
1381
1439
|
.describe("Confirm moving into a completed/canceled project (this reopens it)"),
|
|
1382
1440
|
...dryRunShape,
|
|
1441
|
+
...preserveModifiedShape,
|
|
1383
1442
|
},
|
|
1384
1443
|
annotations: NON_DESTRUCTIVE,
|
|
1385
1444
|
}, async (args) => guard(async () => {
|
|
@@ -1425,6 +1484,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1425
1484
|
mode: z.enum(["replace", "add"]).optional().describe("Default: replace"),
|
|
1426
1485
|
...createTagsShape,
|
|
1427
1486
|
...dryRunShape,
|
|
1487
|
+
...preserveModifiedShape,
|
|
1428
1488
|
...opIdShape,
|
|
1429
1489
|
},
|
|
1430
1490
|
annotations: NON_DESTRUCTIVE,
|
|
@@ -1483,6 +1543,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1483
1543
|
.optional()
|
|
1484
1544
|
.describe("replace: confirm discarding the existing items and their checked states"),
|
|
1485
1545
|
...dryRunShape,
|
|
1546
|
+
...preserveModifiedShape,
|
|
1486
1547
|
},
|
|
1487
1548
|
annotations: NON_DESTRUCTIVE,
|
|
1488
1549
|
}, async (args) => guard(async () => {
|
|
@@ -1558,6 +1619,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1558
1619
|
.optional()
|
|
1559
1620
|
.describe("kind tag: confirm permanent deletion of ALL nested child tags too"),
|
|
1560
1621
|
...dryRunShape,
|
|
1622
|
+
...preserveModifiedShape,
|
|
1561
1623
|
...opIdShape,
|
|
1562
1624
|
},
|
|
1563
1625
|
annotations: DESTRUCTIVE,
|
|
@@ -1579,7 +1641,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1579
1641
|
description: "Restore a trashed to-do or project. A to-do returns to the Inbox without its " +
|
|
1580
1642
|
"previous schedule or project/area. A project is restored in place: its schedule, " +
|
|
1581
1643
|
"area, and children come back exactly as they were.",
|
|
1582
|
-
inputSchema: { uuid: z.string(), ...dryRunShape, ...opIdShape },
|
|
1644
|
+
inputSchema: { uuid: z.string(), ...dryRunShape, ...preserveModifiedShape, ...opIdShape },
|
|
1583
1645
|
annotations: NON_DESTRUCTIVE,
|
|
1584
1646
|
}, async (args) => guard(async () => {
|
|
1585
1647
|
const c = getClient();
|
|
@@ -1666,6 +1728,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1666
1728
|
.describe("add/move: place immediately after this heading (title or uuid)"),
|
|
1667
1729
|
...driveGuiShape,
|
|
1668
1730
|
...dryRunShape,
|
|
1731
|
+
...preserveModifiedShape,
|
|
1669
1732
|
},
|
|
1670
1733
|
annotations: DESTRUCTIVE,
|
|
1671
1734
|
}, async (args) => guard(async () => {
|
|
@@ -1766,6 +1829,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1766
1829
|
uuid: z.string().describe("the to-do's uuid"),
|
|
1767
1830
|
...driveGuiShape,
|
|
1768
1831
|
...dryRunShape,
|
|
1832
|
+
...preserveModifiedShape,
|
|
1769
1833
|
...opIdShape,
|
|
1770
1834
|
},
|
|
1771
1835
|
annotations: DESTRUCTIVE,
|
|
@@ -1775,7 +1839,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1775
1839
|
"Things proxy shortcuts when installed (in place, and the only path for a repeating " +
|
|
1776
1840
|
"to-do); otherwise a non-repeating dated to-do falls back to a URL re-schedule that " +
|
|
1777
1841
|
"briefly moves it to Today and back. Reversible with the undo tool.",
|
|
1778
|
-
inputSchema: { uuid: z.string(), ...dryRunShape },
|
|
1842
|
+
inputSchema: { uuid: z.string(), ...dryRunShape, ...preserveModifiedShape },
|
|
1779
1843
|
annotations: NON_DESTRUCTIVE,
|
|
1780
1844
|
}, async (args) => guard(async () => mutationResult(await getClient().write.clearReminder(args.uuid, writeOptions(args)))));
|
|
1781
1845
|
// -------------------------------------------- GUI-driven (Accessibility)
|
|
@@ -1859,16 +1923,17 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1859
1923
|
server.registerTool("repeat", {
|
|
1860
1924
|
description: "Manage recurrence on a to-do or project (scope) by driving the local Things app's " +
|
|
1861
1925
|
"interface — every action needs dangerously_drive_gui. action start: turn a plain item " +
|
|
1862
|
-
"into a repeating one
|
|
1863
|
-
"
|
|
1864
|
-
"
|
|
1865
|
-
"
|
|
1866
|
-
"
|
|
1867
|
-
"
|
|
1868
|
-
"
|
|
1869
|
-
"
|
|
1870
|
-
"
|
|
1871
|
-
"
|
|
1926
|
+
"into a repeating one (promote-via-clone: a disposable copy is promoted and the ORIGINAL " +
|
|
1927
|
+
"is moved to the Trash, so undo reverses it — it removes the new series and restores the " +
|
|
1928
|
+
"original); give frequency + interval and optionally the weekday set, monthly/yearly day, " +
|
|
1929
|
+
"end bound, or per-occurrence deadline; returns a repeating block with instanceUuid (the " +
|
|
1930
|
+
"visible occurrence), templateUuid (the rule), and replacedUuid. A project holding a " +
|
|
1931
|
+
"nested repeating template is refused. action reschedule: change a repeating item's rule " +
|
|
1932
|
+
"in place, keeping the same item (undoable — it restores the previous rule). action " +
|
|
1933
|
+
"pause/resume: stop or restart its new occurrences, keeping the rule. action add: create " +
|
|
1934
|
+
"an item and make it repeating in one call — it is created first and PERSISTS even if the " +
|
|
1935
|
+
"promote refuses (give a title; for a project give an area to place it or omit it to " +
|
|
1936
|
+
"create in Someday); undo removes the created series. Returns the new template's uuid.",
|
|
1872
1937
|
inputSchema: {
|
|
1873
1938
|
scope: z.enum(["todo", "project"]),
|
|
1874
1939
|
action: z.enum(["start", "reschedule", "pause", "resume", "add"]),
|
|
@@ -1876,13 +1941,13 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1876
1941
|
.string()
|
|
1877
1942
|
.optional()
|
|
1878
1943
|
.describe("start/reschedule/pause/resume: the item (a project also accepts a unique name)"),
|
|
1879
|
-
title: z.string().optional().describe("add
|
|
1880
|
-
notes: z.string().optional().describe("add
|
|
1944
|
+
title: z.string().optional().describe("add: the new item's title"),
|
|
1945
|
+
notes: z.string().optional().describe("add: notes"),
|
|
1881
1946
|
area: z.string().optional().describe(`add (project): destination area (${REF_FORMAT})`),
|
|
1882
1947
|
project_deadline: z
|
|
1883
1948
|
.string()
|
|
1884
1949
|
.optional()
|
|
1885
|
-
.describe(`add
|
|
1950
|
+
.describe(`add: the item's due date — ${DATE_FORMAT}`),
|
|
1886
1951
|
todos: z.array(z.string()).optional().describe("add (project): initial child to-do titles"),
|
|
1887
1952
|
frequency: z
|
|
1888
1953
|
.enum(["daily", "weekly", "monthly", "yearly"])
|
|
@@ -1908,6 +1973,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1908
1973
|
start_days_earlier: repeatRuleShape.start_days_earlier,
|
|
1909
1974
|
...driveGuiShape,
|
|
1910
1975
|
...dryRunShape,
|
|
1976
|
+
...preserveModifiedShape,
|
|
1911
1977
|
},
|
|
1912
1978
|
annotations: DESTRUCTIVE,
|
|
1913
1979
|
}, async (args) => guard(async () => {
|
|
@@ -1915,13 +1981,25 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1915
1981
|
const opts = writeOptions(args);
|
|
1916
1982
|
const { frequency, interval } = args;
|
|
1917
1983
|
if (args.action === "add") {
|
|
1918
|
-
if (args.scope !== "project")
|
|
1919
|
-
return usage('action "add" requires scope "project"');
|
|
1920
1984
|
if (args.title === undefined)
|
|
1921
1985
|
return usage('action "add" requires title');
|
|
1922
1986
|
if (frequency === undefined || interval === undefined) {
|
|
1923
1987
|
return usage('action "add" requires frequency and interval');
|
|
1924
1988
|
}
|
|
1989
|
+
// add-repeating carries only the calendar-anchor rule fields; the base
|
|
1990
|
+
// add owns the item's own deadline, and the rule reminder/start-offset
|
|
1991
|
+
// are set with a follow-up reschedule.
|
|
1992
|
+
const { reminder: _reminder, deadline: _deadline, startDaysEarlier: _sde, ...ruleExtras } = repeatExtras(args, frequency);
|
|
1993
|
+
if (args.scope === "todo") {
|
|
1994
|
+
return mutationResult(await c.write.addRepeatingTodo({
|
|
1995
|
+
title: args.title,
|
|
1996
|
+
...(args.notes !== undefined && { notes: args.notes }),
|
|
1997
|
+
...(args.project_deadline !== undefined && { deadline: args.project_deadline }),
|
|
1998
|
+
frequency,
|
|
1999
|
+
interval,
|
|
2000
|
+
...ruleExtras,
|
|
2001
|
+
}, opts));
|
|
2002
|
+
}
|
|
1925
2003
|
return mutationResult(await c.write.addRepeatingProject({
|
|
1926
2004
|
title: args.title,
|
|
1927
2005
|
...(args.notes !== undefined && { notes: args.notes }),
|
|
@@ -1930,6 +2008,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1930
2008
|
...(args.todos !== undefined && { todos: args.todos }),
|
|
1931
2009
|
frequency,
|
|
1932
2010
|
interval,
|
|
2011
|
+
...ruleExtras,
|
|
1933
2012
|
}, opts));
|
|
1934
2013
|
}
|
|
1935
2014
|
if (args.uuid === undefined)
|
|
@@ -1950,8 +2029,10 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1950
2029
|
}
|
|
1951
2030
|
const extras = repeatExtras(args, frequency);
|
|
1952
2031
|
if (args.scope === "todo") {
|
|
1953
|
-
|
|
1954
|
-
|
|
2032
|
+
if (args.action === "start") {
|
|
2033
|
+
return mutationResult(await c.write.makeRepeatingTodo(args.uuid, { frequency, interval, ...extras }, opts));
|
|
2034
|
+
}
|
|
2035
|
+
return mutationResult(await c.write.run("todo.reschedule-repeat", { uuid: args.uuid, frequency, interval, ...extras }, opts));
|
|
1955
2036
|
}
|
|
1956
2037
|
// scope project
|
|
1957
2038
|
if (args.action === "reschedule") {
|
|
@@ -1963,7 +2044,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1963
2044
|
server.registerTool("duplicate_item", {
|
|
1964
2045
|
description: "Duplicate a to-do or project and return the copy's uuid; a duplicated project " +
|
|
1965
2046
|
"includes its children. Not available for repeating items.",
|
|
1966
|
-
inputSchema: { uuid: z.string(), ...dryRunShape, ...opIdShape },
|
|
2047
|
+
inputSchema: { uuid: z.string(), ...dryRunShape, ...preserveModifiedShape, ...opIdShape },
|
|
1967
2048
|
annotations: NON_DESTRUCTIVE,
|
|
1968
2049
|
}, async (args) => guard(async () => {
|
|
1969
2050
|
const c = getClient();
|
|
@@ -1971,6 +2052,42 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1971
2052
|
? await c.write.duplicateTodo(args.uuid, writeOptions(args))
|
|
1972
2053
|
: await c.write.duplicateProject(args.uuid, writeOptions(args)));
|
|
1973
2054
|
}));
|
|
2055
|
+
server.registerTool("clone_item", {
|
|
2056
|
+
description: "Clone a to-do or project — a faithful content copy through official write surfaces and " +
|
|
2057
|
+
"return the clone's uuid. Copies title, notes, tags, when, reminder, deadline, checklist " +
|
|
2058
|
+
"items and their checked state, container, and completed/canceled state with the exact " +
|
|
2059
|
+
"original timestamp; a project also copies its area, headings, and children. The clone is " +
|
|
2060
|
+
"a new capture (a new uuid, created now, at its container's usual position). Refuses a " +
|
|
2061
|
+
"trashed source and a project holding a nested repeating template. A repeating template is " +
|
|
2062
|
+
"cloned as a NEW repeating series (its content plus the source's rule, minted fresh — no " +
|
|
2063
|
+
"instances copied); this drives the app (requires dangerously_drive_gui) and refuses a " +
|
|
2064
|
+
"rule the Repeat dialog cannot express. Undo trashes the clone (a template clone trashes " +
|
|
2065
|
+
"the new series). Not the same as duplicate_item (Things' own native copy).",
|
|
2066
|
+
inputSchema: {
|
|
2067
|
+
uuid: z.string(),
|
|
2068
|
+
title: z
|
|
2069
|
+
.string()
|
|
2070
|
+
.optional()
|
|
2071
|
+
.describe("Give the clone a different title (default: the source's title)"),
|
|
2072
|
+
preserve_created: z
|
|
2073
|
+
.boolean()
|
|
2074
|
+
.optional()
|
|
2075
|
+
.describe("Copy the source's creation date onto the clone (minute resolution)"),
|
|
2076
|
+
...driveGuiShape,
|
|
2077
|
+
...dryRunShape,
|
|
2078
|
+
...opIdShape,
|
|
2079
|
+
},
|
|
2080
|
+
annotations: NON_DESTRUCTIVE,
|
|
2081
|
+
}, async (args) => guard(async () => {
|
|
2082
|
+
const c = getClient();
|
|
2083
|
+
const clone = {
|
|
2084
|
+
...(args.title !== undefined && { title: args.title }),
|
|
2085
|
+
...(args.preserve_created === true && { preserveCreated: true }),
|
|
2086
|
+
};
|
|
2087
|
+
return mutationResult(itemType(args.uuid) === "to-do"
|
|
2088
|
+
? await c.write.cloneTodo(args.uuid, clone, writeOptions(args))
|
|
2089
|
+
: await c.write.cloneProject(args.uuid, clone, writeOptions(args)));
|
|
2090
|
+
}));
|
|
1974
2091
|
// -------------------------------------------------------------- projects
|
|
1975
2092
|
server.registerTool("add_project", {
|
|
1976
2093
|
description: "Create a project and return its uuid. Optionally place it in an area, schedule " +
|
|
@@ -1993,6 +2110,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1993
2110
|
`(${RESOLUTION_DATE_FORMAT}); cannot seed open child to-dos`),
|
|
1994
2111
|
...tzShape,
|
|
1995
2112
|
...dryRunShape,
|
|
2113
|
+
...preserveModifiedShape,
|
|
1996
2114
|
...opIdShape,
|
|
1997
2115
|
},
|
|
1998
2116
|
annotations: NON_DESTRUCTIVE,
|
|
@@ -2024,6 +2142,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2024
2142
|
no_area: z.boolean().optional().describe("Leave the current area (a project's detach)"),
|
|
2025
2143
|
...positionShape,
|
|
2026
2144
|
...dryRunShape,
|
|
2145
|
+
...preserveModifiedShape,
|
|
2027
2146
|
},
|
|
2028
2147
|
annotations: NON_DESTRUCTIVE,
|
|
2029
2148
|
}, async (args) => guard(async () => {
|
|
@@ -2052,6 +2171,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2052
2171
|
tags: z.array(z.string()).optional().describe(`Tags — ${TAG_REF_FORMAT}`),
|
|
2053
2172
|
...createTagsShape,
|
|
2054
2173
|
...dryRunShape,
|
|
2174
|
+
...preserveModifiedShape,
|
|
2055
2175
|
...opIdShape,
|
|
2056
2176
|
},
|
|
2057
2177
|
annotations: NON_DESTRUCTIVE,
|
|
@@ -2063,10 +2183,18 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2063
2183
|
title: z.string(),
|
|
2064
2184
|
parent: z.string().optional().describe("Existing parent tag name"),
|
|
2065
2185
|
...dryRunShape,
|
|
2186
|
+
...preserveModifiedShape,
|
|
2066
2187
|
...opIdShape,
|
|
2067
2188
|
},
|
|
2068
2189
|
annotations: NON_DESTRUCTIVE,
|
|
2069
2190
|
}, async (args) => guard(async () => mutationResult(await getClient().write.addTag({ title: args.title, ...(args.parent !== undefined && { parent: args.parent }) }, writeOptions(args)))));
|
|
2191
|
+
server.registerTool("log_now", {
|
|
2192
|
+
description: "Move completed items to the Logbook now. The result discloses how many items were moved " +
|
|
2193
|
+
"(observed.logged); when none are waiting it logs nothing — a clean no-op, not an error. " +
|
|
2194
|
+
"This cannot be undone.",
|
|
2195
|
+
inputSchema: { ...dryRunShape, ...preserveModifiedShape, ...opIdShape },
|
|
2196
|
+
annotations: DESTRUCTIVE,
|
|
2197
|
+
}, async (args) => guard(async () => mutationResult(await getClient().write.logNow(writeOptions(args)))));
|
|
2070
2198
|
// -------------------------------------------------- generic + discovery
|
|
2071
2199
|
server.registerTool("run_operation", {
|
|
2072
2200
|
description: "Run any cataloged operation by kind — the generic entry for operations without a " +
|
|
@@ -2092,22 +2220,28 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2092
2220
|
.optional()
|
|
2093
2221
|
.describe("area.delete: delete a non-empty area together with its contents"),
|
|
2094
2222
|
...dryRunShape,
|
|
2223
|
+
...preserveModifiedShape,
|
|
2095
2224
|
...opIdShape,
|
|
2096
2225
|
},
|
|
2097
2226
|
annotations: DESTRUCTIVE,
|
|
2098
2227
|
}, async (args) => guard(async () => mutationResult(await getClient().write.run(args.op, args.params, writeOptions(args)))));
|
|
2099
2228
|
server.registerTool("batch", {
|
|
2100
2229
|
description: "Run several operations in order, each independently — there are no transactions, " +
|
|
2101
|
-
"and a failure does not roll back earlier operations.
|
|
2102
|
-
"
|
|
2230
|
+
"and a failure does not roll back earlier operations. A statically-invalid operation " +
|
|
2231
|
+
"(bad shape, unknown op, a $ref to an undeclared/forward temp_id, a duplicate temp_id) " +
|
|
2232
|
+
"refuses the WHOLE batch before anything runs, naming every bad operation. Otherwise " +
|
|
2233
|
+
"per-operation results return in order. By DEFAULT a runtime failure STOPS the batch " +
|
|
2234
|
+
"(later operations reported not-run, with resume guidance in the summary); " +
|
|
2235
|
+
"continue_on_error runs past failures. " +
|
|
2103
2236
|
"CHAINING: an operation that creates something may carry temp_id (a handle); a LATER " +
|
|
2104
2237
|
'operation references that new uuid as "$handle" in any id/container field (dotted ' +
|
|
2105
2238
|
'"$handle.instance"/"$handle.replaced" reach a repeating op\'s spawned instance / ' +
|
|
2106
2239
|
"replaced source). A temp_id is valid only on a creating operation (not tag.add — " +
|
|
2107
2240
|
"reference a tag by title) and unique per batch. IDEMPOTENCY: op_id makes resubmission " +
|
|
2108
2241
|
"safe — an operation matching an earlier success is reported already-applied, not " +
|
|
2109
|
-
"re-created
|
|
2110
|
-
"
|
|
2242
|
+
"re-created (put an op_id on EVERY operation so a stopped batch can be resubmitted " +
|
|
2243
|
+
"verbatim to resume). The result adds temp_id_mapping (handle → uuid) and undo_token, " +
|
|
2244
|
+
"which reverses the whole batch as one unit via the undo tool.",
|
|
2111
2245
|
inputSchema: {
|
|
2112
2246
|
ops: z
|
|
2113
2247
|
.array(z.object({
|
|
@@ -2133,8 +2267,12 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2133
2267
|
.optional(),
|
|
2134
2268
|
}))
|
|
2135
2269
|
.describe("Ops in execution order"),
|
|
2136
|
-
|
|
2270
|
+
continue_on_error: z
|
|
2271
|
+
.boolean()
|
|
2272
|
+
.optional()
|
|
2273
|
+
.describe("Run past a failed op instead of stopping (default: stop on failure)"),
|
|
2137
2274
|
...dryRunShape,
|
|
2275
|
+
...preserveModifiedShape,
|
|
2138
2276
|
},
|
|
2139
2277
|
annotations: DESTRUCTIVE,
|
|
2140
2278
|
}, async (args) => guard(async () => {
|
|
@@ -2163,7 +2301,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2163
2301
|
});
|
|
2164
2302
|
const batchResult = await getClient().write.batch(ops, {
|
|
2165
2303
|
...(args.dry_run === true && { dryRun: true }),
|
|
2166
|
-
...(args.
|
|
2304
|
+
...(args.continue_on_error === true && { continueOnError: true }),
|
|
2167
2305
|
actor: mcpActor(),
|
|
2168
2306
|
});
|
|
2169
2307
|
// First block: the per-op results, each FLATTENED to the wire shape (a
|
|
@@ -2172,7 +2310,9 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2172
2310
|
// chaining/undo summary, present only when the batch minted a token or
|
|
2173
2311
|
// bound temp ids.
|
|
2174
2312
|
const lines = batchResult.results.map(flattenBatchLine);
|
|
2175
|
-
const hasSummary = batchResult.undoToken !== undefined ||
|
|
2313
|
+
const hasSummary = batchResult.undoToken !== undefined ||
|
|
2314
|
+
batchResult.resumption !== undefined ||
|
|
2315
|
+
Object.keys(batchResult.tempIdMapping).length > 0;
|
|
2176
2316
|
if (!hasSummary)
|
|
2177
2317
|
return jsonResult(lines);
|
|
2178
2318
|
return {
|
|
@@ -2183,6 +2323,10 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2183
2323
|
text: JSON.stringify({
|
|
2184
2324
|
tempIdMapping: batchResult.tempIdMapping,
|
|
2185
2325
|
...(batchResult.undoToken !== undefined && { undoToken: batchResult.undoToken }),
|
|
2326
|
+
// Resume guidance when a runtime failure stopped the batch (Change 2).
|
|
2327
|
+
...(batchResult.resumption !== undefined && {
|
|
2328
|
+
resumption: batchResult.resumption,
|
|
2329
|
+
}),
|
|
2186
2330
|
}),
|
|
2187
2331
|
},
|
|
2188
2332
|
],
|
|
@@ -2224,6 +2368,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2224
2368
|
"day-block intermix both kinds."),
|
|
2225
2369
|
...driveGuiShape,
|
|
2226
2370
|
...dryRunShape,
|
|
2371
|
+
...preserveModifiedShape,
|
|
2227
2372
|
},
|
|
2228
2373
|
annotations: NON_DESTRUCTIVE,
|
|
2229
2374
|
}, async (args) => guard(async () => {
|
|
@@ -2242,9 +2387,14 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2242
2387
|
"cannot be undone here). By default this undoes only changes made through THIS " +
|
|
2243
2388
|
"connection — this client's own writes; it will not touch the user's own edits, or " +
|
|
2244
2389
|
'another client\'s, unless you pass by="*" (all authors) or a specific author name; ' +
|
|
2245
|
-
"pass a txn token to undo one exact change.
|
|
2246
|
-
"
|
|
2247
|
-
"
|
|
2390
|
+
"pass a txn token to undo one exact change. last/by select the N newest NOT-YET-undone " +
|
|
2391
|
+
"changes: a change already undone is SKIPPED, so repeating undo with last:N walks " +
|
|
2392
|
+
"progressively deeper into history instead of re-selecting the same changes. An " +
|
|
2393
|
+
"irreversible change is NOT skipped — it still counts toward N and is reported every " +
|
|
2394
|
+
"pass, so you see it rather than silently reaching past it. Some changes cannot be " +
|
|
2395
|
+
"reversed — permanent deletions, or changes whose prior state is unknown — and are " +
|
|
2396
|
+
"reported as irreversible; a to-do brought back from an undone delete returns to the " +
|
|
2397
|
+
"Inbox " +
|
|
2248
2398
|
"without its schedule. Undoing the creation of an area or tag deletes it " +
|
|
2249
2399
|
"permanently — requires dangerously_permanent. An undo is refused when the item " +
|
|
2250
2400
|
"changed outside this interface since (its list or project, status, schedule, " +
|
|
@@ -2272,6 +2422,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2272
2422
|
"or by another tool) — overwrites whatever the out-of-band change left, instead of " +
|
|
2273
2423
|
"refusing."),
|
|
2274
2424
|
...dryRunShape,
|
|
2425
|
+
...preserveModifiedShape,
|
|
2275
2426
|
},
|
|
2276
2427
|
annotations: DESTRUCTIVE,
|
|
2277
2428
|
}, async (args) => guard(async () => {
|