things-api 0.15.0 → 0.17.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/install-skill.js +9 -2
- package/dist/cli/commands/install-skill.js.map +1 -1
- package/dist/cli/commands/repeat-flags.d.ts +14 -4
- package/dist/cli/commands/repeat-flags.js +113 -33
- package/dist/cli/commands/repeat-flags.js.map +1 -1
- package/dist/cli/commands/todo.js +49 -2
- package/dist/cli/commands/todo.js.map +1 -1
- package/dist/cli/commands/writes.js +241 -66
- package/dist/cli/commands/writes.js.map +1 -1
- package/dist/cli/help.js +23 -13
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/interrupt.d.ts +42 -0
- package/dist/cli/interrupt.js +105 -0
- package/dist/cli/interrupt.js.map +1 -0
- package/dist/cli/main.js +5 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/read-driver.d.ts +2 -2
- 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 +53 -12
- package/dist/client.js +9 -1
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +39 -0
- package/dist/config.js +16 -1
- package/dist/config.js.map +1 -1
- package/dist/contracts.d.ts +9 -2
- package/dist/contracts.js +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +132 -31
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/entities.d.ts +35 -0
- package/dist/model/entities.js.map +1 -1
- package/dist/model/occurrences.d.ts +14 -1
- package/dist/model/occurrences.js +20 -4
- package/dist/model/occurrences.js.map +1 -1
- package/dist/model/recurrence.d.ts +60 -9
- package/dist/model/recurrence.js +76 -19
- package/dist/model/recurrence.js.map +1 -1
- package/dist/paths.d.ts +8 -0
- package/dist/paths.js +10 -0
- package/dist/paths.js.map +1 -1
- package/dist/read/detail.js +49 -1
- package/dist/read/detail.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/shape.d.ts +25 -22
- package/dist/read/shape.js +81 -55
- package/dist/read/shape.js.map +1 -1
- package/dist/read/stage.d.ts +5 -5
- package/dist/read/stage.js +2 -2
- package/dist/read/views.d.ts +1 -1
- package/dist/read/views.js +4 -4
- package/dist/surface-copy.d.ts +2 -2
- package/dist/surface-copy.js +2 -2
- package/dist/sync-health.js +2 -1
- package/dist/sync-health.js.map +1 -1
- package/dist/trace/tracer.d.ts +87 -0
- package/dist/trace/tracer.js +167 -0
- package/dist/trace/tracer.js.map +1 -0
- 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 +24 -21
- 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 +140 -49
- package/dist/write/commands.js.map +1 -1
- package/dist/write/completion-context.d.ts +57 -0
- package/dist/write/completion-context.js +49 -0
- package/dist/write/completion-context.js.map +1 -0
- package/dist/write/failure-hints.d.ts +10 -1
- package/dist/write/failure-hints.js +8 -0
- package/dist/write/failure-hints.js.map +1 -1
- package/dist/write/guards.d.ts +1 -1
- package/dist/write/guards.js +43 -5
- 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 +8 -1
- package/dist/write/move.js.map +1 -1
- package/dist/write/operations.d.ts +140 -9
- package/dist/write/operations.js +4 -1
- package/dist/write/operations.js.map +1 -1
- package/dist/write/pipeline.d.ts +77 -0
- package/dist/write/pipeline.js +298 -20
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/pre-state.d.ts +1 -1
- package/dist/write/pre-state.js +9 -3
- 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 +867 -0
- package/dist/write/promote-clone.js.map +1 -0
- package/dist/write/repeat-anchor.d.ts +110 -0
- package/dist/write/repeat-anchor.js +238 -0
- package/dist/write/repeat-anchor.js.map +1 -0
- package/dist/write/repeat-asserts.d.ts +28 -0
- package/dist/write/repeat-asserts.js +169 -0
- package/dist/write/repeat-asserts.js.map +1 -0
- package/dist/write/repeat-rule.js +27 -12
- package/dist/write/repeat-rule.js.map +1 -1
- package/dist/write/resolution-timestamps.js +22 -0
- package/dist/write/resolution-timestamps.js.map +1 -1
- package/dist/write/reversibility.js +27 -14
- package/dist/write/reversibility.js.map +1 -1
- package/dist/write/undo.d.ts +11 -1
- package/dist/write/undo.js +126 -10
- package/dist/write/undo.js.map +1 -1
- package/dist/write/vectors/session-reachability.d.ts +78 -0
- package/dist/write/vectors/session-reachability.js +103 -0
- package/dist/write/vectors/session-reachability.js.map +1 -0
- package/dist/write/vectors/simulator.js +100 -30
- package/dist/write/vectors/simulator.js.map +1 -1
- package/dist/write/vectors/types.d.ts +117 -10
- 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/vectors/ui-recipes.d.ts +8 -0
- package/dist/write/vectors/ui-recipes.js +120 -22
- package/dist/write/vectors/ui-recipes.js.map +1 -1
- package/dist/write/vectors/ui.d.ts +79 -13
- package/dist/write/vectors/ui.js +415 -59
- package/dist/write/vectors/ui.js.map +1 -1
- package/dist/write/verify/delta.d.ts +12 -14
- package/dist/write/verify/delta.js +35 -34
- package/dist/write/verify/delta.js.map +1 -1
- package/package.json +1 -1
- package/schema/envelope.schema.json +22 -0
- package/skills/things-cli/SKILL.md +7 -3
- package/skills/things-cli/references/banner.md +1 -1
- package/skills/things-cli/references/contracts.md +17 -9
- package/skills/things-cli/references/data-model.md +2 -1
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { armInterrupt, disarmInterrupt } from "../interrupt.js";
|
|
3
|
+
import { isDevVersion } from "../skill.js";
|
|
4
|
+
import { CLI_VERSION } from "../version.js";
|
|
5
|
+
import { addRepeatCalendarFlags, addRepeatingRuleFieldsFromOpts, addRepeatRuleFlags, repeatRuleFlagsFromOpts, } from "./repeat-flags.js";
|
|
6
|
+
import { aggregateExitCode, blockedCode, capabilitiesTable, ClockError, closeCliTrace, describeConfig, errorEnvelope, ExitCode, installCliTrace, getConfigKey, mutationWireData, okEnvelope, openThings, OP_ID_RE, outcomeFailed, ReferenceResolutionError, saveConfigKey, splitWhenSugar, trace, ThingsDbNotFoundError, ThingsDbOpenError, verifyFailedCode, } from "../../index.js";
|
|
4
7
|
import { usageError } from "../read-driver.js";
|
|
5
8
|
import { dim } from "../style.js";
|
|
6
9
|
function addWriteFlags(cmd) {
|
|
@@ -8,6 +11,12 @@ function addWriteFlags(cmd) {
|
|
|
8
11
|
.option("--json", "emit versioned JSON envelope on stdout")
|
|
9
12
|
.option("--db <path>", "explicit database path")
|
|
10
13
|
.option("--dry-run", "preview the planned change and its expected effect; nothing executes")
|
|
14
|
+
.option("--preserve-modified", "keep this change off the modification-date timeline: capture each pre-existing edited " +
|
|
15
|
+
"item's modification date and restore it (to the whole second) after the change, so a " +
|
|
16
|
+
"changes/watch query keyed on it does not surface the edit. A no-op on a pure create. " +
|
|
17
|
+
"Safe with Things Cloud: the restored date syncs to your other devices and stays put, so " +
|
|
18
|
+
"the item stays off the timeline everywhere — unless another device edits the same item " +
|
|
19
|
+
"at nearly the same time, which re-dates it.")
|
|
11
20
|
.option("--vector <id>", "force how the change is delivered: url-scheme | applescript | shortcuts | ui")
|
|
12
21
|
.option("--allow-disruptive", "permit changes that briefly steal window focus")
|
|
13
22
|
.option("--allow-very-disruptive", "permit changes that visibly drive the Things UI")
|
|
@@ -88,6 +97,7 @@ function writeOptionsFrom(opts, extra = {}) {
|
|
|
88
97
|
...(opts.actor !== undefined && { actor: opts.actor }),
|
|
89
98
|
...(opts.dangerouslyDriveGui === true && { dangerouslyDriveGui: true }),
|
|
90
99
|
...(opts.opId !== undefined && { opId: opts.opId }),
|
|
100
|
+
...(opts.preserveModified === true && { preserveModified: true }),
|
|
91
101
|
...extra,
|
|
92
102
|
};
|
|
93
103
|
}
|
|
@@ -138,7 +148,12 @@ function headingPlacement(c, projectUuid, opts) {
|
|
|
138
148
|
/** Add the mandatory GUI-drive acknowledgement to a ui-vector command. */
|
|
139
149
|
function addDriveGuiFlag(cmd) {
|
|
140
150
|
return cmd.option("--dangerously-drive-gui", "required: visibly drives the Things app to make a change it offers nowhere else; " +
|
|
141
|
-
"also needs `things config set ui-enabled true
|
|
151
|
+
"also needs `things config set ui-enabled true`. Driving the app can take over a " +
|
|
152
|
+
"minute on a large database, so allow a generous timeout (180s or more) — if your own " +
|
|
153
|
+
"timeout stops the command first you get empty output, not a result. A timeout or empty " +
|
|
154
|
+
"output is not a success: the change may still have landed, so re-check with " +
|
|
155
|
+
"`things show <ref>` before retrying, and never fire an identical retry (it can duplicate " +
|
|
156
|
+
"the change) — stop and report if the state is wrong or unclear");
|
|
142
157
|
}
|
|
143
158
|
function collect(value, previous) {
|
|
144
159
|
return [...previous, value];
|
|
@@ -179,6 +194,16 @@ async function runWrite(opts, fn, emitFn = emitResult) {
|
|
|
179
194
|
if (!opIdOk(opts))
|
|
180
195
|
return;
|
|
181
196
|
const started = Date.now();
|
|
197
|
+
// Dev-mode step-timeline trace + signal-safe interrupt guard (TRACE1, #487).
|
|
198
|
+
// Scoped to the write driver — the invocations where a per-step timeline earns
|
|
199
|
+
// its keep, and where a mid-drive kill would otherwise leave empty stdout. The
|
|
200
|
+
// sink is a no-op unless tracing is on (a `-dev` build, or config/env forced).
|
|
201
|
+
installCliTrace({
|
|
202
|
+
argv: process.argv.slice(1),
|
|
203
|
+
version: CLI_VERSION,
|
|
204
|
+
isDev: isDevVersion(CLI_VERSION),
|
|
205
|
+
});
|
|
206
|
+
armInterrupt(opts.json === true);
|
|
182
207
|
let client = null;
|
|
183
208
|
const meta = (client_) => {
|
|
184
209
|
let dbVersion = null;
|
|
@@ -232,7 +257,54 @@ async function runWrite(opts, fn, emitFn = emitResult) {
|
|
|
232
257
|
}
|
|
233
258
|
finally {
|
|
234
259
|
client?.close();
|
|
260
|
+
// Close the trace with a final result line (the exit code the caller sees),
|
|
261
|
+
// then disarm the interrupt guard so a later signal during teardown/reads
|
|
262
|
+
// emits nothing.
|
|
263
|
+
trace(() => ({
|
|
264
|
+
phase: "invocation-end",
|
|
265
|
+
exitCode: process.exitCode ?? 0,
|
|
266
|
+
elapsedMs: Date.now() - started,
|
|
267
|
+
}));
|
|
268
|
+
disarmInterrupt();
|
|
269
|
+
closeCliTrace();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* TTY disclosure for `--preserve-modified` on a verified ok result: a dim line
|
|
274
|
+
* naming how many pre-existing rows' modification date was restored (kept off
|
|
275
|
+
* the `changes`/watch timeline), plus a stderr warning per row the best-effort
|
|
276
|
+
* restore could not neutralize. Silent when the flag did no work (the field is
|
|
277
|
+
* absent — a pure create, or an already-silent op).
|
|
278
|
+
*/
|
|
279
|
+
function emitPreserveNote(result) {
|
|
280
|
+
if (result.preservedModified === undefined)
|
|
281
|
+
return;
|
|
282
|
+
const failed = result.preserveFailures ?? [];
|
|
283
|
+
const tail = failed.length > 0 ? `; ${failed.length} could not be restored (see warnings)` : "";
|
|
284
|
+
process.stdout.write(dim(` preserved modification date on ${result.preservedModified} item(s) — kept off the changes timeline${tail}\n`));
|
|
285
|
+
for (const f of failed) {
|
|
286
|
+
process.stderr.write(`warning: preserve-modified: ${f.uuid}: ${f.detail}\n`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* TTY disclosure for the HINTS1 completion-context on a verified complete/cancel:
|
|
291
|
+
* a dim line naming the OPEN work remaining in the to-do's project and/or Today,
|
|
292
|
+
* so an operator (or an agent reading the human render) sees an emptied container
|
|
293
|
+
* at a glance. Silent when neither arm applies (the field is absent).
|
|
294
|
+
*/
|
|
295
|
+
function emitContextNote(result) {
|
|
296
|
+
const context = result.context;
|
|
297
|
+
if (context === undefined)
|
|
298
|
+
return;
|
|
299
|
+
const parts = [];
|
|
300
|
+
if (context.project !== undefined) {
|
|
301
|
+
parts.push(`project "${context.project.title}": ${context.project.remainingOpen} open remaining`);
|
|
302
|
+
}
|
|
303
|
+
if (context.today !== undefined) {
|
|
304
|
+
parts.push(`today: ${context.today.remainingOpen} open remaining`);
|
|
235
305
|
}
|
|
306
|
+
if (parts.length > 0)
|
|
307
|
+
process.stdout.write(dim(` ${parts.join("; ")}\n`));
|
|
236
308
|
}
|
|
237
309
|
function emitResult(result, opts, meta) {
|
|
238
310
|
switch (result.kind) {
|
|
@@ -265,6 +337,8 @@ function emitResult(result, opts, meta) {
|
|
|
265
337
|
? "already applied — matched op-id in the change history, not re-run"
|
|
266
338
|
: `vector=${result.vector}, tier=${result.tier}, verified`;
|
|
267
339
|
process.stdout.write(`ok ${result.op}${uuid} (${status})\n`);
|
|
340
|
+
emitPreserveNote(result);
|
|
341
|
+
emitContextNote(result);
|
|
268
342
|
}
|
|
269
343
|
process.exitCode = ExitCode.Ok;
|
|
270
344
|
return;
|
|
@@ -680,6 +754,11 @@ function group(program, name, description) {
|
|
|
680
754
|
return program.command(name).description(description);
|
|
681
755
|
}
|
|
682
756
|
const containerRef = (value) => value === undefined ? undefined : { uuid: value, title: value };
|
|
757
|
+
/** The clone-specific args (`--title` / `--preserve-created`) for todo/project clone. */
|
|
758
|
+
const cloneArgs = (opts) => ({
|
|
759
|
+
...(opts["title"] !== undefined && { title: opts["title"] }),
|
|
760
|
+
...(opts["preserveCreated"] === true && { preserveCreated: true }),
|
|
761
|
+
});
|
|
683
762
|
export function registerWriteCommands(program) {
|
|
684
763
|
const todo = group(program, "todo", "To-do–scoped operations");
|
|
685
764
|
addCreateTagsFlag(addWriteFlags(todo
|
|
@@ -921,6 +1000,21 @@ export function registerWriteCommands(program) {
|
|
|
921
1000
|
"available for repeating to-dos.")).action(async (uuid, opts) => {
|
|
922
1001
|
await runWrite(opts, (c) => c.write.duplicateTodo(uuid, writeOptionsFrom(opts)));
|
|
923
1002
|
});
|
|
1003
|
+
addDriveGuiFlag(addWriteFlags(todo
|
|
1004
|
+
.command("clone <uuid>")
|
|
1005
|
+
.description("Clone a to-do — a faithful content copy (title, notes, tags, when, reminder, deadline, " +
|
|
1006
|
+
"checklist items and their checked state, container, and completed/canceled state with " +
|
|
1007
|
+
"the exact original timestamp). The clone is a new capture: a new uuid, created now, at " +
|
|
1008
|
+
"its container's usual position; the clone's uuid is printed on success. Refuses a " +
|
|
1009
|
+
"trashed source (restore it first). A repeating template is cloned as a NEW repeating " +
|
|
1010
|
+
"series (its content plus the source's rule, minted fresh — no instances copied); this " +
|
|
1011
|
+
"drives the app (requires --dangerously-drive-gui) and refuses a rule the Repeat dialog " +
|
|
1012
|
+
"cannot express. Undo trashes the clone (a template clone trashes the new series).")
|
|
1013
|
+
.option("--title <text>", "give the clone a different title (default: the source's title)")
|
|
1014
|
+
.option("--preserve-created", "copy the source's creation date onto the clone (minute resolution) instead of now"))).action(async (uuid, opts) => {
|
|
1015
|
+
const clone = cloneArgs(opts);
|
|
1016
|
+
await runWrite(opts, (c) => c.write.cloneTodo(uuid, clone, writeOptionsFrom(opts)));
|
|
1017
|
+
});
|
|
924
1018
|
addCreateTagsFlag(addWriteFlags(todo
|
|
925
1019
|
.command("tags <uuid>")
|
|
926
1020
|
.description("Set or extend a to-do's tags. --set REPLACES the full tag set (an empty value " +
|
|
@@ -1042,36 +1136,39 @@ export function registerWriteCommands(program) {
|
|
|
1042
1136
|
// --- ui vector: GUI-driven transforms (two-key gated) --------------------
|
|
1043
1137
|
const REPEAT_FREQ_HELP = "daily | weekly | monthly | yearly";
|
|
1044
1138
|
const REPEAT_INTERVAL_HELP = "every N units (1–99)";
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1139
|
+
// make-repeating is promote-via-clone: a disposable copy is promoted and the
|
|
1140
|
+
// ORIGINAL moves to the Trash, so `things undo` reverses it (removes the new
|
|
1141
|
+
// series and restores the original).
|
|
1142
|
+
addDriveGuiFlag(addRepeatRuleFlags(addWriteFlags(todo
|
|
1143
|
+
.command("make-repeating <uuid>")
|
|
1144
|
+
.description("Turn a plain to-do into a repeating one. A disposable copy is promoted and the " +
|
|
1145
|
+
"ORIGINAL is moved to the Trash — so `things undo` reverses it (it removes the new " +
|
|
1146
|
+
"series and restores the original). The new template's uuid is printed. Set the rule " +
|
|
1147
|
+
"with the flags below; see `things help repeating`.")
|
|
1148
|
+
.requiredOption("--frequency <freq>", REPEAT_FREQ_HELP)
|
|
1149
|
+
.requiredOption("--interval <n>", REPEAT_INTERVAL_HELP)))).action(async (uuid, opts) => {
|
|
1150
|
+
const frequency = opts["frequency"];
|
|
1151
|
+
await runWrite(opts, (c) => c.write.makeRepeatingTodo(uuid, {
|
|
1152
|
+
frequency,
|
|
1153
|
+
interval: Number(opts["interval"]),
|
|
1154
|
+
...repeatRuleFlagsFromOpts(opts, frequency),
|
|
1155
|
+
}, writeOptionsFrom(opts)));
|
|
1156
|
+
});
|
|
1157
|
+
addDriveGuiFlag(addRepeatRuleFlags(addWriteFlags(todo
|
|
1158
|
+
.command("reschedule-repeat <uuid>")
|
|
1159
|
+
.description("Change an existing repeating to-do's rule in place (the item keeps its identity). Set " +
|
|
1160
|
+
"the new rule with the flags below; see `things help repeating`. `things undo` " +
|
|
1161
|
+
"restores the previous rule.")
|
|
1162
|
+
.requiredOption("--frequency <freq>", REPEAT_FREQ_HELP)
|
|
1163
|
+
.requiredOption("--interval <n>", REPEAT_INTERVAL_HELP)))).action(async (uuid, opts) => {
|
|
1164
|
+
const frequency = opts["frequency"];
|
|
1165
|
+
await runWrite(opts, (c) => c.write.run("todo.reschedule-repeat", {
|
|
1166
|
+
uuid,
|
|
1167
|
+
frequency,
|
|
1168
|
+
interval: Number(opts["interval"]),
|
|
1169
|
+
...repeatRuleFlagsFromOpts(opts, frequency),
|
|
1170
|
+
}, writeOptionsFrom(opts)));
|
|
1171
|
+
});
|
|
1075
1172
|
for (const [verb, op, desc] of [
|
|
1076
1173
|
[
|
|
1077
1174
|
"pause-repeat",
|
|
@@ -1205,7 +1302,7 @@ export function registerWriteCommands(program) {
|
|
|
1205
1302
|
return c.write.moveHeading({ uuid: proj.uuid }, headings, placement, writeOptionsFrom(opts));
|
|
1206
1303
|
});
|
|
1207
1304
|
});
|
|
1208
|
-
addWriteFlags(project
|
|
1305
|
+
addDriveGuiFlag(addWriteFlags(project
|
|
1209
1306
|
.command("move-heading-to-project <project> <heading>")
|
|
1210
1307
|
.requiredOption("--to <project>", "destination project (uuid or unique title)")
|
|
1211
1308
|
.description("Move a heading — WITH its to-dos — to a DIFFERENT project. This is the cross-project " +
|
|
@@ -1215,14 +1312,14 @@ export function registerWriteCommands(program) {
|
|
|
1215
1312
|
"once with `things config set ui.enabled true` and pass --dangerously-drive-gui. Fails " +
|
|
1216
1313
|
"closed when the heading title is shared by another heading in the project, or the " +
|
|
1217
1314
|
"destination title by another project (the drive addresses both by title). No undo — " +
|
|
1218
|
-
"move it back to reverse.")).action(async (projectRef, sel, opts) => {
|
|
1315
|
+
"move it back to reverse."))).action(async (projectRef, sel, opts) => {
|
|
1219
1316
|
await runWrite(opts, (c) => {
|
|
1220
1317
|
const proj = c.resolve.project(projectRef);
|
|
1221
1318
|
const dest = c.resolve.project(String(opts["to"]));
|
|
1222
1319
|
return c.write.moveHeadingToProject({ uuid: proj.uuid }, sel, { uuid: dest.uuid }, writeOptionsFrom(opts));
|
|
1223
1320
|
});
|
|
1224
1321
|
});
|
|
1225
|
-
addWriteFlags(project
|
|
1322
|
+
addDriveGuiFlag(addWriteFlags(project
|
|
1226
1323
|
.command("dissolve-heading <project> <heading>")
|
|
1227
1324
|
.description("Dissolve a heading — remove it but KEEP its to-dos, which become direct children of the " +
|
|
1228
1325
|
"project (in the same order), NOT trashed. This is the opposite of deleting: nothing is " +
|
|
@@ -1230,7 +1327,7 @@ export function registerWriteCommands(program) {
|
|
|
1230
1327
|
"Shortcuts heading-delete cascade, which TRASH the children. GUI-ONLY (drives the " +
|
|
1231
1328
|
"heading row's ellipsis Delete): enable it once with `things config set ui.enabled true` " +
|
|
1232
1329
|
"and pass --dangerously-drive-gui. Fails closed when the heading title is shared by " +
|
|
1233
|
-
"another heading in the project. No undo — re-create the heading and move the to-dos back.")).action(async (projectRef, sel, opts) => {
|
|
1330
|
+
"another heading in the project. No undo — re-create the heading and move the to-dos back."))).action(async (projectRef, sel, opts) => {
|
|
1234
1331
|
await runWrite(opts, (c) => {
|
|
1235
1332
|
const proj = c.resolve.project(projectRef);
|
|
1236
1333
|
const h = c.resolve.heading(proj.uuid, sel);
|
|
@@ -1272,11 +1369,11 @@ export function registerWriteCommands(program) {
|
|
|
1272
1369
|
}
|
|
1273
1370
|
addDriveGuiFlag(addRepeatRuleFlags(addWriteFlags(project
|
|
1274
1371
|
.command("make-repeating <ref>")
|
|
1275
|
-
.description("Turn a project into a repeating one.
|
|
1276
|
-
"
|
|
1277
|
-
"
|
|
1278
|
-
"
|
|
1279
|
-
"flags below; see `things help repeating`.")
|
|
1372
|
+
.description("Turn a project into a repeating one. A disposable copy is promoted and the ORIGINAL is " +
|
|
1373
|
+
"moved to the Trash — so `things undo` reverses it (it removes the new series and " +
|
|
1374
|
+
"restores the original). Refuses a project that holds a nested repeating template. An " +
|
|
1375
|
+
"Anytime project with no area is moved to Someday first (a cleanup-free intermediate " +
|
|
1376
|
+
"step, shown in --dry-run). Set the rule with the flags below; see `things help repeating`.")
|
|
1280
1377
|
.requiredOption("--frequency <freq>", REPEAT_FREQ_HELP)
|
|
1281
1378
|
.requiredOption("--interval <n>", REPEAT_INTERVAL_HELP)))).action(async (uuid, opts) => {
|
|
1282
1379
|
const frequency = opts["frequency"];
|
|
@@ -1288,30 +1385,73 @@ export function registerWriteCommands(program) {
|
|
|
1288
1385
|
...repeatRuleFlagsFromOpts(opts, frequency),
|
|
1289
1386
|
}, writeOptionsFrom(opts)));
|
|
1290
1387
|
});
|
|
1291
|
-
addDriveGuiFlag(addWriteFlags(project
|
|
1388
|
+
addDriveGuiFlag(addRepeatCalendarFlags(addWriteFlags(project
|
|
1292
1389
|
.command("add-repeating <title>")
|
|
1293
|
-
.description("Create a project and turn it into a repeating series in ONE call. Two
|
|
1294
|
-
"
|
|
1295
|
-
"is promoted (which drives the GUI). Give --area to place it, or omit
|
|
1296
|
-
"Someday. The new repeating project's uuid is printed
|
|
1390
|
+
.description("Create a project and turn it into a repeating series in ONE call. Two legs: the project " +
|
|
1391
|
+
"is created (notes/area/when/deadline/child to-dos) and PERSISTS even if the promote " +
|
|
1392
|
+
"refuses; then it is promoted (which drives the GUI). Give --area to place it, or omit " +
|
|
1393
|
+
"it to create in Someday. The new repeating project's uuid is printed; `things undo` " +
|
|
1394
|
+
"removes the created series (trash-both).")
|
|
1297
1395
|
.option("--notes <text>", "notes body")
|
|
1298
1396
|
.option("--area <ref>", "destination area (uuid or unique name)")
|
|
1397
|
+
.option("--when <value>", "today | evening | anytime | someday | YYYY-MM-DD")
|
|
1299
1398
|
.option("--deadline <date>", "YYYY-MM-DD")
|
|
1300
1399
|
.option("--todo <title>", "initial child to-do (repeatable)", collect, [])
|
|
1301
1400
|
.requiredOption("--frequency <freq>", REPEAT_FREQ_HELP)
|
|
1302
|
-
.requiredOption("--interval <n>", REPEAT_INTERVAL_HELP))).action(async (title, opts) => {
|
|
1401
|
+
.requiredOption("--interval <n>", REPEAT_INTERVAL_HELP)))).action(async (title, opts) => {
|
|
1303
1402
|
const todos = opts["todo"];
|
|
1304
1403
|
const area = containerRef(opts["area"]);
|
|
1404
|
+
const frequency = opts["frequency"];
|
|
1305
1405
|
if (opIdCompoundRefused(opts, "project add-repeating"))
|
|
1306
1406
|
return;
|
|
1307
1407
|
await runWrite(opts, (c) => c.write.addRepeatingProject({
|
|
1308
1408
|
title,
|
|
1309
1409
|
...(opts["notes"] !== undefined && { notes: opts["notes"] }),
|
|
1310
1410
|
...(area !== undefined && { area }),
|
|
1411
|
+
...(opts["when"] !== undefined && { when: opts["when"] }),
|
|
1311
1412
|
...(opts["deadline"] !== undefined && { deadline: opts["deadline"] }),
|
|
1312
1413
|
...(todos.length > 0 && { todos }),
|
|
1313
|
-
frequency
|
|
1314
|
-
|
|
1414
|
+
...addRepeatingRuleFieldsFromOpts(opts, frequency, Number(opts["interval"])),
|
|
1415
|
+
}, writeOptionsFrom(opts)));
|
|
1416
|
+
});
|
|
1417
|
+
addDriveGuiFlag(addRepeatCalendarFlags(addWriteFlags(todo
|
|
1418
|
+
.command("add-repeating <title>")
|
|
1419
|
+
.description("Create a to-do and turn it into a repeating series in ONE call. Two legs: the to-do is " +
|
|
1420
|
+
"created (notes/tags/when/deadline/reminder/checklist/--created-at) and PERSISTS even " +
|
|
1421
|
+
"if the promote refuses; then it is promoted (which drives the GUI). The new repeating " +
|
|
1422
|
+
"template's uuid is printed; `things undo` removes the created series (trash-both).")
|
|
1423
|
+
.option("--notes <text>", "notes body")
|
|
1424
|
+
.option("--when <value>", "today | evening | anytime | someday | YYYY-MM-DD")
|
|
1425
|
+
.option("--reminder <time>", "HH:mm reminder (needs a schedulable --when)")
|
|
1426
|
+
.option("--deadline <date>", "YYYY-MM-DD")
|
|
1427
|
+
.option("--tag <name>", "tag to attach (repeatable)", collect, [])
|
|
1428
|
+
.option("--checklist-item <title>", "checklist item (repeatable)", collect, [])
|
|
1429
|
+
.option("--project <ref>", "destination project (uuid or unique name)")
|
|
1430
|
+
.option("--area <ref>", "destination area (uuid or unique name)")
|
|
1431
|
+
.option("--heading <title>", "existing heading inside the target project")
|
|
1432
|
+
.option("--created-at <iso>", "born with this creation timestamp (ISO date or datetime; a date is noon in the effective zone)")
|
|
1433
|
+
.requiredOption("--frequency <freq>", REPEAT_FREQ_HELP)
|
|
1434
|
+
.requiredOption("--interval <n>", REPEAT_INTERVAL_HELP)))).action(async (title, opts) => {
|
|
1435
|
+
const tags = opts["tag"];
|
|
1436
|
+
const checklistItems = opts["checklistItem"];
|
|
1437
|
+
const projectRef = containerRef(opts["project"]);
|
|
1438
|
+
const area = containerRef(opts["area"]);
|
|
1439
|
+
const frequency = opts["frequency"];
|
|
1440
|
+
if (opIdCompoundRefused(opts, "todo add-repeating"))
|
|
1441
|
+
return;
|
|
1442
|
+
await runWrite(opts, (c) => c.write.addRepeatingTodo({
|
|
1443
|
+
title,
|
|
1444
|
+
...(opts["notes"] !== undefined && { notes: opts["notes"] }),
|
|
1445
|
+
...(opts["when"] !== undefined && { when: opts["when"] }),
|
|
1446
|
+
...(opts["reminder"] !== undefined && { reminder: opts["reminder"] }),
|
|
1447
|
+
...(opts["deadline"] !== undefined && { deadline: opts["deadline"] }),
|
|
1448
|
+
...(tags.length > 0 && { tags }),
|
|
1449
|
+
...(checklistItems.length > 0 && { checklistItems }),
|
|
1450
|
+
...(projectRef !== undefined && { project: projectRef }),
|
|
1451
|
+
...(area !== undefined && { area }),
|
|
1452
|
+
...(opts["heading"] !== undefined && { heading: opts["heading"] }),
|
|
1453
|
+
...(opts["createdAt"] !== undefined && { createdAt: opts["createdAt"] }),
|
|
1454
|
+
...addRepeatingRuleFieldsFromOpts(opts, frequency, Number(opts["interval"])),
|
|
1315
1455
|
}, writeOptionsFrom(opts)));
|
|
1316
1456
|
});
|
|
1317
1457
|
addWriteFlags(project
|
|
@@ -1514,6 +1654,23 @@ export function registerWriteCommands(program) {
|
|
|
1514
1654
|
"copy's uuid is printed on success. Not available for repeating projects.")).action(async (uuid, opts) => {
|
|
1515
1655
|
await runWrite(opts, (c) => c.write.duplicateProject(uuid, writeOptionsFrom(opts)));
|
|
1516
1656
|
});
|
|
1657
|
+
addDriveGuiFlag(addWriteFlags(project
|
|
1658
|
+
.command("clone <ref>")
|
|
1659
|
+
.description("Clone a project (target by uuid or unique name) — a faithful content copy: area, " +
|
|
1660
|
+
"headings, headed and root children (one import), notes and deadline, plus " +
|
|
1661
|
+
"logged/canceled children and the project's own completed/canceled state with the " +
|
|
1662
|
+
"exact original timestamps. The clone is a new capture (new uuid, created now); its " +
|
|
1663
|
+
"uuid is printed on success. Refuses a trashed source and a project holding a nested " +
|
|
1664
|
+
"repeating template (named in the refusal). A repeating-template project is cloned as " +
|
|
1665
|
+
"a NEW repeating series (its content plus the source's rule, minted fresh — no " +
|
|
1666
|
+
"instances copied); this drives the app (requires --dangerously-drive-gui) and refuses " +
|
|
1667
|
+
"a rule the Repeat dialog cannot express. Undo trashes the clone and every child it " +
|
|
1668
|
+
"minted (a template clone trashes the new series).")
|
|
1669
|
+
.option("--title <text>", "give the clone a different title (default: the source's title)")
|
|
1670
|
+
.option("--preserve-created", "copy the source's creation date onto the clone (minute resolution) instead of now"))).action(async (uuid, opts) => {
|
|
1671
|
+
const clone = cloneArgs(opts);
|
|
1672
|
+
await runWrite(opts, (c) => c.write.cloneProject(uuid, clone, writeOptionsFrom(opts)));
|
|
1673
|
+
});
|
|
1517
1674
|
addWriteFlags(project
|
|
1518
1675
|
.command("complete <ref>")
|
|
1519
1676
|
.description("Complete a project (target by uuid or unique name). Completing also completes its " +
|
|
@@ -1701,23 +1858,27 @@ export function registerWriteCommands(program) {
|
|
|
1701
1858
|
.description("Run MANY mutations from JSONL (file, or stdin when omitted/'-'): one op per line, " +
|
|
1702
1859
|
'{"op": "<kind>", "params": {...}, "options": {...}} — see `things capabilities` for ' +
|
|
1703
1860
|
"op kinds and params. Ops run sequentially and independently — NO transactions; a " +
|
|
1704
|
-
"failure does not roll back earlier ops.
|
|
1861
|
+
"failure does not roll back earlier ops. A statically-invalid line (bad shape, unknown " +
|
|
1862
|
+
"op, a $ref to an undeclared/forward tempId, a duplicate tempId) refuses the WHOLE " +
|
|
1863
|
+
"batch before anything runs, naming every bad line. Otherwise ops run and per-op " +
|
|
1864
|
+
"results stream as JSONL. Per-op " +
|
|
1705
1865
|
"options carry the confirmation flags (acknowledgeChecklistReset, " +
|
|
1706
1866
|
"acknowledgeProjectReopen, dangerouslyPermanent, acknowledgeTagSubtree). " +
|
|
1707
1867
|
"CHAINING: an op that creates something may carry a `tempId` (a handle like " +
|
|
1708
1868
|
'"proj1"); a LATER op references that new uuid as "$proj1" in any id/container ' +
|
|
1709
1869
|
'param (dotted "$proj1.instance"/"$proj1.replaced" reach a repeating op\'s spawned ' +
|
|
1710
1870
|
"instance / replaced source). A tempId is valid only on a creating op (not tag.add — " +
|
|
1711
|
-
"reference a tag by title) and unique per batch
|
|
1712
|
-
"
|
|
1713
|
-
"
|
|
1714
|
-
"`tempIdMapping` (handle → uuid) and
|
|
1715
|
-
"`things undo --txn <undoToken>`. " +
|
|
1716
|
-
"
|
|
1717
|
-
"
|
|
1718
|
-
"
|
|
1871
|
+
"reference a tag by title) and unique per batch. IDEMPOTENCY: a line's `opId` makes " +
|
|
1872
|
+
"resubmission safe — a matching earlier success is reported already-applied, not " +
|
|
1873
|
+
"re-created (put an opId on EVERY line so a stopped batch can be resubmitted verbatim " +
|
|
1874
|
+
"to resume). The trailing summary line adds `tempIdMapping` (handle → uuid) and " +
|
|
1875
|
+
"`undoToken` — undo the WHOLE batch with `things undo --txn <undoToken>`. " +
|
|
1876
|
+
"By DEFAULT a runtime failure STOPS the batch (later lines reported not-run, with resume " +
|
|
1877
|
+
"guidance in the summary); --continue-on-error runs past failures. --dry-run plans " +
|
|
1878
|
+
"everything without executing. Exit (worst failure wins): 0 all ok · 3 any " +
|
|
1879
|
+
"verify-failed/invalid · 4 any blocked · 5 any drift-blocked · 6 any unsupported.")
|
|
1719
1880
|
.option("--dry-run", "plan every op; execute nothing")
|
|
1720
|
-
.option("--
|
|
1881
|
+
.option("--continue-on-error", "run past a failed op instead of stopping (default: stop)")
|
|
1721
1882
|
.option("--json", "JSONL results + summary on stdout (also the default)")
|
|
1722
1883
|
.option("--db <path>", "explicit database path")
|
|
1723
1884
|
.option("--actor <name>", "author name recorded for the whole batch")
|
|
@@ -1758,7 +1919,7 @@ export function registerWriteCommands(program) {
|
|
|
1758
1919
|
client = openThings(opts.db ? { dbPath: opts.db } : {});
|
|
1759
1920
|
const batchResult = await client.write.batch(ops, {
|
|
1760
1921
|
...(opts.dryRun !== undefined && { dryRun: opts.dryRun }),
|
|
1761
|
-
...(opts["
|
|
1922
|
+
...(opts["continueOnError"] === true && { continueOnError: true }),
|
|
1762
1923
|
...(opts.actor !== undefined && { actor: opts.actor }),
|
|
1763
1924
|
}, (r) => {
|
|
1764
1925
|
const pre = preInvalid.find((p) => p.index === r.index);
|
|
@@ -1777,6 +1938,10 @@ export function registerWriteCommands(program) {
|
|
|
1777
1938
|
// (undo the whole submission with `things undo --txn <token>`).
|
|
1778
1939
|
...(hasMapping && { tempIdMapping: batchResult.tempIdMapping }),
|
|
1779
1940
|
...(batchResult.undoToken !== undefined && { undoToken: batchResult.undoToken }),
|
|
1941
|
+
// ADDITIVE: resume guidance when a runtime failure stopped the batch
|
|
1942
|
+
// (Change 2) — how many lines did not run and whether a verbatim
|
|
1943
|
+
// resubmission is safe.
|
|
1944
|
+
...(batchResult.resumption !== undefined && { resumption: batchResult.resumption }),
|
|
1780
1945
|
},
|
|
1781
1946
|
};
|
|
1782
1947
|
process.stdout.write(`${JSON.stringify(summary)}\n`);
|
|
@@ -1797,8 +1962,13 @@ export function registerWriteCommands(program) {
|
|
|
1797
1962
|
"author's changes (e.g. `--by mcp` to clean up after an agent), or --txn <token> " +
|
|
1798
1963
|
"to undo one exact change by the `undoToken` its result returned (immune to any " +
|
|
1799
1964
|
"changes made in between). Changes made directly in the Things app cannot be " +
|
|
1800
|
-
"undone here.
|
|
1801
|
-
"
|
|
1965
|
+
"undone here. --last/--by select the N newest NOT-YET-undone changes: a change already " +
|
|
1966
|
+
"undone is SKIPPED, so repeating `undo --last N` walks progressively deeper into " +
|
|
1967
|
+
"history rather than re-selecting the same changes. An IRREVERSIBLE change is NOT " +
|
|
1968
|
+
"skipped — it still counts toward N and is reported every pass (so you always see it " +
|
|
1969
|
+
"rather than silently reaching past it). IRREVERSIBLE changes are reported, not " +
|
|
1970
|
+
"guessed: permanent deletes and changes whose prior state is unknown. Partial " +
|
|
1971
|
+
"restores carry notes (e.g. a " +
|
|
1802
1972
|
"delete-undo lands in the Inbox de-scheduled). --dry-run shows every inverse plan " +
|
|
1803
1973
|
"without executing. Undoing a CREATED area/tag deletes it permanently — requires " +
|
|
1804
1974
|
"--dangerously-permanent. An undo is refused when the item changed outside things-api " +
|
|
@@ -1985,7 +2155,7 @@ export function registerWriteCommands(program) {
|
|
|
1985
2155
|
.command("set <key> <value>")
|
|
1986
2156
|
.description("Persist a config key: profile | maxDisruption | actor | auditEnabled | " +
|
|
1987
2157
|
"accepted-fingerprint | certified-app-version | allow-experimental | bounce-enabled | " +
|
|
1988
|
-
"bounce-max-items | ui-enabled | scope")
|
|
2158
|
+
"bounce-max-items | auto-launch | ui-enabled | ui-drive-budget-ms | trace | scope")
|
|
1989
2159
|
.action((key, value, opts) => {
|
|
1990
2160
|
const map = {
|
|
1991
2161
|
profile: "profile",
|
|
@@ -1997,7 +2167,10 @@ export function registerWriteCommands(program) {
|
|
|
1997
2167
|
"allow-experimental": "allowExperimental",
|
|
1998
2168
|
"bounce-enabled": "bounceEnabled",
|
|
1999
2169
|
"bounce-max-items": "bounceMaxItems",
|
|
2170
|
+
"auto-launch": "autoLaunch",
|
|
2000
2171
|
"ui-enabled": "uiEnabled",
|
|
2172
|
+
"ui-drive-budget-ms": "uiDriveBudgetMs",
|
|
2173
|
+
trace: "traceEnabled",
|
|
2001
2174
|
scope: "scope",
|
|
2002
2175
|
};
|
|
2003
2176
|
const target = map[key];
|
|
@@ -2006,12 +2179,14 @@ export function registerWriteCommands(program) {
|
|
|
2006
2179
|
process.exitCode = ExitCode.Usage;
|
|
2007
2180
|
return;
|
|
2008
2181
|
}
|
|
2009
|
-
const parsed = target === "maxDisruption" || target === "bounceMaxItems"
|
|
2182
|
+
const parsed = target === "maxDisruption" || target === "bounceMaxItems" || target === "uiDriveBudgetMs"
|
|
2010
2183
|
? Number(value)
|
|
2011
2184
|
: target === "auditEnabled" ||
|
|
2012
2185
|
target === "allowExperimental" ||
|
|
2013
2186
|
target === "bounceEnabled" ||
|
|
2014
|
-
target === "
|
|
2187
|
+
target === "autoLaunch" ||
|
|
2188
|
+
target === "uiEnabled" ||
|
|
2189
|
+
target === "traceEnabled"
|
|
2015
2190
|
? value === "true"
|
|
2016
2191
|
: value;
|
|
2017
2192
|
// Universal `--dry-run` (../dry-run.ts): `config set` writes local config
|