things-api 0.19.0 → 0.19.1
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 +3 -1
- package/deputy/prebuilt/Things API Helper.app/Contents/CodeResources +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/MacOS/things-reader +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/MacOS/things-deputy +0 -0
- package/dist/audit/schema.d.ts +19 -2
- package/dist/audit/schema.js.map +1 -1
- package/dist/capability.d.ts +166 -4
- package/dist/capability.js +303 -13
- package/dist/capability.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +13 -1
- package/dist/cli/commands/doctor.js +24 -14
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/helpers.js +14 -3
- package/dist/cli/commands/helpers.js.map +1 -1
- package/dist/cli/commands/mcp.js +8 -0
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/repeat-flags.js +34 -2
- package/dist/cli/commands/repeat-flags.js.map +1 -1
- package/dist/cli/commands/setup.js +16 -3
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/writes.js +102 -67
- package/dist/cli/commands/writes.js.map +1 -1
- package/dist/cli/interrupt.d.ts +54 -6
- package/dist/cli/interrupt.js +78 -12
- package/dist/cli/interrupt.js.map +1 -1
- package/dist/cli/main.js +8 -5
- package/dist/cli/main.js.map +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/contracts.d.ts +3 -3
- package/dist/contracts.js +1 -1
- package/dist/deputy/install.d.ts +15 -1
- package/dist/deputy/install.js +46 -6
- package/dist/deputy/install.js.map +1 -1
- package/dist/deputy/routing.d.ts +16 -0
- package/dist/deputy/routing.js +70 -0
- package/dist/deputy/routing.js.map +1 -1
- package/dist/deputy/wake.d.ts +61 -0
- package/dist/deputy/wake.js +149 -0
- package/dist/deputy/wake.js.map +1 -0
- package/dist/diagnose.d.ts +6 -4
- package/dist/diagnose.js +20 -4
- package/dist/diagnose.js.map +1 -1
- package/dist/direct-setup.d.ts +31 -5
- package/dist/direct-setup.js +200 -26
- package/dist/direct-setup.js.map +1 -1
- package/dist/host-access.d.ts +12 -0
- package/dist/host-access.js +12 -0
- package/dist/host-access.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +23 -9
- package/dist/mcp/server.js.map +1 -1
- package/dist/op-result.js +8 -1
- package/dist/op-result.js.map +1 -1
- package/dist/session-grant.js +16 -1
- package/dist/session-grant.js.map +1 -1
- package/dist/surface-copy.d.ts +11 -3
- package/dist/surface-copy.js +11 -3
- package/dist/surface-copy.js.map +1 -1
- package/dist/wizard.js +8 -1
- package/dist/wizard.js.map +1 -1
- package/dist/write/availability.d.ts +0 -15
- package/dist/write/availability.js +7 -62
- package/dist/write/availability.js.map +1 -1
- package/dist/write/batch.js +17 -6
- package/dist/write/batch.js.map +1 -1
- package/dist/write/commands.d.ts +10 -6
- package/dist/write/commands.js +120 -31
- package/dist/write/commands.js.map +1 -1
- package/dist/write/failure-hints.d.ts +29 -6
- package/dist/write/failure-hints.js +9 -14
- package/dist/write/failure-hints.js.map +1 -1
- package/dist/write/guards.js +3 -2
- package/dist/write/guards.js.map +1 -1
- package/dist/write/move.js +9 -12
- package/dist/write/move.js.map +1 -1
- package/dist/write/operations.d.ts +15 -8
- package/dist/write/operations.js +9 -0
- package/dist/write/operations.js.map +1 -1
- package/dist/write/opid.d.ts +67 -23
- package/dist/write/opid.js +141 -19
- package/dist/write/opid.js.map +1 -1
- package/dist/write/param-schema.d.ts +0 -44
- package/dist/write/param-schema.js +10 -3
- package/dist/write/param-schema.js.map +1 -1
- package/dist/write/pipeline.d.ts +38 -7
- package/dist/write/pipeline.js +104 -18
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/pre-state.d.ts +30 -16
- package/dist/write/pre-state.js +88 -20
- package/dist/write/pre-state.js.map +1 -1
- package/dist/write/promote-clone.js +132 -18
- package/dist/write/promote-clone.js.map +1 -1
- package/dist/write/repeat-collateral.d.ts +31 -0
- package/dist/write/repeat-collateral.js +153 -0
- package/dist/write/repeat-collateral.js.map +1 -0
- package/dist/write/resolution-timestamps.js +25 -2
- package/dist/write/resolution-timestamps.js.map +1 -1
- package/dist/write/template-mutation.js +163 -16
- package/dist/write/template-mutation.js.map +1 -1
- package/dist/write/vectors/applescript.js +3 -10
- package/dist/write/vectors/applescript.js.map +1 -1
- package/dist/write/vectors/simulator.js +8 -2
- package/dist/write/vectors/simulator.js.map +1 -1
- package/dist/write/vectors/types.d.ts +211 -1
- package/dist/write/vectors/ui-certification.js +63 -13
- package/dist/write/vectors/ui-certification.js.map +1 -1
- package/dist/write/vectors/ui-chord.d.ts +144 -0
- package/dist/write/vectors/ui-chord.js +428 -0
- package/dist/write/vectors/ui-chord.js.map +1 -0
- package/dist/write/vectors/ui-drag.d.ts +8 -4
- package/dist/write/vectors/ui-drag.js +2 -0
- package/dist/write/vectors/ui-drag.js.map +1 -1
- package/dist/write/vectors/ui-recipes.d.ts +51 -3
- package/dist/write/vectors/ui-recipes.js +452 -44
- package/dist/write/vectors/ui-recipes.js.map +1 -1
- package/dist/write/vectors/ui.d.ts +267 -10
- package/dist/write/vectors/ui.js +1006 -64
- package/dist/write/vectors/ui.js.map +1 -1
- package/dist/write/vectors/url-scheme.js +3 -0
- package/dist/write/vectors/url-scheme.js.map +1 -1
- package/dist/write/verify/delta.d.ts +41 -0
- package/dist/write/verify/delta.js +36 -2
- package/dist/write/verify/delta.js.map +1 -1
- package/dist/write/verify/poller.d.ts +4 -1
- package/dist/write/verify/poller.js +14 -0
- package/dist/write/verify/poller.js.map +1 -1
- package/package.json +1 -1
- package/schema/envelope.schema.json +1 -1
- package/skills/things-cli/SKILL.md +1 -1
|
@@ -11,7 +11,7 @@ import { armInterrupt, disarmInterrupt } from "../interrupt.js";
|
|
|
11
11
|
import { isDevVersion } from "../skill.js";
|
|
12
12
|
import { CLI_VERSION } from "../version.js";
|
|
13
13
|
import { addRepeatCalendarFlags, addRepeatingRuleFieldsFromOpts, addRepeatRuleFlags, repeatRuleFlagsFromOpts, } from "./repeat-flags.js";
|
|
14
|
-
import { aggregateExitCode, blockedCode, buildUpdatePatch, capabilitiesTable, CLI_UPDATE_LABELS, ClockError, closeCliTrace, describeConfig, errorEnvelope, ExitCode, HELPERS_MODES, installCliTrace, getConfigKey, parseHelpersMode, mutationWireData, NOTES_FORMAT, okEnvelope, openThings, OP_ID_RE, outcomeFailed, ParamSchemaError, ReferenceResolutionError, saveConfigKey, splitWhenSugar, trace, ThingsDbNotFoundError, ThingsDbOpenError, verifyFailedCode, } from "../../index.js";
|
|
14
|
+
import { aggregateExitCode, blockedCode, buildUpdatePatch, capabilitiesTable, CLI_UPDATE_LABELS, ClockError, closeCliTrace, describeConfig, errorEnvelope, ExitCode, HELPERS_MODES, installCliTrace, getConfigKey, parseHelpersMode, mutationWireData, NOTES_FORMAT, okEnvelope, openThings, OP_ID_RE, outcomeFailed, ParamSchemaError, ReferenceResolutionError, RESOLUTION_DATE_FORMAT, saveConfigKey, splitWhenSugar, trace, ThingsDbNotFoundError, ThingsDbOpenError, verifyFailedCode, } from "../../index.js";
|
|
15
15
|
import { usageError } from "../read-driver.js";
|
|
16
16
|
import { dim } from "../style.js";
|
|
17
17
|
/**
|
|
@@ -43,11 +43,12 @@ function addWriteFlags(cmd, capability = {}) {
|
|
|
43
43
|
.addOption(opId);
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* The write flags for a multi-
|
|
47
|
-
* kept out of `--help` because that command's execution refuses it
|
|
48
|
-
* `opIdCompoundRefused` / `runMoveCmd`). Every command wrapped in this MUST
|
|
46
|
+
* The write flags for a multi-result COMPOUND command: the same set, with
|
|
47
|
+
* `--op-id` kept out of `--help` because that command's execution refuses it
|
|
48
|
+
* (see `opIdCompoundRefused` / `runMoveCmd`). Every command wrapped in this MUST
|
|
49
49
|
* refuse `--op-id` at runtime, and every command that refuses it MUST be wrapped
|
|
50
|
-
* in this — `test/cli/help-contract.test.ts` holds the two halves together.
|
|
50
|
+
* in this — `test/cli/help-contract.test.ts` holds the two halves together. A
|
|
51
|
+
* compound that records ONE summary the key rides uses plain `addWriteFlags`.
|
|
51
52
|
*/
|
|
52
53
|
const addCompoundWriteFlags = (cmd) => addWriteFlags(cmd, { opId: "unsupported" });
|
|
53
54
|
/** Validate an `--op-id`; on a malformed value emit the usage error and return false. */
|
|
@@ -60,11 +61,16 @@ function opIdOk(opts) {
|
|
|
60
61
|
return false;
|
|
61
62
|
}
|
|
62
63
|
/**
|
|
63
|
-
* Refuse `--op-id` on a multi-leg COMPOUND command.
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
64
|
+
* Refuse `--op-id` on a multi-leg COMPOUND command. The rule is ONE RECORDED
|
|
65
|
+
* RESULT PER KEY: an idempotency key replays exactly one recorded outcome, so a
|
|
66
|
+
* command that records SEVERAL — the variadic move/reorder, the granular
|
|
67
|
+
* checklist edit, clear-reminder, the heading archive pair, project reopen —
|
|
68
|
+
* cannot honor one, and its idempotency is the batch-shaped per-line `op_id`.
|
|
69
|
+
* (A compound that records ONE SUMMARY the key rides — the template-target
|
|
70
|
+
* verbs, the `--completed-at` flip-dance, and the promote verbs — accepts the
|
|
71
|
+
* flag instead; ruling 2026-08-25.) Refusing is honest — dropping the flag
|
|
72
|
+
* silently would leave a resubmission un-deduped. Returns true when refused
|
|
73
|
+
* (the caller returns).
|
|
68
74
|
*/
|
|
69
75
|
function opIdCompoundRefused(opts, what) {
|
|
70
76
|
if (opts.opId === undefined)
|
|
@@ -216,10 +222,15 @@ function splitCsv(value) {
|
|
|
216
222
|
.filter((s) => s !== "");
|
|
217
223
|
}
|
|
218
224
|
/**
|
|
219
|
-
* Open a CLI write invocation: install the dev-mode step-timeline trace and
|
|
220
|
-
* the
|
|
221
|
-
*
|
|
222
|
-
*
|
|
225
|
+
* Open a CLI write invocation: install the dev-mode step-timeline trace, and
|
|
226
|
+
* hand back the two things every write driver needs (TRACE1, #487) —
|
|
227
|
+
*
|
|
228
|
+
* - `openClient(dbPath)`: opens the Things client AND arms the signal-safe
|
|
229
|
+
* interrupt guard once that open has returned. This is the ONLY arming seam
|
|
230
|
+
* for the CLI; see below for why it sits past the open.
|
|
231
|
+
* - `endInvocation()`: the driver's `finally` — closes the trace with the
|
|
232
|
+
* final exit code, then disarms the guard so a signal during teardown or a
|
|
233
|
+
* trailing read emits nothing.
|
|
223
234
|
*
|
|
224
235
|
* EVERY write driver calls this, not just the single-mutation one: the library
|
|
225
236
|
* half (the in-flight marker, the watchdog, the plain-stderr interrupt line)
|
|
@@ -228,6 +239,15 @@ function splitCsv(value) {
|
|
|
228
239
|
* invocation's stdout is machine-readable — drivers that emit JSONL regardless
|
|
229
240
|
* of `--json` (batch, undo) pass true.
|
|
230
241
|
*
|
|
242
|
+
* ## Why the arm sits PAST the client open
|
|
243
|
+
*
|
|
244
|
+
* `openThings` is the read gate, and its `open(2)` on the Things container is
|
|
245
|
+
* synchronous and can block indefinitely behind a TCC dialog. A listener armed
|
|
246
|
+
* across that span cannot dispatch (see src/cli/interrupt.ts's module note), so
|
|
247
|
+
* arming from the top of the driver made a write stalled there swallow SIGTERM
|
|
248
|
+
* — measured exit 137. Nothing can be in flight before the client exists, so
|
|
249
|
+
* the window this gives up is one where the guard had nothing to report anyway.
|
|
250
|
+
*
|
|
231
251
|
* The sink is a no-op unless tracing is on (a `-dev` build, or config/env
|
|
232
252
|
* forced), so this costs a config read on every write and nothing else.
|
|
233
253
|
*/
|
|
@@ -237,22 +257,28 @@ function beginWriteInvocation(json, startedAt) {
|
|
|
237
257
|
version: CLI_VERSION,
|
|
238
258
|
isDev: isDevVersion(CLI_VERSION),
|
|
239
259
|
});
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
260
|
+
return {
|
|
261
|
+
openClient: (dbPath) => {
|
|
262
|
+
const client = openThings(dbPath ? { dbPath } : {});
|
|
263
|
+
armInterrupt(json);
|
|
264
|
+
return client;
|
|
265
|
+
},
|
|
266
|
+
endInvocation: () => {
|
|
267
|
+
trace(() => ({
|
|
268
|
+
phase: "invocation-end",
|
|
269
|
+
exitCode: process.exitCode ?? 0,
|
|
270
|
+
elapsedMs: Date.now() - startedAt,
|
|
271
|
+
}));
|
|
272
|
+
disarmInterrupt();
|
|
273
|
+
closeCliTrace();
|
|
274
|
+
},
|
|
249
275
|
};
|
|
250
276
|
}
|
|
251
277
|
async function runWrite(opts, fn, emitFn = emitResult) {
|
|
252
278
|
if (!opIdOk(opts))
|
|
253
279
|
return;
|
|
254
280
|
const started = Date.now();
|
|
255
|
-
const endInvocation = beginWriteInvocation(opts.json === true, started);
|
|
281
|
+
const { openClient, endInvocation } = beginWriteInvocation(opts.json === true, started);
|
|
256
282
|
let client = null;
|
|
257
283
|
const meta = (client_) => {
|
|
258
284
|
let dbVersion = null;
|
|
@@ -267,7 +293,7 @@ async function runWrite(opts, fn, emitFn = emitResult) {
|
|
|
267
293
|
return { dbVersion, fingerprint, elapsedMs: Date.now() - started, ...(clock && { clock }) };
|
|
268
294
|
};
|
|
269
295
|
try {
|
|
270
|
-
client =
|
|
296
|
+
client = openClient(opts.db);
|
|
271
297
|
const result = await fn(client);
|
|
272
298
|
emitFn(result, opts, meta(client));
|
|
273
299
|
}
|
|
@@ -497,7 +523,7 @@ async function runMoveCmd(opts, fn) {
|
|
|
497
523
|
return;
|
|
498
524
|
}
|
|
499
525
|
const started = Date.now();
|
|
500
|
-
const endInvocation = beginWriteInvocation(opts.json === true, started);
|
|
526
|
+
const { openClient, endInvocation } = beginWriteInvocation(opts.json === true, started);
|
|
501
527
|
let client = null;
|
|
502
528
|
const meta = () => {
|
|
503
529
|
let dbVersion = null;
|
|
@@ -510,7 +536,7 @@ async function runMoveCmd(opts, fn) {
|
|
|
510
536
|
return { dbVersion, fingerprint, elapsedMs: Date.now() - started };
|
|
511
537
|
};
|
|
512
538
|
try {
|
|
513
|
-
client =
|
|
539
|
+
client = openClient(opts.db);
|
|
514
540
|
emitMoveResult(await fn(client), opts, meta());
|
|
515
541
|
}
|
|
516
542
|
catch (err) {
|
|
@@ -757,10 +783,10 @@ function addResultLine(r) {
|
|
|
757
783
|
* in creation order, and nothing else. Exit code is the worst leg's failure.
|
|
758
784
|
*/
|
|
759
785
|
async function runBulkAdd(opts, ops, idOnly) {
|
|
760
|
-
const endInvocation = beginWriteInvocation(opts.json === true, Date.now());
|
|
786
|
+
const { openClient, endInvocation } = beginWriteInvocation(opts.json === true, Date.now());
|
|
761
787
|
let client = null;
|
|
762
788
|
try {
|
|
763
|
-
client =
|
|
789
|
+
client = openClient(opts.db);
|
|
764
790
|
const batchResult = await client.write.batch(ops, {
|
|
765
791
|
...(opts.dryRun !== undefined && { dryRun: opts.dryRun }),
|
|
766
792
|
...(opts.actor !== undefined && { actor: opts.actor }),
|
|
@@ -889,8 +915,8 @@ export function registerWriteCommands(program) {
|
|
|
889
915
|
.option("--project <ref>", "destination project (uuid or unique name)")
|
|
890
916
|
.option("--area <ref>", "destination area (uuid or unique name)")
|
|
891
917
|
.option("--heading <name>", "existing heading in the destination project")
|
|
892
|
-
.option("--created-at <iso>",
|
|
893
|
-
.option("--completed-at <iso>",
|
|
918
|
+
.option("--created-at <iso>", `born with this creation timestamp (${RESOLUTION_DATE_FORMAT})`)
|
|
919
|
+
.option("--completed-at <iso>", `born completed (in the Logbook) with this completion timestamp (${RESOLUTION_DATE_FORMAT}); drop --when/--reminder`)
|
|
894
920
|
.option("--acknowledge-project-reopen", "allow adding into a completed/canceled project")
|
|
895
921
|
.option("--stdin", "read newline-delimited titles from stdin (blank lines skipped); exclusive with title arguments")
|
|
896
922
|
.option("--id-only", "print only the new uuid(s), one per line in creation order, and nothing else (exclusive with --json)"))).action(async (titles, opts) => {
|
|
@@ -1012,7 +1038,7 @@ export function registerWriteCommands(program) {
|
|
|
1012
1038
|
.option("--clear-reminder", "clear the reminder (works while scheduled today|evening)")
|
|
1013
1039
|
.option("--deadline <date>", "YYYY-MM-DD")
|
|
1014
1040
|
.option("--clear-deadline", "remove the deadline")
|
|
1015
|
-
.option("--created-at <iso>",
|
|
1041
|
+
.option("--created-at <iso>", `rewrite the creation timestamp (${RESOLUTION_DATE_FORMAT}); status-safe`)
|
|
1016
1042
|
.option("--completed-at <iso>", "rewrite the completion timestamp of an already-resolved to-do (a canceled one stays canceled); open to-dos are refused — use complete/cancel")
|
|
1017
1043
|
.option("--exception", "repeating to-dos only: change just the NEXT occurrence and leave the series alone " +
|
|
1018
1044
|
"(creates that occurrence if it has not appeared yet); refused when the series already " +
|
|
@@ -1059,7 +1085,7 @@ export function registerWriteCommands(program) {
|
|
|
1059
1085
|
"creating a second occurrence. " +
|
|
1060
1086
|
'--completed-at sets the completion timestamp (also the "Completed on" stamp for a canceled ' +
|
|
1061
1087
|
`item), ${stampNote} — a multi-leg sequence, disclosed in the result and --dry-run.`)
|
|
1062
|
-
.option("--completed-at <iso>",
|
|
1088
|
+
.option("--completed-at <iso>", `completion timestamp (${RESOLUTION_DATE_FORMAT})`)).action(async (uuid, opts) => {
|
|
1063
1089
|
const resolution = opts["completedAt"] !== undefined ? { completedAt: opts["completedAt"] } : {};
|
|
1064
1090
|
await runWrite(opts, (c) => c.write[method](uuid, resolution, writeOptionsFrom(opts)));
|
|
1065
1091
|
});
|
|
@@ -1269,7 +1295,9 @@ export function registerWriteCommands(program) {
|
|
|
1269
1295
|
.description("Turn a plain to-do into a repeating one. A disposable copy is promoted and the " +
|
|
1270
1296
|
"ORIGINAL is moved to the Trash — so `things undo` reverses it (it removes the new " +
|
|
1271
1297
|
"series and restores the original). The new template's uuid is printed. Set the rule " +
|
|
1272
|
-
"with the flags below; see `things help repeating`."
|
|
1298
|
+
"with the flags below; see `things help repeating`. Each re-run makes ANOTHER series, " +
|
|
1299
|
+
"so pass --op-id on a retry: a resubmission with the same key replays the first " +
|
|
1300
|
+
"result instead of making a second series.")
|
|
1273
1301
|
.requiredOption("--frequency <freq>", REPEAT_FREQ_HELP)
|
|
1274
1302
|
.requiredOption("--interval <n>", REPEAT_INTERVAL_HELP)))).action(async (uuid, opts) => {
|
|
1275
1303
|
const frequency = opts["frequency"];
|
|
@@ -1408,14 +1436,18 @@ export function registerWriteCommands(program) {
|
|
|
1408
1436
|
return c.write.run("project.promote-heading", { uuid: h.uuid }, writeOptionsFrom(opts));
|
|
1409
1437
|
});
|
|
1410
1438
|
});
|
|
1411
|
-
addPlacementFlags(addWriteFlags(project
|
|
1439
|
+
addPlacementFlags(addDriveGuiFlag(addWriteFlags(project
|
|
1412
1440
|
.command("move-heading <project> <headings...>")
|
|
1413
1441
|
.description("Reposition one or more of a project's headings as an ordered block — the selection " +
|
|
1414
|
-
"order is the resulting order, and each heading's to-dos follow it. Pass exactly
|
|
1415
|
-
"placement: --first, --last, --before-heading <sel>, or --after-heading <sel>.
|
|
1416
|
-
|
|
1417
|
-
"
|
|
1418
|
-
"
|
|
1442
|
+
"order is the resulting order, and each heading's to-dos follow it. Pass exactly " +
|
|
1443
|
+
"one placement: --first, --last, --before-heading <sel>, or --after-heading <sel>. " +
|
|
1444
|
+
`Each <heading> is a ${HEADING_SEL_HELP}. GUI-ONLY: Things offers heading order as ` +
|
|
1445
|
+
"keyboard shortcuts on a selected heading row and nowhere else, so this drives the " +
|
|
1446
|
+
"app — enable it once with `things config set ui-enabled true` and pass " +
|
|
1447
|
+
"--dangerously-drive-gui. It moves one heading one slot at a time, re-reading the " +
|
|
1448
|
+
"order after each step. Completed and canceled headings are not shown in the " +
|
|
1449
|
+
"project view, so they are skipped over and keep their place in the list; naming " +
|
|
1450
|
+
"one as a heading to move or as an anchor is refused.")))).action(async (projectRef, sels, opts) => {
|
|
1419
1451
|
if (countPlacementFlags(opts) !== 1) {
|
|
1420
1452
|
usageError(opts, "pass exactly one of --first / --last / --before-heading / --after-heading");
|
|
1421
1453
|
return;
|
|
@@ -1436,8 +1468,9 @@ export function registerWriteCommands(program) {
|
|
|
1436
1468
|
"destination. GUI-ONLY: it drives the heading row's ellipsis Move… menu, so enable it " +
|
|
1437
1469
|
"once with `things config set ui.enabled true` and pass --dangerously-drive-gui. Fails " +
|
|
1438
1470
|
"closed when the heading title is shared by another heading in the project, or the " +
|
|
1439
|
-
"destination title by another project (
|
|
1440
|
-
"
|
|
1471
|
+
"destination title by another project (both are addressed by title), and when the " +
|
|
1472
|
+
"destination project is completed or canceled — reopen it first. No undo — move it " +
|
|
1473
|
+
"back to reverse."))).action(async (projectRef, sel, opts) => {
|
|
1441
1474
|
await runWrite(opts, (c) => {
|
|
1442
1475
|
const proj = c.resolve.project(projectRef);
|
|
1443
1476
|
const dest = c.resolve.project(String(opts["to"]));
|
|
@@ -1492,31 +1525,34 @@ export function registerWriteCommands(program) {
|
|
|
1492
1525
|
await runWrite(opts, (c) => c.write.run(op, { uuid }, writeOptionsFrom(opts)));
|
|
1493
1526
|
});
|
|
1494
1527
|
}
|
|
1495
|
-
addDriveGuiFlag(addRepeatRuleFlags(
|
|
1528
|
+
addDriveGuiFlag(addRepeatRuleFlags(addWriteFlags(project
|
|
1496
1529
|
.command("make-repeating <ref>")
|
|
1497
1530
|
.description("Turn a project into a repeating one. A disposable copy is promoted and the ORIGINAL is " +
|
|
1498
1531
|
"moved to the Trash — so `things undo` reverses it (it removes the new series and " +
|
|
1499
1532
|
"restores the original). Refuses a project that holds a nested repeating template. An " +
|
|
1500
1533
|
"Anytime project with no area is moved to Someday first (a cleanup-free intermediate " +
|
|
1501
|
-
"step, shown in --dry-run). Set the rule with the flags below; see `things help
|
|
1534
|
+
"step, shown in --dry-run). Set the rule with the flags below; see `things help " +
|
|
1535
|
+
"repeating`. Each re-run makes ANOTHER series, so pass --op-id on a retry: a " +
|
|
1536
|
+
"resubmission with the same key replays the first result instead of making a second " +
|
|
1537
|
+
"series.")
|
|
1502
1538
|
.requiredOption("--frequency <freq>", REPEAT_FREQ_HELP)
|
|
1503
1539
|
.requiredOption("--interval <n>", REPEAT_INTERVAL_HELP)))).action(async (uuid, opts) => {
|
|
1504
1540
|
const frequency = opts["frequency"];
|
|
1505
|
-
if (opIdCompoundRefused(opts, "project make-repeating"))
|
|
1506
|
-
return;
|
|
1507
1541
|
await runWrite(opts, (c) => c.write.makeRepeatingProject(uuid, {
|
|
1508
1542
|
frequency,
|
|
1509
1543
|
interval: Number(opts["interval"]),
|
|
1510
1544
|
...repeatRuleFlagsFromOpts(opts, frequency),
|
|
1511
1545
|
}, writeOptionsFrom(opts)));
|
|
1512
1546
|
});
|
|
1513
|
-
addDriveGuiFlag(addRepeatCalendarFlags(
|
|
1547
|
+
addDriveGuiFlag(addRepeatCalendarFlags(addWriteFlags(project
|
|
1514
1548
|
.command("add-repeating <title>")
|
|
1515
1549
|
.description("Create a project and turn it into a repeating series in ONE call. Two legs: the project " +
|
|
1516
1550
|
"is created (notes/area/when/deadline/child to-dos) and PERSISTS even if the promote " +
|
|
1517
1551
|
"refuses; then it is promoted (which drives the GUI). Give --area to place it, or omit " +
|
|
1518
1552
|
"it to create in Someday. The new repeating project's uuid is printed; `things undo` " +
|
|
1519
|
-
"removes the created series (trash-both)."
|
|
1553
|
+
"removes the created series (trash-both). Each re-run makes ANOTHER series, so pass " +
|
|
1554
|
+
"--op-id on a retry: a resubmission with the same key replays the first result " +
|
|
1555
|
+
"instead of making a second series.")
|
|
1520
1556
|
.option("--notes <text>", `notes body — ${NOTES_FORMAT} (${NOTES_INPUT})`)
|
|
1521
1557
|
.option("--area <ref>", "destination area (uuid or unique name)")
|
|
1522
1558
|
.option("--when <value>", "today | evening | anytime | someday | YYYY-MM-DD")
|
|
@@ -1529,8 +1565,6 @@ export function registerWriteCommands(program) {
|
|
|
1529
1565
|
const todos = opts["todo"];
|
|
1530
1566
|
const area = containerRef(opts["area"]);
|
|
1531
1567
|
const frequency = opts["frequency"];
|
|
1532
|
-
if (opIdCompoundRefused(opts, "project add-repeating"))
|
|
1533
|
-
return;
|
|
1534
1568
|
if (!(await resolveNotesStdin(opts)))
|
|
1535
1569
|
return;
|
|
1536
1570
|
await runWrite(opts, (c) => c.write.addRepeatingProject({
|
|
@@ -1543,12 +1577,14 @@ export function registerWriteCommands(program) {
|
|
|
1543
1577
|
...addRepeatingRuleFieldsFromOpts(opts, frequency, Number(opts["interval"])),
|
|
1544
1578
|
}, writeOptionsFrom(opts)));
|
|
1545
1579
|
});
|
|
1546
|
-
addDriveGuiFlag(addRepeatCalendarFlags(
|
|
1580
|
+
addDriveGuiFlag(addRepeatCalendarFlags(addWriteFlags(todo
|
|
1547
1581
|
.command("add-repeating <title>")
|
|
1548
1582
|
.description("Create a to-do and turn it into a repeating series in ONE call. Two legs: the to-do is " +
|
|
1549
1583
|
"created (notes/tags/when/deadline/reminder/checklist/--created-at) and PERSISTS even " +
|
|
1550
1584
|
"if the promote refuses; then it is promoted (which drives the GUI). The new repeating " +
|
|
1551
|
-
"template's uuid is printed; `things undo` removes the created series (trash-both)."
|
|
1585
|
+
"template's uuid is printed; `things undo` removes the created series (trash-both). " +
|
|
1586
|
+
"Each re-run makes ANOTHER series, so pass --op-id on a retry: a resubmission with " +
|
|
1587
|
+
"the same key replays the first result instead of making a second series.")
|
|
1552
1588
|
.option("--notes <text>", `notes body — ${NOTES_FORMAT} (${NOTES_INPUT})`)
|
|
1553
1589
|
.option("--when <value>", "today | evening | anytime | someday | YYYY-MM-DD")
|
|
1554
1590
|
.option("--reminder <time>", "HH:mm reminder (needs a schedulable --when)")
|
|
@@ -1563,7 +1599,7 @@ export function registerWriteCommands(program) {
|
|
|
1563
1599
|
.option("--project <ref>", "destination project (uuid or unique name)")
|
|
1564
1600
|
.option("--area <ref>", "destination area (uuid or unique name)")
|
|
1565
1601
|
.option("--heading <title>", "existing heading inside the target project")
|
|
1566
|
-
.option("--created-at <iso>",
|
|
1602
|
+
.option("--created-at <iso>", `born with this creation timestamp (${RESOLUTION_DATE_FORMAT})`)
|
|
1567
1603
|
.requiredOption("--frequency <freq>", REPEAT_FREQ_HELP)
|
|
1568
1604
|
.requiredOption("--interval <n>", REPEAT_INTERVAL_HELP)))).action(async (title, opts) => {
|
|
1569
1605
|
const tags = opts["tag"];
|
|
@@ -1571,8 +1607,6 @@ export function registerWriteCommands(program) {
|
|
|
1571
1607
|
const projectRef = containerRef(opts["project"]);
|
|
1572
1608
|
const area = containerRef(opts["area"]);
|
|
1573
1609
|
const frequency = opts["frequency"];
|
|
1574
|
-
if (opIdCompoundRefused(opts, "todo add-repeating"))
|
|
1575
|
-
return;
|
|
1576
1610
|
if (!(await resolveNotesStdin(opts)))
|
|
1577
1611
|
return;
|
|
1578
1612
|
await runWrite(opts, (c) => c.write.addRepeatingTodo({
|
|
@@ -1603,7 +1637,7 @@ export function registerWriteCommands(program) {
|
|
|
1603
1637
|
.option("--when <value>", "today | evening | anytime | someday | YYYY-MM-DD")
|
|
1604
1638
|
.option("--deadline <date>", "YYYY-MM-DD")
|
|
1605
1639
|
.option("--todo <title>", "initial child to-do, repeatable (seeds the new project)", collect, [])
|
|
1606
|
-
.option("--created-at <iso>",
|
|
1640
|
+
.option("--created-at <iso>", `born with this creation timestamp (${RESOLUTION_DATE_FORMAT})`)
|
|
1607
1641
|
.option("--completed-at <iso>", "born completed (in the Logbook) with this completion timestamp; cannot seed open child to-dos (§5b)")).action(async (title, opts) => {
|
|
1608
1642
|
const todos = opts["todo"];
|
|
1609
1643
|
const area = containerRef(opts["area"]);
|
|
@@ -1636,7 +1670,7 @@ export function registerWriteCommands(program) {
|
|
|
1636
1670
|
.option("--clear-reminder", "clear the reminder (works while scheduled today|evening)")
|
|
1637
1671
|
.option("--deadline <date>", "YYYY-MM-DD")
|
|
1638
1672
|
.option("--clear-deadline", "remove the deadline")
|
|
1639
|
-
.option("--created-at <iso>",
|
|
1673
|
+
.option("--created-at <iso>", `rewrite the creation timestamp (${RESOLUTION_DATE_FORMAT}); status-safe`)
|
|
1640
1674
|
.option("--completed-at <iso>", "rewrite the completion timestamp of an already-resolved project (a canceled one stays canceled); open projects are refused — use complete/cancel")
|
|
1641
1675
|
.action(async (uuid, opts) => {
|
|
1642
1676
|
if (!(await resolveNotesStdin(opts)))
|
|
@@ -1704,7 +1738,7 @@ export function registerWriteCommands(program) {
|
|
|
1704
1738
|
"canceled via the certified flip legs (multi-leg, disclosed); it is refused while the " +
|
|
1705
1739
|
"project still has open children.")
|
|
1706
1740
|
.requiredOption("--children <policy>", "require-resolved (error if open to-dos remain) | auto-cancel (cancel them too)")
|
|
1707
|
-
.option("--completed-at <iso>",
|
|
1741
|
+
.option("--completed-at <iso>", `completion timestamp (${RESOLUTION_DATE_FORMAT})`)).action(async (uuid, opts) => {
|
|
1708
1742
|
await runWrite(opts, (c) => c.write.cancelProject(uuid, {
|
|
1709
1743
|
children: opts.children,
|
|
1710
1744
|
...(opts["completedAt"] !== undefined && {
|
|
@@ -1723,10 +1757,10 @@ export function registerWriteCommands(program) {
|
|
|
1723
1757
|
if (opIdCompoundRefused(opts, "project reopen"))
|
|
1724
1758
|
return;
|
|
1725
1759
|
const started = Date.now();
|
|
1726
|
-
const endInvocation = beginWriteInvocation(opts.json === true, started);
|
|
1760
|
+
const { openClient, endInvocation } = beginWriteInvocation(opts.json === true, started);
|
|
1727
1761
|
let client = null;
|
|
1728
1762
|
try {
|
|
1729
|
-
client =
|
|
1763
|
+
client = openClient(opts.db);
|
|
1730
1764
|
const outcome = await client.write.reopenProject(uuid, {
|
|
1731
1765
|
...writeOptionsFrom(opts),
|
|
1732
1766
|
...(opts.restoreChildren === true && { restoreChildren: true }),
|
|
@@ -1798,7 +1832,7 @@ export function registerWriteCommands(program) {
|
|
|
1798
1832
|
"open to-dos, so an explicit --children policy is required. --completed-at sets the " +
|
|
1799
1833
|
"completion timestamp and backdates it (multi-leg, disclosed).")
|
|
1800
1834
|
.requiredOption("--children <policy>", "require-resolved (error if open to-dos remain) | auto-complete (complete them too)")
|
|
1801
|
-
.option("--completed-at <iso>",
|
|
1835
|
+
.option("--completed-at <iso>", `completion timestamp (${RESOLUTION_DATE_FORMAT})`)).action(async (uuid, opts) => {
|
|
1802
1836
|
await runWrite(opts, (c) => c.write.completeProject(uuid, {
|
|
1803
1837
|
children: opts.children,
|
|
1804
1838
|
...(opts["completedAt"] !== undefined && {
|
|
@@ -2048,10 +2082,10 @@ export function registerWriteCommands(program) {
|
|
|
2048
2082
|
}
|
|
2049
2083
|
// batch streams JSONL regardless of --json, so the interrupt guard is
|
|
2050
2084
|
// armed machine-readable.
|
|
2051
|
-
const endInvocation = beginWriteInvocation(true, Date.now());
|
|
2085
|
+
const { openClient, endInvocation } = beginWriteInvocation(true, Date.now());
|
|
2052
2086
|
let client = null;
|
|
2053
2087
|
try {
|
|
2054
|
-
client =
|
|
2088
|
+
client = openClient(opts.db);
|
|
2055
2089
|
const batchResult = await client.write.batch(ops, {
|
|
2056
2090
|
...(opts.dryRun !== undefined && { dryRun: opts.dryRun }),
|
|
2057
2091
|
...(opts["continueOnError"] === true && { continueOnError: true }),
|
|
@@ -2139,10 +2173,10 @@ export function registerWriteCommands(program) {
|
|
|
2139
2173
|
}
|
|
2140
2174
|
// undo streams JSONL regardless of --json, so the interrupt guard is armed
|
|
2141
2175
|
// machine-readable (each inverse is a real write, GUI drives included).
|
|
2142
|
-
const endInvocation = beginWriteInvocation(true, Date.now());
|
|
2176
|
+
const { openClient, endInvocation } = beginWriteInvocation(true, Date.now());
|
|
2143
2177
|
let client = null;
|
|
2144
2178
|
try {
|
|
2145
|
-
client =
|
|
2179
|
+
client = openClient(opts.db);
|
|
2146
2180
|
const items = await client.write.undo({
|
|
2147
2181
|
...(opts["last"] !== undefined && { last: Number(opts["last"]) }),
|
|
2148
2182
|
...(opts["by"] !== undefined && { by: String(opts["by"]) }),
|
|
@@ -2211,8 +2245,9 @@ export function registerWriteCommands(program) {
|
|
|
2211
2245
|
"EARLIEST one's current slot; --start/--end/--before/--after position the block. A " +
|
|
2212
2246
|
"Today/Evening member also has an index slot in its container, so a set sharing BOTH " +
|
|
2213
2247
|
"axes is ambiguous — pass --in to say which. HEADINGS: same-project heading " +
|
|
2214
|
-
"re-ranking
|
|
2215
|
-
"
|
|
2248
|
+
"re-ranking drives the local Things app (keyboard shortcuts on the heading row) " +
|
|
2249
|
+
"and covers the open headings only — a completed or canceled heading is not shown " +
|
|
2250
|
+
"in the project view, so it is skipped over and cannot be moved. AREAS: this drives the " +
|
|
2216
2251
|
"local Things app (sidebar drag). `things area reorder` and `things project " +
|
|
2217
2252
|
"move-heading` remain as kind-specific spellings.")
|
|
2218
2253
|
.option("--in <target>", "to-dos/projects only — name the axis to reorder on: today | evening | anytime | " +
|